Exposing the War on Design

A long time ago, I watched Sandi Metz' GoRuCo 2011 talk, Less - The Path to Better Design. I really liked that talk, and I felt like I got a lot out of it at the time. She talks about design, and shows how to improve the structure of a piece of code. There were some things I didn't feel like I all-the-way understood the first time around, but it was a really good talk and got a bookmark. Then, one day more recently, I watched the talk again, and I discovered how much I missed the first time around. The ideas about design were much deeper than I originally understood, and those parts I didn't all-the-way understand were suddenly much clearer.

We develop software in a cloud of uncertainty. This is not a failing, but simply a reality. This cloud of uncertainty frustrates us when we try to design our software. It frustrates us so much that we as an industry have taken the argument up a meta-level to a debate about whether we should even try to design, when, and how much. We've tried to develop maxims like scorning Big Design Up-Front (BDUF) and strictly adhering to You Ain't Gonna Need It (YAGNI). These maxims come from lessons hard-learned over decades, but hard-and-fast rules like these can expose us to exactly the same dangers they are meant to help us avoid.

We find ourselves in the middle of a war on design. In the past we over-designed, and designed too early. The answer must be to never let ourselves to that again! In doing so, we move from BDUF to what is often NoDE (No Design Ever). We build as fast as we can now and all design decisions can come later when we refactor! (You know, when there's nothing else to do and we can just do some free refactoring.) We've pushed design forward so far into the future that we may not (and often do not) ever do it.

This is not the answer. Design is hard, even without the uncertainty. Narrowing the scope of design down to Sandi's simple definition of design as the art of arranging code, we still face an obstacle that can feel insurmountable. We can't just avoid it though. So we need to use uncertainty to as best an advantage as we can. I feel the most powerful point made in Sandi's talk is:

You can do design that preserves your right to do design later.

In this statement, we see exactly the power of design, and the constraints by which we must do design. We can design our software such that it is flexible and robust to future change, without needing to over-engineer and without needing to make that future change before we know we need it. Design can be leveraged to mitigate future risk. Risk mitigation is a very business-y idea, and that's important because we sometimes forget that we are creating software for business-y ends. Creating software has costs to people. It also has benefits, and those benefits should outweigh the costs. If all we do is make cost without benefit, we should change course, because we are not serving our customers well.

A question was posed on CodeReview.SE asking why the author's code for an interview was rejected for not being OOP enough. There were several answers offered including my own that offered two different strategies. There was another (posted after mine and a bit critical of all the previous answers) that rubbed me the wrong way. Its first iteration was incorrect according to the requirements (an item could be both imported and sales taxed). But even ignoring that, it struck me as lacking (the author's assertions of a superior answer didn't help). The code didn't seem to follow SOLID. The item class held the tax algorithm and an enum relating to what tax type it should use in that calculation. From there, it called out directly to a global static dictionary to translate that enum into a decimal rate. The maintainability challenges seemed like they might be tolerable, but they were there.

Then the author fixed the bug, and it all came together for me why that code is so wrong. To account for being potentially imported, a boolean flag was added to item objects, then that flag was used to determine if a second call should be made out to that global dictionary. This code did not meet the TRUE-ness test Sandi set out in her talk. The code was no longer as transparent. These fees would have to be added in two different places (constructor, attribute) in two different ways (enum, boolean). It no longer seemed reasonable. Adding a new tax algorithm or new tax type meant changing code rather than adhering to open-closed. It was clearly no longer (re-)usable. The fact that applying the different fees required two different approaches exposed that the original way was not sufficiently reusable. It was definitely not exemplary. The fact that there were two different ways to add a fee meant that there is not a clear path to potentially adding a third.

After a little back and forth where I suggested that the code would be harder to maintain with proliferating conditionals, the author replied with the following:

As the requirements change the design must change appropriately. However we do not want to be over-engineering a solution based on speculation of unknown future requirements.

I am here criticizing the author's response to this particular question, but I am not questioning his integrity or skills. He clearly is interested in showing how to do code right. He has an interest in doing the best for business interests. The quote above shows that. Many of the points he makes in his answer are correct. But the above comment embodies the war on design. We can't guess what the future might hold, so let's not "over-engineer". But we know that we can design code now to be robust to change, code that benefit from design today that preserves for us the right to do design tomorrow. That little extra effort today is worth the prize of that robustness.

Martin Fowler wrote an entire article in support of YAGNI, but had a very important thing to say about it:

Yagni only applies to capabilities built into the software to support a presumptive feature, it does not apply to effort to make the software easier to modify.

YAGNI and avoiding BDUF are not excuses to make code that is harder to maintain. We always need to do some design, even in uncertainty. We should not shy away from it because it is hard. We should not go to war with design and deny it. We should take advantage of the tools we have at hand to minimize risk and to build robust code.


Popular posts from this blog

The Timeline of Errors

Magic Numbers, Semantics, and Compiler Errors

Assuming Debugging