As a software development team grows and adds more members, what worked with one or two people no longer works as effectively. The team realizes they need to set up some sort of standard process for developing software. It need not be complicated, just a lightweight process will do. All they need is a way to get organized with a simple development approach. Here is what has worked well for my teams. Adapt it according to your team’s circumstances.
We begin at the point where you as a developer pick up a story from the product backlog. I’ll walk you through the steps you can expect to take, and what you can expect to happen up to when your work is merged into the develop branch. I won’t get into the sprint planning or prioritization processes; that’s a topic for another article. Neither will I talk about the process for deployment to UAT, Staging or Production; those too are topics for another day.
What are the expected outcomes?
The expected outcomes will usually be in the story specifications. Typically a business or systems analyst writes these. They are your go to people for questions to confirm your understanding of what needs to be done. This could be a bug fix, a new function point, an enhancement to an existing function, etc.
As you read the specs, you may have a better idea than what the analyst had in mind. Have a conversation with them to arrive at a consensus. Alternately, you may have a design in mind that differs from the architectural patterns your team has been using to this point. Have a chat with your architect and pitch your idea to them. The best ideas often come from those in the trenches doing the actual work.
Make a Feature Branch
Also known as a topic branch, this is a branch off of the
develop branch where you will make your changes. Name the branch something useful and recognizable. What I have seen work well is to have the ticket or story number in the branch name, along with an abbreviated form of ticket or story name. For example,
feature/ABC-123-validate-new-domains. Jira‘s integration with Bitbucket makes this very easy if they are in your development stack.
When you crack open the code and figure out what it is currently doing, you may come up with a better way to make it more readable. Here and throughout this process, don’t be afraid to make small incremental changes to improve the code. Whether this is renaming a variable to one that is more meaningful, or breaking up a large method into smaller ones, or even breaking out one or more methods into a new collaborating class, don’t be afraid to do this. As long as the existing unit tests pass, you can be confident you haven’t broken anything.
But that assumes there is good test coverage for the code you are working on. This is not always the case with legacy code that is several years old. In this case, write the tests to assert existing behaviour, then do your refactoring. Periodically run the tests to ensure you aren’t breaking anything.
All that said, the reality is you are up against time constraints. Any of us could spend days refactoring the code to make it simple and elegant to read. But at the end of the day, we need to produce working software within the constraints of scope, time and cost. Use your judgement, talk to your lead developer or architect. You may find there is only time for some of the refactoring you have in mind. That’s fine. Small incremental improvements over time are far better than none at all, thus allowing the codebase to rot.
Write the Tests First
Most of the time you can take the bug report, function specifications, etc. and write unit tests. The one big exception that comes to mind is when you are working with a new or unfamiliar design or framework. In that case you don’t know what success will look like, but you sure can recognize it when it happens. Write the tests once you gain some comfort with the new design or framework,
The whole idea behind writing tests first (Test-Driven Development or TDD) is twofold:
- It forces good design on you; and
- You write enough code to solve the problem, but no more.
How many times have you picked up some legacy code, tried to write unit tests for it, only to realize it will be nearly impossible to do so? Unit tests quickly expose bad design. They expose code that breaks the Single Responsibility Principle, code that fails to use Dependency Injection, code that abuses the Open-Closed Principle or any of the other Five Principles of Oriented Design. If you write the tests first, or at the very least think about how you’re going to test that new class, it will put you on the road to clean code.
So write a test, write the production code, run the test, and keep doing this until the test passes. Then repeat for the next flow in the use case.
Overall test coverage should, in an ideal world, be at least 80 to 85%. When it gets to that point, you can start thinking you might have enough tests.
Commit and Merge – Early and Often
Commit your code to your feature branch often, at least once a day. Similarly, merge changes from the develop branch into your feature branch just as often. This is particularly important for medium to larger teams (ones that would take two large pizzas to feed). For small teams or solo operations where you know no one else is working on the product, just commit once a day.
When composing your commit messages, include the ticket or story number at part of the text. This simplifies tracing of code changes to the ticket or story.
Automated Build of Your Feature Branch
Your Continuous Integration (CI) tool (Jenkins, Travis CI, etc.) should be configured to build your feature branch each time someone pushes a change to Git. It should also send notifications to whoever broke the build. Use this to validate your work on an on-going basis, and to ensure you aren’t introducing something that “works fine on my machine”. A CI tool is an essential component of your team’s Continuous Delivery Pipeline.
Create a Pull Request
When your changes are complete, refactoring is done, and you have sufficient test coverage, create a Pull Request (PR). A PR is a request for someone to review your work, to provide feedback, and to merge it into the develop branch. The various Git repositories – GitHub, Bitbucket, GitLab, etc – provide an easy to use user interface to create a PR.
Review the Pull Request
This is also known as a code review. While there are numerous other articles on how to do this, I’ll summarize the important points here.
The reviewer is primarily looking for:
- Do the changes achieve what the requirements call for? If it is a bug fix, do the changes fix the bug?
- Is the code clean? That is, are class and method variables appropriately named? Method bodies should occupy no more than a screen length. Good code will read like prose; the reviewer readily understand what the developer had in mind.
- Are there unit tests that assert the intended behaviour? Are the tests themselves readable? The reviewer should be able to look at the test method names and quickly understand what the test is trying to do. Similarly, the bodies of the test methods should be written such that one can readily understand what is going on?
- Your CI server should be configured so that builds also invoke a static code analysis. (There are plugins for SonarQube among others). This is extremely helpful in identifying potential bugs, reporting on code coverage, and highlighting possible security vulnerabilities. Code analysis is a very useful tool, but it’s just that, a tool. Don’t be a slave to it, use your judgement when evaluating the results.
Merge Into the develop Branch
It’s up to each team to decide who merges the feature branch into the
develop branch. Some have the reviewer do the merge, others have the do it after a successful PR review. Whatever you decide, do it for all your merges to prevent lost code. Once you merge the finished feature branch back into develop, delete it. This keeps the code base clean and removes clutter.
Here I’ve described one way help you get organized with a simple development approach. For each bug fix or feature, make a feature branch from the
develop branch. Regularly merge changes from
develop into your feature branch. When you’re done, create a PR for your team mates to review before merging into develop.