Working Effectively with Legacy Code
Merlin42 writes "I recently took a Test-Driven-Development (TDD) training course and the teacher recommended that I read "Working Effectively with Legacy Code" by Michael Feathers. First things first, a note about the title. Feathers defines "Legacy Code" a bit different than you may expect, especially if you are not into the XP/Agile/TDD world. I have heard (and used) a number of definitions for "legacy code" over the years. Most of these definitions have to do with code that is old, inherited, difficult to maintain, or interfaces with other 'legacy' hardware/software. Feathers' definition is 'code without tests.' For those not into TDD this may seem odd, but in the TDD world, tests are what make code easy to maintain. When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything." Read on for the rest of Kevin's review.
Working Effectively with Legacy Code
author
Michael Feathers
pages
456
publisher
Prentice Hall
rating
9/10
reviewer
Kevin Fitch
ISBN
978-0-13-117705-5
summary
Excelent overview of how to apply TDD to an existing project
Overall this is definitely an interesting read, and useful to anyone who has ever yelled "FSCKing LEGACY code!" It will be most useful to someone who already has some appreciation for TDD and wants to use it to 'pay down the technical debt' in a legacy code project. In my opinion adding unit tests (a sort of retroactive TDD) is the best ... err ... most effective approach for getting a legacy code project into a more malleable state.
One caveat is that most of the book is focused on working with object oriented programming languages. There is some coverage of techniques for procedural languages (mainly C), but this is not the main focus of the book. In a way this is unfortunate, since there is a lot of really useful C code out there gathering dust. But in the book he states that "the number of things you can do to introduce unit tests in procedural languages is pretty small." Unfortunately I would have to agree with him on this point.
One of the greatest things about this book is that it is written by someone who has worked with a lot of legacy code, and there are numerous real world anecdotes sprinkled throughout the text that really serve to help drive the points home. The code examples are plentiful, but not verbose. They all look like real code you might find lurking in a dark corner at work, not some fanciful made up snippet.
The high level goal of the book is show you how to write good unit tests for code that wasn't designed with unit tests in mind. The first step for writing unit tests is getting individual classes or functions into a test harness where you can apply known inputs, and check the outputs or behavior. To do this you need to break dependencies in the original code. The bulk of the book is dedicated to looking at different approaches to breaking dependencies.
Much of the book is organized like a FAQ. There are chapter titles like: "I Need to Make a Change. What Methods Should I Test?" and "My Project Is Not Object Oriented. How Do I Make Safe Changes?". This organization makes the book work a bit better as reference than as learning material. After the first few chapters there is very little flow to the book. Each chapter tends to stand as an independent look into a particular problem common in legacy code. As a result, you can read the table of contents and usually skip to a self-contained chapter that will help with the problem at hand.
The final chapter of the book is a listing of all the refactoring techniques used throughout the rest of book. So if you have a particular dependency-breaking technique in mind, you can skip straight to the description of the technique you want to use. This can be quite helpful when you need to perform a refactoring before you can get your code into a test harness. The descriptions are straightforward and provide a little checklist at the end that will help you make sure you didn't miss anything.
In conclusion I would definitely recommend this book to a colleague who is trying to introduce unit tests into code that was not designed with testing in mind. In fact I have already lent the book to several people at work, most of whom have bought their own copy.
You can purchase Working Effectively with Legacy Code from amazon.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.
One caveat is that most of the book is focused on working with object oriented programming languages. There is some coverage of techniques for procedural languages (mainly C), but this is not the main focus of the book. In a way this is unfortunate, since there is a lot of really useful C code out there gathering dust. But in the book he states that "the number of things you can do to introduce unit tests in procedural languages is pretty small." Unfortunately I would have to agree with him on this point.
One of the greatest things about this book is that it is written by someone who has worked with a lot of legacy code, and there are numerous real world anecdotes sprinkled throughout the text that really serve to help drive the points home. The code examples are plentiful, but not verbose. They all look like real code you might find lurking in a dark corner at work, not some fanciful made up snippet.
The high level goal of the book is show you how to write good unit tests for code that wasn't designed with unit tests in mind. The first step for writing unit tests is getting individual classes or functions into a test harness where you can apply known inputs, and check the outputs or behavior. To do this you need to break dependencies in the original code. The bulk of the book is dedicated to looking at different approaches to breaking dependencies.
Much of the book is organized like a FAQ. There are chapter titles like: "I Need to Make a Change. What Methods Should I Test?" and "My Project Is Not Object Oriented. How Do I Make Safe Changes?". This organization makes the book work a bit better as reference than as learning material. After the first few chapters there is very little flow to the book. Each chapter tends to stand as an independent look into a particular problem common in legacy code. As a result, you can read the table of contents and usually skip to a self-contained chapter that will help with the problem at hand.
The final chapter of the book is a listing of all the refactoring techniques used throughout the rest of book. So if you have a particular dependency-breaking technique in mind, you can skip straight to the description of the technique you want to use. This can be quite helpful when you need to perform a refactoring before you can get your code into a test harness. The descriptions are straightforward and provide a little checklist at the end that will help you make sure you didn't miss anything.
In conclusion I would definitely recommend this book to a colleague who is trying to introduce unit tests into code that was not designed with testing in mind. In fact I have already lent the book to several people at work, most of whom have bought their own copy.
You can purchase Working Effectively with Legacy Code from amazon.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.
This book is a waste of paper. Everyone knows the proper way to deal with legacy code:
1.) Spend 2 weeks looking at code you don't understand.
2.) Loudly complain about the poor quality of the code, particularly algorithms that you don't understand.
3.) Make derogatory comments about the previous developers. Be sure to paint them as monosyllabic imbeciles who probably got dropped on their heads multiple times as children.
4.) Make minor changes to the code. If they blow up in your face, blame the previous developers for their poor grasp of basic programming practices. Make references to the previous programmers' relationship with their mothers.
5.) Delete the whole thing and start from scratch.
6.) 18 months of fumbling around later, realize that the previous code may have been better than you gave it credit for.
7.) Deny this.
8.) Release cobbled-together mess that lacks half the features of the previous codebase and features twice the bugs.
9.) Get job elsewhere.
10.) Company hires new programmer who starts the process over at step 1.
Buy Martin Fowler's Refactoring instead.
Put all your changes in "int main()", use obscure variable names like xspatyc05 or funct123, always use static buffer sizes for any IO operations and under no circumstances should you add comments, it's a waste of time and no one besides you is ever going to have to understand it anyway.
- I <3 Legacy code
The simple passing of all tests doesn't necessarily means that you didn't broke anything.
It means only that you passed the tests.
If the tests don't provide coverage for ALL the business issues that the piece of software is supposed to solve, then you pass the tests, but will have no clue if you broke or not things apart.
Best approach is to evaluate current test procedures and check if they provide enough coverage for at least all user related actions and all the automated actions.
Only after you know that your testing procedures are sound, you can have that assurance... ;)
testing is a waste of time and only means you satisfy the test conditions. You're better off just proving your code correct.
Do you even lift?
These aren't the 'roids you're looking for.
A legacy system is anything that is in production RIGHT NOW. My coding philosophy has always been "building tomorrow's legacy systems today."
Give a man a fish and he will eat for a day.
Teach him to eat and he will fish forever.
...and the tests will tell automatically you if you broke anything covered by the test cases.
Story submitter missed a few words out.
...release it. ;-)
I'm 2 months into my current job of re-writing stuff someone else did. That's spot on.
How about unit testing in web systems? I never see other kind of test than test the ActiveRecord classes. Even on JSP/Servlets world is hard to do unit testing in web. Anyone has some tips?
Pedantic (but this is Slashdot): "int main()" is not one of the valid forms for "main" allowed by the C standard (another the standard does allow for extensions). It is, however, a valid form for C++.
I push back on this mentality each time I see it from the agile crowd: (FTA/review)
"When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything."
No. (testing FTW and all, but lets get real)
Tests are *helpful*. Multi-user development beyond 2 people accelerates with good tests. Maintenance long term is easier with tests. Changes happen faster and are more robust with good tests. However, tests are extremely difficult to write well and almost impossible that cover all the possibilities for future changes while also telling future programmers automatically when something doesn't work. I think that the best one could say is this:
When a comprehensive set of great unit tests are in place, then code can be changed at will and the tests will help the programmer understand if they broke anything. Test will often tell you automatically about things that are obvious, and usually would be seen with the most basic release testing. The art of writing good tests is understanding the subtle points of how your code functions and the pitfalls future developers may trip over when they extend what you did.
what book can you recommend to me regarding unit tests? After reading the summary, I really got interested in this unit test stuff.
I have some legacy code that straight-up doesn't work; it makes references to non-existent proprietary libraries, uses classes that aren't defined anywhere, and just to make things more interesting, a lot of methods with a lot of code, and variables carefully instantiated, that are never used.
This is what is checked into source control; there is a binary that does, in fact, work, based on this code (or some better flavor of).
What to do then? There is some pretty involved financial algorithms in there that were designed by a mathematician and both the original developer and the mathematician have long since left the building. Yet, here I am, with a bug report that one of the models is wrong, and have absolutely no way to fix it.
An earlier comment suggested that the "real" way to was to decry the original author's skills, parentage, etc., and just re-write. Frankly, this seems to be my only option at this point.
>> then code can be changed at will and the tests will tell automatically you if you broke anything
*Old legacy dev's pessimistic evil smile*
All right, young man, now please refer me to a book where they have a way to write tests to automatically correct my errors while I drink coffee!
That'd be something. Return when you found one.
- Arwen, I'm your father, Agent Smith.
- Well, you're just Smith, but my father is Aerosmith!
I have this bumpersticker posted on my office wall: "Building the Legacy Systems of Tomorrow". I'm not sure who created that phrasing - or the bumper sticker - but I like it.
In short: if it runs, it's a legacy system.
- David A. Wheeler (see my Secure Programming HOWTO)
Funny, my definition of legacy code is "code without documentation". If I have documentation for what the code is supposed to do, I can write tests myself. If I don't have documentation, tests won't save me.
GCHQ Quantum Insert installed. If only our tongues were made of glass, how much more careful we would be when we speak
Did you just refactor his review? BRILLIANT!
Uhh, yes it is. I can't find a single reference online that indicates otherwise. It most certainly is not the "void main()" travesty.
The most successful strategy I have had for legacy code that I have inherited is encapsulation of the old code into a new framework. I first attempt to build a black box wrapper with an API for what ever the legacy code did (wrap 5000 line loops, etc). Then as I can or need to change it, I take the black box and break it into proper libraries or readable functions (or start over). Have been able to do this for some really large bases of code and have a working system while I re-factored the mess a little at a time.
Can you say "Wag the Dog". Anyone can write a test that will pass. Back to Glenford Myers and the "Art of Software Testing".
Every time you write void main(void), God crushes a kitten.
"Say, Ivan -- does your code have tests?" "Nope, it's Legacy Code". "Is it debugged?" "Nope -- legacy." "Does it work?" "Look, I already told you: IT'S LEGACY CODE. GET OFF MY BACK."
Tests ARE documentation.
With the bonus property that the computer can execute them.
This book has been essential to me over the last 2 years: I've been keeping the "legacy" parts of a large web application running while others went on to develop cool new features. Let's just say this, there are two different challenges in software:
Challenge 1) Create a good design from scratch. Lots of folks can do this.
Challenge 2) Move from bad design or bad code to good design, while keeping the product running. Transforming a design in small re-factoring steps is vastly harder than rebuilding, but usually necessary.
This book is about that second, harder challenge. It emphasizes testing and testability, because that is how you ensure the product continues to run while you make the necessary changes. It does tend to assume you know what the good design you aim for is.
This is a very important book to have on your bookshelf, and will probably not become obsolete soon.
My motto: "A cat is no trade for integrity."
the number of things you can do to introduce unit tests in procedural languages is pretty small
It depends on how well the code is written. Good code is modular, and modules can be tested. If the code is so poorly written that it can't be broken down into testable units, that's the programmers fault not the language. Code like that should just be tossed away. If it can't be tossed away, you'll have to use a bigger definition of "unit", but it still works. I've seen code bad enough that the definition of "unit" was a CLI application in a shell pipeline. By testing the CLI applications we were, in a sense, testing units written in a nasty hodgepodge of shell and Perl scripts.
Now, perhaps the author is referring to the lack of "unit testing frameworks". You don't need unit testing frameworks to write unit tests. I know that might come as a shock to some of the youngsters out there, but you can actually test functions in the same language the code is written in. You can even do things that are more sophisticated than assertions, which some of the unit testing frameworks I've seen seem to think are magic. Then have the test compile and build as part of the standard ``check" target in your makefile.
When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything.
Away vile Panacea!
Keep thy sticky tentacles off management soft and pliable brain!
Ye shall shall not destroy another project schedule with your false promises and soul sucking stupidity!
Begone wretched creature!
Live out your days off of the decaying pulp of so many piles of wasted trees and the scraps tossed to you by management consultants!
Testing cannot detect errors with probability significantly greater than zero, unless the system under test is trivially small. For a system that has N interacting features, the number of test cases that are needed to "cover" all combinations of features is O(2^N). And, that is assuming the simplest possible features that are either used or not used in each case. If any features have complicated (more than one bit) inputs, the base of that exponential complexity function increases.
While tests are helpful to detect implementation errors, test sets cannot be complete for nontrivial systems. And because testing cannot be complete, it can never provide sufficient verification. That is a basic fallacy of test-driven development, and of a-posteriori testing generally.
The least-cost way to prevent bugs that will be noticed by users is to avoid making them in the first place. Requirements and designs can be documented, checked, reviewed, communicated, and (most importantly) read and referenced during subsequent phases and iterations of the development process. Test plans and test scripts can be part of that process, but cannot replace the requirements and design phases.
Cost-driven managers don't like to hear that, though, because they think testing is cheap. Non-automated testing can often be done by cheap and easily-replaced labor. And automated testing is essentially free after the test software itself is developed and verified. (Notice, though, that developing the tests also involves requirements and designs, and increases the total amount of software that must be developed.)
So, the least cost development process involves some reasonable amount of testing, but also involves requirements and designs, and reviews at every step. The only way to defeat the combinatorial explosion is by applying heavy doses of "thinking" and "understanding". Nothing else works as well.
Believe me, this is not a singularity in the maintenance universe that I am familiar with.
The best is the opening pitch that the customer gives:
"Yes, this is a typical legacy code, blah, blah, blah."
"Um, but we don't have the source."
Schroedinger's Brexit: The UK is both in and out of the EU at the same time!
Not once, EVER have I worked on code where the unit tests broke because of a bug or a mistake. Instead the unit tests break because the new code has something the test didn't anticipate. This is especialy the case in Easy Mock and TestNG.
Maybe if you're working in a system with complex interdependence patterns, but generally it's a waste of time and money and just a management level masturbatory exercise foisted on engineering.
("I'm a super CTO of Cisco System! I'm going to force unit testing across the board, even where it doesn't makes sense! I'm going to be super ISO certified and John Chambers is going to lick my balls after his retirement when I become CEO!")
The Generation
I'd say something witty here, but I'm not that bright.
And then you have to maintain it anyway.
I currently do this, and I usually succeed (though there have been a few incidents), and I'm proud of that. :-)
You used to work for my current employer, didn't you? And a couple (though, thank Goddess, not all) of my previous employers, too...
Tom Swiss | the infamous tms | my blog
You cannot wash away blood with blood
Unless there is a bug in the unit test code, or some condition the unit test designer didn't anticipate... oh wait, you said "good unit tests" -- have any of these actually been observed in the wild? I have yet to see a unit test simulate what my 7-year old daughter does best -- clicking wildly all over the place until something crashes.
I've abandoned my search for truth; now I'm just looking for some useful delusions.
I have had good luck with legacy code, here is what I do:
#1 Figure out what the code does and document it with comments and write a document on it.
#2 Identify variables and objects and what they are used for and any naming convention the code may use.
#3 You need to stick to the original style of writing or rewrite parts of it into your style if it can give a performance boost or make it more stable.
#4 Try to find programming errors and things that do not make sense and rewrite them so that they make sense. Do error trapping and check for nulls and letters entered into number variables and all other sorts of things most legacy programmers overlook.
#5 Work to make the code stable and not crash and run faster before you start adding new features to it. Users don't want to wait 15 minutes to do a report and then have the program crash after their wait.
#6 Work with the help desk to identify the most serious problems that users complain about the legacy code. Make it a "wish list" and then fix each complaint as you have time to do.
#7 Get direction from your managers, tell them what you are trying to do and any problems you have. You need to work as a team with other developers, the help desk, managers, and users to work out the issues with legacy code. Explain to them when you need more time and cannot make the schedule they gave. Make a deal with them to release a stable version but lacking features that might take more time than they thought to do. Tell the users you had to no add in those features to meet a deadline or ask them if they want to wait until you figure out how to add in those features.
#8 Play Sherlock Holmes and read books or Internet web sites on the language and technology used with the legacy code. Search knowledge bases and blogs and forums for answers to solutions, sometimes someone else figured out what you are trying to solve. If not ask on a forum or blog or web site and see who answers. Many of my answers got that way from the Internet on legacy code, but management didn't understand why I spent so much time on the Internet. It was because they wouldn't buy me the books I needed and I had no documentation or anything to work with except for pure code with no comments and all with serious problems. Sometimes I had to spend 5 hours a day researching on the Internet and 3 or 4 hours coding, but in doing so I saved months of work, but management didn't understand that each web site I went to was work related and I looked at the design of sample code even HTML code to get ideas on how to solve the legacy code problems. Sometimes you have to call up a help desk of a vendor to get answers as well, but they docked me for long distance calls to Canada where Crystal Reports and Segate/Business Objects had their headquarters. Fixing Crystal Report errors would make me spend 5 hours a day on the Internet just to figure out what caused double lines in a report and why only certain users got it and not others.
#9 When in doubt ask for help. Sometimes another pair of eyes can spot errors and mistakes that you cannot see. Diversity is a good thing with team members. Form a dream team of programmers of different backgrounds for best results.
#10 When in danger, when in doubt, don't run in circles and scream and shout. Take a walk, get something to drink and relax. Take a mental health break instead of getting angry at other people for not helping you or not doing their jobs properly, they might be suffering from stress like you are and you don't know it. Be positive, not negative.
Remember, Slashdot does not have a -1 disagree moderation, and no, troll, flamebait, and overrated are not substitutes.
Though it is hard to test spaghetti code, it is hard no matter if the code is procedural or object-oriented. If you have reasonably well written procedural code it is easy to unit test. For C, there is http://cutest.sourceforge.net/ a very simple framework. Lets say you just wrote super_sort. You can write tests to test it with random input, sorted input, reverse sorted input, big input, small input, and the like. If you ever break super_sort, you will find out as soon as you run your unit tests. IMHO, linear algebra may be complex, but there is generally no state. Purely procedural code is fine for it (though C may not be). There are many simple as well as complex things that have no internal state, and will not benefit from object orientation.
I'll do you one better: Legacy code is anything developed under a different process than you're using now. If all you'll ever do is TDD, then Feathers' definition is fine. But if, like me, you've seen a dozen major development philosophies come and go and be refined over the years, you know that TDD will eventually be supplanted. The only thing that remains constant in the recognition of difficult maintenance is this: "We didn't plan to maintain it the way we're maintaining it now."
What do you mean they cut the power? How can they cut the power, man? They're animals!
Um, you're a moron. Online??? Yeah, that's authoritative! Check the STANDARD itself! See 5.1.2.2.1 "Program Startup." The only standard forms are "int main(void)" or "int main(int argc, char *argv[])" or equivalent, such as "int main(int argc, char **argv)"
Feathers defines "Legacy Code" a bit different than you may expect. Feathers' definition is 'code without tests.' Agile programming seems to do this a lot, redefine things till they are happy.
Your code doesn't work? Write Unit Tests. Code still doesn't work? It's because you didn't write "adequate" tests.
In the end you've redefined everything, but you still haven't solved any problems.
When I started out there were two books an structured programming: Constantine and Yourdon, and Page-Jones. There were a few other books on quality programming: Software Tools, Weinberg, etc. later there were other OO books, but still two main books: Booch and Rumbaugh. There were other OO methodology books (Bertrand Meyers, Wirfs-Brock etc ), but they were this diverse group throwing out different ideas.
Now we have tons of Agile books all written by the same clique. But what software have these guys produced? Seems to me that the main purpose of "Agile programming" is to sell books and consultancy classes.
Before you buy Feathers book, I suggest you ask yourself this. Has he been a major contributor to the linux kernel? To Emacs? Eclipse? Mozilla? What software has he written? What do coders whose work we see like Raymond and Stallman think of these guys?
And if Agile is so great, why aren't they writing
some the great software out there?
...is anything you don't like.
I've seen the term thrown around by VB programmers trying to make sense of COBOL or Fortran code. Or IT departments that were going 100% Windows using the term in reference to anything other than a Microsoft product.
To be accurate, it should refer to code (or anything) developed under some other design and maintenance methodology or process than that currently used. That doesn't mean it is bad, old, or untested. In fact, it might be better that the crap you write today.
I used to work for an outfit that did a lot of avionics testing. Back in the old days, the engineering department (the people who build the aircraft systems) designed and built a set of ATE equipment (redundancy noted). One component was a natural language to test code translator. We'd feed in the systems specs written in English (or more often Engrish) and the translator would spit out executable code. The module worked well, was easy to maintain and modify. I was brought in rather late in its life in the company and had no trouble understanding it. But then management mandated all software maintenance to be done by our information systems group (people from the finance department). Since they had no skills in natural language recognition, they just labeled that function 'legacy' and as a result unmaintainable. Their solution was to have the engineering department learn VB and code the ATE tests by hand.
Have gnu, will travel.
I don't know how many of those leaving their pessimistic comments here have actually read this book, but I have. It's actually been on my to-do list to write a book review for Slashdot myself. Long overdue, I thought, given that the book was published in 2005. Now I'm sorry I didn't get around to it, because I think this reviewer, though positive about the book, considerably undersells it.
To those of us stuck doing active development on old, ugly code, every day can feel like we are slogging deeper and deeper into a swamp. Each time we hack in a new change, it makes us feel unclean. We are ashamed of the ugliness of the patch work we are adding to. We know programming used to be fun, but only rarely do we feel the echoes of that now. Mostly we feel dejected. And we've lost our motivation because we are not putting out code we are proud of.
If any of that rings a bell with you then grab Michael Feathers' book the next chance you get. A previous poster said something like "get Martin Fowler's Refactoring book instead", but he's entirely wrong. Not that it isn't a great book, but it won't save you. I've known about refactoring for years without being able to put any of it into practice. The prerequisite to aggressive refactoring is a good set of automated tests, and my projects have not only had no tests, but have seemed down-right untestable.
WELC is your map out of the swamp. And it's a map drawn by someone who has clearly spent a lot of time guiding others out. Feathers knows how tangled your code base is. He knows it doesn't have useful documentation or comments. He knows you are under time pressure but afraid to break funtionality you don't even know about. He has seen it all and he knows how discouraging and hopeless it looks. But he knows the way out, and he'll patiently and calmly
guide you as you break your first dependency, get your first class into a test harness or write your first test case. And before you know it, you are standing on a little patch of solid ground.
Take my advice. Get this book, read it, and put it into practice. It can change your (work) life!
...
"int main()" and "int main(void)" are the same thing, you idiot!
If you're going to be pedantic, at least try not to fuck it up!
"[Regarding the 'cloud,'] ownership was what made America different than Russia." -- Woz
While I completely respect your desire to learn, I advise against rushing out to read a whole book on the subject straight away.
The reason I say this is that unit testing is really a very simple idea: you should try to design your code so that you can test each module independently; implement simple, self-contained, automated tests for each part of the interface functionality; and then run your set of tests frequently, ideally between each change you make to the code. This certainly isn't foolproof, because it relies on having a good, comprehensive set of tests and usually it's impossible to cover everything. However, you can still help yourself to find most bugs quickly, and to identify very accurately and immediately where they come from, by using a good test suite. Of course there are some useful ideas and techniques that can help you to do these things more efficiently and reliably, but the basic principle is always the same.
People write whole "frameworks" to deal with this stuff and some books discuss them, but IME these frameworks are in that category of libraries that everyone seems to write but no-one seems to use. It is often simpler and faster to write your own that fits exactly into your particular project than to learn someone else's, create a dependency on external code, and then adapt it to your specific needs anyway.
Likewise, people write whole books on software development approaches like Test Driven Development, which are heavily based on unit tests. However, while there is plentiful objective evidence that quality can be improved by using unit tests, there is precious little beyond anecdotal evidence that anything other than consultants' incomes is improved by adopting TDD and the like. (If anyone disagrees with this, please spare us all the rant unless you can cite verifiable data to support what you're going to say.)
There are some good comments on unit testing in general software development books such as Code Complete, which you might find interesting and useful. But I advise steering clear of the specialist books on frameworks and methodologies built around unit testing, at least until you have enough experience to separate the snake oil from the real oil.
If you disagree, post your argument. (-1, Overrated) isn't your personal censorship tool for views you don't like.
"... When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything."
Wrong.
I'm a minority race. Save your vitriol for white people.
"int main()" and "int main(void)" are the same thing
In a function definition that is correct. In a C prototype, however, the former is "a function with an unspecified parameter list returning an int" and the latter is "a function with an empty parameter list returning an int", so they aren't the same thing in all circumstances.
If "int main()" was a permitted signature (type) for main, any function which returned an int would be valid regardless of its parameter list.
"The state is that great fiction by which everyone tries to live at the expense of everyone else." - Bastiat
WOW, where did you learn to program??? In C, "foo(void)" mean foo takes no arguments. "foo()" means foo take an unknown number of arguments of unknown types. The former is a prototype, the later is not. They are equivalent in C++, but no one was arguing that. If you're going call someone an idiot, at least check your facts first!
"...then code can be changed at will and the tests will tell automatically you if you broke anything"
And I have a bridge in Brooklyn I can sell you too.
My favorite incident with unit tests was our company's first "agile" project. All the unit tests passed, and you still couldn't delete a named user account.
Unit tests are fun to write, easier than other forms of testing, appealing to developers, and completely inadequate to test any target application.
Sorry, but I've been testing for 15 years. Unit testing is good and has it's place, but I'm leaving the rest of the Kool-aid in the pitcher, thanks.
And you have to escalate properly, so it is like:
11.)???
12.)NAZIs
13.)Cthulhu
14.)D100 san loss
"Malo periculosam, libertatem quam quietam servitutem." -- Jefferson
You in fact made a perfect case for the argument that the code SHOULD have been developed test first, because then all the interdependencies you describe would not exist. Instead your application would be properly designed to be composed of a number of modules which operate independently of each other and only expose well defined interfaces which are ALL tested.
It may well be that when a complex application is being developed you may find that there are several 'layers' of abstraction, and testing some of the higher layers MAY require mocking components to which they delegate functionality, but then the same thing should hold, the lower level code should be so modular and maintain so little state that it should be quite simple to mock. If it isn't then the fault is not TDD, it is the design of your code.
"Malo periculosam, libertatem quam quietam servitutem." -- Jefferson
They're not, though, are they? We write documentation that is separate from code, because code is usually a poor place to provide overviews or describe the details of models and algorithms being implemented by that code. Automated tests are just more code in some form or another, and they have no special pass on this.
If you disagree, post your argument. (-1, Overrated) isn't your personal censorship tool for views you don't like.
Software is not like hardware. Software is much more plastic and it is very often not obvious what the best design is. TDD allows you to build your components one small step at a time, this is the heart and soul of agile development. It also has to integrate with other principles like having a good model/metaphore. If the model is well thought out, then small low level code modules can be developed to adhere to a fairly simple and straightforward set of tests. This feeds into the concept of short iterations where only a very specific set of functionality is built into the code during any one given iteration, thus allowing you to focus on that one thing.
The code written in iteration one may not be capable of addressing the requirements of iteration two, but you CAN now refactor it because you can now test it, and you will find it will be MUCH MUCH easier to test because you were forced to build it to BE testable. Testability needs to be a fundamental attribute of all code, probably the single most critical attribute.
Software is nothing like a product coming off an assembly line. You don't 'duplicate indefinitely'. You construct a module of code and if it is developed correctly according to an agile methodology it WILL be highly reusable, but you should never expect a particular piece of code to function correctly in any old environment. On the flip side every time your code is operating in the environment it IS designed and tested to operate properly in, it should function correctly, or fail gracefully.
"Malo periculosam, libertatem quam quietam servitutem." -- Jefferson
Challenge 1) Create a good design from scratch. Lots of folks can do this.
I wish I worked in your world! In mine, lots of folks think they can do good design, but most haven't the first clue about what good design is.
If you disagree, post your argument. (-1, Overrated) isn't your personal censorship tool for views you don't like.
The tests will tell you if you broke anything they test in a way they test for.
You're still going too far. The tests will tell you if you broke anything they test in a way they correctly test for.
You can be confident in the knowledge that the tests do indeed test the changes you're trying to make, and discover (after much hair-pulling) that your changes now tickle an obscure bug in the tests. This may result in the tests claiming your code is broken when it's not (the good case) or that your code works properly when it doesn't (the evil case).
Of course, we were supposed to assume that we're dealing with good tests. Heck, if we're going to assume impossible things, why not assume that the code works in the first place, so it doesn't need testing! :)
I'm a big fan of testing, especially automated unit tests and regression tests, because they mean that you need to make two matching errors to actually let an error slip through. But I also don't assume that "pass the tests" == "working code". You still need to make readable, maintainable code so that when the whole thing blows up in your face, you still have a slight chance of being able to fix it.
My biggest complaint about the whole TDD crowd is that they seem to think it doesn't matter of the code is maintainable--as long as it passes the tests, it's magically ok. Like a lot of movements, TDD/XP/Agile is a collection of good ideas being marketed as a silver bullet. TANSB. :)
Imbecile!
Mod parent down for not following convention. The convention in this tread is to call the parent a name and every other post should be AC.
Noobs!
"in the TDD world, tests are what make code easy to maintain. When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything."
Isn't this a rather ambitious claim? I've seen many systems with lots of tests with bugs not caught.
-- Programming with boost is like building a house with lego. It's a cool but I wouldn't want to live in it
The only way a reader can read this book and not come away with a pocketful of gems is lack of experience or so much experience that the book bores him. Experience in this case isn't tied to years of programming, it's tied to having to fix truly horrid messes.
This is one of the best introductions into managing a code base that is foreign, hairy, and incomprehensible. I've been doing maintenance programming on and off for years now, and while it's not as glamorous as clean slate design, it's much more difficult.
Fowler's Refactoring gives you the transformation templates, just like Design Patterns gives you the code structure templates. This book gives you the instruction into code maintenance discipline. It's well worth a read, even if it leaves you wanting in a (very) few places. Don't think of it as "Refactoring for Dummies", it's more like "How to use Fowler's books in an enterprise and not go insane". Naturally there's a little duplication of information, but that's not the point of the message.
There seems to be a bit of an anti-testing crowd reacting to passages in the book. While I understand their gripes, let me assure you that either you're going to test your code or the customer will. If the customer finds too many, then you've squandered your good will in exchange for not writing tests. Not a good exchange in my opinion.
Another book along this line that I also highly recommend is "Software Exorcism" by Bill Blunden. Perhaps his quirky humour and old school usenet references will throw off a few acolytes, but there's plenty of treasure in there.
(I browsed at -1 but could not find a Topper thread, so I have started one!)
> One caveat is that most of the book is focused on working with object oriented programming languages.
Hokum Pokum new fangled OO: we support various Mission Critical legacy systems with FORTRAN-77, K&R C; we've only just leapt from VAXes to Alphas, and are moving from Solaris 6 to Solaris 8 (all forced us by non-supportive suppliers). Having said that one of our systems does have one of those new-fangled Web interface thingies, but we try not to touch that :-) Oh, and some of our new hardware came with 2 (two) CPUs, so we switched one off in order to avoid a new class of bugs !!
But I am sure other Slashdot Old Farts can do better than that ?
between 'no design at all' and what agile methodologies advocate. In XP methodology for example you would first develop your system requirements in the form of user stories. Those are used to prioritize features, then you engage in a series of iterations, each one adds certain features. Another tool you use is a system metaphor or model, which provides a way of reasoning about the overall system architecture.
Any given iteration only deals with those requirements which are assigned to that one iteration. Code is written (tests first) to accomplish the requirements of that iteration, and the customer supplies a set of functional tests which verify that the software meets those specific requirements.
During later iterations new tests will be written and either more code written or existing code refactored and extended to add the additional features. More functional tests are developed as well, and the added features plus the existing features are all tested for compliance to the new requirements.
There is no single overall design 'phase'. There could be various points at which the large scale features of the system are fleshed out. In some cases a 'spike' might be used to explore different options. Spike code is considered throw away and simply allows you to determine which of several designs are likely to work best. Usually there will be some specific 'problematic' areas (say 'how can the system achieve the required latency requirements') which a spike would address.
The whole philosophy of agile development essentially rests on the observation that even in what are supposedly more 'up front design' type methodologies that the real shape of the system ends up being determined over the process of development. It is like applying the 'Wu Wei' concept of Daoism to software, don't fight the way things REALLY flow. Waterfall style development is a myth, and pretending it isn't is just paddling upstream.
"Malo periculosam, libertatem quam quietam servitutem." -- Jefferson
Legacy code is any code that a software developer "inherits". i.e., the developer didn't write the code, but is in charge of maintaining it.
TDD is also called analysis. It forces the developer to think about the requirements and how to verify the implementation meets the requirements. Unfortunately, this is bad testing, because the goal of software testing is to verify that the software is incorrect. This is why it has been well documented that someone other than the developer should write and execute the tests. It's hard for the developer to take off the white hat and put on the black hat.
But in the book he states that "the number of things you can do to introduce unit tests in procedural languages is pretty small." Unfortunately I would have to agree with him on this point.
Sorry, but this makes no sense.
In a procedural language it is easy to stub/mock away stuff that you like to be "outside" of your tested unit.
E.G. if you have a "unit" that calls a function/procedure f() and the function f() returns an int ... it is very easy to provide a mock/stub function f() that returns the exact number you need/want in your test.
In oo languages, you are usually call a method g() on an object "o" with type "t". But you don't know/can not determine/can not predict if the object is of type "t", or "t2" or "t3".
So you can not "reliable" stub/mock g() ... so a unit test calling g() is very difficult to craft in oo languages. Unit testing is very simple in procedural languages. You know unit means: FILE. We only test one single f***cking source file. In C/Pascal etc. that is a piece of cake, in C++/Java etc. it is utter shit and hard to do.
angel'o'sphere
Cost free eBook I read (by iBook/Kobo/Amazon/ObookO/Gutenberg etc.): "The Green Odyssey" by Philip Jose Farmer.
Modularise, modularise, modularise.
If you can't describe what a function does in a reasonably short function name, then it needs to be broken up.
Justin.
You're only jealous cos the little penguins are talking to me.
Thought Driven Development a Methodology of Abile Development
If fixing code, just put it inside a REALLY BIG try catch and put an exception for all the types of errors that are presently bugging the system, this will be good enough for a promotion until you get to the next job and move up the ladder....let the guy after you figure out how to proceed.... :P
I just comprehended test-driven development. Enlightenment after all this time of wondering feels GOOD. TDD is for developers who are unable to perform the task of analyzing code, period! This whole system is tons of overhead just to make sure the developer is working in a tiny little box where nothing can go wrong. Well, those of us WHO ARE COMPETENT no not need to spend hours building useless safety rails. I require of myself that I fully understand the ramifications of any code changes I make. This helps me understand the entire code base better and is the sole, I repeat sole, path to possessing guru level knowledge of the code.
So go ahead and work in your TDD prison cell if you like it, fool. You'll never leave.