One of the best parts of being a software developer is occasionally getting to change the way you think in a fundamental way. Besides learning about new domains or getting new development or infrastructure tools, every once in a while you become aware of a compelling new language that allows you to re-experience the joy of learning to write software. I’ve enjoyed the feeling four times in my life (I’ve learned many more languages than that, but not all were joyful experiences) and now my programming-consciousness is expanding as I learn Ruby.
If you haven’t heard much of Ruby, allow me to clue you in. Ruby is a dynamic object-oriented scripting language. It was created by Yukihiro “Matz” Matsumoto in Japan in 1993, and first released publicly in 1995. It escaped note in the West for a while primarily because there wasn’t a good translation of the documentation from Japanese. The popularity of Ruby has now started picking up as more information on the language began to appear - especially when pragmatic programmer Dave Thomas wrote about the language in his book known affectionately as The Pickaxe - Programming Ruby. Today, Ruby continues to gain momentum: the ascent of the language accelerating.
For the Java crowd, the Ruby ‘object-oriented’ concept is object-oriented in the extreme: everything is an object. Not just the domain entities, built-in classes or library widgets - I mean everything. At the high end of Ruby programming, you can write objects that write objects and use them right there; you can add and remove methods and mix-in modules at run-time; your variables don’t have static types and so can hold any object that you assign to them; you can use duck-typing or even call possibly non-existent methods on an object, catch the exception if it occurs and do something else; and you can even pass blocks of code as objects to methods. And at the low end - there is no low end: there are no primitives types in the language. It’s all just objects.
What really makes Ruby tick is the dynamic programming paradigm. Ruby isn’t compiled as Java is. In Ruby, you don’t know for sure if your code will run correctly until you actually run it. Well, you do, but only because you wrote and tested it - not just because a compiler told you your statically-typed program didn’t generate any compile errors. Some of you may immediately ask, “What if I make a horrible mistake?” This isn’t really a new problem - we all know even the best Java code can still crash, even though it compiles just fine. “So what should I do?” You should unit test everything! You do use unit-testing to find your errors, right? And you do create code agilely so that the horrible mistakes are found early, right? If not, it’s time to make these changes before worrying about the false security of type safety. Anyway, the good thing is that when you’re around Ruby for a while you stop thinking about types in the same way you have been - they just sort of go away.
Ruby is a language that focuses on convention over configuration. Instead of writing a lot of machinery into your code, in Ruby you get to piggyback on the shoulders of giants. By following conventions, using mix-ins, duck-typing and passing code blocks, your Ruby code can immediately work within existing frameworks. If you think in the Ruby way when you’re writing your code, others can use your objects seamlessly. Perhaps this is most evident in David Heinemeier Hanson’s Ruby on Rails. Rails will blow your mind if you’re used to doing web application development with J2EE, beans, struts or Microsoft’s .Net. By using the Rails framework and Ruby, you can create serious web applications in hours and days instead of weeks and months. Really. The Rails stuff just works, and much of this is due to the versatility of Ruby.
I am trying hard here to convince you that you need to at least look at Ruby. When I first heard about Ruby, I thought, “I don’t need a scripting language, I’m writing Java. I’m busy.” Then I took some time, looked and learned, and was enlightened. Ruby works. Ruby is cool. You need to learn it and make your software life better and more fun. It isn’t going to replace Java, but it is going to have a place in your systems. And it will put a smile on your face.
Although Ruby is free, learning Ruby requires a significant personal investment. Part of my path to Ruby was by way of re-coding some software I’ve been carrying around and evolving for the last thirty years, and I’m going to discuss one of those efforts here. I’d written much of this code as functions and objects with various levels of sophistication, generally in C, Objective-C, LISP, C++, Java and C#. I’ll focus on two of these objects, the Duration and the Stopwatch. For each I’ll start with the Java class and then work through the transition to Ruby. I won’t go deeply into the details of the Ruby language, as others have already done a good job of that, but instead I’ll concentrate on the change in perspective and the different feel of developing in Ruby.
Duration.java
to duration.rb
The first class I’ll discuss is Duration
, an object I’d brought into Java from my Objective-C and C++ code written ten or so years ago, and was originally formed from C functions I pulled together fifteen years before that.
One good thing about long-lived code like this: it’s solid.
In one form or another it’s been tempered through use for a very long time.
I also chose Duration
because I usually find fairly minimal treatment given to elapsed time in most standard libraries.
A duration measures elapsed time.
It’s a quantity of time-passage that can stand alone (“I’ll be just a minute”), exist relative to a time (“two hours from now”) or be defined by a start and end time (“between ten and eleven-thirty”).
A duration can be added, subtracted, broken into components and compared.
But despite the fact that many of the real-life things we do every day have temporal components, a duration does not typically get first-class object consideration.
In Java, nearly every instance of elapsed time I’ve run across is a simple rendering of the difference between two Date
objects.
I’ve found raising a duration to first class status is quite useful.
When I code an object with the potential for broad use, I spend a little bit more time on it. When I’m not sure how I’ll reuse something, but I’m pretty sure I will, I go the extra mile. The Java code I started from includes seven groups of methods for various purposes: constructing, getting, setting, accumulating, rendering, comparing and rounding. Before I go into the Ruby, lets take a look at the Java code.
First, instances of the class can be written out and compared with other instances - the class inherits from Serializable and Comparable.
Enabling serialization is virtually transparent, while comparability just requires implementation of a compareTo
method.
Next, the basis for units is set into place.
The elapsed time is kept in milliseconds, as that is the lowest common denominator in Java’s Date
class.
True, a java.sql.Date
records nanoseconds, but my class was never modified to manage more than the resolution in java.util.Date
.
Milliseconds, seconds, minutes, hours, days and weeks are covered, but weeks are optional and off by default - many applications required days as the coarsest resolution - only a few needed weeks.
A switch at the class level allows weeks to be turned on or off for new instances, and a switch at the instance level sets the preference of the instance.
Years and months are left out on purpose because their duration is not absolute - years vary in duration because of leap years, and months can have 28, 29, 30 or 31 days - there’s no consensus on their measurement.
There are three constructors: empty, by millisecond, and using another duration.
The static between
methods for two Date values and two millisecond values may also be used to create an instance.
Getters get the duration in qualified elapsed-time value (translated to milliseconds), setters set it similarly with an elapsed-time value or to the same value as another duration.
A few extra getters are also available: getPart
returns the given part (like the minutes part or the hours part), and getIn
returns the full number value in the specified unit (9000 seconds for 2.5 hours, for example).
Accumulation is done using the add
method, adding an elapsed-time value or the value of another duration.
The qualified elapsed-time values are just transformations into milliseconds: 3 hours, 10 minutes, 52.53 seconds would be translated to 11,452,530 milliseconds.
Rendering is just a toString
method with a specification of the highest whole units being passed in (days by default, weeks if they’re enabled).
Creating and using a DurationFormat
object could certainly augment the object, but I never needed to take it that far.
Rounding is handled by a standard technique: biasing the value, removing precision and then promoting it back.
All in all, a good sturdy class with enough configurability and access methods to make it generally useful, all designed to complement Date
s.
Now enters Ruby
I decided to take the transformation a step at a time, starting with a direct Ruby port of my Java Duration
class, and refactoring the code in successive iterations into a more Ruby-like state.
This was surprisingly easy to do as there’s not really anything in Java that Ruby can’t handle - although a good refactoring editor for Ruby would have been quite useful.
(It’ll happen soon enough, I’m sure.)
During the iterations, I also looked at my old non-Java code for Duration
and other elapsed-time functions to get the insight I needed for better overall results.
Right from the start things felt a little weird because I’m not yet a native Ruby speaker, but somehow more natural as well.
Complementing Ruby’s Time
class was my goal, and getting to deal with time values in seconds instead of milliseconds was quite comfortable.
Since I generally think in whole seconds with fractional amounts, and Ruby uses whole seconds with fractional amounts, no more having to use milliseconds made me happy.
Interestingly, Time
in Ruby has microsecond resolution instead of milliseconds because underlying Ruby’s Time is the good old C tm
structure.
So to coexist in Ruby more naturally, I modified the class to be seconds-centric with a resolution to microseconds, extended the list of constants to include microseconds and adjusted the different methods to take in and convert to and from seconds instead of milliseconds.
While I was looking at the unit constants, I knew that in my pre-Java elapsed time code I’d used an enumeration.
Java 1.4 and it’s predecessors didn’t contain enumerations, static final constants being the preferred mechanism instead.
The base Ruby doesn’t come with enumerations either, but a quick Google search led me to something that astonished me.
I knew that Ruby allowed you to modify classes at runtime - to add and remove methods and variables at the class and instance levels - but to my surprise I found a small chunk of code that Brian Schrer had responded to an email with in which someone had asked how enumerations could be done.
Brian extended the Object class with enum
and bitwise_enum
methods.
By using this, any class that inherits from Object
(and that’s all of them) can do enumeration - effectively extending the language.
I encapsulated his Ruby into a file and turned my Java static final constants into a Ruby enum
.
The next thing I considered was Ruby argument defaulting. One of the few things I really liked about C++ was the ability to easily specify defaults on functions arguments. In Java this can be especially painful: you either end up with a lot of method variants (a pain for the class developer), calling general-purpose methods with a lot of arguments (a pain for the class user), or calling a lot of small methods (a dangerous pain if they have to be called in a particular sequence or if something important accidentally gets left out.) In the definition of a Ruby method, the ability to assign a default to an argument reduces the pain considerably. In fact if all the arguments have been defaulted, nothing more than the method’s name needs to be specified to make a call. By picking good defaults, classes can be used with ease - using them in the future can be much simpler. Thus with power like this, the trick becomes picking these good defaults - for the software designer, serious user-empathy is required.
Almost all of the methods in my Java code that had to do with passing and retrieving elapsed-time values also included units - and in the places they didn’t, milliseconds were always assumed.
In Ruby I can specify the units on all of my elapsed-time methods with seconds as the standard default, leaving the user free to ignore them altogether unless something other than seconds are used.
However, I decided to leave the usec
method (to return the seconds fraction as whole microseconds, as the Time class does) and add a usecs
method, returning the value in microseconds.
Technically I didn’t have to have usecs
, but I’ve found this sort of method useful in the past.
The thought occurred that generalizing units throughout Duration
might be useful, but after further consideration, I felt that would be too big a break from the Time
class.
Since Ruby is a dynamic language, its nature is to dispense with static typing: everything is just objects with a rough consensus of convention overlying them.
If the Ruby object you’re interested in responds to a method, you trust that Ruby conventions have been followed and that what you’ve requested from the object is what you intended.
It’s convention like this over a configuration like static typing and interfaces that make Ruby simpler.
Take the to_seconds
method, for example - I created a static to_seconds
method using some of these conventions to transform its object argument.
If the object responds to the seconds
method, the value of calling the method on the object is returned.
Otherwise, if the object responds to usecs
, the value is converted to seconds and returned.
Otherwise, If the object being passed in responds to the to_f
method, conventionally returning a floating point value, that value returned.
If none of these conditions are true, the object is assumed to be okay by itself and is returned.
The conversion is lightweight and I use it when incoming values are received - in instance methods, class methods and constructors.
The beauty of this is that the user can send units or not, or send numbers or not, and by convention everything will come together.
And if it doesn’t, you find out in unit testing.
A quick few words about unit testing: Just do it.
Ruby has a set of easy-to-use unit testing objects that work along the lines of Java’s JUnit.
You write regular Ruby methods with assertions and build up test cases and test suites.
Then when you make a change, you run your unit tests, quickly fix whatever is wrong, add more tests and move forward.
This keeps you from breaking your code, and as a side benefit, shows others how to use it.
It may seem like extra work, but it pays off very quickly.
It was at this point I decided to write some unit tests for Duration
; I hadn’t written any for the Java code because of it’s evolutionary past, but my Ruby code now has a good test suite that I’ll be able to certify with whenever I make changes.
One of the guiding principles of Ruby design is Don’t Repeat Yourself or DRY, for short. The idea is a really good one. By repeated application of the DRY method in refactoring, methods are decomposed into smaller and smaller constituent methods. In this spirit, good Ruby objects typically have many short methods that each do a simple thing very well. More complex methods are composed of coordinated calls to these simple methods, and so on up the chain. It’s a variation on the old problem solving strategy: divide and conquer. An argument might be made that performance suffers when you write this way, and though this might be true in very compute-heavy applications, in those cases you’re probably not going to write code in either Java or Ruby (at least for now). Interpretation at any level (including in Java byte code or Ruby script) is still not as fast as something closer to the machine. (Just so you know, Ruby is implemented in C and so gives you an escape hatch into high-performance if you really need it.) By adhering to the DRY philosophy, you’ll find yourself with more easily-maintainable code that works well.
Looking over my code with DRY and convention in mind, the Java getIn
and getPart
methods were renamed and recoded into truncate and part.
The truncate
change was a no-brainer - by convention that’s what truncate does to a value.
The part
method returns the upward-clipped and downward-truncated value.
These and the to_i
method (that returns seconds part, by convention), usec
(that returns microseconds part, by convention), usecs
(an unconventional shorthand) and to_s
(that returns a string rendering, by convention) are all implemented at successively lower levels using to_f
(that returns seconds and fractional microseconds, by convention), the %
operator and a hash that maps from unit to amount of time in seconds.
The thought is that if your Ruby class participates in shared conventions, it will stand a better chance of being used, and used correctly, without confusion.
It’s the same deal with the +
and -
methods.
They return a new instance whose value is the sum or difference of the current Duration
with the supplied value.
The add
and subtract
methods return the same things as the +
and -
methods respectively.
But the add!
and subtract!
methods are different - they change the calling object’s value.
By Ruby convention, methods that end with an exclamation point change the object in place if there would otherwise be confusion.
The round
and round!
methods also exhibit this behavior.
And interrogative methods - methods that return true or false - typically end in a question mark, as we see in the use_weeks?
and uses_weeks?
methods.
But there are no hard and fast rules: conventions are still just guidelines. For instance, not all methods that change the object have the exclamation point: the set
method, for instance, implies the change though it’s name.
The exclamation point and question mark suffixes just help make code easier to understand.
The last tidbit is the funny looking <=>
method (the spaceship).
This is the comparison operation that the Comparable
mixin is looking for.
By defining it and including the Comparable
mixin, you get the mathematic relationship operators <
, <=
, ==
, >
, and >=
defined in your class for free.
Of course, you could always define one of your own variants of these methods, but you just need to do it after you’ve defined <=>
.
Ruby reads the code it executes sequentially, and you’re free to redefine anything you’d like, and it will happen in the context of what’s transpired to that point.
Remember, dynamic interpretation not static compilation: in Ruby, your software’s meta-level is as fluid as your domain level.
The flavor of the object is now Ruby, and it feels about right.
The conventions have been followed, and the Duration
object is complete, with supporting classes and (exhaustive) test cases.
There’s a lot of meat on these bones - perhaps that’s the only thing that doesn’t feel Ruby-like to me.
From what I’ve read and experienced about Ruby, and from the experiences I’ve been told about by other developers, what’s usually written is only on an as-needed basis, without any extra functionality.
Lean code is what Ruby is all about.
I guess I consider filling the object out as part of wearing the library-developer hat, rather than the program-developer hat.
One can wear both, but not at the same time - I switch them often, but they do have to be switched deliberately.
For this exercise, I’ve got my library hat on.
One thing that immediately jumped out at me is the difference in the amount of code and the versatility of the objects.
The amount of code I had to write in Ruby was significantly smaller than the java - about a third less.
The Ruby methods are shorter and simpler making them easier to maintain.
And by using duck typing to convert values the code is much more versatile - any object with a seconds, usecs
, or to_f
method or is a kind of numeric value (tried in that order) can be used to create a duration, now by my own convention.
The biggest contributors to code reduction seem to be the notational simplicity of Ruby and the brevity of it’s control structures. Having array and hash notations built into the language, and not having to declare a variable’s type turns out to be a tremendous codesaver. The code is still all there, but it’s expressed much more succinctly. And since the control structures are so clean and are able to be used inline, a lot of temporary assignments and setup was eliminated. Basically I was able to write less code to get more functionality in Ruby.
Stopwatch.java
to stopwatch.rb
Using the Duration
class, elapsed time can be set, adjusted, compared and rendered easily in any program.
But the other side of elapsed time is measurement.
Recording durations must be simple and straightforward, as well as handy.
The analogous human tool for precise time measurement in the real world is the stopwatch.
Again, down through the years, I’ve carried and transformed the guts of a stopwatch object with me, starting with C functions and turning them into an object first in C++, then transforming that to Objective C and finally Java.
The code, though simple, remains true to the workings of an old stopwatch I once had.
I could stop it, start it, restart it, lap it, and reset it.
I also differentiate between a lap and current lap: the lap is a snapshot of the total elapsed time of a running watch, while the current lap gives the elapsed time since the last start or restart. For good measure, I threw in a constructor to sync from another instance, and a way to start a new instance running on creation.
Methods to reveal the start time, stop time, elapsed time and state are also present, as well as a renderer and a method to compare two instances.
The code is simpler than Duration
, but there are some invalid state considerations to worry about.
The potential exists for user error with a stopwatch - that’s typically why it has just a few buttons on the real world version.
The methods here are more descriptive, but ultimately more error prone than the click-big-button, click-small-button interface that you’d use for timing a footrace.
A StopwatchException
class is created alongside Stopwatch
to indicate the illegal states: starting or restarting a running Stopwatch or stopping or lapping a stopped Stopwatch
.
As this is Java, the user has to catch the thrown exceptions when using the class.
Converting the Java Stopwatch
to Ruby was done in much the same way as Duration
.
The class is pretty straightforward code in either Java or Ruby, but again, the brevity of Ruby led to much smaller code that is easier to follow and maintain than the Java - especially through use of the if modifier, a more versatile Ruby assignment operator, and the DRY principle.
The if
modifier (the not-if
modifier is called unless
) can be tacked onto the back of any Ruby statement, seemingly as an afterthought: “Do all this if that.”
This leads to nice concise code - as can be seen when I’m raising exceptions.
(The Ruby code follows the same real-world-stopwatch logic as the Java code, and the same exceptions are thrown.)
The Ruby raise
mechanism is also briefer than the corresponding Java, primarily because raise
is a method of the Kernel
object.
The actual mechanism that propagates the exception is hidden, as it also effectively is in Java, but the exception instance creation and the requisite throws
decoration on the method are not needed in Ruby.
There may be some confusion for the Java developer trying to go to Ruby when it comes to exception handing.
The quick translation is that Java’s try-catch-finally is the same as Ruby’s begin-rescue-ensure, and Java’s throw
is the same as Ruby’s raise
(and fail
).
Ruby also has another mechanism: throw-catch.
It’s basically a way to break out of current processing flow with a throw
, transferring control to the matching catch
upstream on the stack.
(If you’re an old UNIX-hacker, this is effectively the non-local goto, implemented using the setjmp
and longjmp
functions.)
The Ruby assignment operator =
does a wonderful thing for code brevity.
It is one of the few operations built into the language (not a method) and besides just assigning one value to one symbol, it can assign a group of values to a group of symbols, respectively.
This allows you to put together a whole lot of assignments in one statement that should all happen together.
You might not think this seems like much of a big deal, but when a few things that are related together can be changed in the same operation it can be a boon to code understanding.
Anything with interrelated state, values and business rules can be approached much more easily when these sorts of operations can be expressed so cleanly.
Finally, by applying the DRY principle, it was clear that a lot of this state setting could be factored into a few common methods.
The internal_copy
, internal_start
and internal_stop
methods were all factored out, and the code in the methods became just plain small.
The class ended up being nice, tight and clean.
This time I actually started by writing a bunch of unit tests first, so the Ruby design was established before I started writing the class. This is a great way to get things done - do just what is needed to satisfy the tests, with an eye to refactoring.
Between Duration
and Stopwatch
, the enum
mixin and their unit tests, I now have some full-featured Ruby code to represent and measure elapsed time that I can use on into the future.
Feel free to use it yourself if you’d like - and let me know if you add any significant features that I didn’t consider.
Moving forward in Java and Ruby
After going through this exercise, I asked myself, “So is Ruby better than Java?” That’s too big a question. Well then, “Can Ruby replace Java?” It could, but it won’t. Though it can potentially do everything Java can - Ruby certainly has some advantages, especially when it comes to faster coding, and it most certainly has the higher OO ground - there’s too large an installed Java base (and similarly large installed bases of C++ and C#) to be displaced as the preferred industrial language any time soon. (If anyone starts selling COBOL LIVES shirts, I want a cut.) Too many successful companies have paid for too much development to shift away from what they’re currently doing, at least in anything but small increments. Such is the inertia of profits. But that doesn’t mean Ruby won’t make its way into the workplace - it’ll just start small and grow, just as Java, C#, C++ and C did.
Investment in technology is tricky business, and the compelling question that should always be asked first is, “Is it worth the money?” As a software developer who wants the latest and greatest cool languages and tools, “Of course it’s worth it.” As a businessman who wants happy stockholders and happier bosses along with higher revenue and lower costs, but doesn’t want to get blindsided, “Maybe, maybe not… perhaps we should do a pilot to see.” As a paying customer who’s buying products, reading mail, surfing and clicking on web pages, “I don’t care as long as I get better stuff for cheap or free.”
In many cases the benefits of adopting Ruby would be positive because the time needed to develop good applications, put them in place and then enhance and maintain them could be dramatically reduced. The more forward-thinking ten percent of large businesses will gravitate to Ruby more quickly than the rest, and small business owners that pay for custom development may jump if the cost of Ruby-based systems is favorable. Otherwise, expect to be working in established languages and frameworks for a while and get funny looks from your management when you start spouting Ruby heresy. But even then, that doesn’t mean you still can’t secretly experiment with Ruby late at night with the stereo blasting and a towel stuck under the door.
There’s also JRuby to consider. The goal of this open-source effort is to allow bi-directional use of Java and Ruby in the same program. In effect, Java can run Ruby Scripts and Ruby can access Java Objects. The potential for this trans-language cooperation is high, especially since JRuby could get Ruby to a large installed base through the back door. But currently this is still mostly potential; the team has not yet completed a first release, and the project went on a hiatus for a while to regroup. It seems to be coming back with strong support, but only time will tell. And even if JRuby doesn’t meet our hopeful expectations, there are other efforts going on - someone will succeed in bringing things together.
The bottom line again, is that Ruby is worth your time to learn. It most certainly was worth mine, as I find more and more uses for it in my own work. Ruby is compelling, and so is Rails, which I’m currently exploring as well. Even though I’ve had some good exposure, I’m still only a novice - I have a lot left to learn about this language and need to unlearn some of the extra cruft the established languages have thrust upon me.
I must do what I must do - at least Ruby makes me smile while I do it.