"Philosophy of Software Design" (4) [Working code is not enough]

Chapter 3 Working code is not enough

Chapter 3 Working Code Isn’t Enough(Strategic vs. Tactical Programming)

One of the most important elements of good software design is the mindset you adopt when you approach a programming task. Many organizations encourage a tactical mindset, focused on getting features working as quickly as possible. However, if you want a good design, you must take a more strategic approach where you invest time to produce clean designs and fix problems. This chapter discusses why the strategic approach produces better designs and is actually cheaper than the tactical approach over the long run.

One of the most important elements of good software design is the way of thinking you use when performing programming tasks. Many organizations encourage a tactical way of thinking, focusing on making features run as quickly as possible. However, if you want a good design, you must take a more strategic approach and spend time on it to make a clean design and solve problems. This chapter discusses why strategic methods can produce better designs in the long run, but are actually cheaper than tactical methods.

3.1 Tactical programming

Most programmers approach software development with a mindset I call tactical programming. In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix. At first glance this seems totally reasonable: what could be more important than writing code that works? However, tactical programming makes it nearly impossible to produce a good system design.

Most programmers develop software with a mentality that I call tactical programming. In the tactical approach, your main focus is to make certain features work, such as new features or bug fixes. At first glance, this seems completely reasonable: What is more important than writing effective code? However, tactical programming is almost impossible to produce a good system design.

The problem with tactical programming is that it is short-sighted. If you’re programming tactically, you’re trying to finish a task as quickly as possible. Perhaps you have a hard deadline. As a result, planning for the future isn’t a priority. You don’t spend much time looking for the best design; you just want to get something working soon. You tell yourself that it’s OK to add a bit of complexity or introduce a small kludge or two, if that allows the current task to be completed more quickly.

The problem with tactical programming is that it is short-sighted. If you are a tactical programmer, you will try to complete the task as quickly as possible. Maybe you have a difficult deadline. Therefore, planning for the future is not a priority. You will not spend too much time looking for the best design. You just want to make something work as quickly as possible. You tell yourself that you can add some complexity or introduce one or two small mistakes. If this can make the current task complete faster, you can.

This is how systems become complicated. As discussed in the previous chapter, complexity is incremental. It’s not one particular thing that makes a system complicated, but the accumulation of dozens or hundreds of small things. If you program tactically, each programming task will contribute a few of these complexities. Each of them probably seems like a reasonable compromise in order to finish the current task quickly. However, the complexities accumulate rapidly, especially if everyone is programming tactically.

This is how the system becomes complicated. As mentioned in the previous chapter, the complexity is increasing. Not the specific things that complicate the system, but the accumulation of dozens or hundreds of small things. If you do tactical programming, every programming task brings some of this complexity. In order to complete the current task quickly, each of them seems to be a reasonable compromise. However, complexity accumulates quickly, especially if everyone is programming tactically.

Before long, some of the complexities will start causing problems, and you will begin to wish you hadn’t taken those early shortcuts. But, you will tell yourself that it’s more important to get the next feature working than to go back and refactor existing code. Refactoring may help out in the long run, but it will definitely slow down the current task. So, you look for quick patches to work around any problems you encounter. This just creates more complexity, which then requires more patches. Pretty soon the code is a mess, but by this point things are so bad that it would take months of work to clean it up. There’s no way your schedule can tolerate that kind of delay, and fixing one or two of the problems doesn’t seem like it will make much difference, so you just keep programming tactically.

Before long, certain complexities will start to cause problems, and you will start to hope that you did not take these early shortcuts. However, you will tell yourself that getting the next function to work is more important than going back and refactoring the existing code. In the long run, refactoring may help, but it will definitely slow down the current task. Therefore, you need a quick patch to resolve any problems encountered. This only adds complexity, and then more patches are needed. Soon the code became a mess, but so far, the situation has been very bad, and it takes months to clean it up. Your schedule cannot tolerate this delay, and solving one or two problems doesn't seem to make much difference, so you just keep programming tactically.

If you have worked on a large software project for very long, I suspect you have seen tactical programming at work and have experienced the problems that result. Once you start down the tactical path, it’s difficult to change.

If you have been engaged in large software projects for a long time, I suspect that you have seen tactical programming in your work and encountered the problems that caused it. Once you follow the tactical route, it is difficult to change.

Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado. The tactical tornado is a prolific programmer who pumps out code far faster than others but works in a totally tactical fashion. When it comes to implementing a quick feature, nobody gets it done faster than the tactical tornado. In some organizations, management treats tactical tornadoes as heroes. However, tactical tornadoes leave behind a wake of destruction. They are rarely considered heroes by the engineers who must work with their code in the future. Typically, other engineers must clean up the messes left behind by the tactical tornado, which makes it appear that those engineers (who are the real heroes) are making slower progress than the tactical tornado.

