There was a time (not so long ago) when we designed everything before we wrote any code.
We gathered the requirements of our application and wrote the requirement specification. We took these requirements and designed an architecture that helped us to fulfill them. We wrote an architecture design document as a guide that we followed when implemented our application.
Unfortunately we never got a chance to do these things.
We were only developers who had to follow plans written by other people. Our job was to write code by following the architecture that was designed by the architects.
Nevertheless, our job should have been simple. We knew all requirements and we had an architecture that should help us to solve any technical problems that we might face.
That was a nice dream. Unfortunately things did not go as planned:
- The requirements were not correct. To make matters worse, we found this out after we had already implemented most (or all) of them. This meant that we had to rewrite large parts of the application. This took time and money.
- The architecture did not help us because the architects thought that they are solving a well structured problem that has only one correct solution. Unfortunately for them, software projects solve ill structured problems. This meant that we had no possibility to learn and we had to follow the original plan no matter what. Bad decisions were not overruled because that would have made the architects look bad.
- We were frustrated because we had no control over our work. We were essentially just typewriters.
Then we heard about agile software development.
Big Design Up Front Is an Anti-Pattern
The Agile Manifesto promised to set us free. It states that:
“Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan
That is, while there is value in the items on
the right, we value the items on the left more.”
We got excited. We started to believe that the big design up front is an anti-pattern that should be avoided. We started writing code by following these principles:
We had only one rule:
“Do the simplest thing that could possibly work”
It made perfect sense to us. The upfront architecture design did not make our job easier. In fact, it made our job harder. We were burned by the waterfall model and we wanted to do something totally different. We abandoned the upfront architecture design and decided to solve all technical problems when we ran into them.
This worked pretty well in small software projects (and in the beginning of a bigger software project). However, when we abandoned the upfront architecture design, we started to ignore the consequences of our decisions.
We were excited, motivated, and we thought that we were doing the right thing. But the truth was that our actions caused three very common problems:
- We wrote our code in small increments and altered it based on the feedback given by our customer. We should have stopped and refactored our code before moving on to our next task, but this requires discipline and we didn’t have it.
- We didn’t handle corner cases, exceptional situations, or errors properly. We did the simplest thing that could possibly work and because handling these situations was hard, we decided to implement it later when it was really needed. The problem was that when it was needed, our code was already such a mess that fixing it would have taken too long. That is why we decided to simply write an error message to the log file and moved on.
- We used different patterns, frameworks, or libraries for solving the same problem. Because we had no technical authority, everyone of us chose the “best” tool for the job and used it. We created a code base that suffers from the lava layer anti-pattern. This might have happened during multiple years, but I have seen this happen during the first month of a greenfield project.
It seems that abandoning the upfront architecture design made us happier and probably helped us to add more value to our customer. However, it did not help us to build better software.
We prioritized our short term productivity over our long term productivity. We created a big ball of mud and ensured that maintaining our application is pain in the ass uncomfortable.
“Upfront Design” Done Right
Am I exaggerating? You bet I am, but my experience has shown to me that if the team does no upfront design, they are very likely to make these mistakes. I have seen this happen over and over again, and the odds are that you have seen this too.
That is why I think that we can benefit from the upfront architecture design, but we should not overdo it. We must remember that the goal of the “old fashioned” upfront design is to find the only way to solve the customer’s problem. We don’t need this kind of upfront design.
We need upfront design that does not tie our hands. We need upfront design that helps us to keep our options open as long as possible and doesn’t prevent us from changing things that we don’t get right at the first time.
It is hard to find the balance between the “old fashioned” upfront design and no design, but it is definitely possible. We can get started by following these five rules:
- We should know our tools. If a certain problem is often solved in the same way, there is probably a good reason for that. That is why we should consider using that method as well.
- We should avoid best practices that aren’t helping us to do a better job. Common sense is the most important tool of a software developer, and we should always remember to use it.
- Before we write any code, we should design how we handle the cross cutting concerns such as error handling, validation, transactions, and security. We should also have a rough idea about the architecture of our application, but we should not carve it in stone. It is just a sketch and we will (or we should) update it when we learn more about the customer’s problem.
- When we implement a single feature, we should do the simplest thing that could possibly work. After we have written the code, we must evaluate the consequences of our decisions. If our changes have a negative effect to the architecture of our application, we must refactor or rewrite our code. This is a favor that we must do to the developers who maintain our application after we have moved on.
- We must ensure that every member of our team is an architect. If all team members are allowed to participate to the architecture design, they are more likely to follow the common guidelines because they helped us to create them.