问题
Just wanted to hear some words of advice (and comfort.. ) that will help me to take control over some complicated spaghetti code -- code that was developed by multiple programmers (usually that never meet each other) over long time. the solution's features are just patched on top of each other.
Usually I tend to see 2 kinds of programmers:
"Scared-to-death programmers" - those guys will not touch anything that they don't have to. they will probably will complete the maintenance task using a quick and dirty fixes that will make the next programmer to start looking for their home address ;-)
pros:
it workscons:
you hope you will never see this code again.."Teachers" - those will probably rewrite the whole code while completely refurbishing its logic.
pros:
well, someone has to do the dirty work...cons:
takes longer time and probably one of the most critical features will magically disappear from the product
It will be nice to hear your personal experience from this darker side of the programmer's life.
I am specifically curious to hear any theoretical/hands-on advice that will help me to dive into the spaghetti maintenance task without feeling so miserable.
回答1:
While the best advice is, as every other poster on this topic has noted, to write tests, I've often been in the situation where this is unrealistic. If the code is that bad(1000 line methods, embedded and undocumented magic numbers, code duplication, etc.etc) it's also likely to have another problem, which is that it's deeply coupled and components are almost impossible to isolate .
(examples - a codebase that loads up, and caches, hundreds of different database objects on startup. Components assume random parts of the cache exist, and parts of the cache assume other parts of the cache - inotherwords, you have to load the whole thing to get anything - code dependant on static, and promiscuously public, state variables, where it's impossible to determine who's setting those variables, or even what they mean)
Best suggestions I can come up with:
- Try to fix the easy/obvious things first. Hardcoded paths, magic number references, obvious code duplication. Perhaps a quick release that gets rid of the worst 20% of the violations will be of low risk and make the next steps easier to accomplish.
- Get organizational support for refarctoring - there's often no will or desire to do work that doesn't add immediate functionality, and risks breaking existing code. There are, of course, great arguments for refactoring, but not everybody buys them. If the organization doesn't support it you may have to give it up.
- Bring other developers around to the same view - if they're any good,they're probably as unhappy with the codebase as you are. If new code is built to better standards and people are motivated, they'll start to fix older code as tweaks need to be applied.
- If a particular subsystem is really, really bad and has reached the point where new development is nearly impossible you can probably use this opportunity to rebuild it.
回答2:
I try to wrap really bad legacy code in tests before I start. I pretty much smother it in tests, to be honest! Then I have some confidence in diving in an refactoring it where I need to.
You might like to read
Working Effectively with Legacy Code
回答3:
Document. Document thoroughly. Every change you make -- every caveat you discover, every weird logic flow you traced, every time you think "this could be done better" -- document it.
It's the only way to slow and eventually eliminate systematic entropy without a substantial rewrite.
回答4:
The only way I know is:
- write tests
- refactor
- run the tests
- fix what's broken
- write tests for the new feature
- introduce new feature
- run the tests
- fix what's broken
You may live through it this way.
NOTE: there are some times where it's really hard to unit test spaghetti code: 3Klines methods which generate HTML, DAOs entangled with business logic, strongly coupled classes... in this cases a good IDE with automatic refactoring (capable of extracting methods, to begin with) and some test tools like Selenium may come in really handy.
回答5:
Take the best of both. Organize your project into logical pieces, preferably using a reverse engineered UML class diagram, and see where the interconnections are. Anything that is loosly connected is ideal for a refactor. In any downtime, inspect the strong connections as well and see which groups can be completely split off into separate modules with the fewest changes. Figure out whats really going on at a high level before just rewriting the whole thing.
回答6:
I am in the middle of bug fixes for a legacy code app. I try to never bash on the previous developers (especially since I am still learning). Everything I change I make extensive comments on so that I can find it again and so that the next person understands how the code has evolved.
Always try to remember that you can't know all of the circumstances in which the code was written. While that doesn't necessarily excuse poor programming or logic it will certainly keep your blood pressure down.
I also take every head-scratcher I come across as a new chance to learn something and/or try something new.
回答7:
Here's how I approached this problem while maintaining a 10 year old legacy perl system. To give a slight bit of background, this was some of the worst of the worst. I was initially depressed when I started working at the company because the code was so poorly thought out and laid out (and it was my first programming job). By the end of that gig I had a pretty good system though.
First of all, every new feature was added as a new module or methods. Nothing was hacked in on top of the existing code. This let me write unit tests for the new stuff, providing the confidence in it, so to integrate with the old stuff was just a matter of a line or two.
Secondly, any bugfix was implemented the same way. I'd spend some time figuring out what was happening with the old stuff (not searching for the bug), and I'd write it out as a new method (or module), and wrap it in tests, and then typically could replace.
This however only works if you've got the time, and the buy-in to get it done. If you don't, keep track of the time that you spend on any given bug. Especially where it's multiple bugs in the same file, or process, or whatever. At some point in time, it's easy to point at a given collection of code and say that this is so bad, has cost N amount of time, it's time to rewrite.
I was successful enough with this approach that I gained the honorary title "Forensic Programmer", it's also a fantastic skill to have, because more often than not the new job already has some code written :P
回答8:
- Be slow and deliberate.
- Write tests when you can, but don't get dismayed when you can't. Spaghetti code is rarely testable code.
- Remember that everybody who edited the code before you had a good reason to do what they did. Assume the best of them, and you will likely be rewarded.
- Understand the code before you attempt to change the code.
- Take notes as you learn, and keep them up to date as you work. Keep and share them on a wiki if you can.
- Use a revision control system, make each change as small and focused as possible, and check in as frequently as possible.
- Don't bite off more than you can chew. As you are making one change, you will no doubt find something else that ought to be cleaned up. Add it to your to do list and remain focused on your original mission.
- Treat the project like a campsite: leave it in better condition than you found it. And remember that "better" is relative; you can't fix everything at once.
回答9:
outsauce it :)
If that isn't an option, well... first, try to make heads and tails of it. For the stuff I've dealt with, this is often the hardest part. You can manually create UML, you can use some of the commercial source code "reverse engineering" tools, or you can just trace the code.
When you have an understanding of the code, it's time to refactor. Plan well. Try to write a bunch of decent tests. Then go through an iterative approach of refactoring, writing additional tests, and checking (working!) modifications back into source control.
Depending on how masochistic you are, this can be great fun or your own personal hell :)
回答10:
While I've never done this myself, the approach makes a lot of sense. Write boatloads of tests around the existing exposed interface to make sure the functionality does not change when you go in and refactor the dickens of the implementation. This will ensure that any refactoring doesn't change how the app functions.
回答11:
I know that in a well known insurance company I used to do sys admin for, the main pricing system used is a 25ish year old mainframe called GenZ. Since the mid nineties, I think, a new system has been in development and is currently being rolled out in beta
The mainframe, probably, worked perfectly well in the 1980s - but since the company grew, more data was added and the system needed to hold more. Patches were applied, more problems happened, and its been like that for years.
Currently, the system interfaces with at least 200 other applications used by said insurance company, and so replacing it will be a very hard job, and is probably why it has been put off for so long.
Now that the new system (called Maxx) is being rolled out, several hundred applications will have to be phased out.
Poor coding isn't about developers being afraid to do things that aren't required, its just that mostly, they don't think far enough ahead, think of the worst scenarios, or think of what it may be like for consumers, employees and developers in the future.
回答12:
- First and foremost , Get the management's buy in that improving code is necessary to provide business value ( like adding features in future becomes easier) . I've personally wasted precious hours myself improving end of life application.
- Understand the domain thoroughly , certain kinds of domains encourage certain kinds of (anti) patterns ..it would help understand why things are the way they are.
- Writing tests around legacy code helps me explore the various nuances in business logic that made the mess of the code it is now . I personally never aim for code coverage with tests around legacy code ( although a generally preached approach ) , It will only drive you crazy .
回答13:
Well, i have to re factor a nightmare website written in coldfusion, javascripts, html, and make use of couples of plugins...I am the only developer left for this app. The last person maintaining the website and database got transferred. I am stuck with this ugly beast. I did re factor some files and functions but problems and bugs keep coming up.
My issue should i concentrate on fixing bugs or refactoring, i would like to completely spend time refactoring vs being pulled to service a change request or bugs.
Some files are 8k of lines because the prev dev(not real dev) kept copy &pasting chunks of code instead of separating functionalities. This is very a tightly coupled code, a small change in one file will requires changes in 4 to 5 other places.
I proposed to break the websites into small units of manageable parts and rebuild from the ground up. So far, they not really listening but keep asking why the website is slow.
I am getting there one at a time by pulling and testing code out of the main website (locally).
回答14:
I've found a lot of times that you can pull apart clumps of the spaghetti into at least seperate source files, sometimes separate classes, without having to rewrite any actual logic. If you can steal some time to do that off of a bug fix or two, you will be way ahead of where you started.
Later, when more bugs come in, you might be able to modularize things a bit better. Slowly, you can get the spaghetti straightened out this way.
回答15:
Well, maybe this just fits in my case, but here it goes:
Taking into account all suggests above, if i were going to work on a legacy code for a long time, i would be cleaning up the dirty code, very smooth, so i wouldn't break it, if don't i won't bother
来源:https://stackoverflow.com/questions/711722/how-to-save-my-sanity-while-maintaining-spaghetti-code