My rules for writing software

Vladimir Terekhov
4 min readJul 26, 2018

--

It’s hard to get experience by reading articles. Almost all of them work retroactively after you make a mistake. You have a mess on your hands, see how you could have avoided it, and it clicks. In fact, the best advice I’ve read looks as if it is almost too obvious to write, so bear with me. All of this advice is technology agnostic, but some rules are specific to web development.

Write readme first

Before writing any code, write the readme for a project as if the code already exists. Describe all of the features. If you are writing a library, write example sections with code that will use it. If you are writing an API, write a script with curl that will cover the endpoints. This will help you see the boundary cases before you write any code.

See Readme driven development.

Write code that is easy to delete

Features should have minimal dependencies, both externally and internally. In big projects this implies grouping the code on the basis of features, not functions. For example, having a payment module instead of models or controllers. Or using separate services in an SOA (service-oriented architecture) for different functionality, e.g. authorization, profiles, leaderboards. Don’t write code to support unknown future features. Your code should solve actual business problems. Understanding what they are is often hardest part of the job.

When the specification changes in an unexpected way you will be able to delete a few modules in the code and rewrite them. In some companies this pattern is called ‘disposable software’.

See Write code that is easy to delete, not easy to extend.

Make minimum use of abstractions

It’s very tempting to write abstractions for your code from the beginning. For example you need to write a formatter that generates CSV reports. So you create AbstractFormatter, which will be used when new formats will be added. It will easily support Excel spreadsheets and other table-based formats. Then you implement HTMLFormatter on top and everything looks good. But you don’t know the future requirements, and quite often they will be orthogonal to your abstraction. It might be a PNG infographic with charts and statistics. Now you will need to refactor AbstractFormatter, HTMLFormatter and only then write your PNGFormatter. Sometimes abstractions work, but usually they do so only for a domain that you know already. Abstractions that are extracted from existing code are usually more likely to live longer.

See the wrong abstraction.

Write stateless if possible and avoid global state

You should be able to kill your service workers without losing user data or states. All of the traffic from one instance of a service should be able to be handled by any other instance. Doing this will allow you to scale your servers horizontally. It is a rule that can be broken for specific reasons, for instance for to improve performance, but otherwise the rule should normally be followed.

See The twelve-factor app.

Use feature flags

When adding functionality to working code, keep both versions of the code and choose when to use the new code with an if-statement that depends on the database, on configuration, or on user settings. This will allow you to deploy the code more quickly (because you can turn on a feature for a subset of users) and with more confidence (you will be able to roll back quickly). Additionally, it’s one of the easiest ways to have continuous delivery with fewer “Oh shit, I have to merge it!” days.

See Parallel Implementations. See Flipping out.

Set timeouts and limits everywhere

Make sure that every piece of code that you write has a reasonable limit if it impacts an external API, such as Queue, database, or REST. Put limits on both size and time. Nowhere in your code should you have a function that can get all the rows in the database when the table is expected to grow.

Examples:

  • A user-uploaded image cannot be bigger than 50 MB.
  • A post cannot have more than 2 K comments.
  • Code that sends an SMS text message must finish in 10 s.

See Anti-Decay Programming.

Fail fast

It’s better to escalate errors that have occurred deep inside the application logic as fast as you can. Put the recovery code for your errors at the very top level of your processing. For example top-level recovery can be done in middleware, as a database transaction wrapper for instance. If an error cannot be recovered, then the app should do as much cleaning up as possible, and then return to the end user.

See Zen of Erlang.

Use schemas to validate input

When dealing with an external input, try to validate it as fast as you can and convert it to a domain object. Be as strict with your input as you can afford. This will save you from havig to use defensive programming and will move all of the validation logic to a single place. When doing validation, try to use existing solutions (e.g. JSON Schema).

See Overcoming Our Obsession with Stringly-Typed Ruby.

--

--