codeslower.com Savor Your Code.

Why Ruby is Not My Favorite Language

Don't get me wrong, Ruby is a great language. I've been around the block plenty. Ruby code can be beautiful, concise, expressive. Blocks were the gateway drug to functional programming for me. Programming in most other languages after significant Ruby exposure is painful. Why then, is Ruby not my favorite programming language?

Programming in the Large

Anyone working on an application with a reasonably long lifetime or sufficient number of versions knows the pain of "unwrapping the onion" when diagnosing or fixing a bug. Is it the client to server XML? The PHP talking to the database? The stored procedure being run? The screen-scraping utility driving a legacy green screen app? As the number of possible causes for a given bug goes up, my ability to track all of them at once gets worse. The more I have to "deduce" about my code's behavior (and all the layers its built on), the longer its going to take me to figure to what's going on. Its not that the bug won't be fixed - it's that the level of effort required is just going to group.

Now, let's step back. Separation of concerns, allowing each individual to focus on their own area of responsibility, is a key tenet in any large project. Most modern programming languages, and many not-so modern, directly support this. The motivation and benefit is obvious - when I'm working in my area of responsibility, I want to worry about as few other areas as possible. In a language or framework properly supporting a separation of concerns I can reasonably deduce the behavior of my code by looking at the source. The meaning of the code isn't going to change underneath me. The behavior of a given piece of code is constrained by the fixed definition of the elements used and varies in well-known ways. The amount of deduction required of me is limited to the definitions I can read in front of me or look up in other areas of the source. Further, the location of those definitions is generally well known and obvious from the code in front of me.

An Illustration

This brings us to Ruby. Loading up irb, what methods existing on every object?

