Are there unbreakable laws ruling the process of software development? I asked myself this question while reflecting on a recent project, and the answer leads to many conclusions, some already known and some more revealing.
Scientific laws reflect reality and cannot be broken. They have strong implications onto how we build things. For instance, there is no point in building a car with vertical propulsion once you’ve observed that the law of gravity will prevent your car from drifting into space anyway. But software lives in the logical world. Does it really follow any laws?
Use the right tool for the job
I’ve heard this old saying many times over the years. We programmers have a lot of rules of thumb to guide us, but this one, in particular, has always felt almost universal to me.
It makes sense that you should not trust a single framework or library to work well for every project, and yet this rule seems to go largely ignored by the many fads we see in software development, miracle libraries that will fit any problem you might have, and even being marketed as such. Angular’s own website calls it the “Superheroic framework”.
This is a cultural problem that has been with us for a long time. “Vim vs Emacs”, “FreeBSD vs Linux”, “React vs Angular”. These are often seen as personal choices when they are actually technical decisions. And it doesn’t even end with developers: an enterprise environment will many times prefer fast solutions over sound architectures, forcing developers to favor fast prototyping tools such as Angular regardless of how well they fit a project because they seem to offer more productivity.
— Paul Lewis (@aerotwist) July 6, 2015
There are always advantages and disadvantages, and that’s why we’re reminded to use the right tool for the job. Every technical decision comes with tradeoffs.Not even just in software: any kind of craft, such as architecture or mechanical engineering, deals with decisions and tradeoffs.
This observation, in fact, looks a lot like a more general version of Newton’s third law, which states that for every action there is an equal and opposite reaction. Is it possible to generalize the other two laws as well? And how can we take advantage of them? Let’s look into it.
The fixed context in every problem
Under fixed circumstances, every solution will behave the same way, unless it has been modified.
Have you seen yourself hitting reload or re-running your program when you saw a bug, in the hopes that it went away, secretly knowing that the problem was still there? Don’t do that!
- Report every observed bug immediately. This might be your last chance of seeing it, and it might end up causing serious issues when users run into that particular edge case later on.
- Write a unit test for the bug right away. Making a code change might fix one problem and break something else, so it’s important to run tests for previous bugs when fixing new ones.
- Other developers should be notified of new bugs as soon as possible, as they might affect the code they’re working on as well. If you see a new bug report, don’t stash it. Read it now.
Impact of each change in software
The impact of a technical decision is directly proportional to the size of the project and to how much of it is affected.
For instance, if you modify a lot of code, there will likely be a strong impact on the codebase. For a larger codebase, even a small change could have a large impact.
- Be wary of updating dependencies to new major versions. A lot of code will have been changed. Whether the tests catch new problems or not will depend on the library’s design, its separation of concerns, and many other factors.
- Use libraries with good code coverage. It’s no guarantee, but it will help.
- Have plenty of tests in place before doing any refactoring.
- Be paranoid if there are no bugs after a refactoring.
Trade-offs in software development
Every technical decision comes with an implied tradeoff.
Have a clear vision of what the job is, and then use the right tool for the job. I’ve already talked a bit about this one.
- There is no “best” anything in software. Figure out what you need and what each choice provides before selecting one.
- Have a habit of reading about the tools you use and the ones you don’t as well, and how they work internally. This will give you valuable insight when making decisions.
- Some tools focus on fast development, others deal better with complex data. In web frameworks, Angular would be an example of the first case, and React would be one in the second case.
- A language that has more libraries is not necessarily better. Recently I had to search for a query-string-building library in JS and not a single one I found would handle nested objects properly the way jQuery does. Languages that make it easy to write libraries will make it time-consuming to choose the right one. This can lead to analysis paralysis.
- Always consider mature libraries even if they are unpopular. They will often cover more edge cases.
I tried to write this with as much practical advice as possible, so if you’re looking for the TLDR, just go back and take a look at the “how to” sections.
This was a fun analogy and I believe it can lead to many more interesting conclusions than I’ve been able to gather myself so far, so I’ll be looking for comments on HN and Reddit!