As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
When I'm writing it?
After I got a part done (Single class/function/if-elses)?
After I got the whole thing working?
The short answer
The short answer is anytime something is non-obvious relative to whose going to be reading it. If its code that is still in flux so you are the only consumer, just comments for you (hours and days). Ready to check in for others to try out - comments for you and your team (days and weeks, possibly months). Ready for wide release - comments for the immediate and future public (months and years). You have to think of comments as tools, not documentation.
The long answer:
When I'm writing it? - Yes
After I got a part done (Single class/function/if-elses)? - Yes
After I got the whole thing working? - Yes
When I'm writing it? - Yes
Drop comments anytime you hit a place where the code isn't immediately clear. For example, describe the class when the class name isn't clear or could be interpreted too widely. Another example is if I'm about to write a non-obvious code block, I'll first add a comment reminding me of what I want/need. Or if I just added some code and I immediately realized there was a gotcha in there, drop a comment to remind yourself. These comments are implementor comments, less to help future maintainers, but rather to help yourself in the coding process.
Drop FIXME - explanation and TODO explanations reminders as you go.
Code is still in flux, so I'm not yet documenting every and all method and parameter.
After I got a part done (Single class/function/if-elses)? - Yes
When I'm reasonably done with a method or class, now is the time to review it. Along with checking scopes of methods, ordering methods, and other code cleanup to improve understandability, now's the time to begin to standardize it against your team standards. Consider what comments are need based on the audience it will be released to (future you is part of the audience too!) Does the class have a header block? Are there non-obvious conditions under which this method should not be called? Does this parameter have any conditions on it, e.g. should not be null?
Check the FIXME and TODO items - still valid? Any you should address now before moving on?
These are still notes for you and your team, but the beginnings of standardized notes for future maintainers.
After I got the whole thing working? - Yes
Now is the time to review everything and finalize comments against your standards.
All FIXME and TODO items addressed (fixed or captured as known issue)?
These notes now are for future maintainers.
Now the dirty little secret
More is not always better. Like unit tests, you have to balance use of your tools weighing costs vs benefits. The fact is that a coder can only type so many physical lines per hour - what percent should be comments? A low percentage means I've got a lot of code, but its confusing and difficult to understand and use correctly. A high percentage means that, in an hour when someone changes a method signature or redefines an interface, all the time is spent fully commenting every parameters of those methods just got trashed.
Find the right percentage based on the stability of the code, how long it will live, and how widely it will be released. Not stable yet - minimal comments to help you and your team. Stable and ready for project - fully commented. Public release? - fully commented (check again!) with copyrights (if applicable). As you gain experience, adjust the percentage.
You should never "add" comments - they are not additions. Comments are part of the code - you use them when you need them. Asking when you should add them is like asking when you should add functions or classes. Though thinking about it, I remember doing a program advice slot at university I worked for where one of the students came in with about 1000 lines of Pascal, with no functions. When I queried why he hadn't used functions, his response was "I'll add them later, once I've got it working."
This is subjective, but sometimes it's better to add them before the actual code, eg. when you implement an algorithm that has clearly defined steps. By that way it's harder to miss steps.
This is a matter of style. Personally, I like writing comments during the coding, not after. Because it I leave it to after, I usually get lazy and don't write them at all. That said, sometimes it's useful to go over a completed piece of code, figure out what isn't obvious from the code itself and document it. In particular, the parts where assumptions are made.
I would suggest writing comments whenever you edit any code, while you are editing it. According to Robert C. Martin in Clean Code, a disadvantage of comments is that the code can change without the comments being updated, making the comments not only useless, but dangerous. To reduce this problem, if you must use comments (because you are unable to express yourself in the code itself), make sure you update them every time you update the code.
You should try writing comments BEFORE you write any code. eg
public string getCurrentUserName() {
//init user database repository
//retrieve logged in user
//return name if a user is logged in, otherwise return null
}
Writing comments before you code, helps you learn how to structure your code without actually coding it and realising that you should have done it another way. It's also a good way to quickly visualise a clean solution to a complex problem without getting bogged down in implementation. It's also good because if you get interrupted, when you come back to your work you can go straight back to it, as opposed to refigure out what you have done and what you need to do next.
Not suited to all situations, but often a good option!
A disadvantage of adding comments later is that a lot of times that will simply not be done, due to lazyness, other tasks, etc.
If you find you can always go back and add the appropriate comments without any problem, then by all means do so, but otherwise making a conscious effort to add them as you're coding or before you code a section may be a way to ensure that you don't leave the code uncommented.
Put a comment ANYWHERE the programmer reading your code, may generate a WTF moment.
If you find yourself commenting every line, perhaps you need to take a look at trying to improve your code with simpler, more elegant statements.
Comments should reflect why you are doing the things the way you do, not what it does. Most of the time the one reading your code can read what it does.
You should explain the the things one cannot reduce from the code.
I tend to put basic comments as I'm going, just to remind myself what I was thinking at the time when I wrote it (i.e. why I wrote it that way). I do this especially if it's code that looks like it might be wrong but is actually right, or code that has an inherent race condition that I don't care about, or code that might not be optimal but is a quick way to get something working, so that even ten minutes later when I go back and look at it I can see that I've thought about the problem already and don't have to waste any brain cycles on it.
When the code is more complete, I'll often go back and review the comments I've written and then have a think about whether I still think the decisions made are reasonable, and whether things could be done better. I'll also often expand the basic comment into a longer comment that's more useful for other people when they come to maintain the code; I usually save comment expansion to the end because a lot of the time basic comments just get deleted during refactoring, so writing a long comment is a waste of time until you know you're going to keep it.
In a nutshell, write basic comments as you go along, and then improve them as your code becomes more stable.
Oh, and also, any time you review a bit of existing code and you're struck with a WTF?! moment but then realise the code is actually decent, put a comment in to save yourself and the next person time when they look at it in the future.
The question should be, when do I add code to my comments?
My practice is to write out the functionality of a module/object/function as a series of comments. Not comments like "add one to counter". Higher level comments like
"sort list by account number". Detailed comments are pretty much redundant with the code. So I avoid those unless I'm writing a very tricky algorithm.
Once I have the functionality "designed" in comments, I act like a human compiler and
add in the code after each line of comments.
Give it a try and let us know how it works!
Personally, I tend to write comments to summarise code where necessary - often before I write the code, as well as to save WTFs. I treat them very exactly as notes - of things to do, things that I have done this way, or will do this way, and as such they are put in when and where I feel the need for them.
Before you forget what specification and design the code is required to implement.
Before you forget that some unfortunate coder will have to read it later on.
Before you forget that the unfortunate coder could well be you.
When you do something non-trivial, as you're writing it.
You gave a lot of cases in your question. I think it depends on what you're doing at the time.
If you're writing a function or a class, comments are a way to declare what's supposed to happen with the function. Things like input variables, output type, special behavior, exceptions, etc. IMHO that kind of comment should be written before the actual code is started, in your "code design" phase. Most languages have packages which process those kind of comments into documentation (javadoc, epydoc, POD, etc, so that stuff will be read by users.
If you're making a bit of code work, I think it's OK to wait until you've got it working to put in a comment triumphantly describing your working solution. That kind of comment is only going to get read by a code reviewer.
Then, as others have said, you want to avoid WTF moments, by yourself or others. I once got an attaboy for a comment I made once in an open-source project. The comment was "Yes, I really do want = and not == on that line."
A. when you decide an arbitrary decion that would be difficult to re-understand.
B. Every thing that you feel that you should remember while writting the code
C. in the beginning of a program explain the logic and use
Advice - instead of commenting a lot use long names for functions and vars that realy explain what the function does or what the variable stands for.
Mostly at time when you write that code. You can go there after the function/block/whatever is done and organize your comments on fresh mind. Most of the stuff we write while coding are not meaningful later.
Early on in my career I added comments to nearly every line of code, as you may do perhaps in an ASM program. As time went by I ran into many of the problems mentioned here. It was a bear to maintain which resulted in not updating comments and then they become stale at best, usually moldy.
I feel that the # of comments should reflect how complex or non-obvious the code itself is. In a more challenging environment, such as ASM, you will probably need more comments to understand what is going on. In more modern languages like C# you shouldn't need a whole lot of comments in most cases.
Generally I use tools that evaluate the complexity of my methods in C#. Those that are high on the complexity scale first get refactored. Then when I'm satisfied with the complexity remaining and I still have some code that is not obvious, or even more important, seems obvious but does something different, then I tack a comment on it.
I add comments while writing any code that is not easily understandable. I find that if I don't do it immediately then it gets forgotten. I (or more likely someone else) then spends more time figuring what I did than it would have taken to write the comment.
To be more precise, commenting immediately after the code is written is the best avenue to ensure comments actually get written.
Related
I need some advice on how to work with legacy code.
A while ago, I was given the task to add a few reports to a reporting app. written in Struts 1, back in 2005. No big deal, but the code is quite messy. No usage of Action forms, and basically the code is one huge action, and a lot of if-else statements inside. Also, no one here has functional knowledge on this. We just happened to have it in our contract.
I'm quite unhappy about this, and not sure how to proceed. This application is invisible: Few people (but all very important) use it, so they don't care whether my eyes bleed while reading the code, standards, etc.
However, I feel that a technical debt is to be paid. How should I proceed on this? Continue down the if-else road, or try to do this requirement the right way, ignoring the rest of the project? Starting a huge refactor, risking my deadline?
Legacy code is a big issue, and I'm sure people will not agree!
I would say that starting a big re-factor could be a mistake.
A big re-factor means doing a lot of work to make it function exactly the way that it does now. If you choose to take this on on your own, there won't be a lot of visibility of what you are doing. If it works, no one will know the hours of work you put it. If it does NOT work, and you end up with tidy code, but add some bugs (and who has ever written code without adding some bugs) then you will get 'why did this change' type questions.
I have currently nearly completed a project working on a 10 year old code base. We have done quite a few bits of re-factoring along the way. But for each re-factor we have made we can justify 'this specific change will make the actual task we are doing now easier'. Rather than 'this is now cleaner for future work'. We have found that as we worked on the code, fixing the issues that we actually come up against one at a time, we have cleaned up a lot of it, without breaking it (much).
And I would say before you can re-factor much, you will need automated tests, so you can be fairly happy that you have put it back together right!
Most re-factoring is done to 'make maintenance and future development easier'. Your project sounds like there is not a lot of future development coming. That limits the advantage a re-factor will give the company.
Rule #1: If it ain't broke, don't fix it.
Rule #2: When in doubt, reread rule #1.
Unfortunately, legacy code can very rarely be described as "it ain't broke." Therefor we must tweak the existing code to correct a newly found bug, tweak the existing code to modify behavior that was previously acceptable, or tweak the existing code to add new functionality.
My experience has taught me that any refactoring must be done in 'infinitesimally' small increments. If you must break rule #2, I suggest that you start your search with the inner-most nested loop or IF structure and expand outward until you find a clean, logical separation point and create a new function/method/subroutine that contains only the guts of that loop or structure. This won't make anything more efficient but it should give you a clearer view of the underlying logic and structure. Once you have several new, smaller functions/methods/subroutines you can refactor and consolidate those into something more manageable.
Rule #3: Ignore my previous paragraph and reread the first two rules.
I agree with other comments. If you don't have to, then don't do it. It usually cost far more then it's worth if the code base is more or less dead any way.
On the other hand, if you feel that you cannot get your head around the code then a refactor is probably unavoidable. If this is the case then, since it's a web application, can you create a solid suite of functional tests using selenium? If so this is the fastest and most rewarding test approach for such code and will catch most bugs for the buck.
Second, start with the extract method refactoring to create compose methods of the big difficult methods. Every time you think to your self "This should have a comment to explain what it does" you should extract it to a method with a name that replaces the comment.
Once this has been done, if you still can't add the functionality required, you can go for more advanced refactorings, and perhaps even adding some unit tests. But I usually find that I can add what is required/fix the bug in legacy code by just creating self documenting code.
In a few words: before make any modifications to legacy code its good idea to start from automated unit tests.
This will give developer understanding about key things: dependencies this piece of code has, input data, output results, boundary conditions so on.
When it’s done most likely you will better understand what this code does and how it works.
After that its make sense (but not must) clean code a bit giving more accurate names to local variables, moving some functionality (repetitive code, if any) in to functions with clear human friendly names.
A simple clean up could make code more readable and at the same time save developer from regression issues with unit tests help.
Refactoring – make small changes, step by step, when you have a time and understanding of the requirements and functionality, regularly unit testing the code.
But do not start from refactoring
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
It's no question that we should code our applications to protect themselves against malicious, curious, and/or careless users, but what about from current and/or future colleagues?
For example, I'm writing a web-based API that accepts parameters from the user. Some of these parameters may map to values in a configuration file. If the user messes with the URL and provides an invalid value for the parameter, my application would error out when trying to read from that section of the configuration file that doesn't exist. So of course, I scrub params before trying to read from the config file.
Now, what if down the road, another developer works on this application, adds another valid value for this parameter that would pass the scrubbing process, but doesn't add the corresponding section to the configuration file. Remember, I'm only protecting the app from bad users, not bad coders. My application would fail.
On one hand, I know that all changes should be tested before moving to production and something like this would undoubtedly come up in a decent testing session, but on the other hand, I try to build my applications to resist failure as best as possible. I just don't know if it's "right" to include modification of my code by colleagues in the list of potential points of failure.
For this project, I opted not to check if the relevant section of the config file existed. As the current developer, I wouldn't allow the user to specify a parameter value that would cause failure, so I would expect a future developer to not introduce behavior into a production environment that could cause failure... or at least eliminate such a case during testing.
What do you think?
Lazy... or philosophically sound?
"I just don't know if it's "right" to include modification of my code by colleagues in the list of potential points of failure."
It isn't right to prevent your colleagues from breaking things.
You don't know what new purposes your software will be put to. You don't know how it will be modified in the future.
Instead, do this.
Write simple correct software, put it into production, and stop worrying about somebody "breaking" something.
If your software is actually simple, other people can maintain it without breaking it.
If you make it too complex, they will (a) break it anyway, in spite of everything you do and (b) hate you for making it complex.
So, make it as simple as possible to do the job.
It sounds like you're taking reasonable steps to protect against incompetence by doing some scrubbing of the input. I don't believe that you're responsible for protecting against any possible misuse of your code or bad input. I'd go further than that and say that as long as your code explicitly documents what is and isn't an acceptable input then you've done enough, especially if the added "idiot error checking" code is bloated or (especially) slower.
A procedure that documents exactly what inputs are acceptable is reasonable for an inner api. That being said, I often code (over) defensively, but that's mostly due to the environment I'm in and my level of trust in the rest of the code.
Lazy... or philosophically sound?
Lazy... and arrogant. By coding in a way that makes mistakes show up quickly, you protect the app against your own mistakes just as much as against the mistakes of others. Everyone makes more mistakes than they think.
Of course, rather than adding more code to detect whether the config file and the parameter checking match, it would be much better if the parameter checking were based on the config file so that there's only one place where new values are added and an inconsistency is not possible.
I think it is the future developer's responsibility to ensure that she does not introduce bugs or failure points into your code. When you sign off from the project (if ever?!), then part of the signing process should at least be that the code has been presented as bug free as possible, this would then limit the liability you hold for future problems.
If your code is kept in a version control system, it would be trivial for you to create a tag which would mark the point at which you handed over your code, thus enabling you to compare the current codebase to your original should a bug arise which some one may be trying to blame on you (if this is the angle you're coming from!), therefore allowing you to prove that it is the changes that have been made to your original implementation which has caused these bugs. (Assuming of course that the implementations do cause unexpected behaviour and don't fix your "bug-free" code grin).
One method I have used in the past to ensure data integrity (and it isn't fool proof), is to check an input at specific offsets for specific values, ensuring that input wasn't tainted.
Hope that helps.
Both ways are fine, philosophically speaking. I would make the judgment based on how likely you think it is for this to happen. If you can be almost positive that somebody will break your code in that way, it might be polite for you to provide a check that will catch that when it happens.
If it doesn't seem particularly likely, then it's just part of their job to make sure they don't break the code.
In either event though, your technical notes (or other appropriate documentation) should clearly indicate that when the one change is made, the other change is also required.
I would use an inline comment for future developers, or for a developer like me who tends to forget what was going on in every part of the application after I haven't worked on it for months.
Don't worry about actually coding to foil future coders, that's impossible. Just add all the information someone needs to support or extend what you're doing within the source code context.
This is documentation, as Steve B. mentioned. I'd just make sure it's not external, as that has a tendency to get lost.
What do you do when you're assigned to work on code that's
atrocious and antiquated to the point where it's almost incomprehensible?
For example: hardware interface code, mixed with logic, AND user interface code, ALL in the same functions?
We see bad code all the time, but what do you actually do about it?
Do you try to refactor it?
Try to make it OO if it's not?
Or do you try to make some sense of it, make the necessary changes and move on?
Depends on a few factors for me:
Will I be maintaining this code in the future, or is it a one-off fix?
How long until this system is replaced entirely?
How busy am I at the moment?
Ideally, I'd refactor all bad code I had to maintain, but the reality is there are only so many hours in the day.
As is frequently the case, "It Depends".
I tend to ask myself some of the following questions:
Are there unit tests for the existing code?
Is refactoring the code an acceptable risk for my project?
Is the author still available to clarify any questions I might have about the code?
Will my employer consider the time spent on changing existing, functioning code to be an acceptable use of my time?
And so on...
But assuming that I have the capacity to do so, refactoring is preferential as the up front cost of fixing the code now will likely save me a lot of time and effort later in maintenance and development time.
There are other benefits as well, including the fact that the more clean and well maintained you keep your code base, the more likely other developers are to keep it that way. The Pragmatic Programmer calls this the Broken Window Theory.
Developers have an instinct to assume that code is always ugly because of other, inferior developers. Sometimes, code is ugly because the problem space is ugly. All that ugliness isn't just ugliness - it is sometimes institutional memory. Each line of ugly in your code probably represents a bug fix. So think very carefully before you rip it all out.
Basically, I would say that you shouldn't touch code like this unless you actually have to. If there's a real bug that you can solve, refactoring is reasonable, if you can be sure you're maintaining the same amount of functionality. But refactoring for the sake of refactoring (eg, "make the code OO") is what I would generally classify as a classic newbie mistake.
The book Working Effectively with Legacy Code discusses the options you can do. In general the rule is not to change code until you have to (to fix a bug or add a feature). The book describes how to make changes when you can't add testing and how to add testing to complex code (which allows more substantial changes).
You try to refactor it, in the strict sense on the word, where you're not changing the behaviour.
The first target is usually to break up giant methods.
Given the strength of some of the adjectives you use, i.e. atrocious, antiquated and incomprehensible, I'd bin it!
If it is in such a state, like the example you give, it's probably not got any test code for it either. Refactoring is mentioned in many of the other answers but, sometimes, it is not appropriate. I always find that, when refactoring, you generally need a clear path through which the old code can be gradually morphed into the new in a number of well defined steps.
When the old code is so far removed from how you want it to look, such as the extreme cases you seem to be suggesting, you could probably redesign, rewrite and test the new code in a shorter time than it would to take to refactor it.
Scrap it and start over, using the compiled legacy application as a business requirements document.
And spending time in analysis with the users to see what they want changed.
Post it to www.worsethanfailure.com!!!
If no modifications are needed, I don't touch it.
If at all possible, I write automated unit tests first, especially focused on the areas that need modification.
If automated unit tests are not possible, I do what I can to document manual unit tests.
I am just using the tests to document "current" behavior at this point.
If possible, I always keep a version of the code and executable environment that runs things the "original" way (before I touched it) so I can always add new "behavior documentation" tests and better detect regressions I may have caused later.
Once I start changing things, I want to be very careful not to introduce regressions. I do this by continually rerunning (and or adding new tests) to the tests I wrote before I started writing code.
When possible, I leave bugs as-is if there is no business need for them to be fixed. Those bugs may be "features" to some users and may have unclear side effects that wouldn't be clear until the code was redeployed to production.
As far as refactoring, I do that as aggressively as possible, but only in the code that I need to change otherwise anyway. I may refactor more aggressively in my own personal copy of the code that will never be checked in, just to improve the readability of the code for me personally. It's often times difficult to properly test changes that are only made for readability reasons, so for safety reasons, I generally don't check those changes in / deploy them unless I can confidently test that the code changes are completely safe (it's really bad to introduce bugs when you are making changes that are unnecessary for anything but readability).
Really, it's a risk management problem. Proceed with caution. The users do not care if the code is atrocious, they just care that it gets better without getting worse. Your need for beautiful code is not important in this scenario, get past it.
Just like any other code, you leave it slightly better when you leave it than it was when you entered it. You do not ever, ever rewrite the whole code. If that is the work it takes for some reason, then you start a project (small or large) for it.
I am assuming we are talking about a substantial amount of code here.
Not every day is a great day at work you know :)
The first question to ask is: does it work?
If the answer is yes, that would be a huge disincentive to simply ditch it and start over. There may be thousands of man-hours in that code which address edge cases and nasty bugs. Worse yet, there may be other modules in the system that depend on the current incorrect (but known and possibly documented) behavior. Don't mess with it if it isn't broken.
If you are keen on cleaning it up, start by writing test cases for the current behavior. When you run across an instance where the behavior differs from the specification, you must decide whether to accept the behavior as "correct" or go with what the spec say it ought to do.
Only once you have written test cases that all pass should you begin to refactor. The tests will tell you whether your efforts are breaking anything.
I'd talk to my manager and describe the code. Most managers would not want a program held together by banding wire and duct tape per se. If the code is really that bad there are sure to be some business logic errors, hardcoding etc. stuffed in there that will eventually just destroy productivity.
I've come across some pretty bad code before (single letter variable names, no comments, everything crammed onto one line, etc.) and once I mentioned/showed it to my manager they almost always said "go ahead and re-write it", because not only are you taking the hit for reading and changing the code but future co-workers will have to go through the same pain. Better that you take a longer period of time just once to rewrite it rather than having each person who touches the code in the future have to go through and comprehend and decipher it first.
There is an old saying. If is isn't broke, do not fix it. If you have to maintain it then reverse engineer it and document it so the next time you come across it you will know what it does.
You do not know the situation the developer was in when he or she wrote the code. He or she may have been under a time crunch when it was written, (management was all over the developer, etc)
There are also situations where he or she wrote the code per the spec, The spec then changed several times, the developer had to patch the code, as rewrite is out of the question due to time constraints. This happens all of the time.
If the code impacts the performance of robustness of the application and is modular then you can re factor or re-write. Document the situation to assist future programmers in understanding.
Also many programmers consider reverse engineering other developers code as beneath them.
they would rather rewrite without considering the ramifications of doing so.
If you have never done so, try it sometime, it will make you a better developer.
Thanks
Joe
Kill it with fire.
Depends on your time frame and how important that code is to you. If you have to "just make it work" then do that and rewrite the module when time allows.
If its an important or integral part of what you do then refactor refactor refactor.
Then find the guy/girl who wrote it and send them a rude postcard!
The worst offender (in my experience) of really AWFUL code is the ease with which people can do cut & paste these days. Cut & paste should be used rarely. If you think that's the right solution, it's generally better to step back and generalize the problem a little.
Anytime you see code that is "nearly incomprehensible", PROCEED WITH CAUTION. You need to assume that any major re-factoring will result in new bugs being introduced that you'll need to find and correct.
Additionally, I've seen this scenario many times (even fell victim to it myself once or twice): Programmer inherits legacy code, decides code is ancient & unmaintainable and decides to refactor it, ends up deleting key "fixes" or "business rules" subtly patched in over the years, ends up spending a lot of time tracking down and re-introducing similar code when users complain about "a problem fixed years ago is happening again".
Re-factoring (and debugging) almost always takes longer than expected and should never be considered as a "freebie" that comes along with whatever task you're supposed to be doing.
"If it ain't broke, don't 'fix' it" still has a lot of truth.
Im my company we always Refactor Mercilessly. so we still come across atrocious code but LESS and Less and less ...
We write a lot of in-house code and the company is run for about 100 years by the same family. Management usually tells us we have to maintain the code base (evolve) for another 50 years or so. In this setting having code you don't dare to touch is considered a bigger risk to the long term survival of the company then the prospect of downtime because some under-tested code broke because of refactoring.
I run copy-paste detector and findbugs on all legacy code that comes my way.
I then plan my initial refactoring:
remove unused code, unused variable and unused methods
refactor duplicated code
set up a single step build
build a basic functional test
By that point the code meets the basic minimum for maintainability. It can be easily built and basic errors can be found via an automated test.
I often add code like this:
log.debug("is foo null? " + (foo == null));
log.debug("is discount < raw price ? " + (foo.getDiscount() < foo.getRawPrice()));
Some of that code will be recovered for unit tests when I can refactor to it.
I've worked places where we ship that kind of code.
I try to make sense of it, make the necessary changes, and move on.
Of course, making sense of it usually involves some changes; at the very least, I move around the whitespace and line up corresponding braces in the same column like so:
if(condition){
doSomething(); }
// becomes...
if(condition)
{
doSomething();
}
I'll also often change variable names.
And very often, "the necessary changes" require refactoring. :)
Get the idea of what they're doing and the deadline to finish. A larger deadline, typically rebuild much of the code from the ground up, as I find it a very worthwhile experience to not only decipher terrible code and make it legible and document, but somewhere in your brain those neurons are pressed to avoid similar mistakes in the future.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
Do you do it when you’re in the code doing something else?
When your manager approves it? (Seems this never happens)
I guess some of this depends on the impact of the changes. If I change the code and it affects nothing outside of the class, to me that is low impact.
What does it become a design change? When it effect X object or X projects?
I’m just curious how others teams tackle this...
As part of original development (red/green/refactor)
When suggested by a code reviewer
When we've noticed a design pain-point
When making another change, if the refactoring is low impact, i.e. typically not affecting any other files.
If it affects the public API, I generally like to make the refactoring a single source code commit which doesn't change behaviour (and then build new behaviour into another commit). If it affects other projects too, there needs to be consensus over it and I would want to get permission to change their code to go in the same refactoring commit.
I find I refactor when revisiting code (presumably to add/extend functionality) more than 3 months after it was written.
If it takes me more than 2 minutes to discern what a chunk of code is doing, I'll break it apart to make it more immediately understandable (or just add some more comments.)
as soon as all of the tests run.
I work in a large system, so I only change things I have to. It is easy to have bad side effects to changes.
I will refactor sections of code that are performing poorly, not working properly, or needs new functionality.
I never just decide to fix things, I would never be done. if it works, and no one is asking for changes or complaining about problems, move on. life is too short to fix everything.
I often refactor my code when there is a user requirement change or bug fixes. Then there will be a chance for people to review your changes.
Otherwise, I normally don't touch the workable code even it smells.
We found small refactorings are best done while we were working on a bit of code - do what's required, preferably paired.
For bigger things, we had a Technical Debt section on the wall - if you spotted something and didn't have the time to address it, or it was going to take some discussion to solve, you'd add it to the wall and they would be scheduled for future iterations (or when free time cropped up).
Refactoring while you're already in the code is sometimes easiest, especially if your manager does not support the initiative, but if you only change a small part it will break consistency with surrounding parts. In these cases it's better to be selective and, as you suggested, do things that are low-impact. It may also be helpful to refactor long select/switch statements into functions and delay on refactoring the inner code until sometime later.
At a previous job, I was the manager, so I refactored whenever I wanted. At my current job, I'm an analyst so most of the code is not directly my responsibility. When I do write code, I avoid impacting anything that I'm not writing. I have one project which is entirely under my own control and I refactor any time I learn a better way to do something.
We refactor as often as we can. Having unit tests to ensure that everything works pre- and post- refactoring really helps.
Code review processes often help with this. If I touch some code, it gets reviewed, reviewer asks, "why did you do it this way?", I say, "I had to because of (insert ugliness here)". This is a sign that the code should be refactored right after the review is done.
To look at our company, we have decided that our upcoming application release is mostly dedicated to performance optimizations rather than new functionality. This was something we felt was needed and also was requested by some clients.
Therefore we have spent a lot of time identifying performance bottlenecks in our app and reviewing code and refactoring it to make things run faster.
So in our case we did it because management approved us doing it for this new release, because we showed to them how much performance improvement could be gained.
Refactor when needed:
when you need a better understanding of the code you are working on (pairing often helps here), examples are: renaming, method extraction etc.
when the current design doesn't allow for a 'clean' change: at this point you can actually argue with your manager on a value basis (e.g. what is this new feature worth to the project)
I am always making small refactorings in my code. I know as long as I have my unit tests to verify that everything is still functioning properly afterward, I see no harm in doing it as I go. That way you don't get that vague "needs refactoring" feeling every time you work on it.
Now if it requires a large refactoring, it's best to plan for that and set aside some time.
Seems most other posters are resistant to refacotring mercilessly. Of course this isn't possible if the system you're working on doesn't support this through extensive unit tests. But in general, If I can see an opportunity to make the code tighter without spending more than a few minutes or hours at most, I go for it. If I'm not sure what I should be working on, I look for something to refactor.
I refactor when I'm fixing a bug or adding a feature and the process of refactoring makes the code easier to read and easier to maintain.
Following DRY principles vehemently will often be a trigger for me to refactor.
Insufficiently often, thus building up technical debt.
Sad, but so.
Do as I say, not as the team I work on does.
As you work in a legacy codebase what will have the greatest impact over time that will improve the quality of the codebase?
Remove unused code
Remove duplicated code
Add unit tests to improve test coverage where coverage is low
Create consistent formatting across files
Update 3rd party software
Reduce warnings generated by static analysis tools (i.e.Findbugs)
The codebase has been written by many developers with varying levels of expertise over many years, with a lot of areas untested and some untestable without spending a significant time on writing tests.
Read Michael Feather's book "Working effectively with Legacy Code"
This is a GREAT book.
If you don't like that answer, then the best advice I can give would be:
First, stop making new legacy code[1]
[1]: Legacy code = code without unit tests and therefore an unknown
Changing legacy code without an automated test suite in place is dangerous and irresponsible. Without good unit test coverage, you can't possibly know what affect those changes will have. Feathers recommends a "stranglehold" approach where you isolate areas of code you need to change, write some basic tests to verify basic assumptions, make small changes backed by unit tests, and work out from there.
NOTE: I'm not saying you need to stop everything and spend weeks writing tests for everything. Quite the contrary, just test around the areas you need to test and work out from there.
Jimmy Bogard and Ray Houston did an interesting screen cast on a subject very similar to this:
http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/05/06/pablotv-eliminating-static-dependencies-screencast.aspx
I work with a legacy 1M LOC application written and modified by about 50 programmers.
* Remove unused code
Almost useless... just ignore it. You wont get a big Return On Investment (ROI) from that one.
* Remove duplicated code
Actually, when I fix something I always search for duplicate. If I found some I put a generic function or comment all code occurrence for duplication (sometime, the effort for putting a generic function doesn't worth it). The main idea, is that I hate doing the same action more than once. Another reason is because there's always someone (could be me) that forget to check for other occurrence...
* Add unit tests to improve test coverage where coverage is low
Automated unit tests is wonderful... but if you have a big backlog, the task itself is hard to promote unless you have stability issue. Go with the part you are working on and hope that in a few year you have decent coverage.
* Create consistent formatting across files
IMO the difference in formatting is part of the legacy. It give you an hint about who or when the code was written. This can gave you some clue about how to behave in that part of the code. Doing the job of reformatting, isn't fun and it doesn't give any value for your customer.
* Update 3rd party software
Do it only if there's new really nice feature's or the version you have is not supported by the new operating system.
* Reduce warnings generated by static analysis tools
It can worth it. Sometime warning can hide a potential bug.
I'd say 'remove duplicated code' pretty much means you have to pull code out and abstract it so it can be used in multiple places - this, in theory, makes bugs easier to fix because you only have to fix one piece of code, as opposed to many pieces of code, to fix a bug in it.
Add unit tests to improve test coverage. Having good test coverage will allow you to refactor and improve functionality without fear.
There is a good book on this written by the author of CPPUnit, Working Effectively with Legacy Code.
Adding tests to legacy code is certianly more challenging than creating them from scratch. The most useful concept I've taken away from the book is the notion of "seams", which Feathers defines as
"a place where you can alter behavior in your program without editing in that place."
Sometimes its worth refactoring to create seams that will make future testing easier (or possible in the first place.) The google testing blog has several interesting posts on the subject, mostly revolving around the process of Dependency Injection.
I can relate to this question as I currently have in my lap one of 'those' old school codebase. Its not really legacy but its certainly not followed the trend of the years.
I'll tell you the things I would love to fix in it as they bug me every day:
Document the input and output variables
Refactor the variable names so they actually mean something other and some hungarian notation prefix followed by an acronym of three letters with some obscure meaning. CammelCase is the way to go.
I'm scared to death of changing any code as it will affect hundreds of clients that use the software and someone WILL notice even the most obscure side effect. Any repeatable regression tests would be a blessing since there are zero now.
The rest is really peanuts. These are the main problems with a legacy codebase, they really eat up tons of time.
I'd say it largely depends on what you want to do with the legacy code...
If it will indefinitely remain in maintenance mode and it's working fine, doing nothing at all is your best bet. "If it ain't broke, don't fix it."
If it's not working fine, removing the unused code and refactoring the duplicate code will make debugging a lot easier. However, I would only make these changes on the erring code.
If you plan on version 2.0, add unit tests and clean up the code you will bring forward
Good documentation. As someone who has to maintain and extend legacy code, that is the number one problem. It's difficult, if not downright dangerous to change code you don't understand. Even if you're lucky enough to be handed documented code, how sure are you that the documentation is right? That it covers all of the implicit knowledge of the original author? That it speaks to all of the "tricks" and edge cases?
Good documentation is what allows those other than the original author to understand, fix, and extend even bad code. I'll take hacked yet well-documented code that I can understand over perfect yet inscrutable code any day of the week.
The single biggest thing that I've done to the legacy code that I have to work with is to build a real API around it. It's a 1970's style COBOL API that I've built a .NET object model around, so that all the unsafe code is in one place, all of the translation between the API's native data types and .NET data types is in one place, the primary methods return and accept DataSets, and so on.
This was immensely difficult to do right, and there are still some defects in it that I know about. It's not terrifically efficient either, with all the marshalling that goes on. But on the other hand, I can build a DataGridView that round-trips data to a 15-year-old application which persists its data in Btrieve (!) in about half an hour, and it works. When customers come to me with projects, my estimates are in days and weeks rather than months and years.
As a parallel to what Josh Segall said, I would say comment the hell out of it. I've worked on several very large legacy systems that got dumped in my lap, and I found the biggest problem was keeping track of what I already learned about a particular section of code. Once I started placing notes as I go, including "To Do" notes, I stopped re-figuring out what I already figured out. Then I could focus on how those code segments flow and interact.
I would say just leave it alone for the most part. If it's not broken then don't fix it. If it is broken then go ahead and fix and improve the portion of the code that is broken and its immediately surrounding code. You can use the pain of the bug or sorely missing feature to justify the effort and expense of improving that part.
I would not recommend any wholesale kind of rewrite, refactor, reformat, or putting in of unit tests that is not guided by actual business or end-user need.
If you do get the opportunity to fix something, then do it right (the chance of doing it right the first time might have already passed, but since you are touching that part again might as well do it right time around) and this includes all the items you mentioned.
So in summary, there's no single or just a few things that you should do. You should do it all but in small portions and in an opportunistic manner.
Late to the party, but the following may be worth doing where a function/method is used or referenced often:
Local variables often tend to be poorly named in legacy code (often owing to their scope expanding when a method is modified, and not being updated to reflect this). Renaming these in line with their actual purpose can help clarify legacy code.
Even just laying out the method slightly differently can work wonders - for instance, putting all the clauses of an if on one line.
There might be stale/confusing code comments there already. Remove them if they're not needed, or amend them if you absolutely have to. (Of course, I'm not advocating removal of useful comments, just those that are a hindrance.)
These might not have the massive headline impact you're looking for, but they are low risk, particularly if the code can't be unit tested.