irb(main):001:0> Object.methods.sort
=> ["<", "<=", "<=>", "==", ...

In my Ruby version (ruby 1.8.5), Object.methods has 73 elements:

irb(main):001:0> Object.methods.length
=> 73

Let's load 'rubygems' and see what methods we have now:

irb(main):002:0> Object.methods.length
=> 79 

Nice, six new methods. I wonder what they do or which they are? I leave it to vigilant readers to compare the two lists and point out the differences.

Now let's load 'activesupport', used in the Rails framework, and see what we get;

irb(main):003:0> require 'activesupport'
=> true
irb(main):004:0> Object.methods.length
=> 172

I think the only reasonable response here is OMFG. And we haven't even addressed the issue where a method's behavior has been fundamentally changed.

Sometimes Less IS More

Ruby's flexibility is amazing but also it's Achilles heel. Understanding the behavior of any given piece of code means understanding the changes made by any previously executed code. The world is no longer fixed - even the concept of "class" doesn't mean much, except as a nice place to attach functions you'd like to be able to find again (if you are lucky).

I've seen PHP used for large scale development and it's not pretty. Every "required" or "included" file can introduce variables into global scope. Tracking down the last piece of code that set one of those variables is a nightmare. But it's nothing like what Ruby allows - at least in PHP you aren't able to redefine the meaning of a function or the definition of a class. PHP is dangerous enough to make life difficult but not so much it is unusable. Ruby "in the large", even when executed by a team of very disciplined programmers, will require Herculean effort. Tracking down the source of a breaking change will demand powers of deduction beyond most mere mortals. Not every developer thinks that way or or wants to. Of course all bugs are solvable (well, almost all) but Ruby can only increase the resources and time required to do it.

Don't Let the Door Hit You ...

Ruby is a beautiful language and it's changing the world. I just hope it isn't here to stay, because I don't want to be debugging legacy Ruby apps ten years from now. My brain already hurts.

Category: None

Please login to comment.

17 Comments

  1. added methods on require rubygems by Marc (2008-10-20)

    bla = Object.methods
    require "rubygems"
    blubb = Object.methods
    puts (blubb - bla).sort

    gem
    require
    taguri
    to_yaml
    to_yaml_node
    to_yaml_properties
    to_yaml_style
    yaml_as
    yaml_tag_class_name
    yaml_tag_read_class

  2. ActiveSupport considered harmful. by Bob Aman (2008-10-20)

    I think a significant fraction of the non-Rails Ruby community is in complete agreement with you. Maybe not about the "Ruby isn't my favorite language" part, but certainly the "Don't go adding to or redefining Object willy-nilly" bit. Unfortunately, there's enough programmers out there who don't share this view that poisonous trash like ActiveSupport are going to plague us forever. So I can totally understand why someone would look to greener pastures. I would too if those pastures had as much community support as Ruby does.

  3. Re: Article by Leo Soto M. (2008-10-20)

    Sure, it's the difficult balance between freedom, readability and productivity (which is not just "sensing" what some piece of code does, but also about not being too difficult to realize how is that thing done). As Guido van Rossum said:

    Too much freedom and nobody can read another's code; too little and expressiveness is endangered

    Unfortunately, I think that Ruby slightly leans to the "nobody can read another's code" end. It's a quite enjoyable language, though.

  4. It's kinda like... by Mason (2008-10-20)

    I understand your OMFGness. On the flip side, I would argue that that ActiveSupport's dramatic addition of methods sort of shines light on how Ruby has borrowed from the functional programming world.

    Take Erlang or Lisp, for example. The languages are actually quite small. But as you use them and expand them for your specific domain, you add so much additional functionality to the base that you end up with an entirely unique flavor. And after using that flavor for a long period of time, it's sometimes hard to remember that certain functionality is not apart of the language at large (and likely shouldn't be). The ability to add functionality such that it looks like the native language is an incredibly powerful (and likely incredibly abused) feature.

  5. Though, I admit... by Mason (2008-10-20)

    I do admit that ActiveSupport is incredibly large, and really shouldn't be yoinked into your code if you're not doing a Rails app. It's often much easier and lighter to just include the bits you like manually.

  6. But maybe not... by Brian Palmer (2008-10-20)

    In theory I get what you're saying, but in the last 8 years I've worked on some massive Ruby projects (and some pretty darn big Rails projects that have grown over 3 years) and it's simply never been a problem in practice. YMMV I suppose.

  7. Hm,... then switch. by George Petsagourakis (2008-10-20)

    Give Lua a try. Amazing speed and less is more language with a prefectly small vm and an even greater syntax to go with this.

    www.lua.org

  8. RE: Article by Orion Edwards (2008-10-20)

    I agree. It gets messy, and we've encountered some obscure and strange bugs caused by people (mostly third party gems and rails-plugins) overriding built-in methods.

    Ruby could fix this by allowing 'scoping' of includes - similar to how C# extension methods are only available if you 'using' the namespace which defines them.

    This would mean all the string-formatting helper methods that active-support provides would only be available in rails' views, and so on, and IMHO would go a long way towards solving this.

    Unfortunately I've never heard of them planning to implement such a feature - it would be fairly major I imagine

  9. Re: Article by mark (2008-10-20)

    So what exactly is your favourite language?

    I mean you can avoid using rubygems too - I do not use any rubygem at all.

    I never felt that there was an inherent lack.

    I was never able to write a IRC bot in php, but i did so in 2 months in ruby (i was much more a noob back then than I am now, and I still have a lot to learn) - this is 5 years ago by now.

    Today I am using my own web framework that has replaced php for my use completely, a pseudo shell that is capable of interpreting ruby code and my own "instruction" code, and so much more.... in short, I do not understand which other programming language you offer - EVERY

    EVERY

    Programming language and apps therein will have BUGS.

    I hate bugs, I want languages to never have any bug anymore and stay terse and consise - but I see no alternative you can present.

  10. Evolution by Ze (2008-10-21)

    Ruby is pretty cool.

    I'm sure it'll evolve constantly until it becomes Perl.

  11. That's what you get when you take a good language and massacre it. by Pascal Bourguignon (2008-10-21)

    As we all know, Ruby is a massacred Lisp. (See http://web.archive.org/web/20060522191515/http://ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-talk/179642 )

    So it's not a surprise if things work less well in Ruby than in Lisp.

    In Common Lisp, adding methods is no problem, because there are packages (namespaces), and method names are symbols that can belong to different package. When you load a given system, if it wants to define its own method to manipulate some existing class, it will do so in its own package, so there's no risk of name collision and method overriding between different systems.

    People, why do you stand ersatz? Use the real thing, Common Lisp!

  12. These are extensions and they're DRY, not dirty by remi (2008-10-21)

    I take it that you, the author, also don't like many statically typed languages like .NET? In Ruby (or .NET or JavaScript or Python or ?), if I perform a certain function on a string often, without extensions I have to do: StringExtensions.do_something("foo")

    What's wrong with refactoring that to look like: "foo".do_something()?

    The writers of a language provide us with many basic objects, eg. we usually have a String class to work with. If, in lots of my applications, I often perform the same functions on a string, over and over, I would personally prefer to create a string extension with these functions and extend the String class with them. Then, I can simply: "foo".custom_function().

    The writers of Ruby gave us "foo".upcase. Maybe I want to be able to capitalize words easily but the writers of Ruby didn't provide us with that function ... so I extend String so I can call "foo".capitalize.

    There's no mystery here, in my opinion. In .NET, you can load a library (.dll) and it will add extensions to classes. Your can do the same with Ruby. It helps you try up your code and make it much clearer.

    Example: If I want to find out what methods a string has in Ruby, I call "".methods. If I don't extend String and, instead, I use a utility-type class and I call MyString.do_something(""), then that method isn't discoverable!!! I'm never know that someone wrote a do_something() method for String unless I find the MyString class. That's totally unintuitive, in my opinion.

    With regard to ActiveSupport, I used to think that including ActiveSupport was overkill and shouldn't be done in Ruby projects. Over time, however, I found myself building by own library of helpful functions and, one day, I looked at my own library and realized that I was just recoding things that were already included in ActiveSupport. To say that using ActiveSupport is harmful is ridiculous. That's like saying "you should never use anyone else's helpful libraries, you should always write your own!" We know that's ridiculous. If you don't need ActiveSupport, don't use it. But, if you find yourself wanting functionality included in ActiveSupport, go ahead and include it! There's nothing wrong with it - it's nothing more than a library that adds all kinds of helpful functionality. Definitely not "harmful."

    That's my opinion. Keep in mind that even some statically typed, non-"dynamic" languages (eg. C#) support this functionality.

  13. Beware Backticks ` in your Markdown Comments :P by remi (2008-10-21)

    Sorry, my code in my previous comment appear on newlines instead of inline ( backticks are supposed to be used for inline code per http://daringfireball.net/projects/markdown/syntax ).

  14. Not Quite Right by Austin Ziegler (2008-12-09)

    Your assumptions here aren't quite right, since Object#methods returns ancestor methods, too -- including modules (like Kernel). Getting only those things defined in Object gives a very different result.

    (Object.methods - Kernel.public_instance_methods - Module.public_instance_methods).count #=> 3

    Removing Class.public_instance_methods gets rid of those last three things.

    The items defined in Object.public_instance_methods are also primarily defined in Kernel.

  15. Re: Article by Brian Clapper (2008-12-10)

    That's the thing with powerful tools: Used improperly, they can cut your finger off, or worse. That doesn't mean we should never use powerful tools. It does mean, however, that we ought to have good guidelines for using them.

    The power to redefine things on the fly can, indeed, totally screw things up. In the right hands, it can lead to elegant time savers that actually make your code cleaner and easier to follow. In the wrong hands, though, it can lead to an absolute maintenance and debugging nightmare. So the real objection is, I think, putting that kind of power in the hands of inexperienced or mediocre programmers, without providing direction--kind of like giving your 10-year-old kid a chain saw. Monkey patching is powerful, but its wholesale use can lead to some obscure bugs and inscrutable code. That's not the fault of Ruby, the Language, though. It's a failing of Ruby, the Community--specifically, a failure to set community standards that dictate where and where not to use these powerful tools.

    But that's not an argument for taking the tools away.

  16. TESTS? by Ryan Walker (2008-12-12)

    Ruby “in the large”, even when executed by a team of very disciplined programmers, will require Herculean effort. Tracking down the source of a breaking change will demand powers of deduction beyond most mere mortals.

    What you're describing is the problem debugging any mass of uncovered code in any language, not a Ruby-specific problem. If the project has good test coverage, you're wrong. Moral of the story: TATFT.

  17. Functions in ruby are methods in Kernel. by Miklos (2008-12-16)

    Note that in ruby there are no 'functions' just methods. Functions defined outside classes are actually methods defined on Kernel class.

    Kernel.methods.size => 136 def foo() end Kernel.methods.size => 137

    Also the purpose of 'activesupport' is to extend base classes with functionality considered usefull for webdevelopment. So no wonder it defines extra methods on Object (again you would call many of those 'functions' in other languages like C++). IMHO 'activesupport' is a great example of the correct use of open classes. You're right: open classes can be a disaster when used in an ad-hoc fashion. But it's not an excuse for banning this feature from a language. Good programmers will recognize that and bad programmers will write umaintable code anyway.