 | Level: Introductory Bruce Tate (bruce.tate@j2life.com), President, RapidRed
20 Sep 2005 The Java™ programming language is powerful, but it has significant limitations for lightweight development. For certain problems, other programming languages such as Ruby may lead to better productivity. Find out what's important for productivity in an application's language.
I was on a wide, powerful river in Colorado. Used to paddling in steep, narrow creeks, I found this experience unnerving and awkward. Although I'd paddled plenty of class V rapids, this mere class III river gave me fits at first. Eventually, I learned to use the power of the river to my advantage. I also found that the more rivers I paddled, the less time it took to adapt.
Programming is like that, too. You might be able to navigate your way through Java programming problems quite easily, but find other languages cumbersome and awkward. In this article, I'm going to take you out of your comfort zone.
All the previous articles in this series have been limited to Java programming. But in this article, I discuss a heretical idea: Sometimes, the Java programming language can hurt your productivity.
A brief history
Before I poke a few holes in the Java technology dirigible, I should remind you of a little history. The Java programming language came from an unlikely source (Sun Microsystems) to vie with the dominant language controlling the server side (C++) at a time when a programming paradigm was on the way out (procedural client-server code). The Internet exploded, and suddenly Netscape, with its built-in Java virtual machine (JVM), was on every desktop. To achieve critical mass, the Java language embraced the C++ community with several important compromises:
- It was statically typed like C++, rather than dynamically typed like Smalltalk.
- It allowed both primitives and objects, like C++.
- It embraced the C++ syntax and control structures.
To capitalize on some good timing, Sun kept things just close enough to C++ to capture that community. The Java language didn't have to be better than all other object-oriented languages. It just had to be better than C++. Now, some of those compromises are starting to hurt the Java language.
Closures
The Java language allowed higher abstractions than C++, such as managed memory and strings. Still, the Java language has a long way to go to match the higher abstractions of languages like Ruby or Smalltalk. Take iteration: To iterate over an array with Java technology, you'd type something like this:
for(i=0;i<array.size();i++) {
System.out.println(array[i]);
}
|
In Ruby, you'd type something like this:
array.each {|element| puts element}
|
You don't have to worry about how the iteration happens, so you expend much less effort. You just pass the code in braces (we call this block a closure) to the array, and each method knows how to iterate over itself. Java collections that require iterators are even worse than the example I've shown here because you're forced to declare and use a separate iterator. Of course, Ruby also has loops and iterators, but you don't use them as much because you have a higher abstraction available in the form of a closure.
Closures are not just for iteration. They make many problems easier to solve:
- Iterating through a database is easy:
dbh.execute("SELECT * FROM words") do |s|
s.fetch_array do |row|
puts row.join(", ")
end
end
|
- Demarcating transactions is natural:
Account.transaction(david, mary) do
david.withdrawal(100)
mary.deposit(100)
end
|
- You can hide the tedious details of resource management and exceptions within a main body of shared code. Using this strategy, file processing is much more concise.
Using closures alone, you can dramatically limit the number of lines of code you write. Of course, you can simulate closures in the Java language with much more code using anonymous code blocks, but such a technique is awkward to write and read.
Continuations
A continuation is another abstraction that's growing in importance. A continuation lets you capture execution state (with instance variables and a call stack) at any point in time. You can return to that point in time much later. For Web servers, you might capture a continuation before you ask the user for information. You can then resume the continuation if and when the user returns control to the application server. Using continuation servers, such as Seaside and Iowa, you can code stateless Web applications just as you would any other stateful user interface.
Listing 1 shows an example of the checkout method for a shopping cart in Seaside.
Listing 1. Checkout method in Seaside
go
| shipping billing creditCard |
cart _ WAStoreCart new.
self isolate:
[[self fillCart.
self confirmContentsOfCart]
whileFalse].
self isolate:
[shipping <- self getShippingAddress.
billing <- (self useAsBillingAddress: shipping)
ifFalse: [self getBillingAddress]
ifTrue: [shipping].
creditCard <- self getPaymentInfo.
self shipTo: shipping billTo: billing payWith: creditCard].
self displayConfirmation.
|
This Smalltalk code probably looks unnatural to you. The punch line is that this method calls tasks that solicit input from the user. Based on the user's response, the flow of the application changes. For example, if you ask for a separate billing address, you get an additional billing window. A multipage shopping cart checkout is reduced to a single-page method.
Continuations are managed under the covers. You don't have to know anything about state. The usual alternatives in the Java language look brutal by comparison. You need to decide what data to keep and where to keep it each time you process a request. Frameworks like Cocoon simulate this control, but without continuations, it's much more difficult, and you don't get anything remotely similar to the power of Seaside.
Metaprogramming
Metaprogramming, or programs that write programs, is a powerful concept. You've seen two examples in the Java language:
- Metaprogramming in Hibernate with byte-code enhancement and reflection lets you add persistence to objects without forcing you to write a single SQL query.
- Metaprogramming in Spring lets you add services to plain old Java objects (POJOs) without cluttering them.
In fact, the Java community is spending increasingly more time on metaprogramming to provide better transparency of services, but the Java programming language isn't a particularly good language for metaprogramming. In other languages, metaprogramming is even more important. In Ruby, it provides the explosive power behind the growth of the Ruby on Rails framework. The Rails sensation has been sweeping across this industry because metaprogramming allows the framework's users to build in incredible functionality with very few keystrokes. The framework uses naming conventions, smart defaults, and metaprogramming to discover the contents of a relational database table, then build a model that has a property for every column in the database dynamically. This process occurs at run time, so changes in the database schema can be reflected in the user interface, if you so desire.
As in the Java language, you can find metaprogramming frameworks to handle persistence, transactions, and XML. In fact, as you've seen in the past three articles of this series, Java developers increasingly seek tools that provide better transparency. Of course, to do so, coders must stretch the Java language beyond its intended purposes. To get better transparency, you need to use techniques, such as code generation, byte-code enhancement, proxies, interceptors, reflection, and now aspect-oriented programming (AOP). Frameworks like Spring, Hibernate, JBoss application server, and HiveMind increasingly use these techniques to give you better transparency as you create more sophisticated enterprise applications.
Languages such as Lisp, Python, Ruby, and Smalltalk all make transparency much easier to achieve by giving you better reflection and opening up the structure of a class. Let's take a couple of examples in Ruby.
Reflection
Reflection is the ability to use an API to discover the basic building blocks of a given object. In Ruby, reflection is easy. Everything is an object, and everything is a class. You can ask any class about its class, its parent class, or the methods it supports. For example, you could ask the number 4 about its class and the methods it supports using the code:
irb(main):001:0> 4.class
=> Fixnum
irb(main):002:0> 4.methods
=> ["%", "between?", "send", "<<", "prec", "modulo", "&",
"object_id", ">>", "zero?", "size", "singleton_methods",
"__send__", "equal?", "taint", "id2name", "...and so on.
|
In Ruby, it's easy to use the results of reflection. To invoke a method called to_s (convert to string) on an object, you can just send it a string:
irb(main):003:0> meth="to_s"
=> "to_s"
irb(main):004:0> i=4
=> 4
irb(main):005:0> i.send meth
=> "4"
|
So you can see the simplified reflection. To make matters easier, you don't have to worry about primitives or special cases for enumerations, arrays, or generics. It's just pure, natural object-oriented bliss. Java examples are much more tedious. Still, they are necessary because you need reflection to achieve better transparency.
Ruby shines in still another place. In Ruby, you can hook into any part of an object. For example, interceptors are particularly easy. Because Ruby lets you modify the methods on a class or object on the fly, you can simply rename an existing method and insert a new method with the old name. The following extreme example, based on an example in Dave Thomas' book Programming Ruby intercepts the new method on the Class class:
class Class
alias_method :original_new, :new
def new(*args)
result = original_new(*args)
print "Interceptor on Class.new "
return result
end
end
|
You can use the same techniques to introduce new methods or change the behavior of old methods. AOP and dependency injection haven't received as much attention in dynamic languages like Ruby, mostly because they're not as necessary.
Other factors
The Java language has other limiting factors besides reflection and transparency. Taken independently, none of these limitations is critical. But taken together, they add up quickly:
- Static typing
You must declare every variable and every parameter, which requires more time for much less benefit than you think, if you accept the idea that automated unit tests should catch many of your mistakes.
- Not fully object oriented
You must deal with all the exceptions, such as primitives and arrays, when you write reflective code.
- Long feedback cycle
Many other Web frameworks let you change code, save, then click Reload in your browser to see changes immediately.
- Literal structured data
The Java language doesn't express structured data well, so you see XML everywhere -- even where there's little added value.
In short, the Java language just isn't a very productive applications language. The founders made some wise compromises to wrestle control away from C++, but we're starting to pay for those compromises. In my book Beyond Java, I discuss these problems in detail. I still use the Java language for most of my projects. It's easier to find frameworks doing exactly what I need. I can quickly find programmers and tools. It's a proven programming language. Many of my clients have too much legacy code and momentum to change pervasively.
Still, I've begun to use different languages for certain paid projects. Ruby on Rails is effective for Web-based user interfaces on a big relational database. Seaside works well for applications with limited scalability and availability demands but with sophisticated navigation. Many languages handle rich client development better than the Java language.
I also find that startup companies are willing to accept more risk if the rewards are greater. When productivity is your overriding concern, teams are small, and you're solving a problem that's well suited for a dynamic language, it makes sense to use one. I saved one client 60 percent of his projected budget by moving him to a more dynamic language and a framework that fit his project better. I enabled another client to retain a 30-percent larger staff by moving the organization to a language with less exhaustive education requirements than the Java language.
The bottom line? Sometimes, it pays to look beyond the Java language when you're thinking about lightweight development. In the next few articles, I'll move beyond the Java language to look at a new language and a few lightweight frameworks.
Resources Learn
-
Beyond Java, by Bruce A. Tate, looks at why Java was successful, and also the limitations in the language for lightweight development. Then, it explores several emerging programming langauges, and frameworks that could serve as catalysts for the next major programming language.
-
Better, Faster, Lighter Java, by Bruce A. Tate and Justin Gehtland, (O'Reilly, 2004) provides a good overview of lightweight development.
-
Check out HiveMind for examples, blogs, and code.
-
Programming Ruby (Pragmatic Bookshelf, 2004) by Dave Thomas is the best Ruby book out there.
-
Read Continuations Made Simple and Illustrated for information about continuations in the Python language.
-
Martin Fowler has written a good article about closures.
-
Ruby on Rails is a rapidly growing framework that's far more productive than the Java language for certain problem domains.
-
Read "Fast-track your Web apps with Ruby on Rails" for more information about how you can use this framework for rapid development.
-
Seaside is an amazing framework based on the Smalltalk language and continuations.
-
The developerWorks series "AOP@Work" offers a deeper look at AOP for those already familiar with the concept. Mik Kersten kicked off the series with "AOP tools comparison."
-
Lightweight development is a huge topic, and developers throw the term around so often that it's hard to tell what it means. "Secrets of lightweight development success, Part 1" introduces the core principles and philosophies behind the movement.
-
Heavyweight architectures, such as Enterprise JavaBeans, can be overkill for everyday problems. Part 2 of the series introduces lightweight containers and explains how they can provide the services your business needs without tying you to a given programming model.
-
Learn the basics of lightweight containers in Part 3 of the series.
- In Part 4 of the series, three popular containers are compared: Spring, HiveMind, and PicoContainer.
- Lightweight development works best with a lightweight process, but it can be tough to get a conservative company to adopt agile methodologies. Learn how to propose and promote lightweight processes in your organization in Part 5 of the series.
- In Part 6 of the series, compare and contrast Enterprise JavaBeans, Hibernate, Kodo JDO, and iBATIS to help choose the best persistence framework.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Get evaluation products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere® and start building applications and deploying them on IBM middleware. Select the Linux® or Windows® version of the Software Evaluation Kit (SEK).
-
Innovate your next open source development project with
IBM trial software, available for download or on DVD.
Discuss
About the author  | 
|  | Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released Spring: A Developer's Notebook. He spent 13 years at IBM and is now the founder of the J2Life, LLC, consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby. |
Rate this page
|  |