Simplicity is Complicated

The favorite rhetorical fallback of politicians of every stripe is “it’s for the children”. Anything can be justified in terms of making things better for children if you frame it right. Lately I’ve begun to think that the word “simple” is the programmer’s version of “for the children”. We use it to justify all manner of decisions.

I’m not excepting myself, either. I see myself doing this all the time. “It’ll make things simple”, I say. Which by a strict interpretation is true. Whatever direction I’m advocating, it usually makes [some] things simple. It usually also makes other things more complicated.

Let’s look at an example of two opposing kinds of simplicity. My old boss Chris Strom recently wrote about some common Ruby newbie mistakes (it’s a great article, you should read the whole thing). Here is an example he gave of code written by someone new to the Ruby language:

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

After several iterations of successively introducing Ruby idioms and features, the code looked like this:

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

Which example is simpler? Well, that depends on what you mean by “simple”. As Chris points out, the latter example is much closer to the domain. The line sum = times.map(&:to_time).inject(&:+) neatly expresses the intent of the code without getting bogged down too much in implementation. It’s also more concise.

On the other hand, the first example uses language constructs that are familiar to almost all programmers, not just Ruby programmers. It is simpler in the sense that even someone who has only basic programming skills could probably work their way through it, without needing to understand concepts like #inject and symbol-to-proc.

What do we mean when we say the word “simple”? I’ve realised that we programmers use “simple” to mean a lot of different things. Some of the ways we use the word “simple” include:

Minimizing unnecessary effort. Characterised by the classic admonition to “Keep It Simple, Stupid”. Or as the XP folks say, “do the simplest thing that could possibly work“. Of course, sometimes when you choose to do less you force someone else to do more. And sometimes that someone else is you, two weeks down the road.

Hiding complexity. Ruby on Rails made web development simple, relative to tools that came before it. It accomplished this feat by embracing “convention over configuration”. In order to do this, it had to incorporate a great deal of extra complexity in the form of algorithms which guess the intent of the programmer instead of forcing her to specify her wishes explicitly. Anyone using it eventually has to become familiar with these hidden rules in order to understand why Rails behaves the way it does.

Avoiding difficult-to-understand features. In the name of simplicity, some programmers advocate avoiding “magic” language features they see as complicated and difficult, such as generics, recursion, or metaprogramming. Other coders say there is no such thing as magic if you have an adequate understanding of the language, and that arbitrarily naming certain features “magical” is superstitious nonsense.

Avoiding formal architecture. There are programmers who feel that complexity is the inevitable result of spending too much time thinking about design, and that simple code is code that does it’s job without being fastidiously decoupled and thoughtfully abstracted. Others say that this kind of thinking leads to an unmaintainable “big ball of mud“, the antithesis of simplicity.

Elegance of design. Some insist that elegant orthoganality is what defines simplicity. Simple components which do one thing well, which have few interdependencies and can be composed into various configurations. Others retort that all abstractions are leaky and that trying to abstract the leaks away only complicates the job of solving real problems.

Staying close to the domain. To some, simplicity is ability to write code which looks like a plain-English explanation of the problem. But the underlying scaffolding which makes the code so understandable to a domain expert may be baroque and brittle in it’s implementation.

Clearly, simplicity is not a simple subject.

I think the reason for this is the nature of complexity. Complexity is like an air pocket trapped under a layer of airtight plastic. If you push it down it just pops up somewhere else. Most of our attempts to “simplify” software really amount to pushing the complexity somewhere (or some-when) else. And our disagreements about what is meant by simplicity are really about where we think the complexity belongs. As a result, the goal of simplicity can be used to justify just about any course of action.

