Friday, May 30, 2008

Frustrated when people just don't get it...

Ok, I'm not one of those "Ruby (or whatever language) will kill java" people. I don't like Java. I'm a proficient Java programmer. I probably know it better than any of my preferred languages, since I've had to use it so much for work. I don't wish it would die, though I do wish I could get away from it for a while...

Still, I found myself getting a bit hot under the collar when I read 13 reasons why ruby, python and the gang will push java to die… of old age.

Just look at some of the things that spiked my blood-pressure:


Reason number 1: Syntax is very important because it builds on previous knowledge. Also similar syntax means similar concepts. Programmers have to make less effort to learn the new syntax, can reuse the old concepts and thus they can concentrate on understanding the new concepts.

Reason number 2: Too much noise is distracting. Programmers are busy and learning 10 languages to the level where they can evaluate them and make an educated decision is too much effort. The fact that most of these languages have a different syntax and introduce different (sometimes radically different) concepts doesn’t help either.

Reason number 6: There is no great incentive to switch to one of the challenger languages since gaining this skill is not likely to translate into income in the near future.


To me, this seems like arguing for ignorance. Learning languages is too hard. Learning a new syntax is too hard. Why bother.

And, perhaps it's true for a certain number of programmers. Heck, I know people at work who feel exactly this way. It may even be the majority opinion, for all I know.

But that doesn't make it a good attitude.

I prefer to follow the Pragmatic Programmer's advice. I try to learn a new language every year.

Now, I don't expect to use all these languages on real projects. I'd like to, don't get me wrong. I can often be found whining about how Project X would be so much easier if we could only use Language Y. But that's beside the point. I study programming languages, because studying a variety of programming languages makes you a better programmer, even if you never use them professionally.

Let's move away from computer languages for a second and look at natural languages. It's often said that languages affect how we think. My wife is Japanese, and I know enough Japanese to get by (though her English is much better than my Japanese will ever be). There are definitely concepts in Japanese that I just cannot express in English. We just don't have the words. Sure, I can describe the idea in a round-about way. But I cannot say it directly.

This is often frustrating. If I've been using Japanese a lot, I often find myself unable to express certain feelings accurately in English. Now, the truly odd part is, I never had a need to express those feelings until I learned Japanese. My mental model of the world simply did not include those ideas, at least, not as a crisp, well-defined concept.

The same thing happens with programming languages. Knowing how to decompose a problem for a functional language is very different than knowing how to decompose a problem for an object-oriented language. It provides an entirely different set of tools for breaking down and understanding complexity.

Also, learning a new language often forces you to examine your preconceived notions. Often you find that your preconceived notions are just wrong.

And here's the best part. Just because you cannot use the tools and techniques from one language directly in another, doesn't mean the effort was wasted. Often you can borrow a really useful gem or two. Having programmed in Lisp, I now find it much easier to program recursive traversals of complex data structures. After using Smalltalk for a while, I find I write much smaller methods in Java--which makes my Java code much easier to read and maintain.

So, yes. Learning one programming language is hard. Learning a second is somewhat easier, but it's still a chore. But, by the time you get to your fourth or fifth language, it gets a lot easier.

Finally from a section labeled "Why many of the new languages will never be popular"


Some languages have very difficult to “get” concepts. For example most of the supporters of functional languages are proud of how concise statements are in their language. This is not really useful for somebody used to think procedural or object oriented. If the only gain from binding and twisting your mind is typing a few less lines then any experience programmer will tell you that this is not the main activity. Writing the first version is just a small part of the life cycle of a project. Typing the code is even smaller compared with the design time. From the second version the game changes dramatically. Maintainability is way more important. Also very important is to be able to add features and to refactor the code. Readability is paramount from version two, and for both development and support teams.


This entirely misses the point. First, I'll repeat my argument from above. binding and twisting your mind is good because it forces you to approach problems from a new perspective. That should be justification enough. But wait, there's more!

