Highly effective tips for average Software Engineers Series: Using Git

There was a debate a few months back on Twitter about 10x developers, if they really exist, what is the definition of such unicorn, or even if 10x developers are only bad developers who look good to some management.

The truth is, if we assume that "effectiveness" can be measured (whatever definition you want to put in that word), without prior assumptions, it will most likely follow a bell curve. You the reader and I the writer are mathematically most likely to be in the middle of the curve. In other words: an average developer.

$P(avg_dev) > P(10x_dev)$

If we, the average developer, want to get more effective, instead of only looking up at highly effective developers, we need to define the characteristics of an average developer. This allows us to overcome the Survivor bias.

In that case, what would be an average developer? Depending on your definition of "effective" it could be:

If you recognize yourself in one of those bullet points, congratulation, you are an average developer! (Spoiler: everybody is average, just select your metric accordingly).

That said, there are heuristics that you can follow to look less average, these tips won't make you make fewer mistakes or make you smarter per se, but following them will help you introspect those biases.

I've tried to make them as practical as possible.

Tip 1: Using git to proofread your code, twice

This is probably the most important one, doing pair-coding with junior developers, I've seen so many instances of the following:

*make a change*
git add -A
git commit -m "Fix typo"
git push
*Click on re-request review on github*

This is probably the worse thing you can do, 95% percent of the time you will look fast and swift, but in the 5 other percent, you will look like a fool in front of your colleague reviewing your pull request.

Let's break down to a more careful and methodic way to create commits.

a. Stashing changes

The first advice is to never, ever, use git add -A. With -A you don't know what you are committing, you could still have a debug statement remaining, forgot to clean up the last issue you were working on, or simply staging files you don't mean to commit.

When you create a commit, not only make sure to know exactly which file you are committing, but also make sure you have read each line before staging them. A great tool to use is -p:

# Short form
git add -p

# Long form
git add --patch

The -p parameter allows you to see each patch of a commit you are going to create. This way you know exactly which change you are going to commit, and make sure you read every single line of each patch!

In particular, before adding a patch, look carefully for:

Once you have staged your changes, don't hesitate to have a quick glance at the changes you made with:

# Changes staged for commit:
git diff --staged

# Changes not staged for commit:
git diff

Make sure that you did not forget any new file in the staging area!

b. Creating the commit

Now on to actually create the commit. Here again, I advise against using git commit -m. Even if -m is swift, it encourages the developer to write unhelpful commit messages.

Prefer to use git commit in verbose mode:

git commit -v

When creating the commit message, you will then see the full change that will be created. This serves two functions:

  1. Make sure again at a glance that you are committing what you want to commit. You don't need to read every line, did it during staging, but have a glance at the patch and trust your subconscious mind to spot any irregularity
  2. When writing a commit message, you now have under your eyes the changes you are about to commit. This is a great way to write a relevant commit message.

There are already a ton of articles on how to write a great commit message, so I won't spend too much time on that. Basically, those good ideas can be summarized to:

When creating the commit message, especially for trivial changes, resist the urge to set an irrelevant commit title like:

You don't need to have a long commit message with a body each time, especially if the changes are trivial. But try to force yourself to always write a relevant commit message. This might take 5 seconds more than simply writing fix, but this will save countless times when another team-mate will find the source of a bug in a tree of 10 000 commits.

Here are examples of better commit titles for trivial changes:

TODO: add links to relevant pieces of advice on creating good commit messages

c. Pushing the changes

Once your commit is created you can now push your changes with git push or git push -u origin <branch-name> if you are creating a new branch.

If you need to push force (after a git commit -v --amend or a git rebase) prefer git push --force-with-lease over git push --force.

The difference being if the remote branch is different from your local one, git won't override it. This is a great failsafe to avoid overwriting one of your colleagues' work.

d. Creating the Pull Request

If your team is using Github when creating the Pull Request don't hesitate to give a last glance at the diff, seeing your change in another context than your text editor is a great way to catch last minutes typos.

Using git hooks

Depending on which language you are developing in, the compiler might not be bundled with static analysis that could catch common pitfalls. For those languages, you might already rely on a linter on top of your test suite.

Linters not only serve the role of catching code errors or style violations, but they are also a great source of information for learning to write idiomatic code.

This article is not about linters (that a topic for another day), however, integrating linting and testing to your git workflow allows you to catch linter errors rather sooner.

Git has a feature called hooks that allows a developer to do actions based on different events. Those events could be creating a new commit, pulling or pushing some changes, etc...

For example, to run your lint suite before each commit:

cat << EOF > .git/hooks/pre-commit
#!/usr/bin/env bash

exec make lint

chmod +x .git/hooks/pre-commit

With this hook, from now on your lint suite will be run each time you create a new commit.

There are some cases when you don't want to run the lint suite. This can be if you know it will fail, or only want to create a temporary commit. You can skip the hook by adding -n to git commit:

git commit -v -n

If your test suite is fast enough, you can also add it to a git pre-commit hook.

You don't need to type that much

You might have noticed, all those pieces of advice are here to slow you down and try to give your system 2 more chances to engage. Most mistakes we do are because we are overconfidently relying on our system 1, however, we are only human.

That said, you need to slow down your mind, but not necessarily your fingers. And to avoid that and help build these habits I suggest you create aliases for all those git commands. Here is an excerpt of my shell configuration for example:

# ~/.bashrc

alias gs="git status"
alias gd="git diff"
alias gds="git diff --staged"

alias ga="git add --patch"
alias gc="git commit --verbose"

alias gp="git push"
alias gpf="git push --force-with-lease"

# Push a new branch:
alias gpu="git push --set-upstream origin \`git rev-parse --abbrev-ref HEAD\`"

Note the use of long-form parameters in scripts, this is a great way to self-document each parameter.

Tip 2: Execute every line of code that will be pushed

Even if code should be written for humans first, the ultimate juge if a piece of code is correct or incorrect is a machine. Proof-reading a code a thousand time, by a thousand human eyes won't change the fact can't prove that the code is correct.

The smaller the change, the more likely you will think it is a trivial, and the more likely you'll do a typo or forget a trivial case.

Tip 3: Invert your conditions, or: don't jump on the happy path, fix the error path first

Tip 4: Write down everything

Spend 5 minutes at the end of your day, before you close your work laptop to write down at least a sentence on what you worked today.

Tip 5: You don't understand TDD

New feature? bug fix? use the Red, Green, Blue technique:

  1. Red: Create a failing test case
  2. Green: Fix it as simple and quickly as possible
  3. Blue: Once the code works re-read your code, think of what no longer makes sense, and refactor what is necessary

Tip 6: Refactoring: the rule of 3

Tip 7: Spend some time on your tools, but not too much

Tip 8: Communicate, communicate, communicate