Good Reasons to Re-write Code from Scratch
Good Reasons to Avoid Re-writing Code from Scratch
- Required changes cannot be made using the existing code base.
- We can't get the existing code to compile, or we can't even find the existing code.
- Nobody can figure out how it works.
- The architecture or underlying infrastructure is changing dramatically.
- It is so bug-ridden or ill-suited to the requirements that it is completely useless as-is.
- We are just doing it for fun or to exercise our development skills.
- IntellectualProperty or licensing issues make it desirable to stop using someone else's code.
- When the problem becomes too complex for the original language. For example, if I have a sed script whose complexity is getting out of hand, best rewrite it in some other language. Remember, not every program is "big".
- We are going to be held responsible for it over a long term, and are uncomfortable with accepting responsibility for the existing code base.
(Note that items 1 to 3 in the above list are also "Good Reasons to Avoid Code Refactoring".)
Bad Reasons to Re-write Code from Scratch
- Nobody knows what all the features are of the LegacyCode, or what requirements it is supposed to fulfil.
- No tests are available to verify that new code is functionally equivalent to LegacyCode.
- There are more-important things we can do with our time than re-writing code that already works. We don't need to be ReinventingTheWheel.
- If the re-write takes longer than expected (and re-writes often do), then customers may move to other products/vendors.
- We can re-write it a bit at a time rather than throwing it all away.
- We have no reason to believe that a new team will do a better job than the original team did.
Bad Reasons to Avoid Re-writing Code from Scratch
- It's really ugly.
- We don't like the coding style.
- We don't want to read it to figure out how it works.
- It's not done the way we would have done it.
- We don't like the people who wrote it. All their stuff sucks.
- It was NotInventedHere (unless you're doing so as a loophole regarding IntellectualProperty issues)
- It's too slow in some unspecific way. Better solution: find the hotspots (ProfileBeforeOptimizing) and optimize those. Only after quantifying the performance problems can it be determined that a total re-write is needed.
(In particular circumstances, some of the "good reasons" may be "bad reasons", and vice versa, but the above are generally true.)
- We don't trust the developers to do it right. (This reason is even worse if it's valid.)
- My grand-daddy always told me "IfItAintBrokeDontFixIt."
Never re-write the entire program from scratch.
, 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
s 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
), 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?
The context is important in the above examples, though. Was Win32 a success because of the rewrite? Was Mozilla a success when Netscape spent four years rewriting instead of releasing browser updates? And the rewrite of Linux is not subject to the same assumed forces acting on an internal rewrite of an application.
- I would consider Win32 to be a counterexample. It was a reimplementation of the Windows API, under the WindowsNt operating system. Barring a few fairly stupid gaffes, it was a successful rewrite. -- TimLesher
- Aren't Linux, FreeBSD, and OpenBSD more-or-less complete rewrites of the original AT&T Unix? How many years did it take before Linux was anything more than a toy for tinkerers? If Linus were a company whose core product had been a flavor of Unix and decided to put it into a several-year long feature freeze, would that company have survived long enough to get Linux out the door? Linux succeeded because it wasn't a for-profit venture, developers donated their time, there were no customers to retain, and no bosses to answer to.
- Aren't Opera, Mozilla, and MicrosoftInternetExplorer more-or-less complete rewrites of the original Mosaic graphical Web browser? (Netscape took the Mosaic source code as their starting point for Netscape, right?) Mozilla is Joel's canonical "failure". Because they threw the code away, they spent the next 5 or 6 YEARS getting from zero back up to the level where they could compete with IE. Apparently he likes MozillaFirebird now, though. Firebird being, in some respects, a rewrite of Mozilla. http://www.joelonsoftware.com/news/20030601.html
- I have been involved with several rewrites that were dramatic successes. One was a messaging system. Another was a reversible programming language. Both succeeded primarily because the first effort taught us what they didn't need to do to meet the requirements. The first messaging system had a separate process for managing queues and enqueueing and dequeueing messages. After we worked with that for a few years we realized we didn't really need all the process context switches that required. The next version pushed those responsibilities to the enqueueing and dequeueing processes. Everything got simpler and faster. With the reversible programming language (ReversibleProgrammingLanguage) we discovered that we didn't need a language at all and could achieve the same goals with a C++ library. In both cases, the original software was successful, useful and mature. In general, my experience is that the 2nd time I write something it's vastly superior. The key is to apply the old acceptance tests to the new code. This could also be called IterativeSuccess?.
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."
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
- Leave it alone. (IfItIsWorkingDontChange). If it's working adequately, perhaps your time would be better spent somewhere other than applying the last coat of polish.
- Incrementally improve it. I hear that LinusTorvalds started with a working, but very limited, system (MinixOperatingSystem) and incrementally improved it. If you have ProgrammerTests for every important thing that the original code does, you can make sure the new code doesn't lose anything. (At least not anything important). In some cases, removing unwanted functionality may be an incremental improvement.
- Use translation tools to convert the code. (Say, you have old ugly code in FORTRAN, and you want to call that functionality from a C++ program. Rather than manually re-writing that code, you can run f2c to get even uglier C code. Then just call that translated code without looking at it. Sometime in the future you might incrementally improve it.)
- buy replacement code (perhaps someone else has already re-written the 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 UnitTest
s for it as there may be no easily discernible units. I would suggest starting with AcceptanceTest
s to cover all know requirements, making sure that the existing software passes those tests, then a re-write from scratch can create new UnitTest
s along the way. -- LeonGierat?
However, the book WorkingEffectivelyWithLegacyCode
does introduce a number of techniques to jam UnitTest
s 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.
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?
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.
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.
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.
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.
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:
- It's never as easy as it seems.
- It's probably not the battle you should be fighting.
- Lots of people have tried and failed.
- Politics matter.
The first 3 are re-statements of reasons Good Reasons to Avoid Re-writing Code from Scratch
. The last ("Politics matter") seems to add something new: Imagine that the re-write fixes the most outstanding flaws in the current system. Will people really switch to your new thing with only marginal improvements (and likely to have quirks and outright bugs of its own) rather than stick with what they know, are comfortable with, and has known bugs that they know how to work around? Maybe you could get 4 or 5 people to switch, but 1000 people? NetworkExternalities
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.
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.
- You can sell the API as an optional "add on"
- If the add-on is intended to do something that normal users should be able to do, then (if the main system treats it as skeptically as a normal user) it's less likely that a bug in the add-on will cause security problems, than the same bug integrated into the main system.
- It's much easier to turn on and off a side system, than to maintain 2 different versions of the main system and switch between them.
Is this the same as a "mash up" ? -- DavidCary