Hello and welcome to even-keeled, a podcast about the craft of creating software and effective development teams. I'm your host, Jon Kinney, and I'm super thankful that you've spent some time with me each month, digging into development, methodologies, and best practices that help teams build successful software.
Today on the podcast, we're going to be talking about the art of storytelling through commit messages, pull requests and documentation like read me's we'll highlight why it's important to have changes that are related, introduced to a code base together and cover some, get rebased tips for editing your code story into an easy to understand narrative that either future you or your team will appreciate.
This episode is sponsored by Headway. Headway helps startups and corporations bring entrepreneurial ideas to market and keep them there. Whether you want to bring a new idea to life or improve the one you already have headway can help through product strategy, design, and development. For more information, you can head to our website at headway.io. Through this podcast, Headway is excited to give back the community because we all know a rising tide lifts, all ships, so go forth and make waves.
If you're interested in hearing about the product development journey from other startup founders and corporate innovators, check out our podcast Seaworthy, hosted by Headway CEO and Head of Design, Andrew Verboncouer. In the latest episode, Andrew interviews, Ben Huggins, Head of Design at Humu. They talk about Ben's journey into user experience and dig into using timely triggers to change user behavior. Head over to seaworthy.fm to give it a listen.
So today we're going to discuss storytelling through pull requests and get commit messages. After the content we'll summarize what we discussed. Then we'll have some application questions for you to think about how to apply this craft to your everyday work. We'll make some show notes available for download with any links and references. And this month it'll include our template for creating the perfect pulll request.
If you want to get new episodes along with the notes and supplemental materials delivered to your inbox each month, head over to even keel.fm and send us your email. We'll let you know as soon as new episodes are released. So let's dive in.
What is storytelling? - well the national storytelling network defines it this way. They say storytelling is the interactive art of using words and actions to reveal the elements and images of a story while encouraging the listener’s imagination.
They go on to say that storytelling among other things is:
- uses words
- and uses actions or gestures
I don't think any of this is much of a surprise, but you might be wondering how that applies to software and how we should go about telling the story of our code?
I don't know about you, but when I talk about software features or entity relationship diagrams, or anything related to UI, I love to describe those things by waiving my hands all around and trying to paint a picture for whoever I'm discussing it with. It's harder to paint that picture as enthusiastically in a text based commit message or pull request description, so let's dig into more about the basics of storytelling to get a better idea of how to make our commits and PRs interesting and engaging.
So number 2 on Pixar's list is that "Great stories have a clear structure and purpose Pixar often follows a simple formula called "The Story Spine". It goes like this:
Once upon a time there was [blank]. Every day, [blank]. One day [blank]. Because of that, [blank]. Until finally [bank].
That may sound a little boring, but there's comfort in knowing what you're going to get when you sit down to watch a Pixar film. My youngest son is 3 and he gets scared of the bad guys in movies, but my wife and I can always tell him with confidence "Don't worry, this story will have a happy ending."
Wouldn't it be nice if every time you opened up a pull request you could be confident that it, too, would have a happy and predictable ending. Creating a story spine for PRs that works for you and your team might take a bit, but the good news is that once you have it defined, Github (and many other git hosted platforms) have a great template feature that you can take advantage of to make sure the story spine of all of your pull requests is well thought out and followed consistently by the team. We have the template that we use at Headway linked in the show notes to get you started.
Digging a little bit more into this aspect of a "clear structure" of a commit message, there are several community accepted guidelines that come from people like Linus Torvalds, the creator of git and Linux, Tim Pope a prolific Ruby developer and Vim plugin author, and Chris Beams who has the best blog post on writing commit messages that I've found so far which will be linked in the show notes.
Chris has 7 rules for creating good commit messages. They are:
- Separate subject from body with a blank line
- The subject line is simply the first line that you type when making a commit. The Body is everything else. And Chris says to separate those two by a blank line.
- Limit the subject line to 50 characters
- Capitalize the subject line
- Don't end the subject line with a period
- Use the imperative mood in the subject line
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how
If you're not familiar with "the imperative mood"
- This just means “spoken or written as if giving a command or instruction”.
- For example:
- Clean your room
- Close the door
- Take out the trash
Why is this a good idea? One reason is that Git itself uses the imperative whenever it creates a commit on your behalf. For example if you had a feature branch called 'style-login':
- Merge branch 'style-login'
- Revert "fixes stuff"
Or when clicking the “Merge” button on a GitHub pull request:
- Merge pull request #42 from style-login
Chris goes into great detail in his post, so I won't belabor the point, but if a readable history is important to you - and it should be - this is a great thing to rally your team around.
Some of you might be thinking "But I don't always have a great story or concise set of changes to commit when I'm first exploring the work". That's fine, and you should still commit early and often. We'll talk later about using interactive rebasing to clean up your commit history and prepare it for merging into your master branch.
As for pull requests, that's a bit tricker of a subject. While git history and individual commits travel along for the ride with any hosted git platform (meaning, if you move your repo, you'll still have all of that metadata), pull requests are a bit more ephemeral. That is, if you change from Github to Bitbucket, you're going to lose your PR history. And when you're working in your editor of choice, the pull request info isn't as readily available as the individual commit history is. So my recommendation for pull requests is to not add any detail in a PR description that isn't already in a git commit. The only exception for this is if in the context of reviewing that particular PR there is some setup that's needed or some instructions for how to help QA use the feature, that can be useful since it's not as necessary to have that type of info stick around after the PR is merged.
Speaking of merging PRs, I'd highly recommend that all PRs be merged using the --no-ff flag. This preserves valuable branch history by grouping together the commits that belong to that feature or bugfix under that branch name, effectively building an easy to read, grouped, history of merges into master. The good news is that by default, the big green "Merge Pull Request" button on Github will do that non-fast forward merge - preserving the commits grouped to their feature branch. There are other options on github such as "squash and merge" but that (as the name implies) squashes all of the commits into a single commit and injects that commit into the main line of master as if the branch it came from never existed. Somewhat similarly, the "Rebase and merge" option ensures that all commits from the topic branch are added onto the base branch individually without a merge commit while using the fast-forward option. In my opinion, these alternative merge methods are too easily abused. "Squash and commit" can result in big bang feature additions in single commits in the main branch, and "rebase and commit" doesn't preserve the branch name that they came from. Both of these choices make it harder to reason about a repo when spelunking through history later. That's why I recommend sticking with the default non-fast forward merge commit option after honing the individual commits that comprise the PR itself.
To complement this non-fast forward merge strategy and keep history super clean and readable, I recommend using a manual rebase strategy for keeping branches up to date with master. Whenever I say just "rebase" that's sometimes what people refer to as a "regular rebase". Which is different than an "interactive rebase" which as I mentioned earlier, we'll talk about in a little bit. Updating a feature branch via rebase will eliminate the trivial "bubble merge" commits that result from "merging master into your feature branch" where no conflicts occurred. By using a rebase strategy, you can get your feature branch up to date with the latest changes on master so they will be guaranteed to apply cleanly and without merge conflict while also avoiding the unnecessary bubble merge noise in your history. Unfortunately that doesn't mean you don't still have to resolve merge conflicts, it's just that they happen in the context of the rebase instead of all at once in the context of the larger merge of master into your feature. This is a good thing, because you have a smaller set of conflicted files to deal with. What trips people up early in their rebase experience though, is thinking that when a rebase stops, requiring the fix of a conflict, that you should be fixing the code in its ultimate end-goal state. That's not true, you should only be fixing the code as it needs to be for the specific commit that is being applied at the time.
For example: let's say you have a method called name which only takes one argument. Then you introduce a commit augmenting it to take two arguments allowing for a first name and a last name. In a second commit, you alter the method to be called full_name. If when rebasing this feature branch to master, you get a conflict regarding the first change of accepting 2 arguments, fix the code in that commit's context only. You haven't re-named the method yet as far as the rebase is concerned, so keep the method at its old name while accommodating any new code from master. Once the conflict is fixed and you stage all your changes and tell git to continue the rebase, the commit that alters the method to be called full_name will either be applied successfully or have its own conflict that needs to be resolved. When all conflicts are fixed, the rebase will report that it was successful and then you can merge your branch. Resolving conflicts in a rebase - commit by commit - can seem like more work, and sometimes it is, but it ensures that each of the changes that is in conflict with master is reviewed and dealt with systematically. The alternative (and the workflow we're avoiding) is merging master into a feature branch resulting in all of your feature branch commits that conflict with master requiring conflict resolution at the same time. In a messy conflict scenario and a feature branch with 6-10 commits, that is a lot of context to hold in your head and fix up all at once. If fixing merge conflicts in a rebase is new to you, check out the video I linked in the show notes on the topic.
We have a rule at headway that a PR can be marked "ready for merge" but that the author needs to rebase to master and resolve any conflicts before actually performing the merge. The reason for this is that the author is in the best position to be able to understand how the commits in their PR apply to the latest vetted code on master.
Backing up a bit to talk more about the commits themselves that comprise a good PR, let's dig into how to craft the story of your pull request with a slightly more advanced feature of git called interactive rebasing. Interactive rebasing allows you to rewrite history by altering commits through several commands. We'll focus on: pick, reword, edit, squash, and fixup. What's more is you can re-order commits in an interactive rebase, and you can remove them as well by deleting a specific commit from the list. Let's dive a bit more into each of these commands to describe how they can help tell a story more effectively:
And then fixup, or "f" for short, is like squash but it discards the commit's log message. So if you just need to alter what you were doing about a previous commit, like for example, adding that file back in, that 8th file, but you don't care about what the commit message was as you were adding that 8th file, which in our prior scenario would probably be the case, you can just use fixup instead of squash, and then when it presents you with the options for ultimately committing that change, it'll discard that 2nd commit's log message.
The nice thing about interactive rebasing is knowing that you don't have to get each commit message and group of related files 100% correct while working on a feature or bugfix branch. This is liberating and allows you to commit early and often, and then be intentional about editing down your commit messages (and the files contained within those changes) to a meaningful set of atomic commits. We'll talk more about atomic commits in a bit.
To enter the interactive rebase prompt, note the number of commits you have on your feature branch ahead of master (we'll assume 2 for this example), then from your command line, type:
This will take the last 2 commits you created on your feature branch and bring them up in a git log view of sorts but in your editor. I have mine set to Vim, but you can use whatever editor you like.
From here, perform the relevant action, perhaps combining a few "oops didn't work yet" or "delete one more file" type commits by using the squash command, or use the reword command to edit a commit message after noticing a spelling mistake. Or perhaps you need to stop midway through a set of 4-6 commits to change the way some code works entirely by using the edit command. One tip that I recently started applying is to use the fixup command if a commit that you're squashing in is like one of the previous examples I gave where the commit message is just throwaway text like "delete one more file".
I hope this quick walkthrough gives you some confidence to go try your first interactive rebase, or start using them more effectively in your day to day work.
So back to the list - Numberon Pixar's list of 22 rules for good story telling says "Great stories are simple and focusedThis is where Atomic commits are really important. What are "Atomic commits"?
The word "Atom" comes from science, but it's been co-opted quite heavily in technology. Not only with regard to git commits, but also design systems. Brad Frost, the author of "Atomic Design" says atoms are the basic building blocks of all matter. So then it should follow that an “atomic” change consists of a single feature, task or bugfix.
The book Atomic Design goes on to describe how the rest of a design system is comprised of molecules (which are made up of many atoms) and then organisms (which are made up of many molecules). In this same pattern of thinking, software applications are comprised of feature branches which typically are brought into a codebase through a pull request (the combination of which can be thought of as a molecule) and then as those features build we create the entirety of the app which is the organism.
Functionally, that means you wouldn't have a single git commit that does too much at once. For example you wouldn't want a single commit to add a new field to a database to deal with middle names of people, then adds some UI changes to better represent the dollars of a purchase price, along with a query optimization to better list the locations of a store. Each of those things would be in their own commits, and ideally in their own pull requests since they aren't very related to each other.
If you want to learn more about how to wield Git, there's a great free online book called "Pro Git" which will be linked in the show notes.
Alright, on to some more general ways that working on telling better stories can help us with crafting better software. Number 7 on Pixar's list is: Come up with your ending before you figure out your middle.
- This aligns with one of Headway's guiding principles of: Outcomes not features
- It's important that everything we create in our applications has a purpose and isn't just fluffy feature work that users aren't asking for.
- Once we know what the feature ultimately has to do, we can fill in the middle part of it much more easily. This aligns well with behavior driven development or "BDD", sometimes simplified as "outside in" development, and often leads to a much more user-centered approach to creating software.
Number 8 is finish your story, let go even if it’s not perfect. In an ideal world you have both, but move on. Do better next time.It's said that The perfect is the enemy of the good. Which is important to realize, because it's hard to iterate on a feature with your team, your stakeholders, or end-users, if they don't have a starting point to begin testing what you've built.
Reid Hoffman, the founder of LinkedIn says if you are not embarrassed by the first version of your product, you’ve launched too late.
Number 9 - When you’re stuck, make a list of what WOULDN’T happen next. Lots of times the material to get you unstuck will show up. We use a technique with the applications that we build for clients called MoSCoW analysis. This stands for:
- Must Have
- Should Have
- Could Have
- Won't Have
It's an important way to help keep our team focused on driving to the proper outcomes and getting the scope discussion front and center with our clients right away. Defining what a product won't have is just as important as what it will. You can read more about this in a blog post on our site if you just google "Headway MoSCoW" it's under a blog called "Prioritizing Your Product Features with the MoSCoW Method".
Number 11, putting it on paper, lets you start fixing it. You might have a perfect idea, but if it stays in your head, you’ll never share it with anyone.
This is similar to #8 which is "finish your story", but #11 "putting it on paper" focuses a bit more on the getting started piece, which can be very difficult to do. Test Driven Development, or TDD, is a great way to get the ball rolling when building a new feature. The red, green, refactor cycle is a great positive feedback loop that helps keep momentum going.
But when you don't know how to write the code to make a thing work, knowing how to write the test for that code in a TDD context is tricky. In those cases getting started with a spike is a great way to explore new code or ways to do things.
A spike, if you're not familiar, is just experimenting with code to get your arms around the problem you're trying to solve. The best practice is to learn from a spike so you can better estimate the "real effort" of building the feature properly with tests and potentially different implementation. And then throw the spike away and go back and build it from scratch.
The best part of working to get things "on paper" early is that with interactive rebasing (which we covered earlier) you can rewrite the story after you know where it's going, so don't hesitate to commit early and often. You can edit your rough draft down later.
Number 14 - The final item we'll cover of Pixar's 22 rules for good storytelling is...
Why must you tell THIS story? What’s the belief burning within you that your story feeds off of? That’s the heart of it. I saw a presentation last year by Simon Sinek where he was talking about "the why" of starting a business. Simon is the author of a great book called "Start with Why" - and his concept also applies to pull requests. He says over 90% of new businesses fail within the first 3 years. So what was so important, so powerful, the solution missing that others really needed to know about, that was worth risking overwhelming odds of failure...that it was worth doing. Those origin stories are very important.
Chris Beam's blog post "7 rules for creating good commit messages", which we discussed earlier, shares that same idea in number 7, which says "use the body to explain what and why versus how". Obviously the "how" is pretty evident if you read the code, so the commit message itself, is way more important in figuring out why a given line exists. The answer could be as simple as a stakeholder saying "this button needs to be blue". Or it could be that a complex regulation added of 2019 means that new home loans including a co-borrower have to be processed in a certain way that requires we have separate signatures for each signer. If someone looks at that co-borrower code a year from now, the capturing of that why of the new signature field existing is really important in telling the story of how that code came to be.
At this point we've covered some of the ways that writing code, testing, and creating git commits or pull request descriptions are similar to telling a story. But what other aspects of software development apply?
Storytelling is important in README files, in test plans, and other forms developer communication like API docs. And it's especially important when rolling out new features and functionality to stakeholders or end-users. How many times have you updated a desktop or mobile app where the changelog just says "bug fixes". I think we can do better than that.
So to summarize -today we discussed:
- What storytelling is and how it's an important aspect of developer communication through pull requests and commit messages.
- Having a consistent story spine to describe the changes your making in a pull request
- The 7 rules for creating good commit messages
- Some strategies for editing the story of your code using a git interactive rebase
- How to group similar things into atomic commits
- That it's important to define the ending or outcome of your feature before starting
- If you're stuck make a list of what wouldn't happen next
- Put the ideas down on paper and refine it later
- And finally how important the WHY of your code is to future developers
- Does your team have a consistent and shared pull request experience that enhances your throughput and effectiveness? If not, take a look at the attached pull request template that we use at Headway for a starting point.
- Have you ever wasted hours tracing down why a given line exists in a file? Or deleted a seemingly unimportant line only to realize later how critical it was?
- What aspects of story telling through code are you going to do better in the future?
Well, that wraps it up for today. If this content is helpful to you, it'd mean a lot to me if you could rate and review the podcast on iTunes or wherever you get your podcasts from so we can help share this info with even more people. Until next time, I'm your host, Jon Kinney and this is the Even Keeled podcast.