Summary
In a recent discussion, there were assertions that C++ was a poorly-designed language. I was on the C++ Standards Committee for 8 years, and saw the decisions take place. I think it's helpful to understand the language choices for both C++ and Java in order to see the bigger perspective.
Advertisement
That said, I hardly ever use C++ anymore. When I do, it's either examining legacy code, or to write performance-critical sections, typically as small as possible to be called from other code (my preferred approach is to quickly write an app in Python, then profile it and if necessary improve performance by calling small portions of C++ using Python's ctypes library).
Because I was on the C++ Standards Committee, I saw these decisions
being made. They were all extremely carefully considered, far more so
than many of the decisions made in Java.
However, as people have rightly pointed out, the resulting language
was complicated and painful to use and full of weird rules that I
forget as soon as I'm away from it for a little while -- and I figured
out those rules from first principles while I wrote books, not just by memorizing them.
To understand how the language can be both unpleasant and complicated,
and well designed at the same time, you must keep in mind the primary
design decision upon which everything in C++ hung: compatibility with
C. Stroustrup decided -- and correctly so, it would appear -- that the
way to get the masses of C programmers to move to objects was to make
the move transparent: to allow them to compile their C code unchanged
under C++. This was a huge constraint, and has always been C++'s
greatest strength ... and its bane. It's what made C++ as successful as it
was, and as complex as it is.
It also fooled the Java designers who didn't understand C++ well
enough. For example, they thought operator overloading was too hard
for programmers to use properly. Which is basically true in C++,
because C++ has both stack allocation and heap allocation and you must
overload your operators to handle all situations and not cause memory
leaks. Difficult indeed. Java, however, has a single storage
allocation mechanism and a garbage collector, which makes operator
overloading trivial -- as was shown in C# (but had already been shown
in Python, which predated Java). But for many years, the partly line
from the Java team was "Operator overloading is too complicated." This
and many other decisions where someone clearly didn't do their
homework is why I have a reputation for disdaining many of the
choices made by Gosling and the Java team.
There are plenty of other examples. Primitives "had to be included for
efficiency." The right answer is to stay true to "everything is an
object" and provide a trap door to do lower-level activities when
efficiency was required (this would also have allowed for the hotspot
technologies to transparently make things more efficient, as they
eventually would have). Oh, and the fact that you can't use the
floating point processor directly to calculate transcendental
functions (it's done in software instead). I've written about issues
like this as much as I can stand, and the answer I hear has always been some
tautological reply to the effect that "this is the Java way."
When I wrote about how badly generics were designed, I got the same
response, along with "we must be backwards compatible with previous
(bad) decisions made in Java." Lately more and more people have gained
enough experience with Generics to see that they really are very hard
to use -- indeed, C++ templates are much more powerful and consistent
(and much easier to use now that compiler error messages are
tolerable). People have even been taking reification seriously --
something that would be helpful but won't put that much of a dent in a
design that is crippled by self-imposed constraints.
The list goes on to the point where it's just tedious. Does this mean
Java was a failure? Absolutely not. Java brought the mainstream of
programmers into the world of garbage collection, virtual machines and
a consistent error handling model (especially if you subtract checked
exceptions, which is possible using techniques I show in Thinking in Java, 4e). With
all its flaws, it moved us up a level, to the point where we are now
ready for higher-level languages.
At one point, C++ was the leading language and people thought it would
always be so. Many think the same about Java, but Java has made it
even easier to replace itself, because of the JVM. It's now possible for
someone to create a new language and have it run as efficiently as
Java in short order; Previously, getting a correct and efficient
compiler took most of the development time for a new
language.
And we are seeing this happen -- both with higher-level static
languages like Scala, and with dynamic languages, both new and ports,
like Groovy, JRuby and Jython. This is the future, and the transition
is much smoother because you can easily use these new languages in
conjunction with existing Java code, and you can rewrite bottlenecks
in Java if necessary.
Java itself will diminish, just as C++ did, to be used in special
cases (or perhaps just to support legacy code, since it doesn't have
the same connection to hardware as C++ does). But the unintentional
benefit, the true accidental brilliance of Java is that it has created
a very smooth path for its own replacements, even if Java itself has
reached the point where it can no longer evolve. All future languages
should learn from this: either create a culture where you can be
refactored (as Python and Ruby have done) or allow competitive species
to thrive.
I think that one serious burden of C++ was the lack of a standard library. If I recall it right, Stroustroup wrote in "The Design and Evolution of C++" that he delayed a standard library in favor of multiple inheritance. I would have enjoyed it more the other way. In 1996 I felt that C++ was as a lot more bureocracy in my code with moderate benefits, except for GUI programing, that I did not do. Things that I would have easily done in Ansi C where somewhat shorter to code but not that much easier. Later, I made a project with Borland C++ and STL and had a lot of fun. In the meantime I already had switched to Java, because all the nightmares of a large project I worked in, that caused weeks of debugging and testing, would have just been compilation errors. I had to swallow a virtual machine that I would not have asked for and a poor GUI tolkit, but the language was so attractive. Today I think that JRuby is a fantastic way for a Java shop to lighten up some of the tasks with a dynamic language.
"Java brought the mainstream of programmers into the world of garbage collection, virtual machines and a consistent error handling model"
Not to mention, design by contract. Up until then, only academic languages carried that flag forward. While I salute Java for pushing these things, I'm pretty sure I won't look back and miss the constraints of it. Darwinian language evolution, survival of the fittest features will continue to prevail. It's a good thing languages are replaced, it means we learned something.
This article is presents itself as a history of technical advances of languages but is in fact a record of the egotistical turmoil of seeing your backed technology fade from the mainstream.
As much as the studied decisions of the C++ design committee taught the world about programming languages, their true legacy is those things they taught us not to do.
> <p>It also fooled the Java designers who didn't understand > C++ well > enough. For example, they thought operator overloading was > too hard > for programmers to use properly. Which is basically true > in C++, > because C++ has both stack allocation and heap allocation > and you must > overload your operators to handle all situations and not > cause memory > leaks. Difficult indeed. Java, however, has a single > storage > allocation mechanism and a garbage collector, which makes > operator > overloading trivial -- as was shown in C# (but had already > been shown > in Python, which predated Java). But for many years, the > partly line > from the Java team was "Operator overloading is too > complicated." This > and many other decisions where someone clearly didn't do > their > homework is why I have a reputation for disdaining many of > the > choices made by Gosling and the Java team.</p> > I'm not sure where you got the impression that the choice of leaving out operator overloading was because someone didn't do their homework. I remember asking this Gosling in one of my interviews of him why he left out operator overloading (I don't think that question and answer ended up getting published). What he basically said to me, is what I heard him say in other contexts: Gosling felt that the level of operator overloading abuse he had seen in practice in other languages (I'm not sure if this was just C++, but certainly included C++) had outweighed the benefits of it. That's all. It was a subjective design choice.
Either way it is a tradeoff. Scala allows you to define methods with operator names. This is one way Scala can help you design libraries for which the client code is clear and concise, but I've already seen places where operators were used in places I think non-operator names would have been better. I think it would be tough to allow operator overloading in a language without making it also possible for people to abuse them. And of course one person's example of operator abuse can be another person's lightbeam of code clarity.
Anyway, I think the decision on what to do about operators is a non-obvious choice in language design, and each way you can go has pluses and minuses. One thing Scala does is it really goes in the other direction of letting you use operator characters in just about any identifier. It doesn't restrict you to just methods, and it doesn't restrict you to just things like +, -, *, /, etc. It is a different attitude. One thing I remember Gosling saying is that the one use case he felt was very compelling for operator overloading was numeric analysis. i.e., number crunching apps.
> <p>There are plenty of other examples. Primitives > "had to be included for > efficiency." The right answer is to stay true to > "everything is an > object" and provide a trap door to do lower-level > activities when > efficiency was required (this would also have allowed for > the hotspot > technologies to transparently make things more efficient, > as they > eventually would have). > By trap door do you mean something at the source code level? I asked this question of Gosling also, and he said:
Bill Venners: Why are there primitive types in Java? Why wasn't everything just an object?
James Gosling: Totally an efficiency thing. There are all kinds of people who have built systems where ints and that are all objects. There are a variety of ways to do that, and all of them have some pretty serious problems. Some of them are just slow, because they allocate memory for everything. Some of them try to do objects where sometimes they are objects, sometimes they are not (which is what the standard LISP system did), and then things get really weird. It kind of works, but it's strange.
Just making it such that there are primitive and objects, and they're just different. You solve a whole lot of problems.
This was back in 2001. I'm not sure how the state of the art at that time differs from today, but what the Scala compiler seems to show is that you can model everything as an object at the source code level and let the compiler compile to primitives where possible. This way you get to have your cake and eat it too. Having the cake is that at the source level, you have one consistent object model. But you still get the optimization benefits of primitives in the bytecodes. There's no "trap door" at the source level in Scala, and the compiler seems to do a pretty good job of figuring out when to box and when to use primitives. The one thing that shows up in Scala's object model is that you can't check "value types" (which map to Java's primitive types) for reference identity. They don't have a method for that.
> <p>When I wrote about how badly generics were designed, I > got the same > response, along with "we must be backwards compatible > with previous > (bad) decisions made in Java." Lately more and more > people have gained > enough experience with Generics to see that they really > are very hard > to use -- indeed, C++ templates are much more powerful and > consistent > (and much easier to use now that compiler error messages > are > tolerable). People have even been taking reification > seriously -- > something that would be helpful but won't put that much of > a dent in a > design that is crippled by self-imposed constraints.</p> > My understanding of the decision to go with erasure had to do with Sun had just shipped a new collections library, and they didn't want to ship another one. I think they felt it would be too disruptive for users. But it wasn't just collections that would have needed duplicating. Every library would need to be replicated to be generified, and by that time Java had a lot of libraries. Servlets for example. You'd need a new one of those to generify it. And so on and on.
The downside of erasure is that there's now two different notions of the type system you have to keep in your head, the compile-time one and the run-time one. And that adds complexity. One upside of erasure is that reification would have driven the creation of another version of every Java library designed at that time, which would have added a different kind of complexity. Another upside of erasure I had heard expressed once is that it may leave room for more type system innovation in languages that run on the JVM. I actually heard this as a complaint about the CLR having reified types, in that they kind of constrained languages to just those types.
So again, here's a tradeoff. I would still rather have reified types, but given the situation, I'm not convinced it was a bad decision to go with erasure on the JVM. If they had gone with reification, we'd have a Java API filled with generic and non-generic versions of most things. Yes, because of erasure you have to keep two different models in your head, but I'm able to handle it. Most of the time I don't use reflection, so usually I just have to keep the compile-time model in my head. And once again, Scala showed me that there are ways to get at the erased type information when you need it. It is possible. In ScalaTest, for example, you check that a bit of code throws an expected exception like this:
Here, intercept is a method call that will ensure the bit of code throws a StringIndexOutOfBoundsException. But if you think about it, how could this work because the exception type is specified as a type parameter, which is erased on the JVM. Well Scala provides a way for me to find out at runtime what that type parameter was in the source code, which is basically and end-run around erasure.
I've read a lot of articles about the relative merits of different languages and the 'big idea' with Java seems to be missed by so many authors. Where Java succeeded that so many fail to acknowledge is in the area of team development. It's a language that says you should have the power of a given feature but only if that feature doesn't create problems for other people trying to read and maintain your code. Note at this point that I believe that it has had some significant failures in this regard.
Most languages either tout ease or power. C++ was about power. Want to flex your nerd muscles. C++ is your language. VB was about ease. If you just need to get something up and running in short order, VB's your language.
Java doesn't address either of these well. And I think this is where a lot of people get stuck trying to understand its success. It's not exteremely powerful and it's not extremely easy. Kind of mediocre, one might think. But when you throw a large team into the mix, Java starts to make sense. For example, I've never heard of Bruce's alleged reason for the lack of operator overloading before. I've always heard that it's because it's too easy miss that an operator is overloaded. That is, you are looking at the code and thinking that '+' does the obvious but it does something else that you didn't consider. Personally I am on the fence about operator overloading but I haven't missed it much in Java.
When I read a lot of these kinds of discussions about Java, it seems to me that the author is only thinking about how the language affects the lone developer working in isolation. And a lot of languages seem to be focused on that. They don't consider how the features in the language will affect team development. Java is a language where the designers have considered that. I can look at any Java code snippet and have a very good understanding of what it does, that is, I know exactly what I need to find out to understand it perfectly. I personally might want to change the semantics of a call here and change the meaning of a operator there but I don't want anyone else doing that on my code base.
Having said that, Java ad the Java community has made some big errors in this department. Reflection greatly reduces the transparency of code and byte modifying tools are likewise obfuscating. The combination of these two tools are the foundation of pretty much all of the popular Java frameworks that Java developers are expected to use. Generics and other 1.5 features greatly increased the complexity of the language. This makes it harder for developers to communicate through code.
But the legacy of Java is really that it addresses the problems of groups of programmers and not just the problems of the individual programmer.
> But the legacy of Java is really that it addresses the > problems of groups of programmers and not just the > problems of the individual programmer.
It's always looked much simpler to me. Java was created, from Oak of settop box fame, to run applets. That wasn't working out so well.
Were it not for the Servlet, from which sprang tomcat, jasper, and thence WebSphere, Weblogic, et al we would not be talking about java. The java web server did improve upon Apache/CGI enough to matter. It doesn't improve upon C(++) enough to matter in that arena; I would argue that as a portable assembler/systems programming language it matters not at all.
Most java programmers, few will 'fess up, are [FOLM] Framework programmers acreting ever more code into a giant hairball. Which is why some view java as COBOL with pixels.
Let's not kid ourselves. The 'big picture' with Java is, without a doubt, platform independence via the virtual machine. Don't get me wrong, Java is a pretty good language, but it wouldn't have made a dent without the VM.
The innovation springing from Java, past and present, can all pretty much be attributed to the platform, not the language per se. Java's success on the server, like the client, was at first marketing hype, Java was the steaming cup in the browser... neato. What really gave it traction, however, was platform independence. And what made it stick was Hotspot, which, aside from the VM itself, is probably the most innovative Java technology ever.
Today we see loads of interesting technology in the Java space. But it's not coming in the form of new language features. Instead a significant part of it is coming in the form of new language*s*; languages that could not exist without the VM. We have Scala, Groovy, Jython, JRuby, and many others. I'll go out on a limb and say one of these, probably one we don't know about yet, will someday unite what has become a fragmented Java community. I'll hope for it anyway... who am I kidding...
> and well designed at the same time, you must keep in mind > the primary > design decision upon which everything in C++ hung: > compatibility with > C.
I don't think that C++ is badly designed because of compatibility with C. ADA is compatible with C, but it is a very good language, with strong engineering leading its design.
What is wrong with C++ is source code level compatibility with C. In order to be able to include C headers, C++ maintained the preprocessor system of C. That led to a strange non-context free grammar, which led to a very strange syntax which had to stay compatible with C.
I don't see why we don't have a language that:
1) has the run-time system of C, i.e. no virtual machine. 2) has optional garbage collection. 3) has the template system of C++ which makes the run-time efficient (or better yet, a compile-time language subset). 4) is orthogonal to allocation strategies, i.e. the type of access (by reference/by value) is not hardcoded into the type of the variable. 5) has an orthogonal type system, where there are no 'primitive' types, and everything is a class.
Operator overloading has other issues beyond those specific to C++ (multiple allocation models and lack of GC).
If you have a large undisciplined team, then code quickly becomes unfathomable as developers add various operator overloads classes to save keystrokes -- with little thought to clarity.
Seeing "d = ( a + b ) / c;" could mean an arbitrarily complex and deep code path in any language with operator overloading. In Java you know "there's nothing funny going on on this line", just normal arithmetic.
Sure, if you note that a, b, c, and d are not primitives, then you know there must be something special going on here. There's still the issue that operators are essentially encouraging and rewarding developers to give their methods single character names. That's great for cases where the method really is a mathematical addition or multiplication, but even then it gets messy -- for instance, vectors have dot and cross products, not just one multiplication operation.
Operator overloading is one of those features that's great if you're on a one man project where you can make all these decisions wisely for yourself as the maintainer/reader -- and a horror for a large team.
> Operator overloading is one of those features that's great > if you're on a one man project where you can make all > these decisions wisely for yourself as the > maintainer/reader -- and a horror for a large team.
I think this is excessive worrying. Python has operator overloading and I have never heard of a single horror story about it.
> Let's not kid ourselves. The 'big picture' with Java is, > without a doubt, platform independence via the virtual > machine. Don't get me wrong, Java is a pretty good > language, but it wouldn't have made a dent without the VM.
I'll go out on a limb and say that most Java shops are enterprise IT. A lot of these shops couldn't care less about platform independence. Some do but it's not that big of a deal for most.
What has made Java a good fit for these shops is that it is easy to follow Java code. Yeah, you can write unmaintainable code in any language but you have to work at it in Java. It doesn't provide a lot of the features that are so easy to abuse in other languages.
From an individual's perspective, this is terrible. If I don't want to use a feature I won't use it. Who is Sun to tell me what I should do and shouldn't do? But when you take a group of developers who have different ideas about what a good approach is, the game changes. Having a limited number of ways to do things starts making sense.
This isn't just my theory. Go on Sun's Java Forums and look at how people are advised not to use esoteric language features or tricky approaches just to save time or effort. Clarity is generally considered to be the most important aspect of Java code. The time it takes to add new features to Java is centered around whether the feature adds something significant to the language that can't easily be done in other ways.
> I'll go out on a limb and say that most Java shops are > enterprise IT. A lot of these shops couldn't care less > about platform independence. Some do but it's not that > big of a deal for most. > > What has made Java a good fit for these shops is that it > is easy to follow Java code. Yeah, you can write > unmaintainable code in any language but you have to work > at it in Java. It doesn't provide a lot of the features > that are so easy to abuse in other languages. > > From an individual's perspective, this is terrible. If I > don't want to use a feature I won't use it. Who is Sun to > tell me what I should do and shouldn't do? But when you > take a group of developers who have different ideas about > what a good approach is, the game changes. Having a > limited number of ways to do things starts making sense. > I think Java code can be easy to follow in the sense of all the details of how the code works, while at the same time harder to follow in the sense of what the programmers higher level intent was for that code. This may be the right tradeoff for enterprises, but I see it as a tradeoff. Because Java's syntax is more rigid, when you look at Java code you can figure out what's a method call and what's a field access, etc. But you also get a lot of boilerplate and verbosity that can hide the intent of the programmer.
One of the things I've noticed about Scala is that it can go in the other direction. The syntax is very flexible such that you can get rid of boilerplate and verbosity. When you design a library in Scala, you can simmer the client code down to just the most intent-revealing miminum. I think that helps readability in the sense of figuring out the programmer's intent. But it hurts readability in the sense of figuring out how the code is actually working. There are ways to figure out how things are working, of course, but it isn't as obvious up front as in Java code.
I suspect the readability of Java's rigid syntax was indeed part of its success, but the verbosity that goes along with that syntax predictability is now one of the main complaints people have about it compared to other languages.
Flat View: This topic has 210 replies
on 15 pages
[
123456
|
»
]