Good Reasons to Re-write Code from Scratch
Bad Reasons to Re-write Code from Scratch
Never re-write the entire program from scratch.
"ThingsYouShouldNeverDo, Part I" by Joel Spolsky 2000-04-06 "The single worst strategic mistake that any software company can make: [Deciding] to rewrite the code from scratch." "a cardinal, fundamental law of programming: It's harder to read code than to write it." http://www.joelonsoftware.com/articles/fog0000000069.html
The point of Spolsky's article is that LegacyCode is often ugly because it contains a lot of bug fixes, feature additions, and architectural changes that result from long-term usage and growth of the application. When one decides to re-write from scratch, one is likely to lose the knowledge and functionality hidden in all that messy code. A lot of that dirty stuff buried in the mud is really gold, and you shouldn't throw it all away. CodeLearns.
Note the context. Spolsky is talking about rewriting, from scratch, an entire product; the "hidden gold" he's talking about is a direct result of VoodooChickenCoding: "It works, but I don't know why, or the last programmer who did retired/left/got hit by a truck" (TruckNumberFixed). ProgrammerTests are mitigating factors here, since they have the potential to become the final arbiter of what the code does and doesn't do.
"Part 1"? Does that mean there's a "Part 2"? Not as of 2003-08-18.
Isn't the statement "It's harder to read code than write it" in conflict with "never rewrite from scratch"?
Maybe Spolsky is saying "It is harder to read mature well-tested code written by someone else than to write new untested code yourself."
Maybe Spolsky is saying Reading code is "harder" than re-writing code in the sense that it's more mentally exhausting (ActualGummiBearsForEstimation), and yet reading code takes less time than re-writing code (kind of like the 400 meter sprint is faster but more exhausting than a 400 meter walk). The "feedback" link http://www.joelonsoftware.com/news/fog0000000215.html mentions "Most people have trouble reading code because their eyes are used to reading at a certain speed from reading text written in human languages. ... It takes some skill to learn how to read code slowly and carefully, and many programmers are not patient enough (so they wind up rewriting the code from scratch). But you have to remember that it's still faster to read than to rewrite!"
See the excellent book CodeReading for good TipsForReadingCode.
Spolsky's article lists several rewrite-from-scratch efforts that failed miserably, sinking the products and sometimes taking the owning companies down with them. Does anyone know of any counter-examples: that is, complete rewrites that were dramatic successes?
You also need to define success. Mozilla is successful because a good product was created. It was a failure because it allowed IE to get market share. TwoThingsCanBothBeTrue.
"Never re-write the entire program from scratch."
Sorry, disagree. I have had lots of confirmation for this, but one outstanding case was a thermal transfer printer where the, um, "software" that I inherited was so bad as to be useless. A total pile of kaka. (Sorry, that's my professional assessment, not an opinionated observation. I can't repeat that in polite company like Wiki.) Anyway, asking for and receiving authority to start over made my day a little easier, especially when I devised tests that proved that the basic electronic and electromechanical subsystems would never meet the published specifications for the end product. Without starting over I never could have established the tests required to prove these shortcomings in the infrastructure. Oh, well.
I had another embedded project that somebody else had started, played with for a year or so, then dropped like a hot brick. This one was a roller platen continuous printer, a device that would print continuous pin feed forms (pre-printed checks) with a fixed imprint (a signature plate). The original code was a terrible combination of C and 8051 assembly. It wasn't even close to being done, but it did enough stuff that the management thought I should be able to pick it up and run <cough, hack> with it. I had to trash everything to get the low-level hardware control fast enough to meet the operating requirements. Only two interrupts, you know.
Another project like that was a motion/speed/position control instrument that used a 68000 to directly (!) drive a 20-character display in addition to everything else you would expect a CPU in a complex motion controller to do. The UI code was in C, but the entire parameter set was stored as text (!) values in a list for ease of display. Oh, and if you wanted to actually change some parameter you had to extract the text, convert it into a numeric value, play with that, and then convert it back to text before storing it. Holy mackerel, there, Saphire!
In all of these cases, the product did something when I acquired the code. None of them could come close to specified performance, of course. In each case, I had to start over from scratch just to get the basic performance of the underlying hardware sorted out. Never mind the fact that all these beauties had code carved out of stone with a hammer and chisel. If I hadn't started over, none of these projects would have gone anywhere. As it is, one of them was dropped completely and the other two were eventually turned over to other consulting houses. Craps. All that work for nada. Oh, well.
If it was so bad as to be useless, then a re-write would be in order. Spolsky's advice applies to any proposal to throw away the existing code for a successful, useful, and mature application that has strategic importance for the company. If something just doesn't work, and never did work, then throwing it away and starting over can make a lot of sense.
alternatives to re-writing code from scratch
Odd thing is, back when I was first getting in to this back in the '80s, I had a spiffy book of cool Apple II hacks written by "Barnes and Barnes." In this book was a phrase: "The secret to better programming: Reprogram." It's still true more often than not.
Modifying code that hasn't been properly maintained (read; refactored) can amount to a complete rewrite or a major architectural change. I've seen it happen. If you do this on a module-by-module basis and the code was fairly modular to begin with, your code base can evolve without you having to do a complete rewrite from scratch. At least, that's what I've found. -- BruceIde
"and the code was fairly modular to begin with" Ah, there's the rub, me laddie. Too many times I have been dropped into a mass of LegacyCode that had great sounding names for source files, "modules," etc., only to find that everything was tied together with globals or relied on a previous system state or some other stupidity. In cases like that the re-write was just about the only solution. I've had a couple of examples in my microcontroller days where the client wanted "just one more" feature added to the ten pounds of nails already stuffed into that five pound bag. There was simply no exasperating way the new feature was going to fit. The solution was to blow away a ton of the LegacyCode and make a new system that performed like the old one but had room for more nails.
I strongly question the placement of "Nobody can figure out how it works" under "Good Reasons to Re-write Code from Scratch"
From personal experience, I would say this is one of the worst reasons to rewrite code from scratch. This is just asking for a project disaster as the developers uncover more and more reasons why the code got so bad in the first place. The end result is typically a code roll back in the version control system and management edicts to never change working code. Rather than rewrite, attempt to refactor and tease out understanding of the code. It is far less painful and far more likely to succeed.
Strongly Agree. If you can't figure it out, you can't rewrite it. If you can write UnitTests for it, you can figure it out.
Sort-of. If the LegacyCode base has got truly nasty, with globals and such-like all over the place, despite having a good understanding of the system it may not be possible to write UnitTests for it as there may be no easily discernible units. I would suggest starting with AcceptanceTests to cover all know requirements, making sure that the existing software passes those tests, then a re-write from scratch can create new UnitTests along the way. -- LeonGierat?
However, the book WorkingEffectivelyWithLegacyCode does introduce a number of techniques to jam UnitTests into the most calcified of LegacyCode.
Counterexample: a bunch of pricing code. It uses a GrandCentralStation model of organization. The pricing is externally verifiable (against a third party system). It was then quicker to rewrite that code from a clean core. However I agree it's harder when no external verification of a successful rewrite is available. Better then to split into subsystems and rewrite those one by one.
Bad Reasons to Avoid Re-writing Code from Scratch: We don't trust the developers to do it right.
Treading gently, I would suggest that this is the primary reason to avoid re-writing code from scratch.
From my own painful experience, I have found developers (in general) tend to under-estimate the difficulty in re-writing code. It is usually easy to duplicate the major operations into a clean re-write, but then details start cropping up. About the time all of the details are resolved, one has overrun the schedule by 100% and the resulting code is almost as bad as the original.
I would recommend that one take the refactoring approach over the re-write approach except in extreme cases such as a major language port. When re-writing from scratch, schedule conservatively and plan for many iterations.
language port:
Just about every EmbeddedSystem I've seen (that wasn't pure AssemblyLanguage) had a mixture of AssemblyLanguage and C. Occasionally, I convert a single C subroutine into AssemblyLanguage or re-write a single AssemblyLanguage subroutine into C. There are also lots of applications written in a mixture of C and C++.
I find it unfortunate that other language ports seem to be AllOrNothing? - there's no way to slowly move one function at a time over to the other side of the fence. (Or am I missing something?)
In particular, it seems that I often want a program to do 6 different things, and the best way to do 1 and 2 seems to be Lisp, 3 and 4 is in C, 5 is in Prolog, and 6 seems to be assembly language. I've given up on the QuestForThePerfectLanguage - why can't I just PickTheRightToolForTheJob for each part, then glom together all the pieces?
-- DavidCary
Can't you write your code in a Lisp with a decent ForeignFunctionInterface, drop down to C for the low-level parts, embed a Prolog interpreter a la Paul Graham's OnLisp, and include inline assembly within the C modules for the really tricky parts? -- JonathanTang
Yes, in theory I could put that all together. But in practice, it seems difficult to grow an application into that kind of architecture.
Imagine I have an application written in C that partially works. Then a new user story comes in that I think would be easier to write in assembly language than in C.
The SimplestThingThatCouldPossiblyWork is to stick in a bit of in-line assembly.
Imagine I have an application written in C that partially works. Then a new user story comes in that I think would be easier to write in Prolog than in C.
The SimplestThingThatCouldPossiblyWork always seems to be to try to do the best you can in C. The user just wants to add one simple thing. Even if it's only 2 lines of Prolog code, it always *seems* easier to write couple of pages of C code than to try to integrate a Prolog interpreter.
Thanks for the link to OnLisp, though - I'll have to check that out.
-- DavidCary
I've found a generally satisfactory balance in some cases is to use a mix of Python, C and Guile (a Scheme). These three mesh quite smoothly, at least on a Unix-style system (never tried on Windows). This doesn't fit every problem, but outside of cases that are a natural fit for a full-service pure FP language its usually easy to write a project in Python with a scripting interface in Guile, and wherever appropriate the Python prototype code can be rewritten in C. The Python to C thing is usually done for performance reasons, but there are a (very few) things that are possible in C that aren't straight forward in Python and a (very very few) things that seem more natural in C than Python. When it comes to the big behaviors or user scripting or whatever Guile is my all-time favorite (but I don't recommend trying to write an entire application in it, no matter how easy it makes writing easy-to-read prototype code). Python, C and Guile have obvious symbiosis on Unix, but it seems any language that provides a C interface could do well in this way -- that would mean always making C your backbone, of course, and this isn't always a good idea or even an option.
-- CraigEverett?
See AlternateHardAndSoftLayers and SymbioticLanguages
Why we use Ant (or: NotInventedHere)
"Why we use Ant (or: NIH)" by Bruce Eckel (http://mindview.net/WebLog/log-0046) lists these reasons to avoid re-writing code from scratch:
While it is not as common as one might expect, there are definitely cases where the "the language currently used doesn't scale" is a key reason for a re-write. To bring up a case I've mentioned in AlarmBellPhrase and elsewhere, I once worked on set of applications written in MicrosoftAccess whose codebase had become unmanageable. This was in large part because early versions of Access Basic lacked a library facility (AFAIK, it still doesn't have an adequate one**), and thus the six closely related packages had to be maintained as separate codebases. I had floated the idea that, after the current release was finished, that the common parts of the codebase be migrated into VisualBasic. Mind you, this would not have been a complete re-write (and thus not exactly on topic for this page); VBA for Access and compiled VB 5 were similar enough that about half of the code could have been carried over with minimal changes. This would have reduced the overall code base by at least half, and vastly simplified maintenance, even with the added complexity of library DLL and OCX calls (which it already used to access system functions in any case). Nonetheless, the company owner was indifferent to the proposal, and the chief programmer (the one mentioned in BadProgrammer) was resistant to any form of refactoring. I understand that after I left, the company that bought them out did a full re-write, only to be bought out themselves and the product sunk in favor of a competing product line. -- JayOsako
( **It is quite easy to place common code in a library data base, and add that common .mdb as an add-in for the other .mdb files that need to share the code. -- JimRussell)
Thank you for that; I still sometimes do work in Access, and that may prove useful. However, from what I understood at the time (the project we were working being mostly a migration from Access 2.0 to Access 97), it wasn't possible. And the chief programmer could not be bothered to do it, even it were possible (I asked him). As it happens, we spent too much time FightingFires to ever look ahead to the next version. -- JayOsako (small edits from DavidCary)
Does doing the entire design in UML and then doing it in a coding language count as rewriting code from scratch?
No. It wasn't code to start with. Nor was it "the entire design"...unless UML has suddenly improved in expressive power such that the model can be forward engineered into working software without human intervention (in which case, TheModelIsTheSourceCodeIsTheDesign).
I think we may be in agreement and the culprit may be my poor attempted use of sarcasm. Nevertheless, there is a belief held by many (who do not actually program), that the detail work is done in the design, and that writing code is merely a matter of accurately transcribing the design into a computer language.
side systems
Yet another technique: Write a independent "side system" that talks to the original code. The side system either talks through a special API, or it pretends to be a person and talks through the same interface that normal people use.
Advantages:
See: WhatIsRefactoring, ThingsYouShouldNeverDo, LegacyCode, OtherPeoplesCode, VersionLateDotSlow, ExtractAlgorithmRefactor