Almost every software development organization has at least one developer who takes tactical programming to the fullest: Tactical Tornado. Tactical Tornado is a prolific programmer who can extract code much faster than others, but works entirely in a tactical manner. When implementing quick functions, no one can complete tasks faster than a tactical tornado. In some organizations, management views tactical tornadoes as heroes. However, the tactical tornado left traces of destruction. They are rarely regarded as heroes by engineers who must use their code in the future. Usually, other engineers have to clean up the chaos left by the tactical tornado, which makes the progress of those engineers (who are the real heroes) seem to be slower than the tactical tornado.

3.2 Strategic programming

The first step towards becoming a good software designer is to realize that working code isn’t enough. It’s not acceptable to introduce unnecessary complexities in order to finish your current task faster. The most important thing is the long-term structure of the system. Most of the code in any system is written by extending the existing code base, so your most important job as a developer is to facilitate those future extensions. Thus, you should not think of “working code” as your primary goal, though of course your code must work. Your primary goal must be to produce a great design, which also happens to work. This is strategic programming.

The first step to becoming a good software designer is to realize that working code is not enough. It is unacceptable to introduce unnecessary complexity to complete the current task faster. The most important thing is the long-term structure of the system. Most of the code in any system is written by extending the existing code base. Therefore, as a developer, the most important job is to facilitate these future extensions. Therefore, although your code must of course work, you should not consider "working code" as your main goal. Your main goal must be to make a great design, and this design will work. This is a strategic plan.

Strategic programming requires an investment mindset. Rather than taking the fastest path to finish your current project, you must invest time to improve the design of the system. These investments will slow you down a bit in the short term, but they will speed you up in the long term, as illustrated in Figure 3.1.

Strategic programming requires an investment mentality. You must spend time to improve the design of the system, not take the fastest way to complete the current project. These investments will slow you down in the short term, but will speed up your pace in the long run, as shown in Figure 3.1.

Some of the investments will be proactive. For example, it’s worth taking a little extra time to find a simple design for each new class; rather than implementing the first idea that comes to mind, try a couple of alternative designs and pick the cleanest one. Try to imagine a few ways in which the system might need to be changed in the future and make sure that will be easy with your design. Writing good documentation is another example of a proactive investment.

Some investments will be positive. For example, it is worth spending some time to find a simple design for each new category. Instead of implementing the first idea that comes to mind, try several alternative designs and choose the simplest design. Imagine several ways in which the system may need to be changed in the future and ensure that the design is easy. Well-written documentation is another example of active investment.

Other investments will be reactive. No matter how much you invest up front, there will inevitably be mistakes in your design decisions. Over time, these mistakes will become obvious. When you discover a design problem, don’t just ignore it or patch around it; take a little extra time to fix it. If you program strategically, you will continually make small improvements to the system design. This is the opposite of tactical programming, where you are continually adding small bits of complexity that cause problems in the future.

Other investments will be passive. No matter how much you invest upfront, errors will inevitably occur in design decisions. Over time, these errors will become obvious. When you find a design problem, don’t just ignore it or fix it. Take some extra time to fix it. If you do strategic programming, you will continue to make small improvements to the system design. This is the opposite of tactical programming. In tactical programming, you continue to add some complexity that will cause problems in the future.

3.3 How much to invest? How much to invest?

So, what is the right amount of investment? A huge up-front investment, such as trying to design the entire system, won’t be effective. This is the waterfall method, and we know it doesn’t work. The ideal design tends to emerge in bits and pieces, as you get experience with the system. Thus, the best approach is to make lots of small investments on a continual basis. I suggest spending about 10–20% of your total development time on investments. This amount is small enough that it won’t impact your schedules significantly, but large enough to produce significant benefits over time. Your initial projects will thus take 10–20% longer than they would in a purely tactical approach. That extra time will result in a better software design, and you will start experiencing the benefits within a few months. It won’t be long before you’re developing at least 10–20% faster than you would if you had programmed tactically. At this point your investments become free: the benefits from your past investments will save enough time to cover the cost of future investments. You will quickly recover the cost of the initial investment. Figure 3.1 illustrates this phenomenon.

