The last post #28 Avoid “Test Script” Fever was about simplifying an implementation that was more elaborate than it needed to be. There was a waterfall-style project in exactly this situation, and leader responded saying “It has already been coded the other way, and if your goal is to save programmer time, rewriting will just waste more time.” No, it won’t, and this post explains why.
When source code is being written it is easy to rewrite at that time. In this case the benefit will be seen for many years of maintenance, and yet a rewrite might only add 1% or 2% to the total effort if it is done before testing. It is counter-intuitive that re-writing a job will take a fraction of the entire time.
Programmer is fully prepared. The developer involved has invested a lot of time in learning the particulars around that problem, and all the code around it. The details are loaded into the programmer’s memory so to speak. At that point in time, it is trivial to re-write. Just because the code has been written should not be an argument against re-writing, because if it is done at that moment, it will take almost no time, and you will get the benefit for free.
Actual writing is a small part of the job. In any programming exercise, the concept to be implemented is usually very easy to conceive, but the work in programming is 90% in investigation of the specific program that this concept has to be implemented in. There are many many details, and those details have to do with the code that is already written, and mostly about code that will not change as a result of this implementation. Below is a breakout of approximately how time is spent implementing a feature
- 30% Understanding requirement
- 10% discussing with customer
- 10% investigation into competitive implementations
- 10% clarification and questions to/from customer
- 15% investigation of current code to see what is there and what can be changed.
- 10% proposing design, writing spec
- 10% review of design, receiving feedback, incorporating into design
- 10% writing the code,
- 3% inventing details like variable names, method names,
- 3% data structures,
- 2% determining where to put the new code.
- 2% actual typing time
- 10% building/developer testing/committing changes, mechanical aspects of making sure that all the right parts are there.
- 15% integration tests, proving that it works with all works, reviewing all the documentation for correctness, supporting the others on the change.
This is not the result of a serious measurement, but just a representative approximation of the times that might be spent. These values will change from case to case any way. If you feel that the times in you experience are different, then feel free to use your own estimates. The point is simply that there is a lot of time spent on things that are not typing in code.
Preparation takes time. There is a lot of overhead in preparing to make a feature change. There is investigation into the solution space (the various ways that the feature might be implemented). There is also investigation into the constraint space (the possible limitations imposed by the existing code). The programmer considers many options over the course of this investigation, and eliminates approaches that will not work. The details about this will remain in the programmers head a few weeks or so. A rewrite does not require that preparation to be repeated as long as you do it within a few weeks.
Rewrites are faster than the original. The fact is that the programmer knows the most about the problem and the code the solution is being put into immediately AFTER implementing it the first time. The process of actually writing the solution forces the programmer to intimately understand many things about the codebase. It is that understanding of the code that takes the time, not the typing. I have often used the example that if a programmer spends a week implementing something, and then loses it all in a disk crash, it generally only takes a few hours for the programmer to reimplement that code. Because in their head they have all the details worked out. All they need to do is to type it in again. It takes some time, but usually only a small fraction of the original time. And actually, if you ever get the chance to do this, the second time it is usually a lot cleaner and better organized.
Rewrites are never full rewrites. Deciding to take a different approach will often leave large parts of the source unchanged. There are aspects of the original job that will not have to be changed, even though the entire approach has changed. This is hard to explain, but even a complete re-factoring of class files will often involve cutting and pasting of existing code, along with modification of small parts. Thus while writing originally took 10% of the time of a project, a complete rewrite might take only an additional 1% or 2% of the time. Structure has many dimensions, and even a complete rewrite will leave some dimensions untouched.
Delayed rewrites are expensive. Testing of written code takes time. It is true that after the code is all tested, and after the code is released, then a re-write becomes expensive again. If you wait a long time, like 2 or 3 months, the programmer will not have a full recollection of all the constraints inside the system, and will have to spend time “refreshing” the details of the code. Essentially, you will have to do all the preparation again, of course coding again, and go through the release cycle again. All of the normal costs of writing code applies.
Get it right while you can. If, during implementation, you discover that there is a better way to implement it, then the fact that the code is already written should not be a barrier. This is a variant of the “do it right the first time” rule. While the code is “opened up” you should do everything possible to get it right.
Rapid Iteration. This is why rapid iteration in development makes sense. What you want is a fast cycle, where the programmer implements something, and it is reviewed almost immediately. If the feedback comes within a few days of implementation, then it is trivial for the programmer to fix it, even if it requires a complete re-write. If the feedback on the implementation comes within a few days, the programmer is in a position to respond. Waiting 6 weeks, or 3 months for feedback is such a long time, that programmers are usually off working on something else at that time. Making a fix then is expensive.
Coding vs. maintenance. The particular instance that prompted this blog post, a suggestion was made to simplify the code so that maintenance would be reduced. You always want to reduce maintenance if possible, because that will pay back in the future. A poor, complex implementation that increases the amount of work in the future, robs the team of future possibilities. At the time of implementation, it is possible to do a little bit of work to simplify the implementation. That additional coding time will be trivial. It is especially trivial if the code simplification is done before testing, because then you really incur no additional overhead beside the coding. And, once the preparations have been made, once the programmer is fully prepared, doing a re-write to simplify the code can be fast and cheap. This inexpensive change will pay off over many years, and will leave you in a position to be more competitive in the future.
Always worth some effort to simplify the code. Usually after implementation, the programmer understands the problem and situation better than at any previous time. It is always worth taking some time at that point and making a pass to simplify the code (reduce complexity). Unnecessary variables, parameters, and methods should be eliminated.
Thought experiment. If my arguments above have not convinced you, I would urge you to do a simple thought experiment. How hard is it to change method parameters? Imagine that you have just spent a week writing 1000 lines of code. If at that time you were to do a global search and modify the parameters, it would be a trivial exercise. You are already familiar with all the places that the method is used, and can more or less instantly resolve any conflicts. Familiarity with the call sites means you can easily rearrange the parameters at the call sites using variables from the context. This might take only a few minutes for a method used at 10 points in the code. By contrast consider being asked to modify the parameters of a method you have not looked at in 3 years in code that has been maintained by dozens of other people. Now, the simple operation of changing parameters becomes a daunting task. First you have to cautiously look for all the call sites, and see what other variables exist. Since you are unfamiliar with the variables at the call sites, you often have to go back and find where they are declared, and then walk through the code to determine what values they actually carry. Instead of a few minutes, this task will take a few hours, and possibly multiple days in complex situations. Code that is fresh in the mind can to 10 to 100 times easier to change!
Waterfall mythology. The reason that some managers fail to realize how important it is to rewrite until it is right, is because they see software engineering as a kind of manufacturing process that produces code. The “code” is the end product, and thus re-write is always a waste. They do not distinguish between code that has an active programmer on it, and code which has never been touched in the past 10 years. To them, a line of code is a line of code. The process is to plan in detail exactly what it is you are going to write, before you write a line of code. Then, once you write it, it is immutable, because that is the product. Furthermore, mid course corrections might break something else which had been planned on this code. A re-write appears to them as duplication of work. Any change should go through the entire planning cycle, and they don’t seem to see that all the work determining the requirements is work that does not have to be done again.
In the head of the programmer. I have always maintained that the software is not the source code that is written on disk. It is really in the minds of the programmers. You work for years to build a team with certain habits and agreements on how things should be done. Once such a team is built, you will find they can implement software source very quickly. An experienced team can add new features up to 200 times faster than a team that is not experienced on a particular code base. The source code becomes an inconsequential part of the product, useful only for carrying the idea from the programmer’s brains to the customers workstation. A good team can mold and change that product easily and as needed by customers and a competitive marketplace. Source code without a team is almost useless because soon the marketplace leaves the current implementation behind. It is a fallacy to think of programmers as producing source code. Instead, you should think of the source code as a vehicle, and trained programmers as being skilled in driving the product to different destinations. Never, ever lose a chance to simplify the code for the sake of making it more responsive in the future.