This post is a technical follow-up to a previous one about identifying trade-offs in decision making. If you haven’t read that I suggest doing so as I’m going to assume you have it in mind. In this post I’ll be jumping right into the nitty gritty of technical trade-offs.


Within engineering there are constant decisions to be made at every level: within code, architectural, selecting frameworks, resolving bugs, what feature to build next, and so on. The most common mistake I see is rushing forward with blinders. It’s easy to see the merits of our ideas, but we often miss some critical trade-offs happening in the process. Entire systems can become a complete mess one small decision at a time with unchecked and unnoticed compromises being made.

The goal of this article is to introduce some questions and concepts to help us make better technical decision. They are in no particular order or weight. If you find a decision conflicts with one of these points, that doesn’t mean it’s immediately disqualified; simply be mindful of what you are taking on with your decision.

1. Do you know the problem?

I have a saying I like to use and pound into the mind of my teammates:

You cannot possibly know if your idea is the right solution if you don’t intimately know the problem it’s intending to solve.

This may seem obvious, but I can’t tell you how many times I’ve caught myself and others tripping up on this. We assume we know the problem. But have you really stopped and wrestled with the problem? Could you explain it to someone else without rambling and in an understandable manner?

We use Github at my work and I require that all Pull Requests have a corresponding Issue. The issue should clearly describe the Issue, convincing the reader that the person who wrote it know the problem better than anyone. The Issue should not include the solution. Just know the problem. If it’s a bug, what is the full scope and affect of the bug? If it’s a feature, what problem does it solve for the user? How do we know it will solve the problem? Finally, the Pull Request should describe the proposed solution and be clear as to how it effectively and smartly solves the Issue.

2. There is no silver bullet

This is another one that feels obvious, but it still has a way of creeping into our thinking. When exploring ideas, I try to make it a point to avoid words like “best” or “right”. For example, “What’s the best front-end framework?” That’s the wrong question as it denies context. Vague problems get vague solutions.

So think about about the problem you’re trying to solve and ask “what solution most agreeably fits this problem?” The next time a similar problem comes along don’t assume the same solution fits it again — it may, it may not.

A good way to think of this is to regard all frameworks and solutions as tools. If you hired a carpenter to build an addition to your home and they showed up with nothing but a hammer, you’d have good reason to be concerned. Jobs require the right set of tools that work together to get it done. The job determines the tools, not other way around. This explains the idiom:

If all you have is a hammer everything looks like a nail.

3. Complexity

Oh boy. Complexity. This, particularly in web development, is one of the most neglected trade-offs I come across. Case in point, how much effort does it take for a JS developer to work in Typescript? With versioning? With CSS-in-JS? Yeah.

There’s a saying amongst learned programmers:

The best code is no code.

It’s hyperbole but to make a point. The best solution is often the simplest solution that does exactly what is needed and nothing more. Principles like the Single-Responsibility Principle try to guide folks towards this, but it’s hard. Its’ like the Mark Twain quote, “If I had more time I would’ve written a shorter letter.” I’m fascinated by inventions such as the mouse trap which possess what’s called Irreducible Complexity, meaning you can’t remove a single part without the whole thing fundamentally failing — it’s a simple as it gets.

I digress a little, but I wanted to emphasize the contrast the tension of complexity with simplicity. By introducing anything, we’re increasing the overall complexity of the solution. With complexity comes a higher chance of bugs, greater required competency to understand it (more on that later), more to keep track of, and so forth.

Let’s say, for example, you’re working on a small project and you want to write some JavaScript. Time for Webpack, TypeScript, and Babel! Wait. Why? If you’re writing 100 lines of code, do you really need that? What if your code wasn’t even minified? Does it really matter? On the upside, you can now write your code and not have to transpile anything. You don’t even need NPM. For a small project, this is fine! Just keep it simple and don’t add unnecessary complexity.

4. Existing Competency

When selecting a new tool or framework, it’s important that you’re aware of what your team already knows. If you work on a team where everyone is comfortable with React, why would you suddenly suggest Vue? But what if Vue does something magical that’s harder with React? That’s fair, but is it really worth the fact that the solution can only be worked on by a subset of your team? What Vue does it probably not that big of a deal.

Even if you’re a solo developer, give yourself permission to not have to learn everything. It’s better to be amazing at a few things that get your kind of jobs done than to be scattered and mediocre at a dozen things.

It’s often better to build on what you know than to break out of your existing competency entirely.

5. Team Competency

Similar to the previous, but the emphasis here is on looking forward. If you build some amazing, complex DevOps tool, what’s required to use the tool? Is it hard to learn? Also, who is going to maintain this tool? Do you have a single person on your team who actually knows how it works? What if they leave? If the tool is having problems, how will it effect productivity until someone is able to fix it?

Being aware of what your team knows and the effort it requires to guide them into new competencies is important, especially as teams scale.

6. Risk & Unknown Problems

One of the most tempting decisions to make in development is the rebuild. You think to yourself, “This existing code is a mess! We constantly run into limitations and bugs! If we scrapped it and built it this new way, it would be so much better.” Now you may be right! This isn’t to say that thought is always wrong. It does however, come with risk.

Be weary of trading of set of known problems for unknown problems.

Risk is the attempt to anticipate, quantify, and qualify unknown problems. Evaluating the risk of something wrong is critical. Risk has many sources:

  • What level of competency is required and who has it?
  • How complex is the problem?
  • How long will it take to solve?

If you’re refactoring a single, small function with no parameters, your risk is pretty low. If you’re rebuilding something that took 3 years to build, has been battle tested, and is 50k lines of code, that’s very high risk — that is, the likelihood of there being a multitude of problems you can’t see from the start is severe.

I have a rule of thumb for estimating a task. I first do my best to think of how much time I think it will take. Then I multiply it by a risk factor:

  • Small: x1.2
  • Medium: x1.5
  • High: x2.5
  • Severe: x4-10

The last may seem crazy, but I’ve found it’s accurate more often then not. Being honest with yourself that there will be problems you simply can’t predict from the onset and preparing yourself and your team for it is important.

As a final note, especially with rebuilds, I encourage you to always explore what it would be like to rebuild incrementally. Break the pain points down into smaller chunks, and see if it’s possible to work on those piecemeal. There may be a point-of-no-return at some point, but at least you can minimize the severity of the moment through preparation, and enjoy certain benefits earlier.

Conclusion

I want to finish this by reemphasizing that this is about trade-offs, not disqualifying. If a new tool is going to solve a significant amount problem, it may simply be worth the added complexity. The key to all of this is going into your decisions with your eyes wide open, being ready to reap the benefits and deal with the costs.

I can assure you that growing in this will be huge in taking you from a good engineer to a great one. I tell my team:

A significant distinction between a mid-level developer and a senior developer is that mid-level developers know how to build things well; senior developers know what to build and when.

Similar Posts