Your Code is My Hell

It occurred to me recently that my experience as a Rails developer may be somewhat unique.

I often get brought in to help preexisting Ruby/Rails projects evolve and mature in a sustainable way. As a result, the vast majority of Ruby projects I’ve worked on have been well-established by the time I arrived. In fact, offhand I can only think of one commercial greenfield Ruby project I’ve participated in. All the rest have been “legacy” from my perspective, in the sense that there was a sizable codebase in production before I showed up. (I’m not counting personal and internal projects.)

I’ve realized that in this my experience may be somewhat unusual among Ruby and Rails developers. With its fast churn and startup-heavy community, a lot of Rubyists are working on projects that they started recently.  My work is more in the codebases where the original programmers have moved on.

Rails’ dirty secret

In the days before I got paid to write Ruby, I worked on some legacy codebases that had histories spanning multiple decades and 100s of KLOCs. That’s a lot of opportunity for bad code to accumulate; and in some cases, the accumulations were impressive.

But here’s the dirty little secret of Rails development: the messiest, nastiest big-ball-of-mud code I have seen in my entire career has been in Ruby on Rails projects. I’ve seen Rails projects that accumulated enough technical debt and waste in two years to make 10 year-old C/C++ programs look clean and elegant by comparison. And it wasn’t just one project. I’ve seen it over and over.

In a way I think this is a testament to the power of the platform. If you’re getting a 500 error in a Rails app, you can keep adding kludge after kludge and hitting “reload” until it works. No need to ever write a test or refactor. In languages and frameworks with a slower turnaround time, this kind of tweak-it-till-it-works workflow is simply impractical. Ruby on Rails has an impressively low barrier to fiddling.

Unfortunately, as a result a lot of projects I come to on have hit what I think of as the productivity crash. At some point the cumulative effect of all those little shortcuts catches up with the development team, and changes that would once have taken a day start taking two weeks as all the dependencies and unintended consequences are sorted out.

As a somewhat ranty aside: this is also the point where, often, original members of the team start moving on to bigger and better things. Meanwhile the crew that inherited the codebase is left to field questions from management about why they can’t seem to push out changes nearly as fast as the old team. The new team is confronted with the problem of getting the codebase under better test coverage and a little more modularized before they can ramp velocity back up; thus perpetuating the notion among the business types that testing and refactoring just slows things down. And/or that the original team were some kind of wizards.

Okay, rant over.

But Rails is different!

Rails developers are sometimes accused of being arrogant and judgmental. I’m not sure how true this is; I don’t see it all that much, but maybe I’m too close to the community and/or arrogant and judgemental myself to be a fair observer.

What I do see is a kind of “Rails exceptionalism”.  Remember back in the first dot-com boom, when some economists were saying that no, this time it was different, the Internet had changed the game this time the markets would just keep going up and up? The phenomenon I see is similar in spirit. it’s a belief, perhaps not fully conscious, that Ruby on Rails development is somehow different, and not subject to the forces affecting other software projects.

Here are a few examples, just to give you an idea of what I’m talking about:

  • “Design Patterns are a Java thing. In Ruby you just write code.”
  • “The warnings Ruby produces are dumb; just disable them.”
  • “Sure they aren’t technically Unit Tests, but isolating objects turned out to be kind of hard and besides nobody else is doing it.”
  • “Monkeypatching is frowned on in other languages, but in Ruby it’s fine. The downsides almost never materialize.”
  • “Stuff like the Law of Demeter isn’t really as important in Ruby code”
  • “Dividing methods into private and public is for control freaks, you don’t need it in Ruby”
  • “That’s only a code smell when it’s in Java code”
  • “That’s only a problem in large projects” (implying that this project will never become large).

I also see a fair amount of project or subsystem-level exceptionalism: “I know they say classes shouldn’t be this big, but for this class it just makes sense for all of that stuff to be in one place”.

Welcome to Lilliput

The truth is, Ruby on Rails projects are exceptional in a way: they are really small. In James Gray’s terrific keynote at Lone Star Ruby Conf this past week, he mentioned “huge projects” of 40+ KLOC. That gave me a smile, because the first two Rails projects I was ever paid to work on were 50KLOC and 70KLOC, respectively. And while that may seem like a lot of code, that’s small by industry standards.

There are a few reasons for this. Ruby is a more expressive language than, say, Java, so to some degree Rails projects will always be smaller than equivalent projects in higher-ceremony languages.

It’s also possible that Rails programmers have embraced the wisdom of breaking systems many, small, intercommunicating apps. I’d like to believe this, but experience suggests this strategy has seen only spotty uptake.

No, I think the biggest reason for the diminutive nature of Rails apps is also the most obvious: they are all pretty young. It’s a young framework, and there’s a lot of churn in this community. A Rails app that lasts three years is ancient.

I think it’s safe to say that this situation won’t last. We’re going to see larger and larger codebases. And here’s a not-very-daring prediction: a lot of projects are going to hit the very same architectural roadblocks that Lisp, Smalltalk, Pascal, C++, and Java projects hit before them.

You are not a special snowflake

It’s funny reading programming literature from the 80s. Dynamic, object-oriented systems navigating the transition from “small” to “medium-sized”. Sound familiar?

Every revolutionary believes his revolution is special, and won’t devolve into the partisan bickering and venal bureaucracy that the last revolution led to. And it’s easy to believe at first. Everyone’s excited and eager to help; the problems are relatively small; and the marketing drones haven’t latched onto the movement yet.

The truth is, the problem you are solving probably isn’t as special as you think it is. And those byzantine patterns you thought were a relic of a bygone age were invented by people using languages surprisingly similar to Ruby.

Don’t panic

Relax. I’m not here to tell you that the last few years were all just a lovely dream, and you’re really still strapped to a chair in the Ministry of UML.

Ruby is still a wonderful language, and the terrific thing about it is that it adapts to large-system design patterns remarkably easily, and with very little ceremony. Dependency Injection? It’s a one-liner. Object delegation and composition? Piece of cake. Contrary to misconceptions, Ruby doesn’t obviate solid design patterns and SOLID principles; what it does is make them very easy to express. In fact, the ease of expressing robust architectural styles was what attracted some of us early-adopters to the language in the first place.

Just please, do me a favor: before you tell me that Ruby and Rails doesn’t need any of this discipline, have a chat with the guy or gal who is still maintaining the first Rails app you worked on.