Code Quality Tools and CI For Solo Developers
These days, any serious software project should have a Continuous Integration (CI) pipeline setup, which will automatically test various aspects of the application and report any issues automatically.
Many teams also take this a step further, and utilise Continuous Delivery (CD), to automatically deploy each build to production. Normally this would happen as a final stage in the CI pipeline, but only assuming ALL of the automated tests have passed.
I won’t go into more detail about CI or CD here, as much has been written about it already.
What I would like to talk about here is CI/CD for solo developers, which I believe are a rather under-represented segment of the developer population.
In fact the vast majority of articles, discussions and advice seems to stem from people working in, or referring to, a team or multi-team environment.
I have discovered the hard way that taking much of this advice does not provide the correct value if in fact you are a solo developer working on a project.
So let’s discuss adapting the mainstream advice on CI/CD for use by solo developers.
Typical CI Configuration
Most discussion of CI will talk about three general stages:
- build
- test
- deploy
These are pretty high level groupings - your CI may have more stages, or sub-stages or whatever. The one we will consider today is the test
stage.
The test
stage is essentially the nightclub bouncer of an exclusive club, whose job is separating the riff-raff outside from the VIPs inside.
If a pull-request does not pass the tests, it’s not getting into the “production club”.
The test
stage can include a huge range of things such as:
- code linting
- unit/integration/end-to-end tests
- code coverage checks
- static analysis
- code style checks
- package security checks
- performance testing
- intrusion detection
- architecture analysis
When you work in a team environment, all of these things need to be in the CI test stage, to ensure that all code from the various development teams has passed all of these checks before being deployed into production.
The problem with this setup for solo developers is that the feedback loop is too slow.
Fail Fast with Fast Feedback
One of the key things you should look for when building workflows for your application development is fast feedback, and the behaviour of “failing fast”. In a nutshell, this means that if there’s a problem, you want to know about it as soon as possible.
Test driven development is a good example of this - the idea here is that you make a small change, run the test, if the test fails you know EXACTLY which line of code has caused the error - you get fast feedback about the problem, and your mind is still in the “zone”, so the fix is quick and easy.
The same concept applies to CI builds. If you push a PR and your CI build takes 15 minutes to report back to you that you need to make some small tweak, by that time you’ve wandered off for a coffee, started another task, made a phone call or whatever - but your mind is no longer in the zone and you have lost the context you were working in.
Change The Process For Solo Developers
To improve the situation, as a solo developer you don’t need to be so draconian about all these different test stage tasks. You don’t HAVE to run all these steps in CI. There aren’t other developers involved, and we can trust ourselves to follow our own procedures somewhat more.
Instead, we can run the checks closer to the coder!
The goal is to improve the speed of the feedback loop. You should look at your processes and examine what slows you down, and try to bring that check closer to the point where you are writing the actual code.
The coding process follows the following steps:
1) Write code and tests
2) Run the tests, get them passing
3) Commit changes to Git
4) Push to remote Git repository
5) CI build is triggered
So instead of running all these jobs in the CI build, some of them can be moved closer, to the point where you commit the code or push the code.
Git Hooks are shell scripts that can be quickly configured to run before or after various Git commands. You can utilise these to run some of your tests when you commit or push. You then get feedback from these tests without waiting for the CI.
The following is a description of where I configure each of my test tasks to run. There are some PHP specific stages here - if you use a different language then you’ll easily get the general idea.
Pre-Commit
Generally, jobs should go in the pre-commit
stage that prevent incorrect code being committed to the repository. E.g. code formatting, linters etc, as long as they can run quickly.
Order | Task | Note |
---|---|---|
1 | Code Style | |
2 | Static Analysis | Changed files only as this is much quicker |
3 | Unit Tests | |
4 | Composer Validate | Only if composer.json has changed |
5 | Composer Normalize | Only if composer.json has changed |
6 | Yaml Lint | Only if Yaml files have changed |
7 | Twig Lint | Only if Twig templates have changed |
Pre-Push
Place jobs that prevent more structural issues, and/or would be annoying in the pre-commit
stage because they are too slow into the pre-push
stage.
Order | Task | Note |
---|---|---|
1 | Unit Tests | Run first so can fail fast |
2 | Integration Tests | Run next so can fail fast-ish |
3 | Doctrine Schema Validation | |
4 | Static Analysis | Run on entire codebase - much slower but catches everything |
5 | Deptrac | Check for architecture rule violations |
6 | Composer Require Checker |
CI Build
The remaining checks should go in CI. Do not duplicate what has already happened.
Order | Task | Note |
---|---|---|
1 | Security Check | A CVE can be announced, we need to pick this up in our nightly builds so it must be in CI |
2 | Unit Tests | Run first so can fail fast |
3 | Integration Tests | Run next so can fail fast-ish |
4 | End to End Tests | Run last as they are slowest |
As you can see, any task that provides very quick feedback is moved closest to the coding process in the pre-commit
stage. For example, this means that you won’t even be pushing code that has any code style violations. This also has the benefit of only allowing correctly formatted code in your repository.
Anything that takes longer is in the pre-push
stage. You might argue that you don’t want to wait 2 minutes whilst
all these pre-push
checks take place before the code is actually pushed, but this is waaaay better than pushing it
immediately and then getting a notification from your CI server 15 minutes later that there’s a problem.
Finally, there are some checks that really must stay in CI, such as the CVE security checks - your code may not change for a month, but if you run a nightly build, as soon as a related CVE is announced, the nightly build will fail, alerting you to the issue.
Conclusion
Always spend some time analysing your workflow. Just because advice you are given says to do one thing, don’t blindly follow it but consider how to best adapt it to your needs.
Since changing my workflow to bring tasks closer to my coding, I’ve been much more productive. My CI builds now typically take less than 5 minutes (that’s build, test and deploy), and most of the time I’m not waiting for CI to fail, as I know the important checks are already passing before I even git push
.
Resources
Git hooks are simple shell scripts. You can write them manually yourself if needed. However, tooling exists to help you manage the hook tasks:
- GrumPHP - a PHP / Composer based system.
- Captain Hook - another PHP based solution.
- Pre-Commit - written in Python but with support for many languages.