In my role as software development consultant, I am often called on to boost software development practices up to the next level. Although I’ve written checklists in the past for doing this across teams, often it’s a case of looking at the team’s current challenges, and getting a sense of which are the most impactful areas to address at the moment, rather than following a precise formula.
However, many teams have the same problems, so in this article, I’ll recall a process I went through a couple of years ago with a team that worked really hard to move forwards. The success of the transformation was a result of:
- Excellent discussion with team and management on their present needs
- Consultation with the team over potential solutions, making the most of what they had
- Fitting the proposed solutions to their actual problems and aims, rather than going from a textbook answer
- Constructing and sharing concrete solutions, working through issues with them
- The team adopting and adapting to the new challenges in a positive way
This required a combination of consulting, coaching, training and execution. Missing out on any of those would make it a harder transformation.
The surface area of the problem included:
- Source control
- DevOps practices
- Test automation
- Technical architecture – i.e. how the code achieves the solution architecture
- Plain old coding
The above was also the approximate order of approaching the layers of the problem.
I’ve written before how a linear git strategy makes life easier for a variety of things. What’s most important when setting a team on an even keel is that everyone is doing the same thing with their repos and have achieved agreement on what that is and why.
It’s my experience that some team members struggle with the various moves for git. This is worth persisting with until everyone gets into the groove, and I’ve been the git doctor for a few teams now. It eventually gets easy, and is worthwhile.
The key thing here is that you can’t trust your DevOps pipeline to deliver to your highest environment automatically if you can’t trust what’s in a given branch of your codebase!
One might reasonably put testing before CI/CD, in that you probably don’t want automatic deployment of code that’s broken! In this particular case study, there was an amount of test automation, but not necessarily enough. By increasing the degree to which deployment was automated, we increased the priority of test automation, and the two grew in sophistication hand in hand.
Sometimes, to break the mexican stand-off between test and CD, you need to increase the pressure by deploying!
In this particular case, we started out by automatically deploying to two lower-level environments, with a manual gate to get from the higher one into production. Longer-term that manual gate was turned into an automated gate.
Loosely following the test pyramid, there should be:
- Tons of low level unit tests
- Some amount of tests on a per-module basis that weaves things together to see they work in practice
- A few end-to-end tests to show everything wires up in a real environment
Emphasis on the loosely here. Precisely how to apply the above depends on the technology, environment, problem etc. The key thing is to put the right tests at the right tier, and find where the important gaps are and fill them.
As an end results, what we want is:
- Fail fast when something is broken
- No wishful thinking in terms of the tests – the test should practically test the code against real use cases in a realistic way
- The tests WILL stop the deployment if the deployed app CAN’T work
There are lots of ways to code things. There are a lot of frameworks and techniques that can be harnessed. To get things onto an even keel, so we can move forward quickly and cleanly:
- We should use relevant versions of software
- The techniques we use should fit the problem, not make things harder
- We should DRY out our software where necessary
- We should DRY out our test code too
- The code should allow for the scaling patterns of the solution
- Things should be unsurprising and follow conventions and standards
It’s easy for a team to copy and paste themselves into a rut, or grow an amount of technical debt that deviates from the above principals. It generally happens slowly and invisibly, until suddenly things seem sticky. Understanding root causes of this and undoing them is an important way to move forward.
It doesn’t follow there should be a grand refactoring, more a resetting of the standards, hitting the worst areas, and moving forward with a new approach.
Plain Old Coding
Coding is often the fun part. I put it last because you don’t get to enter the lego-like construction until you do the groundwork. There are lots of code-related problems to solve at every stage of the above, of course. However, let’s treat the continuous coding piece as the reward for solving those earlier problems.
The more the team gets to do continuous development, rather than the stop start of troubleshooting or release activities, the better the team has become at the full lifecycle. Until that point, it will feel bumpy.
Though I’ve obscured many of the private details of the particular client I worked with, the above describes the approximate events of my engagement with them. The problem notably changed between phases of the contract. Somewhere halfway through we’d set up pipelines and had way more focus on continuous test. Various libraries to help with coding and testing had sprung up to streamline the work.
Some of the initial proof of concept ideas for their solution became fine tuned and refined, solving some previously hidden problems with the ability to scale things. A few wrong turnings were gently turned back.
By the end, the process of refining the services had become a cookie cutter exercise, and there was a richer environment in which new features could be added. This had taken a lot of teamwork and adapting.
This process is worth considering for future teams and engagements, though each environment is unique and the precise problems need a bespoke approach.