How to Write Software

Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.

Summary: Writing software alone and in a long term sustainable manner requires a lot of discipline. Upfront planning and avoidance of feature creep can turn a stressful project into a pleasure.

UPDATE: I originally wrote this piece back in 2010. I was recently reminded of it and thought it would be good to revive this article from my old blog.

A guide for the solo programmer

I have created a planning and organization process for developing software. The process works for me. I need to organize my work ahead of time or I get stuck working on unimportant details.

I have built minimalism into the process. I tend to work alone. Solo programming imposes very real limitations on the scope and completeness of software. One man can only do so much.

My process includes nothing that cannot be found in Worse is Better, The Cathedral and the Bazaar, Paul Graham's writing, and Getting Real. These are well-written and well-known works. I thought I understood them when I read them, but I was wrong. It wasn't until recently, after projects stalled and failed due to feature creep, that I understood the essence of the release early, release often idea. I have attempted to formalize the idea to be more actionable instead of philosophical.

Here is my hard-earned process for writing software. Note that this is not a strict algorithm. Sometimes I will apply filters from Step 2 when I am doing experiments in Step 3.

  1. Choose a purpose.

    The software must serve a single purpose. Clearly defining it will illuminate the entire software process.

  2. For each release:

    1. List features.

      List out all of the features you want in the software. Not all of these will make it into the release, but if it is not on this list, it will not make it into the release.

    2. Apply the following filters and transformations, in any order, repeatedly, until there is nothing further to filter out from the list of features.

      • Break features into subfeatures.

        If a feature can be broken into two features, do it. All features must be atomic. For instance, an easy one would be if I want to send messages by email and through a web API, that's two features. Rule of thumb: if it can break, it's a separate feature.

      • Eliminate unnecessary features.

        Features that are not essential to the purpose are unnecessary. Features that can be put off until the next version are unnecessary. In the above example, maybe email can wait until the next release. If it can wait, it will wait. You are trying to make a bee line to release.

      • Fill in "hidden features".

        Remember, if it can break, it's a feature. Sometimes there are features that need to be listed because of the existence of other features. If you support two different communication channels, you need a way to choose between them. Whatever mechanism presents that choice is a feature, too. List it.

      • Simplify features.

        If a feature can be done more simply or more easily than what you originally intended, replace it. The primary way to release quickly is to simplify the release. Do you really need it to be customizable?

    3. Design and carry out experiments.

      Within your minimal set of features, there are often questions as to the best means of implementation. The experiments determine the answers to those questions.

    4. Order the features in order of dependency.

      The first feature is the one that does not depend on any others. Dependency is a complex and sneaky issue. You might say that a function needs a button to activate it. But the button is useless without the function. Which depends on which? Personally, dependency means dependent on another feature for usefulness. If I can activate a function in a very simple way (like immediately when the program starts), then that function does not depend on the button. The button depends on the function for its usefulness.

    5. Sketch out the first remaining feature on paper.

      Sketch out the code. The experiments should have instructed you in how to implement the feature. Rewrite the code repeatedly, eliminating fluff and abstraction until the code is minimal and perfect.

    6. Type the code in.

      Build it and test it.

    7. Make the code rock solid.

      Clean it up. As a solo programmer, you are never going to come back to this code and clean it up. You won't have time. You will never have the time to hunt for bugs in this code. Make sure it works in every possible case. The code must catch every exception. It must handle the null case. The feature is not done until it is unbreakable. If you are doubting whether it's worth your time to work this much on one feature, maybe you should eliminate the feature.

    8. Loop to Step 5 until you run out of features.

    9. Release.

The essence of this process is to follow the release early, release often philosophy by reducing the amount of development between releases. I mistakenly thought that release often meant "write code really fast" in the past. Now I understand that it means write less but more important code between releases but write it correctly.