These days it’s very rare to see a project that’s not using source control, as it’s regarded as an essential part of development. On the Apple platform, for example, the source code editor actually defaults to starting a project in a git repository (the industry standard for source control).
One of the main benefits most synonymous with source control is the ability to roll back your source code to a previous version. I, however, have found this to be the least used feature. The power of source control runs far deeper and, as such, I’m often surprised when I talk to industry peers who say they’re using source control but aren’t really getting any benefit from it. This, in my opinion, is because they don’t use the more advanced and, I believe, more important features. Source control, used in this context, becomes nothing more than a slightly advanced ‘backup’ for when your laptop catches fire!
If you’re using SC but don’t REBASE, use FEATURE and RELEASE branches and don’t HOTFIX, you aren’t tapping into its potential. In order to quantify the full power of source control, we have to work backwards from the product release and ask what is needed to ensure optimal quality. In this post, I will explain why you should utilise these features and how they will help you to unlock the full power of source control and Agile release procedures.
We have to accept that the world is not perfect. Despite implementing good quality control, due to the dynamic and complex nature of the modern mobile platform, a production app will still have bugs. End of story. Even if your code has zero bugs, an operating system change or a third party library could introduce one into your app through no fault of your own.
Operating system changes are perhaps one of the most frustrating types of bug because code that tested perfectly, on lower versions of the platform, can suddenly have issues due to external changes. This means that you then have to revisit something you’ve already completed! With this issue in mind, we need a way to easily reproduce and fix these bugs efficiently. This issue is something source control and a good release procedure can help with.
A release should be easily traceable to the exact source code that makes up the build in order to easily create a build of the app with the exact code for that release. This is invaluable when trying to reproduce production bugs. Source control acts as a guide, since every commit is logged, alongside the exact changes made since the previous recorded commit, so we can roll back to the exact code that makes up any build. This may sound easy, since source control has it covered, but with larger projects and larger teams it soon becomes difficult to isolate features and bugfixes if you don’t follow a strict procedure when contributing changes to a project.
Features we release should be disconnected from features we are working on. This is essential when it comes to keeping team members informed and productive. Features may have dependencies that have delays. They may have taken longer than anticipated or the feature may even no longer be required by the business. A release, however, waits for no one and continues with any existing features that made it on board. By disconnecting features we work on from features we release, we are able to release code on a clockwork schedule (per sprint, every 2 weeks). This is where branching, one of the most powerful features of source control, can be used.
By following a branching procedure (git flow) we make it easy to pick and choose which features make up a release. We start a project with a root branch, which used to be referred to as a ‘trunk’. We call this branch the development branch (dev). Each feature has its own branch off of the development branch which contains only the code changes for that sole feature. The feature is then tested; depending on the size, it could either be developer/unit tested or have a separate QA test. It’s mostly developer tested at this stage.
Once it has passed the testing phase, we utilise another useful feature of source control – the pull request. This informs the team that the feature is ready and can be included in the next release. The pull request essentially means that the developer wants to pull the changes from their feature branch into the main development branch. This then means it’s ready to be peer-reviewed.
Before a pull request can be opened, the developer needs to make sure their work is tested with any work that their peers may have completed since they branched from dev. In order to do this, we use another advanced SC feature called rebasing. Rebasing allows the developer to take any features that their peers have completed and replay their own work on top of them. This means that each feature branch is tested on top of all existing completed work. This is a synchronization point and, as such, it can create a race to get your feature in before a peer so that you don’t have to rebase your code again. This may seem like a drawback but I’ve found that it motivates developers to remind their peers to review and it also encourages social interaction, which is essential for team morale. For example, a game of pool could decide who gets to merge first…
At this stage, there may be integration bugs or conflicts that need to be resolved. This means that no code can make it into the main dev branch without being tested with what is already there. This is hugely important because if you merged a feature branch to the development branch instantaneously, any bugs that may be present as a result of the combination of the work would be introduced to the development branch. These bugs would then trickle down into each new feature risking a lot of time and effort. The bugs would only have the release QA as a last line of defence, meaning that they’re more likely to slip into a release. These kinds of bugs are usually referred to as ‘merge bugs’ by developers.
By rebasing and testing their own branch on top of all existing work, a developer is able to be more mindful of their peers and the overall build. In following this strategy, everyone can be sure that they’re starting from a stable base each time they start a new feature.
Since the feature branch contains only the changes for that specific feature, and has been thoroughly tested with existing features, code reviews are shorter and there should be less of a need to download the code and run the app as part of the review. This speeds up the review process, affording us the ability to have multiple team members review as much as possible. Here at We Are Mobile First, reviewers are not limited to their specific languages. For example, Swift developers can review Java pull requests, etc.
We use Bitbucket for this stage, as it provides an excellent interface to view the changes on the web without the need to download the changes to your local machine. Once the pull request is approved, normally by at least one team member, it can be safely merged to the dev branch.
Towards the end of the sprint, normally at the 75% mark, we create a new release branch from the current dev branch. This has been referred to as a ‘code freeze’ in the past and it means that only the features that have made it to dev will be included in the next release. This is where most of the QA work is completed. No feature work can happen on the release branch – only bugfixes.
Bugfixing the current release takes priority but this can be a back and forth process between developer and QA. Developers can continue their feature work in their feature branch whilst QA is testing and can then switch to bugfixing when a bug is raised. This means that the sprint can produce features that are complete but won’t be included in the release. This is essential for maintaining a clockwork release cycle.
The master branch is a branch containing only the releases. It’s a record of the exact code that has been submitted and gone on sale via the App Store. Once our release has passed QA and is ready to go live, we create a pull request in order to pull the release into the master branch.
This pull request allows us to have one final look at all of the changes that there have been since the last release and it’s also used to automatically generate the release notes. Each commit to source control has to include a comment from a developer describing what the commit is. We have a simple system of prefixing the comment with the ticket number from our task management system, Jira. The pull request concatenates all the comments and, thus, generates meaningful release notes that can be traced back to the tickets describing the features.
Despite all of the careful planning and rigorous QA, there’s still a possibility that an issue will arise in your production app. This can be dealt with quickly if you have a plan in place and that’s where your hotfix comes in. If you have a problem with your production app, you need to fix it as soon as possible – without making it worse by breaking something else!
Simply including a fix in the next release may be too slow. It may also contain other features which will complicate testing. If a bug is important enough, we need a system that will only introduce the fix and nothing else. The best way to do this is to branch from the master branch, creating a new hotfix release branch. A hotfix release has the same rules as a normal release branch – only bug fixes, no features. By creating a hotfix release, you are ensuring that only the exact code to fix the problem is included. It can then be quickly tested and deployed.
Firstly, developers should always QA their own work. They are the first line of defence. A bug spotted at the development stage is 100x cheaper to fix. Developer QA could be in the form of unit tests or manual tests. It can, however, be easy for developers to miss bugs in their own work. They can be too close to it. This highlights the importance of a third party QA. This could be in the form of another developer or, even better, a non-developer dedicated tester. That’s the approach we take here at We Are Mobile First.
QA can happen at three main points:
Developer QA ensures that the dev branch is stable, leaving less chance of us being greeted by a whole world of pain at the release stage.
Early QA allows the tester to get a head start on testing as features are completed.
Release QA normally includes full regression testing and it’s the final stage that makes sure the build is fit for release.
The process that I have described in this post is largely inspired by the GitFlow procedure with some small additions, such as rebasing feature branches, and using a pull request when merging to the master branch in order to generate the release notes.
Were you underestimating the power of source control and agile release procedures? If so, I hope that this post has inspired you to try them out for yourselves – you could save yourself a lot of time and energy and, in turn, release a more accurate and more efficient product.