So, what is the correct investment amount? A large upfront investment (such as trying to design the entire system) will not be effective. This is the waterfall method, and we know it doesn't work. As you understand the system, the ideal design tends to appear fragmented. Therefore, the best way is to continuously make a large number of small investments​​. I suggest you invest 10% to 20% of the total development time. The amount is small enough to not have a significant impact on your schedule, but large enough to generate significant benefits over time. Therefore, your initial project will take 10-20% longer than pure tactical methods. The extra time will lead to better software design, and you will begin to experience these benefits within a few months. Soon, your development speed will be at least 10-20% faster than tactical programming. At this point, your investment will be free: the income from your past investments will save enough time to pay for future investments. You will quickly recover the cost of the initial investment. Figure 3.1 illustrates this phenomenon.

[External link image transfer failed. The source site may have an anti-leech link mechanism. It is recommended to save the image and upload it directly (img-agNP25XG-1600223412160)(./figures/00011.jpeg)]

Figure 3.1: At the beginning, a tactical approach to programming will make progress more quickly than a strategic approach. However, complexity accumulates more rapidly under the tactical approach, which reduces productivity. Over time, the strategic approach results in greater progress. Note: this figure is intended only as a qualitative illustration; I am not aware of any empirical measurements of the precise shapes of the curves.

Figure 3.1: From the beginning, tactical programming methods will progress faster than strategic methods. However, under the tactical approach, complexity accumulates faster, thereby reducing productivity. Over time, the strategic approach will bring greater progress. Note: This diagram is for qualitative illustration only; I am not aware of any empirical measurements of the precise shape of the curve.

Conversely, if you program tactically, you will finish your first projects 10–20% faster, but over time your development speed will slow as complexity accumulates. It won’t be long before you’re programming at least 10–20% slower. You will quickly give back all of the time you saved at the beginning, and for the rest of system’s lifetime you will be developing more slowly than if you had taken the strategic approach. If you haven’t ever worked in a badly degraded code base, talk to someone who has; they will tell you that poor code quality slows development by at least 20%.

On the contrary, if you do tactical programming, you can increase the speed of completion of the first project by 10% to 20%, but over time, the accumulation of complexity will reduce the speed of development. Soon, your programming speed will decrease by at least 10-20%. You will quickly return all the time you saved at the beginning, and throughout the life of the system, your development speed will be slower compared to adopting a strategic approach. If you have never used a severely degraded code base, please contact someone with experience. They will tell you that poor code quality will reduce development speed by at least 20%.

3.4 Startups and investment

In some environments there are strong forces working against the strategic approach. For example, early-stage startups feel tremendous pressure to get their early releases out quickly. In these companies, it might seem that even a 10–20% investment isn’t affordable. As a result, many startups take a tactical approach, spending little effort on design and even less on cleanup when problems pop up. They rationalize this with the thought that, if they are successful, they’ll have enough money to hire extra engineers to clean things up.

In some environments, powerful forces run counter to strategic methods. For example, early-stage startups feel tremendous pressure and need to release their early versions as soon as possible. In these companies, even 10% to 20% of the investment seems unaffordable. As a result, many startups have adopted a tactical approach, spending very little effort on design, and spending less effort on cleaning up when problems arise. They believe that if successful, they will have enough money to hire additional engineers to clean up the problem and thus rationalize it.

If you are in a company leaning in this direction, you should realize that once a code base turns to spaghetti, it is nearly impossible to fix. You will probably pay high development costs for the life of the product. Furthermore, the payoff for good (or bad) design comes pretty quickly, so there’s a good chance that the tactical approach won’t even speed up your first product release.

If you are a company moving in this direction, you should realize that once the code base becomes spaghetti, it is almost impossible to fix. You may pay high development costs for the life of the product. In addition, the rewards of good (or bad) design will come soon, so the tactical approach will most likely not even speed up your first product release.

Another thing to consider is that one of the most important factors for success of a company is the quality of its engineers. The best way to lower development costs is to hire great engineers: they don’t cost much more than mediocre engineers but have tremendously higher productivity. However, the best engineers care deeply about good design. If your code base is a wreck, word will get out, and this will make it harder for you to recruit. As a result, you are likely to end up with mediocre engineers. This will increase your future costs and probably cause the system structure to degrade even more.

Another thing to consider is that one of the most important factors for a company's success is the quality of the engineers. The best way to reduce development costs is to hire good engineers: their costs will not be much higher than ordinary engineers, but the productivity is much higher. However, the best engineers are deeply interested in good design. If your code base is cruel, then the words will become useless, which will make it difficult to recruit. As a result, you may end up with ordinary engineers. This will increase your future costs and may lead to further degradation of the system structure.

