Part of the reason to come to SF was to get better at my craft. I’ve learned a lot since moving here 6 months ago. Last week I learned about evolving a codebase instead of designing one. Nobody actually presented it that way to me, they simply told me how they work and that’s my attempt to characterize it.
How I used to work (design everything then type it in):
- Know everything about the code
- Think about the new feature you want to implement
- Figure out the smallest diff between what you have and what you want
- If results of #3 are elegant enough, do that.
- If results of #3 aren’t elegant enough, keep thinking until you come up with something better.
- Type it in.
- Commit your changes, usually an entire feature.
This patterns works really well under a couple of circumstances. It requires you know the entire codebase intimately, which means you work alone and you have been doing so on this project for a great deal of time. Previous to my current employment situation I spent almost a decade being either the only or one of two programmers working on my target project at any given time. Because I have a pretty good memory, I can basically remember everything I’ve ever written on a project, particularly if that project is one I’ve been working on everyday for more than a year.
It turns out that when you join a team that is working on several different projects, and everyone is touching everything, you cannot possibly know everything about the code. Someone is always changing it without you knowing. You cannot reason about the code in a logical manner without a structure or pattern to the code. I thought at my new position I would be able to get to know the codebase and then get back into the mode I was working in before where I could reason about the code without looking at it. I thought other people structured their code so that this was possible. NOPE.
How I’m learning to work now (iterate everything):
- Have well defined requirements for the functionality of the code.
- Pick one of those requirements, as small a piece as you can reasonably break off.
- Write some tests for that functionality from #2
- Write the code to fulfill those tests.
- Commit results of #3-4.
- Pick another tiny requirement and repeat steps 2-5. Continue to do this until you have all the functionality defined in #1 completed.
- You’ve got working code and passing tests; time to refactor.
- Pick a tiny refactor that lets the code be more modular, easier to work with, easier to understand. Basically anything from the Refactor book.
- Commit the refactor.
- Keep doing #8 until you run out of time or until you reach a refactor that is so large you cannot justify doing it.
You evolve it. The rules of the system are the tests. Nobody bothers to learn them because that’s why you have tests. When you ask someone who’s been working this way for some time about a portion of the code they wrote, they invariably say “lets look at the code.” The reason is that they have no idea what they did because they didn’t have a full design in their mind when they started. They evolved it from the interaction between the requirements, the tests and the existing code.
I suspect the second methodology is a reaction to time requirements combined with working on a codebase with multiple people. I’m not yet sure what the long term consequences are for using the iterative methodology. Time will tell.
How do you work?