As developers we strive to write high-quality, well-tested code that works correctly. It should be easy to extend and simple to understand, both for our future selves and other developers. Within our field we constantly have conversations about writing maintainable code and coming up with good abstractions for the underlying business domain. Metaphors and rules-of-thumb in this area are plentiful. There’s an oft-repeated saying along the lines of, “Always write code as though it will be maintained by a homicidal, axe-wielding maniac who knows where you live.” But is striving for perfection in the code we write always the right decision?
At thoughtbot our lifeblood is client projects - the first text you see on our homepage is “Let’s build something that your users will love.” Working with clients, specifically product owners and founders, and making decisions together about the direction of the product is a constant in our daily work. Naturally, those product owners and founders are excited to move at a rapid pace, delivering features to users as quickly as possible. So how do we balance that desire with making the right decisions technically? Sometimes creating software in a maintainable, scalable way can be slower than simply building something which works, and the benefits might not be immediately obvious to a non-technical founder or product owner.
Like so many aspects of our work, at least part of the answer here is good communication within the team. Having an open and honest conversation with the product owner about the trade-offs on each side of a technical decision is important. In my experience, these conversations tend to be longer and more fleshed out the first few times they happen on a project. As a project progresses, trust between developers and the product owner builds, and we tend to implicitly understand each other’s judgements and motivations better. As developers we have a responsibility to communicate clearly why we’d recommend a particular technical decision - it could be for reasons of code quality/maintainability, performance, or something else. What might the repercussions be of not taking that route right now? As the product owner, it’s important to communicate the context of a decision. Is there a specific urgency around getting this feature shipped right now that makes taking a technical shortcut worth it?
An important part of the conversation is the concept of technical debt. Technical debt brings a financial analogy to our project codebases. The idea is that shortcuts or compromises we make while writing code and making code design decisions result in debt. The debt can exist in the form of badly organized code, poorer abstractions and worse code quality in general. Unless the debt is paid off by addressing those compromises, we’ll pay interest on the debt through changes and additions to the codebase taking longer. This ultimately results in features being slower to ship. Furthermore, because concepts in the code are less clear, it becomes easier to accidentally make the wrong change and ship bugs to your users.
This description of technical debt makes it sounds like an inherently Bad Thing which should be avoided at all costs. But, treated the right way, technical debt can be a valid, useful option during a project. Martin Fowler has an excellent blog post in which he describes a technical debt quadrant. I won’t paraphrase that post too much (please go and read it though!) but the lesson I try to keep in mind is that technical debt can be used positively if it’s accumulated deliberately and prudently. So what does that mean? I think it means that we have a responsibility to make sure such decisions are well- considered and not made in isolation. Generally we work in small teams so usually there are other developers or designers to have the conversation with, as well as the product owner. Another important part is to have a plan in mind for addressing the debt. What might a better solution look like? Are there future features which touch on a similar area of the codebase which might provide a good opportunity to tackle the debt when constraints are different?
In the context of a client project, an important part of the learning process and feedback loop is to openly call out scenarios where technical debt is slowing us down, and raise whether now is the right time to pay off the debt through refactoring or revisiting past architecture decisions.
If you and your team would like help understanding and taming the technical debt in your application, thoughtbot can help.
Of course, consciously accumulating a small amount of technical debt doesn’t give us a free pass to throw good software design out of the window! Two of the tools which we regularly use to help maintain high standards are pair programming and code reviews. One of the benefits of both of these is that they force you to have a conversation about compromises made while writing code. The important thing to remember is that even if we’re making trade-offs, we always want to ship high quality code.
Honest conversations between developers and product owners are critical. Coming to a good understanding of each other’s motivations is an import step in any project. I’ve found that it’s useful to get into the habit of having these conversations early in a project, to avoid having them for the first time when a milestone is approaching and the pressure might be higher.