Facebook is an example of a startup that encouraged tactical programming. For many years the company’s motto was “Move fast and break things.” New engineers fresh out of college were encouraged to dive immediately into the company’s code base; it was normal for engineers to push commits into production in their first week on the job. On the positive side, Facebook developed a reputation as a company that empowered its employees. Engineers had tremendous latitude, and there were few rules and restrictions to get in their way.

Facebook is an example of a startup that encourages tactical programming. For many years, the company's motto has been "Move fast and break the predicament". Encourage new engineers who have just graduated from university to dive into the company's code base immediately; it is normal for engineers to commit to production during the first week of work. On the positive side, Facebook has a reputation as a company that empowers employees. Engineers have great freedom and there are almost no rules and restrictions.

Facebook has been spectacularly successful as a company, but its code base suffered because of the company’s tactical approach; much of the code was unstable and hard to understand, with few comments or tests, and painful to work with. Over time the company realized that its culture was unsustainable. Eventually, Facebook changed its motto to “Move fast with solid infrastructure” to encourage its engineers to invest more in good design. It remains to be seen whether Facebook can successfully clean up the problems that accumulated over years of tactical programming.

Facebook has achieved remarkable success as a company, but its code base has been affected due to the company's tactical approach. Many codes are unstable and difficult to understand, have almost no comments or tests, and are painful to use. Over time, the company realized that its culture was unsustainable. Eventually, Facebook changed its motto to "move fast with a solid infrastructure" to encourage its engineers to invest more in good design. It remains to be seen whether Facebook can successfully clear the problems accumulated in tactical programming over the years.

In fairness to Facebook, I should point out that Facebook’s code probably isn’t much worse than average among startups. Tactical programming is commonplace among startups; Facebook just happens to be a particularly visible example.

To be fair, I should point out that Facebook's code may not be much worse than the average start-up company. Tactical programming is commonplace in startups. Facebook happens to be a particularly obvious example.

Fortunately, it is also possible to succeed in Silicon Valley with a strategic approach. Google and VMware grew up around the same time as Facebook, but both of these companies embraced a more strategic approach. Both companies placed a heavy emphasis on high quality code and good design, and both companies built sophisticated products that solved complex problems with reliable software systems. The companies’ strong technical cultures became well known in Silicon Valley. Few other companies could compete with them for hiring the top technical talent.

Fortunately, it is possible to succeed in Silicon Valley through a strategic approach. Google and VMware grew at about the same time as Facebook, but both companies took a more strategic approach. Both companies attach great importance to high-quality code and good design, and both companies have developed complex products that solve complex problems through reliable software systems. The company's strong technical culture is well known in Silicon Valley. Few other companies can compete with them to hire top technical talent.

These examples show that a company can succeed with either approach. However, it’s a lot more fun to work in a company that cares about software design and has a clean code base.

These examples show that a company can successfully use either method. However, working in a company that cares about software design and has a clear code base is much more interesting.

3.5 Conclusion

Good design doesn’t come for free. It has to be something you invest in continually, so that small problems don’t accumulate into big ones. Fortunately, good design eventually pays for itself, and sooner than you might think.

Good design is not free. It must be something you keep investing in, so that small problems will not accumulate into big problems. Fortunately, a good design will eventually pay for itself, and sooner than you think.

It’s crucial to be consistent in applying the strategic approach and to think of investment as something to do today, not tomorrow. When you get in a crunch it will be tempting to put off cleanups until after the crunch is over. However, this is a slippery slope; after the current crunch there will almost certainly be another one, and another after that. Once you start delaying design improvements, it’s easy for the delays to become permanent and for your culture to slip into the tactical approach. The longer you wait to address design problems, the bigger they become; the solutions become more intimidating, which makes it easy to put them off even more. The most effective approach is one where every engineer makes continuous small investments in good design.

It is essential to consistently apply strategic methods and treat investment as something to do today, not tomorrow. When you are in a crisis, it is easy to postpone cleanup until after the crisis is over. However, this is a slippery slope. After the current tightening, it will almost certainly happen again. Once you start delaying design improvements, it's easy to make the delay permanent and plunge your culture into a tactical approach. The longer you wait to solve a design problem, the bigger the problem becomes; the solution becomes more daunting, which makes it easier to postpone the solution easily. The most effective method is for each engineer to make a small continuous investment in a good design.

Guess you like

Origin blog.csdn.net/WuLex/article/details/108616630