We’ve previously discussed the first step, the Clean Start protocol.
Given that you’ve followed that advice, you are ready for the second stage in the process.
The Basic Steps
There is a rather lightweight discipline here. It’s unfamiliar to many people, but it isn’t difficult to learn. You may find these steps described more succintly in Intentional Commits’ treatment of scope control
First, choose what you will do. This means the next VERY SMALL step, possibly as small as writing a function or even adding a log message, that moves you a little closer to the goal you have in mind. We’ll discuss this more, below.
As you work, run all of your checks. That includes the reformatter (either habitually while editing, or automatically on save), the lint checkers, the static security scans, and all the tests fast enough to support your minute-by-minute development work.
You can commit any time all the checks are fully successful (they “run green”).
You can pull from the main branch after any commit.
You can push as long the tests are green and the application works.
If tests fail, you can either add a fix in the next step or else make a graceful retreat.
Choose What You Will Do
The mindset of taking smaller steps is described as “iterative and incremental development,” “walking skeletons,” “ tracer bullets,” or “evolutionary design.”
Here, we assume that you have read up on these concepts and are ready to implement your big feature as a series of small end-to-end changes. It’s too much to try to introduce incremental development as a sidebar to this Easy Recovery topic.
What step can you take now (ideally within the next hour) that would help solve some part of the problem for some set of people?
As a side note: sometimes the thing you most need to do is explore the application and understand the flows you are fitting your change into.
You may wish to modify code experimentally to learn how the system will react. Since you began with a Clean Start, there’s nothing to fear; you can always return to that starting position.
Once you have made your choice, that is the ONE thing you are going to do.
Commit to Doing Just One Thing (at a time)
I’ve become a fan of Intentional Commits.
It’s a simple idea: choose what you will do next, control your scope, and do the thing.
If you think of some good side quest, don’t pursue it. Instead, write it down and consider doing it next, after you finish the current task.
Intentional commits are a lot like Test-Driven Development.
In case you need a refresher on TDD:
- Choose what behavior you want to create next
- Write a test (to a hypothetical API) that will expose that behavior
- Write the code that will pass the test
- Ensure that the new test and all prior tests are passing
- Refactor the code toward the design you would like it to ultimately have.
- Integrate: collect the changes your teammates have pushed and share your changes with the team. When this seems like overkill, at least “save your game” by committing locally.
With intentional commits, you:
- Choose what you will want to do next, perhaps composing the commit message in advance.
- Make the change you have planned, and only that change.
- Anything you realize you also want to do? Write it down. Don’t do it yet.
- When the change is made and all is “green”, commit with the message you composed earlier.
Both cases rely on your ability to slice work small and do just one thing at a time.
The advantages are convenience, safety, and time:
- All the time previously spent composing meaningful commits by selecting passages of code from various files? Gone.
Since you only did one thing, there are no stray changes to separate. Most of the time you can just add
everything (
git add .
) and commit. - Your commit will be appropriate for a code review (if your company is still doing asynchronous code reviews).
- Once you push, you are completely free to do something else. That might be exploration, testing, refactoring, UI improvements, expanding the functionality you worked on in the last commit, or picking up a side quest you’ve recorded for yourself.
- Your work is more visible more often. This means that managers, sponsors, bosses, supervisors and that lot will not have the same pressure to micromanage you.
There is safety, convenience, and freedom in working this way.
The problem, of course, is that 95% of preference is familiarity, and most programmers are only familiar with “load it all in your head and do all the programming, then look for the problems” from prior practice. Since that’s what they know, and it’s gotten them this far, that’s what they prefer to do. This is why people tend to order familiar items off of a restaurant menu, and go places they’ve already been.
This isn’t a moral imperative, just an advantageous way of working. There is no moral “should” involved. If you don’t already do this, that doesn’t make you a bad person. If you like to stick with the old way of working in big steps with fear of loss, that doesn’t make you a bad person either.
If you choose to work in small steps, you will get certain advantages. It is an adjustment. You may fall back on habit sometimes. Maybe you can experiment with this on the side, or try it only on small jobs, or when pairing or working in an ensemble.
If you get used to it, you may not want to return to the old way.
Graceful Retreat
Once I learned to do TDD (probably around 2004?) I got used to the idea that all the tests passed 5-10 minutes ago. Since I was working in small steps, I could safely and easily delete my most recent change.
In the Industrial Logic eLearning, Joshua Kerievsky described it as a “graceful retreat”: you can revert your local changes using your version control tool.
In git, that’s git reset –hard
I know people who would rather amputate a finger than type that sequence of characters. They imagine, perhaps rightly, that they have too much invested in their current unit of work. The solution sounds overly-simplistic, but it is actually a simple idea: Don’t invest that much between commits.
For safety’s sake, we strive to never have a large commitment at risk.
Instead, we do one thing at a time and commit when the tests run green.
We call these microcommits.
Gamers talk about “saving their game”. If you save your game (at least before a boss fight) you can gracefully return to your healthy pre-battle state.
It’s the same with software. Thanks to Distributed Version Control Systems (like git), you can save your game at will.
You certainly should do so before attempting anything tricky or error-prone.
If you commit locally while the code is in a good state, you can return to that good state with all tests running successfully and your application still working.
I was talking with Bryan Beecham, Mike Hill, Ron Jeffries, and Chet Hendrickson and I don’t remember who said what or in what order, but it was mentioned that we don’t use the reset command often enough. Everyone agreed that it is better to reset to the last good state and start over rather than fight through a badly started change.
This is only a sensible thing to do if you are saving your game regularly.
Calling Your Shot
I’ve gone so far as to create a set of zsh functions to help with my intentional, tiny steps.
function gnext {
echo "$*" > .gnext.intention.txt
}
function gdone {
chosen=$(cat .gnext.intention.txt)
read "choice?Message [${chosen}]: "
usable="${choice:-${chosen}}"
git commit -am "${usable}"
rm .gnext.intention.txt
}
I don’t know if you find this useful or not, but for me it is handy to pre-write my git message, just as it is to write the test before the code in TDD.
Even if you don’t use TDD, you could use gnext
and gdone
to see if it helps you to think in intentional, small steps.
This is provided as an example, no guarantees of any kind included. It works, and it’s the script I use, but take
responsibility for your use of it.
I addedgnope
, which I run when I decide that the whole mission is a bad idea – having it delete
the .gnext.intention.txt
and also git reset –hard
It’s easy enough to write on your own
But WHY?
Fear of loss drives poor practice.
When you aren’t afraid to revert, you aren’t afraid to move forward.
Test-Driven Development and microcommits are not common (enough) practice, so the whole “easy recovery” thing is a bit of an open secret.
Developers often do a large amount of work at once, and their changes are spread out among many lines of code in many source code files.
It’s not unusual in some projects to have many hundreds of lines of code in several dozen files, all as part of making a single change. Sometimes it’s thousands of lines in hundreds of files.
These are undifferentiated changes in the file system. They’re not segregated and indexed in a way that describes why the change was made or when. If the developer doesn’t practice the Clean Start protocol, these changes may be mixed with unrelated changes made previously.
Eventually, developers will run the code, and maybe even the tests1.
They will find the errors they made hours (or days) ago and will fix the ones they find. They will likely test for side effects and fix those as well if time and system knowledge are sufficient.
Then they will commit the current mega-change-set so that they can push it to a shared (or sharable) code branch. This may be the only time they save their changes other than locally in source files.
But let’s say it doesn’t go quite that well.
If they find that they have made a change with puzzling and unwanted consequences, they may have to undo their changes and redo parts of the work. There is no easy and clear way to do this, so they may spend hours combing over files, undoing changes, looking for potential side-effects or mistyped algorithms, seeking the error, separating the good changes from the bad ones.
In the worst-case scenario, it may be so baffling and complex that they are forced to revert the whole change set and lose all the work they’ve done so far.
The fear of losing that much work is so significant, they may press on desperately, making change after change.
We’ve seen people spend days debugging and experimentally “hacking” at the code to avoid losing a prior afternoon’s work, always certain that they are only one or two more little problems from completion.
The problem here is that the change is “all or nothing.”
This kind of practice involves individuals making large changes without any safety mechanisms in place. They don’t think much about that, though, because they’re used to having a lot of work at risk and usually it turns out okay. It’s “ normal.”
It shouldn’t be normal. It turns out badly often enough that it causes fear. It encourages people to leave code in a poor state. It makes the goal of actually implementing a feature such a big accomplishment that people seldom will stay in the moment and tidy up after themselves. The fact that they managed to get it to work at all is success, and eliminating the collateral damage feels like a pointless chore to them.
Mind you, this isn’t the single worst problem in software development, but it is enough of a drag on developers that people avoid improving the design of the software or trying new ways to achieve new results. They work in a pathologically conservative way and the code base quality degrades.
We find it safer to work in small (incremental) steps so we’re not at the ragged edge of human cognition at all times. We also make it safer by working together, so that we can cover each others’ blind spots..
Tiny Steps Make It All Work
You may find it’s faster and easier to start over than to “gut it out” when things are confusing.
With a lower cost of failure, you may find yourself being more innovative!
I am confident that you will feel less stress and make more progress when you always know exactly what you are doing and that you can cheaply and safely step backward.
It works even better in pairs or ensembles.
Notes
Not a joke. We found that a lot of developers never run the test suite at all, but send their changes blindly to build-and-test server, hoping that the tests will pass there.