There appears to be this notion among the project management team that stating that "it works" means it should then be deemed 100% complete. Most programmers know that isn't always the case. If I'm trying alternative approaches to get a piece of functionality working, that doesn't necessarily mean I found the best solution, or it won't require some rework after reviewing with other developers. I'll often get done with something, step back, and then ask myself what I can do better after the business rules are satisfied. Should this "I can do better" time actually fit somewhere within the timeline? I'm of the opinion that the best approach is that you always leave code better than when you found it (to a degree), which could mean post launch refactoring. However, project teams are often extremely uncomfortable with this approach because again, if it works, and has been tested, then why fix it?
- Anybody can ask a question
- Anybody can answer
- The best answers are voted up and rise to the top
There is one overarching principle that governs the need to refactor and optimize, both in waterfall and Agile: YAGNI (You Ain't Gonna Need It). A second principle is the corollary of the first: "Premature optimization is the root of all evil", the coding equivalent of the general proverb "the enemy of excellence is perfection".
Let's take the priciples and apply them. You have the requirement to build an ETL algorithm that takes a file of a particular type, extracts its information, then puts that information into a database. Your goal for this week (for our purposes it doesn't matter whether you're in an Agile or SDLC shop) is to get that done.
You're a smart fellow, and you have been given a glimpse of the big picture. You know that this is not the only type of file for which the project will need an ETL. So, you consider implementing this ETL algorithm to also work on another type of file, which has only minor differences. Doing this would violate YAGNI. Your job is not to develop the algorithm for that other file; it is to develop the algorithm for the one file that is needed by the end of the week. To meet that goal and pass the acceptance tests, you need to develop that algorithm and make it work correctly. You "ain't gonna need" the additional code to make it work with the other file. You may think it will save you time to incorporate it now, and you might be right, but you might also be terribly wrong; the algorithm for the other file might need to be used in an area of the system your code can't be used, or the requirements for the new file might be different than for yours in ways you don't know (in Agile, those requirements may not exist yet). In the meantime, you've wasted time and unnecessarily increased the complexity of your algorithm.
Now, it's next week, and as a dubious reward for your excellent work on the first algorithm, you have been given the task of creating the algorithms for two new file types. Now, you DO need additional code to make your algorithm work with more files. You may extend your existing algorithm using a template method pattern that will use a basic pattern with file-specific individual steps, or you may simply derive a common interface from your existing algorithm, develop two new ones that follow the interface, and plug them into an object that can choose which algorithm to use.
While developing, you know you have a requirement that the system be able to process 10KB of raw data per second. You do a load test and find your initial draft algorithm handles 8KB/s. Well, that's not going to pass the AATs. You take a look and see that there's some O(my God)-complexity loop structure in your algorithm; you streamline it and get 12KB/s. "Pretty good", you think, "but if I had that poor a loop in the code, what else can I shave off?". buzz You just violated the "premature optimization" rule. Your code works, and passes all requirements. You're "done", until such time as the requirements are updated to require 15KB/s. If and when that happens, THEN you pull the code back up and look for things to improve.
Follow this simple process while developing, whether in Agile or in traditional SDLCs: "On the first pass, make it work. On the second pass, make it pretty. On the third pass, make it SOLID." What this means is, when you first create a line of code, make that code do its job correctly and bug-free, but don't pay too much attention to design rules within this code, as for all you know right now you'll never touch this area again. The next time you visit that line of code, you just proved yourself wrong; it's no longer a one-off piece of the system. Refactor it for readability, conciseness of code, and/or DRY principles (you may have copy-pasted some code to do something five times; refactor that into a loop and/or a method call). The third time you are working in or around that line of code, it should be considered a key area of the system, and you should refactor it for SOLID structure, perform easy-to-spot optimization enhancements, etc. Follow this, and you'll find that the areas that need refactoring receive it, but at the same time no time is wasted.
This may go against your own personal temperament as an engineer/programmer, but if it's working, what business value is there for you to continue refining it? Will it make it easier to maintain over time? If so, then working under the agile methodology, you should be able to create new items in your backlog to refine and refactor your existing code, and those would get prioritized with the other items in the backlog. That's part of the value of the agile process: the team decides together what's most important and what gets done next.
Our team also tracks what we call "technical debt", so if you get something working but you know it could be done better, you log it as technical debt. We use scrum, and sometimes you'll finish all the work in a sprint early (you should finish a little early roughly half the time if you're fairly close with estimates), but not have enough time left to pull in a completely new user story, so we spend the extra time going back and working down our technical debt. It's not tracked formally like our user stories in our backlog, and we can pretty much work on it whenever we have time available.
It's also more or less a judgment call on your part when you call the task "done"; if you're not comfortable with the state your code is in, don't mark the task complete.
Just before you start coding the next release.
Don't refactor based on "intuition".
Refactor based on the next sprint's actual stories.
Don't mark the code as 100% complete until you are satisfied with the refactoring. You just have to constantly evaluate the cost/benefit to refactoring the code because if you study enough you will always see ways to make the code better.
I use the red green refactor method of TDD. So my refactoring is built into my development. For big refactorings like changing the underlying model or something similar I would get management buy in to spend the time first.
"post launch refactoring" has a hidden cost in regression testing and QA time that you are ignoring, plus it carries the opportunity cost of not working on reported bugs and new/requested features and changes. TANSTAAFL
If it is worth doing, it is worth making a task to get prioritized via your normal process and not as a special exception. You are part of a team, after all, and working on common goals and arbitrarily extending your schedule to accommodate fixing working code impacts them too.
So for a real answer: if you know that you'll want to refactor then schedule that time as part of the task. If you're doing scrum/agile then time box a clean-up task. If you're waterfall/spiral then make the refactor part of the process to code review and accept the modules.
...In which case you are not 100% done yet...
If code reviews and subsequent rework is part of your development lifecycle, again the feature is not done until all of these are finished.
It depends. If it means refactoring, it should be part of the original development task. If it means experimenting with a potentially better algorithm, it could be a separate task.
In brief, because code can be broken on many levels.
It is one thing that it works right now. It is an entirely different thing whether it is clean, extendable and maintainable in the long run.
For more detailed answers, see this thread.