Technical debt is a curious issue and a plague to say the least. Here are some tips to automatically keep it out of your code.
The problem
Wanting to finish our code quickly, for whatever reason, sometimes leads us to take on some technical debt. This could mean badly designed code, bandage solutions or even causes for an unnecessary amount of manual intervention.
Once technical debt is in the code, we will rarely take the time to pay it back. We'll happily leave dirt hacks in the code as long as they make the difference between a working system and a non-working system.
I'm not going to go into the causes of technical debt. Both human and technical factors could be at the root of the problem, but I want to focus on some solutions. These are some ways to pick the low hanging fruit.
Git hooks
We can put some systems in place to automatically help us avoid technical debt. It's important that these systems enforce discipline automatically and don't require manual initiation.
Git hooks allow us to run arbitrary code at specific points in the version control process. In any git repository, we can find a .git
repository with a directory of hooks:
.git ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── prepare-commit-msg.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ └── update.sample
Specifically I want to show what you can do with the pre-commit
and pre-push
hooks.
Style
As a warmup, you can implement automatic code formatting enforcement. You will need a command-line too that takes care of the formatting. Either a code formatter or a code style suggestion tool (like hlint
) will do.
Let's assume the tool is called linter
. It should have an exit code other than 0
if there are any warnings or style errors.
We put it in .git/pre-commit
:
set -e # Exit on first error linter
Now, any time we run git commit
, Git will call linter
and prevent you from committing if there are any errors. Of course you can also circumvent this by adding --no-verify
to the git command.
Recommended checks
Now that we've added our first git hook, we're ready to start discussing what other checks should be enforced automatically. This discussion should ideally involve all the project's contributors.
In my opinion a number of code health checks are important. These include checking that files don't contain trailing whitespace, that code is indented correctly, that the linter doesn't produce any warnings. Feel free to add anything you can come up with. Use the tools that are at hand for the appropriate programming language.
I also recommend checking that all the code still compiles without problems, that all of the tests still pass and maybe even that benchmarks haven't gotten worse. Add checks for all predicates that you would like to be invariant under development.
Installing the hooks.
Even if you add the git hook scripts to your repository, you cannot force contributors to install them. This is a security feature of git. You can, however, make it easier for your contributors to install the hooks so that they don't have to copy scripts around. Just put your hook scripts in a scripts
directory in your git repository and add this ħooks.sus
file to the top level directory:
card hooks { into .git/hooks outof scripts pre_commit_test.sh -> pre-commit pre_push_test.sh -> pre-push }
Then add a line to the README
that tells contributors to run spark deploy hooks.sus
before contributing.
Helpers
I have been using these git hooks in some of my personal projects and have made helper functions. Feel free to use any that you like:
Colors:
ESC_SEQ="\x1b["
COL_RESET=$ESC_SEQ"39;49;00m"
COL_RED=$ESC_SEQ"31;11m"
COL_GREEN=$ESC_SEQ"32;11m"
COL_YELLOW=$ESC_SEQ"33;11m"
COL_BLUE=$ESC_SEQ"34;11m"
COL_MAGENTA=$ESC_SEQ"35;11m"
COL_CYAN=$ESC_SEQ"36;11m"
print_colored_text () {
local color="$1"
local text="$2"
local color_code="COL_$color"
echo -n -e "${!color_code}${text}${COL_RESET}"
}
A 'higher order' checker function:
check () {
name="$1"
shift
command="$*"
echo -n "Check: ${name}... "
OUT="/tmp/out.txt"
ERR="/tmp/err.txt"
${command} > ${OUT} 2> ${ERR}
if [[ "$?" == "0" ]]; then
print_colored_text GREEN "Success\n"
else
print_colored_text RED "Failure\n"
echo ${command}
cat ${OUT}
cat ${ERR}
return -1
fi
}
With it, you can rewrite the above linter example as follows and get some nice output.
set -e # Exit on first error
check "Code format" linter
$ git commit Check: Code format... Success