Restraint-Driven Development

September 28, 2016

We spend a lot of time reading, talking, and blogging about the techniques we use to write world-class code. Topics like design patterns, best practices, and refactoring are well-covered. Far less time, however, is devoted to honing our ability to know which code not to write.

Test-Driven Development (TDD) tends to be a polarizing concept. There are devs who swear by it and devs who swear at it. For some, it is the only way they are willing to write code. The result—they claim—is better-tested, more concise, and well-structured/decomposed code. For others, it produces mountains of tiny, ill-conceived, and ineffectual tests that sacrifice cohesive, thoughtful tests for higher coverage numbers. As with most polarizing topics, the truth is likely somewhere in between the two extremes.

For those unfamiliar, TDD can be summed up as the "red, green, refactor, repeat" methodology. First you author a unit test for the piece of code you want to write, which fails when you run it (the "red" stage). Then you write the bare minimum amount of code required to make the test pass and run it again (the "green" stage). Next you evaluate your approach and make any necessary refactorings to improve the implementation ("refactor"). Finally, you repeat the whole cycle for the next piece of code.

Regardless of your feelings toward TDD, there is one piece of the cycle that we should embrace at levels higher than individual units: doing the minimum required to achieve your aim. We're familiar with this concept at much higher levels, under the monikers "minimum viable product" and "lean manufacturing." MVP is usually limited to the beginning of projects and rarely persists down to the level of writing production code for existing systems. This is a concept that we would be well-served to apply to all of our development activities.

As engineers, we are fundamentally problem-solvers. There are few things that are more enjoyable than sinking our teeth into a real challenge and flexing our intellectual and creative muscles to arrive at a solution. Very often we embark on a seemingly straightforward task and find ourselves working on multiple branches, fighting merge conflicts, refactoring huge swaths of unit tests, and watching our PRs stretch out over weeks. But by practicing restraint and maintaining our focus on the specific requirements we're addressing, we can easily avoid this quagmire.

"But wait", I hear you screaming, "all of those other pieces are important, too! We need to have good session management/configuration handling/etc.". You are correct. We shouldn't lose sight of the bigger ecosystem into which we are placing our code. However, we need to remain vigilant to ensure that each PR doesn't grow into an ecosystem of its own. Take a moment now to look to your left and then to your right. There's a reason why we put so much effort into building great teams of great people. No one is expecting any one person to do everything. Instead of letting yourself go down these paths, restrain your enthusiasm and create a new ticket. Let one of the amazing people around you join in on some of the fun.

Furthermore, how certain are you that these extra pieces are actually needed? We have processes for validating the work we do, specifically to avoid spinning our tires on things that are ultimately YAGNI—You Aren't Gonna Need It. Even in the event that we add something that is needed, we'll still incur the "cost of delay" on the originally planned work.

"Small yagni under the radar of project planning. As a developer it's easy to spend an hour adding an abstraction that we're sure will soon be needed. Yet ...a lot of small yagni decisions add up to significant reductions in complexity to a code base, while speeding up delivery of features that are needed more urgently. "
Martin Fowler (emphasis mine)

At the code level, we're all familiar with the concept of decomposition, and we'd most likely prefer to not build our systems from thousand-line functions all the time. Breaking things down into more manageable pieces makes as much sense at the task level as it does for code. Rolling a session management system into your task to add a REST endpoint to retrieve metadata should be just as distasteful as that thousand-line function.

At the design level, this approach can be used to counteract the "not invented here" syndrome. When viewed through the lens of "what is the minimum needed to satisfy our requirements?", building our own version of something which is readily available from, say, Amazon, will rarely be the optimal approach. In those cases where building something is the right answer, we can rest easy knowing that it was carefully considered and found to be the most appropriate method to achieve our goals.

It's easy to acknowledge and applaud our skills and successes when we have something demonstrable and reviewable to evaluate. It can also be quite difficult to give kudos to our co-workers for the code they don't write. Oftentimes, however, this can be even more valuable than some of the best code we produce. Showing restraint and focus can often pay dividends that can exceed the return on even the most elegant and clever code.