Functional languages (or any expressive language) often allow you to model complex ideas concisely, but we're not interested in just saving keystrokes. Concise languages have less code. There is a direct correlation between the size of your codebase and the number of bugs in your program. There's also a correlation between size and maintainability.

Let's make this concrete. When I took my Natural Language Processing course, I did all the assignments in Lisp. The TA was a friend of mine, and he often commented that my code was an order of magnitude smaller than the other students. I could do more with 200 lines of Lisp than they could do with 2,000 lines of Java. That's not "just a little typing." My code had fewer bugs. It typically ran faster, and I never had memory issues that often plagued other students.

Now, maybe that's not a fair comparison. These projects fell squarely within Lisp's sweet-spot: command line tools that manipulated lists of symbols. If I was building a UI, I'd probably pick a different tool.

Now, before someone else mentions it, I admit it. Nothing I have said here contradicts 13 Reason's main argument. These languages that I love may never become popular. They may never kill off Java. But, does that really even matter? Popularity has never been a measurement of quality. Trust me. My daughter loves Hannah Montana. As far as I can tell, every tween girl in the country loves Hannah Montana. Yet, I know in my heart that it is not quality music (or quality TV for that matter).

I do hope we move from a monolithic language approach to a polyglot language approach--but Java doesn't have to die for that to happen. It just needs to learn to share.

-Rich-

Tuesday, May 13, 2008

Two new articles.

I just received my May issue of MacTech Magazine, and I have two articles in this issue. Part II of my RubyCocoa article and an overview of the iPhone SDK (at least, what I can talk about without violating my NDA).

Hopefully I'll be able to talk a lot more about the iPhone SDK soon. However, I can safely say, good things are coming.

-Rich-

Saturday, May 3, 2008

The problem with JUnit

I'm a big unit test fan. However, I often feel that the goals of testing run counter to the goals of good Object Oriented design. At least in static languages like Java.

Object oriented design is based on the idea of encapsulating behavior. Testing is an attempt to reveal and examine behavior. You can't have both.

I often run into this problem when implementing algorithms inside an object. From an OO perspective, I often want to declare all the methods that perform the scary math as private. They should never be called from the outside. However, I really need to test them somewhere.

Problems also crop up when calling methods that change an object's state. The state is not always directly exposed, and sometimes it's nearly impossible to indirectly detect the change.

I don't feel the same tension when testing in dynamic languages like Ruby. Usually, these languages have a stronger reflexion or metaprogramming functionality. They let me safely encapsulate the things that should remain hidden, but still allow me to pry my objects open and root around in the guts.

I can write tests using reflection in Java, but the syntax is painful. I can get around that with a library of helper methods--but for some reason, reflection makes many Java developers uncomfortable, even when only used in testing. And, truth be told, it never feels as natural as the dynamic language tests.

Similarly, I can use a mixture of interfaces and mock objects (for example, using EasyMock) to let me examine the inner workings of a class. Often this simplifies writing the tests, but the results can be incredibly brittle. Building useful mock objects generally requires a detailed understanding of our classes inner workings. If I change the implementation, I will break my mock objects, and then break my tests--even if the new version is functionally identical to the old.

Of course, even the reflection-based testing is somewhat brittle. Reflection, by definition, looks at the implementation, not the interface. But, our interactions with the implementation tend to be more surgical and specific. So, while these tests are somewhat brittle, they tend to be more resilient than the mock-object versions.

I can try to get around all these problems by redesigning my objects. Move my algorithms to a utility class, where they are publicly exposed and easy to test, or add accessors to the internal state, even if the accessors should never be used for non-test code. While this works, it can lead to unnecessarily awkward designs, or exposing more of the implementation than is really necessary.

I think, ultimately, a mixed approach is best. Like many software engineering tasks, we must examine our design and decide which sections are likely to change, and which are likely to remain the same. Static sections can often be effectively tested using reflection or mock objects. Sections that are likely to change should be encapsulated and tested as separate objects.

The trick is then to successfully separate one from the other.