Less Code is More

As coders, we should always strive to get as much feedback as soon as possible. Agile tells us we should get frequent feedback from our customers in order to make sure we’re always on track. Unit testing and the green-bar loving are all about knowing exactly when your code breaks and when you’re safe.

A kind of activity for which feedback is harder to get is our design. A big part of TDD is refactoring frequently. Refactoring often allows us to smooth out the design continuously as we work. The problem is, how can you tell you’ve done something good? On the one hand, creating abstractions is something good programmers do. On the other hand, taking that too far will usually cause more problems.

When making bigger changes, the best feedback will be from getting another pair of eyeballs go over the change. Pairing is the best way for getting a second opinion. Code reviews can be effective too. Problem is, a lot of people don’t get to pair or review most of their code (and yeah, I know that makes Corey Haines a sad panda).

When I work by myself about the bigger things, there’s a handful of things I try to keep in mind, like the SOLID principles. But, the measure I like the most of how much shorter am I making the code. I’m not talking about obfuscating code. Readability is a must. I’m talking about abstractions that are really useful right now – they already make the code base smaller. Refactorings that make the code DRY and thus shorter.

delete key

Whenever I commit and git tells me I deleted more code than I inserted I get this fuzzy warm feeling. One of the agile manifesto principles says “Simplicity – the art of maximizing the amount of work not done – is essential”. Problem is I’m not a perfect coder, and because of that I often perform more work than necessary. But I try to simplify my code when I notice this.

Paul Graham has an essay where he once showed a way for him to know he’s making good progress with Arc, the language he created. He kept track of the number of lines it took him to implement an application (Hacker News) and whenever he made some changes he checked to see if they made the application simpler.

Of course, as any rule, this one has exceptions. Eventually, you will need to add code! But, it always helps me see that what I thought was really good just adds clutter. Try and take a look at the output of git log --shortstat – do you tend to add more lines whenever you clean up?

You should follow me on twitter!

  1. Mike Lewis says:

    Reuse and not reinventing the wheel can be important too. I once rewrote 2,650 lines of C as a seven line shell script. The previous programming team had written a data transfer program with their own implementation of ftp. I just used the one that was already on the computer.

    • abyx says:

      Wise words Mike!
      That’s a combination of both not inventing the wheel, and using the right tool for the job. Well played :)

  2. Programming is recursive. Decisions made on higher levels cascade to lower. It is essential to come up with a reasonable vocabulary to use in solving the problems given. In OOP, designing a proper class(ifying) hierarchy does this by default.

    If you are dealing with inadequate “grammar” it becomes ankward to solve problems on lower level. Sometimes the programming language chosen constrains this. There might not be an easy way to bypass the limitations set by the language.

    DSLs try to solve this in their own way. Essentially the idea is that instead of modeling the problem using classes, you come up with a language using constructs pertinent to the domain. I haven’t played around with DSLs but there’s something very intriguing about the idea.

    TDD is not a design methodology by definition. Extensions such as BDD and ATDD take it closer to the user but I don’t think even those solve the problem of design. They do help you to keep track of quantitative constraints. I consider this a very important thing.

    Design is something you should sort out separate of actual implementation while keeping given constraints in mind. I suppose the YAGNI principle should help in this. A proper design should be as lean as possible on the very highest level. You should be able to build the complexity on the lower levels.

    I know it’s hard, sometimes impossible, to come up with a good design at first. Prototyping and tracer bullet type approaches may provide a good starting point in this case.

    So just to summarize I think your very point is correct. In my view the way to “less code” consists of proper design (software as an onion :) ) and a disciplined implementation that states the constrains set on it and conforms them. In terms of LOC a nice design might actually cause you to have more code than a “flat” one. That’s not the point, however. On the very high level you code should be extremely simple and straightforward. Let the lower levels hide the complexity.

    • abyx says:

      Juho, I agree with most of what you wrote. Indeed, good design sometimes requires more LOC, which is why I wrote the disclaimer in the last paragraph.

      Regardless, I do think that the right layering can make the difference between a ball-of-mud architecture, and something well designed. But, I also think that TDD really has a lot to do with designing. Just as DSLs allow you to write your code in the domain of the problem, TDD allows you to write tests that use the code as you’d like it to be, instead of letting it transform to whatever it feels like. That’s a major part of design – knowing how you want your interfaces and classes to behave, and then making sure they do so.

  3. abyx:
    I agree that TDD helps you to enforce the API. It gives you scaffolding. It guarantees only specific kind of interface, not more. Specific internal details are better handled using assertions. Possibly other approaches (DbC comes to mind) work adequately as well.

    In the end it comes down to personal style. For me it’s all about making the dang thing work given specific set of constraints. :) Finding these constraints is usually the tough part.

    This line of thought easily leads to ideas that have been encapsulated in the fifth generation programming languages. By definition these languages should be able to generate a solution based on a given set of constraints as it happens. This works well for certain AI problems (CSP and such). It does not appear to be a workable solution for any “real” problems, however.

    Related to this problem I have been pondering about code generation based on tests. It’s probably easy enough to come up with simple tools to generate classes and simple methods with right return values but the whole thing breaks down at one specific point: tests can cover only a limited set of the functionality desired. To paraphrase “Absence of proof is not proof of absence.”.

    Still, even in a limited form, some sort of interactive code generation/validation system might be a fun thing to play around with. At least it should give some boilerplate code that can be then expanded to a proper solution. :)

    • abyx says:

      I think that’s pushing it too far. For me, code generation is only acceptable if I never, ever ever ever, have to look/change/think about the generated code. Creating a skeleton from generated is just so ugly, and will create such contorted messes, I’d rather roll it by myself. But hey, whatever floats your boat! :)

      TDD can also help you with “getting the dang thing to work”. I try my hardest too keep my feature-envy at bay, and not add code that isn’t going to be used, or that’s not pulling it’s own weight, which usually makes me get to working code faster.

  1. There are no trackbacks for this post yet.

Leave a Reply