A lot of my advice here will encourage a methodical approach. I’ve worked on code that is more than a decade old. In that sort of situation the original author has probably moved on, perhaps most people who were working on the team at the time. Having comments and commit messages that explain what the code does and why that approach was taken helps a lot. Having no comments and obtuse commit messages is… frustrating. When it’s harder to understand the code everything takes longer. I’m not propose abandoning that but there is a place for a less restrictive approach.
Revolutionary prototyping
It’s often phrased as “building one to throw away”. The idea being you can prototype a system to make sure that what you want is possible. It can be done the quick and dirty way. Maybe it will only work for one particular situation. Maybe it’s slow or has to be run on better hardware than normal. Maybe it’s written in a different language, with different libraries, or on another platform entirely. Once you know you know it can work you go back to the beginning and do it all properly. None of the old code makes it into the new system.
I often use a lesser version of this. It’s not about the a whole new system. It can be used for any ticket but is well suited for doing something radical but that won’t take too long. It starts like this:
- Take a copy of the original repository.
- Create a branch for the prototype work. This branch is never going to be merged back to the mainline.
- Check in to the branch regularly but commit messages don’t matter too much.
- Do whatever needs to be done:
- Large files broken up into small ones.
- Small ones combined into big ones.
- One library ripped out and replaced with another.
- Hard coding values whenever is need.
- No need for a proper testing program.
This is not something that you should start, put away for a couple of months, and then try to come back to! The goal is to get something working, or at least working enough to prove that it will work if done properly. It goes on like this:
- Return to the original repository.
- Create a branch for the real work.
- Use a methodical approach with proper comments and commit messages.
- Set up a visual diff between the two repositories. This shows the code that needs to be changed even if the prototype isn’t exactly what’s needed.
- The goal becomes to reduce the differences between the two repositories.
- Consider the best steps to reach that goal and think about each step as it’s taken.
- Often it’s best to clear up some of the biggest differences first:
- Adding, removing or renaming files.
- Moving chunks of code inside or between files.
- Then move on to smaller differences:
- Add configuration for all the values the hardcoded in the prototype.
- Choose identifiers that clearly convey their purpose.
- Use the best algorithm for each problem.
- Write units for new code.
- Each step in the new branch should be as good or better than the one in the prototype branch.
- As the new branch is changed also make these changes to the prototype.
- The difference between the branches gets smaller so it’s easier to see what’s left to do.
- Continue like this until both branches become the same or the prototype is surpassed.
This might seem like a bunch of extra steps to get to the same place as the prototype. However each step is easier so individually they can go faster. During the first phase you can concentrate on creating new functionality and don’t have to worry about details. During the second phase you can concentrate on doing it right but know the end goal is achievable. By the end you have a clear commit history that takes a straight line to a great solution.
Evolutionary prototyping
The sibling of revolution prototyping is evolutionary prototyping. Here the prototype branch will, eventually, be merged back in to the mainline. I think this technique is less suited to the wild and crazy changes of revolutionary prototyping. Although you can make that sort of change you have to deal with cleaning them up afterwards. It’s easily miss edge cases. Going back and finding all of those edge cases can be difficult. Even with testing I think this is more likely to lead to bugs cropping up later on. It’s also harder for anyone trying to understand it afterwards as the history contains both the steps and the missteps, all the back and forth to get to the end.
For evolutionary prototyping I think it’s better to keep a methodical approach. You can do big things but you still have to think about the edge cases as you go. Leaving yourself “todo” comments as you go so you can go back and pick up all the pieces afterwards.
In either case the end goal is to produce a mainline with high quality code that other developers can understand.
Leave a Reply