I challenge myself and anyone who reads this, next time you use the word “simple”, to stop and think about what you mean by it. As an exercise, try to rephrase it in terms of where you are pushing the complexity. Let me know if you gain any insight from looking at it in this way.
[ad#PostInline]

34 comments

    1. Thanks for the note. The original challenge those examples come from was a pure-Ruby challenge, so the entrants would not have had ActiveSupport available to them.

      A question for you: what does your choice to use ActiveSupport say about where you prefer the complexity to be?

  1. what you missed is the concept of essential complexity and accidental complexity. If a problem has essential complexity, no amount of simplification will make the complexity go away. e.g., designing complex concurrent systems that must be on all the time, is like that.

    accidental complexity is introduced when the programmer made a design error, or implemented, or decided to use the wrong framework to solve the problem at hand. square peg in a round hole so to speak.

    1. This is a legitimate point, and you're right that I didn't address it. I've also seen the words “complexity” and “complication” used to differentiate: complexity is an inherent property, whereas complication is something that people add. In this article I'm talking more about the language people use when they have stripped a problem down to it's core complexity and are deciding how to distribute that complexity.

      Thanks for the comment!

  2. D'oh! I just noticed that chii already made this point, but I see a useful distinction between essential and accidental complexity. Essential complexity is the “air pocket trapped under plastic” kind. Whereas, accidental complexity is introduced by the way that you solve the problem, and could be eliminated by using a different solution. Accidental complexity should definitely be eliminated.

    Second, I see the issue more as one of readability, and simplicity is part of this, because human beings can only hold so many things in their mind at once. Your first example may be easy for a Java programmer to grasp, but may actually be harder for a Ruby programmer to grasp, and vice versa for the second example. This is because each language has its own idioms, and moving outside of those idioms (while not to be totally avoided) should be done with caution.

    I have a problem with people writing “Java code” with Ruby, and writing “Ruby code” with Java. That is what creates readability problems, and perhaps complicates someone's attempt to understand the code.

    More here:
    http://paul.stadig.name/2009/10/simplicity-and-

    1. Paul, as I replied to chii, this article is definitely about how talk about essential/inherent complexity and doesn't really deal with the accidental kind.

      I have pretty strong opinions about the the virtues of different styles of coding – I tend to agree with you, for instance, about staying within the idioms of the language. I tried to keep my opinions out of this article, however. The intent was to provoke reflection on what our definition of simplicity is says about our preferences regarding where the complexity belongs.

      I'll definitely check out your article!

  3. I think simplicity should describe how you organize that pocket of complexity, perhaps by subdividing it into smaller pockets, which are simply organized… and second to the accidental and essential complexity. I think getting down to what is essential.

  4. I think you confuse “simple” and “clear” here. I think they're not the same.

    You can get a handle on “complex” by counting the number of moving parts: variables, operators (including “.”), etc. You can have very simple code that is cryptic or simple code that is clear.

    Clarity is about communicative power, and that depends on shared experience with the target audience. This is a tough goal, but you can find some further commentary here: http://agileotter.blogspot.com/2009/03/simple-a

    Another problem with “clear” is that it depends on how much the context can be taken for granted. If the complexity is hidden away, but you must interact with it “just so” then you have smaller code (simple=less complex) but it has a great “implicity” (having prerequisites to understanding). There are a great many ways to be unclear. I think that is a separate concern from “simple”.

    The other thing about “simple” is that others confuse it with “easy to write”. As you mention, sometimes the easiest change to write is not simple at all, but introduces complexity in moving parts and unclarity by having more modes of failure and modes of misunderstanding.

    Here is a conversation from a while back:
    http://blog.objectmentor.com/articles/2007/06/1

    I think that we need simple (fewest moving parts) and clarity (fewest modes of misinterpretation) but I don't have a good way to measure the latter.

    1. Great insights, Tim. I'd think a lot of programmers confuse simplicity with clarity, which is a delineation I failed to clearly (heh) draw out in this article.

      I love the word “implicity”, I'm going to use that in future.

      Thanks for the links, I'll check them out!

  5. “As an exercise, try to rephrase it in terms of where you are pushing the complexity”

    I love this line.

    One of my previous employers was distrustful of 'programmers' due to previous experience. When we would be defining a program and its scope 'simple' is the term that would be used frequently in keeping the scope of the custom software limited. This seems perfectly reasonable until you realize that by keeping the program simple we were shifting complexity to the user.

    Instead of a task based UI that was cognizant of the business rules, we would have a data based system were those business rules must be known by all users (and consequently fixed by the Help Desk when they made a mistake). But the software was simple.

    My personal goal was to build software that was easy to use even if it was complex.

    It is like the Zen of Python:

    Simple is better than Complex.
    Complex is better than Complicated.

    1. Your example reminds me a little bit of a pet peeve I have about certain ticketing systems (Lighthouse being one example): a drop-down selector for ticket status. This is workflow, I don't want to pick a status from a list. I want to see a button that lets me advance the ticket to the next state, and a button to back it up to the previous state in the workflow.

      A pick-list of states is certainly “simpler” from the programming side but it imposes an additional cognitive burden of complexity on the user side.

  6. In terms of logic, both examples are the same. They both perform the same action, and outcome.

    Whether the 2nd example is more readable is subjective. I definitely agree with the comments about having fewer moving parts, and few points of misinterpretation.

    If you're doing proper TDD/BDD you should only have the minimum amount of code required to pass each test anyway. Coding standards are a great way of getting your team on an even grounding. Using .Net we can enforce it with StyleCop and have intellisense suggestions in Visual Studio as you type. Pairing is another good way of making sure you and others will understand the code.

    1. As regular readers know, I'm a huge proponent of both BDD and pair programming. I find that, if anything, frequent pair programming has opened my eyes to the diversity of opinion between different programmers regarding what is “simple” and what is complicated.

  7. There's also that other kind of complexity. The first example runs in O(N) while the second requires O(2N). It likely wouldn't have made much difference in this context, but sometimes code complexity does matter, and you have to write your code less “simply”. You don't need to fall back to writing loops, but you won't get much use out of those pretty Symbol#to_procs.

  8. Last year I would have found your first example simpler. But after working with scala, I parsed the second version almost instantly and I don't know ruby. The first example now takes me significantly longer to grok.

    When there are fundamental principles being used to express something concisely then things are simple. Having to learn about those principles is necessary though, but many confuse having to learn those concepts with complexity in the solution that uses them.

    1. And yet the complexity is still there, you've just internalised it. This is an example of what I'm starting to think of as the accessibility/expressiveness continuum: do we push the complexity out in the open to be more accessible to the lowest common denominator, or do we count on certain principles being pre-loaded in the readers' brains and realise the benefits of greater expressiveness?

      Thanks for the comment!

  9. Last year I would have found your first example simpler. But after working with scala, I parsed the second version almost instantly and I don't know ruby. The first example now takes me significantly longer to grok.

    When there are fundamental principles being used to express something concisely then things are simple. Having to learn about those principles is necessary though, but many confuse having to learn those concepts with complexity in the solution that uses them.

  10. As you say, simplicity is a difficult concept to pin down. However people have been making software for a while and it possible to look back and see which types of simplicity have worked best in practice. For the type of work I do (servers, embedded systems and shrink wrap software) the types of simplicity outlined in http://en.wikipedia.org/wiki/Unix_philosophy have won hands down over and over again.

    1. Indeed. When choosing a “default” philosophy of design, I think Unix philosophy of small, sharp, composable tools is one of the safer bets.

  11. As a scientist, I would say refering to the concept of entropy:

    Make things Complicated is Simplistic, make things Simple is Complex 😉

    Complex = Reduce to the most simple things as possible but not less (paraphrasing $Einstein: make things simple but not simpler).
    Complicated = SmokeScreen which hides the Simplicity.

  12. I learned many years ago to avoid using the word “simple” or “easy” in any discussions with either other programmers or especially customers/clients.

    While these words express only relative concepts (something cannot be simple or easy on its own, but in comparison to something else), most people hear them as absolute and interpret them to mean that the work will be done quickly or cheaply.

    Words like “straight-forward” more accurately express the intent almost every time.

    Making your code straight-forward is a matter of making it understandable and malleable by the expected audience. You have to expect the others to have a certain skill level and write to that level, even if it is “simpler” than your own. That's how teams work.

  13. “ Programs must be written for people to read, and only incidentally for machines to execute. ” – Abelson / Sussman

    Your examples show code snippets with different levels of clarity in it. I can communicate an idea really briefly, if you know about the domain I'm talking about. But if you don't know, I can't take shortcuts and use domain language, and will need to explain everything step-by-step.

    What we see on the examples is this difference. The first one is a new programmer to Ruby (one unaware of the domain), so he uses imperative programming to explain everything step-by-step. The second example, the programmer is now aware of the domain, and can take shortcuts and use domain lingo (inject).

    Simplicity in software, as in anything that is built of parts, comes from design. But clarity is all about communication. What different languages and APIs try to do is giving the programmer the vocabulary necessary to express with enough clarity the problem he needs to solve, and one that makes sense in the domain of the problem.

  14. “ Programs must be written for people to read, and only incidentally for machines to execute. ” – Abelson / Sussman

    Your examples show code snippets with different levels of clarity in it. I can communicate an idea really briefly, if you know about the domain I'm talking about. But if you don't know, I can't take shortcuts and use domain language, and will need to explain everything step-by-step.

    What we see on the examples is this difference. The first one is a new programmer to Ruby (one unaware of the domain), so he uses imperative programming to explain everything step-by-step. The second example, the programmer is now aware of the domain, and can take shortcuts and use domain lingo (inject).

    Simplicity in software, as in anything that is built of parts, comes from design. But clarity is all about communication. What different languages and APIs try to do is giving the programmer the vocabulary necessary to express with enough clarity the problem he needs to solve, and one that makes sense in the domain of the problem.

  15. Thought provoking stuff. I have a tendency to stick to what I know when coding. I finally twigged that I've been wasting a lot of time doing stuff in C++ the C way. I try to make a conscious effort to actually use the more advanced features of newer languages rather than stick to the basic stuff.

    Of course, if you go nuts with clever features it's possible to end up with a real mess. To my mind simple == clear. If it's clear what's going on, it follows that it's simple to understand. I try to write my code so that I'll still be able to read it in six month's time, I can tell the things I've rushed or bodged because I can't figure what they're doing until I've read, re-read and pondered.

Leave a Reply to avdi Cancel reply

Your email address will not be published. Required fields are marked *