Light Agile & continuous delivery
Agile is a development methodology. For those unfamiliar with the methodology:
“Agile is a time boxed, iterative approach to software delivery that builds software incrementally from the start of the project, instead of trying to deliver it all at once near the end.” - Jonathan Rasmusson, Agile in a Nutshell (2015)
For the more visual reader:
Traditional software development:
Agile software development:
We believe that a full Agile SCRUM based process is often too cumbersome for small, nimble teams. It makes sense when you have 30-40 people all working on a project at once, but under our method of keeping teams small, it adds more overhead than value.
A simple overview of our light form of Agile is:
- Daily 10-minute morning stand-ups
- 1 or 2 week sprints
- Continuous release cycles supported by CI
- A pull-based work backlog, with tickets brought into the iteration in a free-form manner
- Feature-centric deliverables
Beyond this we think it is best for teams to simply expand or collapse the Agile process to suit them best.
The reality of project based work is that contractually, we are often scoped to a fixed set of deliverables outlined within a fairly fixed specification. We try to make the best of this and provision a minimum feature set, milestone based delivery, and continuous delivery even well before the full specification is met.
We also try to provision for a certain level of flex in the specifications. As a project progresses, a customer will often express a desire to deviate from a specification. Sometimes, it makes sense to try and accommodate those changes if they do not impact detrimentally on the delivery timeline or any work already completed. Our focus on an Agile development approach gives customers a greater insight into their project as it progresses.
There is often a more realistic ability to perform true Agile style development with our long-term, ongoing contracts. This is where we can add the most value for customers through continuous improvement and rapid changes that arise from feedback.
Beyond the light Agile method above, continuous delivery is an important part of how we run projects.
Continuous delivery means the constant addition of tangible features or blocks of functionality to the master branch of development, such that the software could be deployed at any time (released to the public). Continuous delivery allows for continuous deployment - however they are not quite the same thing. Continuous deployment is an extension of this.
The opposite approach would be building as many features as possible and having a ‘big bang’ go live date. What could possibly go wrong with that…
As shown here, when combined with rapid prototyping, we may loop through the 5 stages of development several times for a single feature. Once this feature is a releasable state, it can be packaged up and shipped out straight away.
We encourage customers to embrace continuous delivery and to start using the software we are building as soon as possible, even if they only use a small set of the final functionality. By doing this, it helps us avoid mistakes as we continue, iterate faster, test the current application, and set everyone’s expectations about what the software does and does not do as early as possible.
Continuous delivery relies on good test coverage, good infrastructure, good team ethos, and good application architecture.
We do not always use true continuous deployment. Depending upon the customer’s preferences, we might use an ad-hoc deployment method instead. As each phase happens, we choose to deploy or not deploy.
Writing code is a subject that could have its own book. Many books have already been written on that subject. Here, we shall just cover some high-level principles we follow:
- Do the simplest thing that could possibly work.
- Follow these four simple design rules:
- Avoid duplication
- Express your intent
- Minimise the number of classes and methods
- Fulfil the test criteria
- Follow SOLID:
- Single-responsibility principle
- Open-closed principle
- Liskov substitution principle
- Interface segregation principle
- Dependency Inversion Principle
- Aim for high cohesion but low coupling
- Tell do not ask
- Do not lump things together; aim for cohesion. Group resources by their actions not by object types.
- Always consider open source (in both directions, consuming and publishing)
The top 3 books we aim for every new Purepoint employee to read are:
- The Mythical Man Month (Frederick P. Brooks Jr.)
- The Pragmatic Programmer: From Journeyman to Master (Andrew Hunt)
- Code Complete (Steve McConnell)
Code style guides
We stick to fixed style guides company wide. It helps keep code readable and generally does not present too much of a problem for programmers needing to adopt a style.. The style guides for each language are as follows:
- Ruby - Ruby style guide
- HTML - Google style guide
- SCSS - Purepoint SCSS guide
- JSON - Google style guide
- Python - PEP
- C# - TBD
- Java - Android Guidelines
- Objective-C - TBD
- Swift - Github Swift style guide
How these are enforced is set per project.
Testing is just as important as programming if you want to deliver a quality application.
There are various types of testing to consider:
- Vulnerability & penetration testing
- Exploratory testing
- Load & performance testing
- Acceptance & unit testing
- Accessibility testing
It’s not always practical to test everything all the time. Testing must be done pragmatically. In an ideal world everything would be tested all the time, but resources are limited and delivering working software is the goal. The level of test coverage depends largely on the nature of the system.
- We aim for a high level of test cover but 100% isn’t necessary. Be pragmatic.
- Use automated testing as much as possible
- Testing should be done iteratively to fit in with continuous delivery
- We take a best-effort approach to testing
We follow the best practices of Test Driven Development (TDD) and Behaviour Driven Development (BDD).
Run your tests often. Always aim ‘to add just enough to fail’.
Smoke-testing and manual testing still make up an important part of the testing process. Automating everything is not a nirvana and should be balanced with other types of testing where it makes sense.
We have a strict rule that no code hits master without being reviewed. We try to do code reviews in an asynchronous fashion - based on pull requests. This enables the reviewer flexibility to review in their own time and the writer can address concerns in their own time too. A code review that takes a couple of hours may therefore happen over the course of a couple of days. This lowers the pressure associated with code reviews that happen in real time between both parties.
Code review - Reading other people’s code:
Code reviews should focus on the follow areas:
- DB schema
- Code schema
- Fulfilment of specification
- N+1 problems
We consider code reviews to be ‘best efforts’ in that they cannot always be perfect. There is a big factor of getting ‘close enough’. They take time and require someone to walk in someone else’s shoes.
While conducting code reviews, try and keep these principles in mind:
- Review manageable blocks. If a pull request is too large ask for it to be broken up.
- Keep reviews under 1 hour. If they are going on for longer than that, refer to the first principle.
- Be positive as well as simply highlighting and catching errors - code reviews are as much a time for identifying areas of merit and praise as they are for finding problems.
- Try and follow a standard flow for each code review.
Code reviews should be used pragmatically. Not everything needs reviewing. New, complex features, and changes with wide implications should be reviewed. Lots of day-to-day nuts and bolts programming will not benefit hugely from structured code reviews.
Before engaging in refactoring, stop and ask - why am I doing this? What is the return on investment?
Cleaner or more abstracted code for the sake of it might appeal to the pure programmer in you but often makes little sense from the customer’s point of view or that of the team.
Refactoring carries a weight as well as a benefit in the form of time. Additionally, there is a consequential impact upon everyone else who might be working around that code.
In cases where refactoring makes sense, for example when large changes have happened within a system, setting up solid tests beforehand is a good idea.
Keeping the principles of SOLID (See the ‘Writing Code’ section for an explanation) and ‘Don’t Repeat Yourself’ (DRY) in mind will help. Try to avoid over-abstraction. A good rule of thumb is the rule of 3. If something is repeated 3 times then consider an abstraction, otherwise keep it simple.
Abstraction carries a similar weight to refactoring, mostly the impact on other people coming in to work on the system later.
Optimising user flows
Where possible we favour multi-variant testing over plain A/B testing. It allows for more flexibility and given enough data, provides more decisive results.
A good overview as to the advantages of multi-variant testing can be read here.
Multi-variant testing is not without its flaws, but when used correctly it can be a powerful tool.
It is important to test the ‘right things’ that we can control. Testing the effectiveness of different implementations of the same goal is a good use of multi variant testing. For example, testing 3 different approaches to a user ‘adding an item to their basket’ and recording which one results in the most successful progressions to the checkout. It is important to collect enough data to have statistical confidence in the result and to design tests in such a way that you are testing the things you mean to be testing. In the example above that would mean making sure you are not inadvertently testing which colour button converts more highly when you are really trying to test which cart format works best.
There should always be a good business case for why a test is being run.
The process of simply trying to optimize random things for the sake of it often has little ROI if it misses the larger picture of ‘is the application functioning in the best way overall’.
Git is our version control tool of choice. In the words of the Git project:
“Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.”
Git is a complex tool. So, we stick to some basic methods of working with it across all our projects:
- Use feature branches (Generally running for no more than 3-5 days)
- Master should always be in a releasable state
- Use pull requests for any reasonable size feature branch to enable code review
- Rebase on master
- Use continuous integration where possible