Friday, October 30, 2009

Simplicity and Complexity in Software

Avdi Grimm wrote "Simplicity is Complicated" where he argues that simplicity isn't simple. It brought to mind a few pet peeves of my own.

I think I agree with what Avdi has to say, but I might couch it in different language and come at it from the complexity side of the issue. I also have some opinions on simplicity in programs, which basically boil down to readability.

I like to think of complexity as being of two kinds: essential, and accidental. Accidental complexity is complexity that is introduced by the way in which you solve the problem. It is complexity that is unnecessary, and that could be removed by solving the problem in a more elegant fashion. Essential complexity, however, is complexity that is inherent to the problem, and cannot be reduced no matter how you solve the problem.

I had this conversation with a coworker recently. We were working on several reports for a Rails application. We were discussing whether it would be better to have a single controller with sixteen actions, or sixteen controllers with one action each. This is an example of essential complexity. If you have sixteen reports, then you are going to have sixteen "somethings," there is just no way around it. This is like Avdi's example of a pocket of air trapped under plastic, if you squeeze on the actions, then sixteen controllers will pop up somewhere else.

Then when it comes to simplicity in programs, my view is to reduce the accidental complexity as much as possible, and also to stick to the conventions and idioms of your language as much as possible. Obviously, there would be no innovation if we only stuck to conventions, but as much as possible, a Ruby programmer should be able to read and quickly understand a Ruby program. Consider these two examples from Avdi's article:

Example 1


sum = 0
i = 0
while i < times.length
  time = times[i]
  # parse / manipulate the time
  sum = sum + time
  i = i + 1
end

Example 2


def average_time_of_day(times)
  sum = times.map(&:to_time).inject(&:+)
end

I don't think that Avdi is necessarily arguing for this viewpoint, but he says that "[Example 1] uses language constructs that are familiar to almost all programmers, not just Ruby programmers," and that this can be considered a form of simplicity. While it's true that one might see that as a form of simplicity, I think the most important thing when writing a Ruby program should be writing it in a way that it is easy for Ruby programmers to understand.

When a Ruby programmer sees Example 1, he has to stop and think about what is going on, because it is not idiomatic Ruby. When he sees Example 2, he can immediately grasp the essence of what the code is doing. This is more an issue of readability than simplicity.

I'm not saying that the Ruby programmer grasps it easier because it is more terse. A Java programmer would look at Example 2 and perhaps be a bit befuddled. What I'm saying is that a Ruby programmer grasps it easily because it is idiomatic Ruby. I do not think it would be appropriate to write something like Example 2 in Java.

I would not advocate writing Java code with Ruby (like Example 1), nor would I advocate writing Ruby code with Java. Stick to the idioms and conventions of the language you are working within. And to those who say, "Well some Ruby programmers wouldn't easily grasp the essence of Example 2," I say, "You mean newbies?" Tough. They should master their tool. They should read more code written by others. That's part of being a professional programmer.

So to summarize, the distinction between essential and accidental complexity is, I think, a useful and important one in thinking about complexity of code. Second, to me the issue isn't so much simplicity as readability, and the key to readability is to stick to the idioms of your language as much as possible.

No comments: