We work in iterations that are either 1 or 2 weeks long (depending on the client and project at hand, either iteration length might be more suited; we choose one when beginning a project and will adapt later on if necessary). Iterations are a team effort and we plan and execute them collaboratively.
Our project teams are generally flat teams without hierarchies. For any given iteration, one of the team members will take on the role of "Iteration Lead" who is responsible for planning the iteration and ensuring smooth execution. This is a rotating role so that every member of the team will assume it every once in a while (unless they opt out).
The main responsibility of the iteration lead is to consult with the product experts (and all other relevant project stakeholders like marketing etc.) and prepare the iteration. Once the iteration started, any changes to it that are requested go through the iteration lead for assessment (who might consult with other project stakeholders for priorization).
The goal of the iteration preparation phase is to define the tasks that are most relevant to be worked on during the next iteration. These issues will then be presented to the team in the planning meeting that kicks the iteration off. In order to prepare these issues, the iteration lead synchronizes with the product experts and other project stakeholders to:
Well-prepared issues are a key element of an effective process. They provide guidance for the project team's work, allow external parties not involved with the project directly to get an understanding of what is happening, and can serve as future reference to understand what was done in a project, and for which reasons.
Good issues aim to:
If a particular task is associated with too many open questions or uncertainties to be converted into a well-prepared issue, it is better to plan a spike first in order to resolve these open questions. Spikes should have:
The result of the iteration preparation phase is a prioritized list of well-prepared issues and spikes. This list of issues will then be presented to the team in the planning meeting.
The iteration planning meeting is a joint meeting with all of the project team, the product experts and other stakeholders that are involved in the project. During the meeting, the iteration lead presents each issue to the project team so that everyone has a good understanding of what each issue is about and gets a chance to ask questions and/or raise any points that might have been overlooked in the iteration preparation phase.
In the end of the planning meeting, the team collaboratively decides whether it thinks it can reasonably work on and complete all of the issues that have been presented to them in the planning meeting (plus past issues that are potentially moved over from the previous iteration). If the team considers the presented issues to be too much for the iteration, they collaboratively decide which ones are moved to a later iteration to be considered again at a later point. If any of the issues are found not to be ready to be worked on (e.g. because dependencies of the issue not being ready), the issue is moved to a later iteration as well.
The iteration, once planned, is not a binding agreement. It is still possible for all project stakeholders to react to changes to features or priorities and the project team cannot guarantee all planned issues to be completed by iteration end as new challenges might only be uncovered once work on an issue starts. The iteration plan is merely a snapshot of feature requests and priorities at the time it is made as well as a best-effort estimate by the project team of which issues it thinks it can complete within the iteration. Ideally though, an iteration remains unchanged once it has been planned to enable smooth execution which also leads to increasingly predictable estimates as a project progresses.
After the iteration has been planned, execution starts and the planned issues are being worked on based on descending priorities. For non-trivial issues, the first step is often to plan the implementation and necessary code changes by breaking the issue down into small steps. This can be done by two team members in a pairing session. As an issue is started to be worked on, the respective team member(s) will self-assign it. Issues are only assigned once work on them is actually started. Once an issue is closed via a pull requests or if it is blocked and cannot progress, the team member(s) will self-assign another issue from the iteration backlog. We recommend releasing changes to production (or at least a staging) system as they are completed.
If there are any changes requested to the iteration after the planning meeting (e.g. due to unforeseeable changes to features or severe bugs popping up in production), all of these potential changes go through to iteration lead for assessment. The iteration lead might consult with the product experts or other project stakeholders to determine validity and priority of the incoming request. If an issue is considered necessary to be added to the iteration after the planning meeting, it will be added but another issue might have to be removed from the iteration for it.
If an issue is blocked and cannot progress, the iteration lead is responsible to try and solve the impediment, potentially synchronizing with the product experts or other project stakeholders that can help resolve the situation. Likewise, if all issues in an iteration are completed early and there is no more work left to do, the iteration lead will synchronize with the project stakeholders and the iteration lead of the following iteration to discuss which issues should be added. Often that will mean moving issues from the following iteration into the current one.
Our engineering workflow is lean and based on established practices from the open source community. It supports distributed project teams and asynchronous communication and does not rely on any particular tools.
We work in 1 or 2 week long iterations and break all change requests down into issues. A list of well-prepared and well-understood issues constitutes each iteration.
As an issue is started to be worked on, the respective engineer(s) will self-assign it (not all issue trackers allow assigning issues to more than one person at once so if multiple engineers collaborate on an issue, they might have to choose one to assign it to). Issues are only assigned once work on them is actually starts - we do no pre-assign issues during planning or after that to avoid situations where already assigned but not yet started issues are blocked for everyone else to work on.
Once an issue is resolved via a pull request or if it is blocked, the engineer(s) will self-assign another issue from the iteration backlog. If an issue is blocked and cannot progress, the engineers working on it contact the iteration lead who - in collaboration with whomever necessary - tries to resolve the impediment.
All discussions around an issue should happen on the particular issue's page. Of course at times it is convenient to have discussions in person or over online chat but even in those cases, a brief summary of the discussed points and the outcome should be posted on the issue. This is a necessity for distributed teams and allows everyone access to all of the context of a particular issue at any time. Even teams that are not distributed benefit from this practice as all information that is relevant to a particular issue is and remains available for everyone interested.
All changes to a project are done in branches. Nothing should generally be committed to the master branch (or whatever the project's main branch is) directly. There should generally be at least one branch per issue - for larger issues it often makes sense to split separate steps into separate branches and merge them one after another.
All changes in a branch should also be related to the same "topic" - e.g one branch should not address more than one issue or change entirely unrelated aspects of the application.
If the testing setup, hosting environment and potentially other requirements present for the delivered product allow it, we recommend setting up continuous deployment so changes get deployed to production as the respective pull request gets merged. If that is not possible, we recommend setting up continuous deployment for a staging system at least so all project stakeholders can follow the project's progress.
Just as all changes in a branch should be related to the same "topic", all changes within a single commit should be related to the same step in implementing that topic. Each commit should only do one "thing", ideally not touching on too many different parts of the code base. All commits should also have good commit messages that make clear what the particular commit does.
Branches are not merged back to the master branch directly but via pull requests (or whatever mechanism the tool used in the particular project provides). The pull request should have a meaningful description with a rough overview of the changes included in it and the issue it refers to. If the pull request closes an issue, the pull request's description should contain a comment like "closes #" - most tools will then automatically close the referenced issue once the pull request is merged.
As with issues, all discussions around a particular pull request should happen on the pull request's page. If discussions happen in person or over online chat, a summary should be posted to the pull request so all information and context is accessible to everyone interested at any time.
It is perfectly fine to create pull requests early on while implementation is still ongoing and they are not yet ready to be reviewed or merged. Doing so is a good way to get early feedback and share the status of something with the rest of the team. Such pull requests should be marked as "Work in progress" though, usually be prefixing their title with WIP:. Some tools will even block "Work in progress" pull requests from being merged.
Pull requests are reviewed before they get merged back into the project's main branch; pull requests that have not been reviewed should usually not get merged. In order for a pull request to be ready for review, though, it has to meet some pre-requisites:
When a pull request is ready for review, its author should actively ask for another team member to review - ideally via the tools used in the particular project if those support it or over online chat etc. if not. Everyone asked for review should reply in a timely manner - even if it's to ask for someone else to be chosen if they do not have the time to do a proper review.
Once the reviewer approved the changes and CI passes, the pull request can be merged by any team member including the pull request's author. If the original reviewer would like a second review by another team member, potentially one more familiar with the aspects of the application that are changed by the particular pull request, they will ask for it. In case anything comes up in the review that cannot be resolved between the reviewer and the author of the pull request, a third person should be brought in to resolve the deadlock.
Reviewing and potentially criticizing other people's work is a sensitive issue which is why we follow a set of rules when doing so:
Testing is an integral part of our work and a necessity for delivering high quality results that also do not deteriorate over time. We generally do not merge untested changes and would usually not even review them as we cannot know whether the code under review actually works for all relevant scenarios.
While different languages and frameworks provide different testing mechanisms, a good approach generally is
We require continuous integration to be set up in all projects we work on. While it should always be possible to run tests locally, they also need to run automatically for every pull requests and after each merge to the project's main branch.
Refactoring is an essential part of any software project. As requirements change and frameworks and languages progress, code written in the past will eventually not be ideal anymore in the present and future. Constant refactoring ensures the code base does not become stale and improves productivity overall by keeping technical debt at a minimum and avoiding big, painful and risky rewrites that otherwise often become necessary down the line.
When working on the code base, we will keep an eye open for parts that need to be refactored and either do so immediately in case of simple changes, or bring them up as individual issues for one of the next iterations.
Communication is key for a successful project team - be it distributed or not. In order for communication to be beneficial for both the team culture as well as productivity, rather than a liability or cause of constant stress, all team members needs to keep some basic rules in mind:
Pairing is a great way of spreading knowledge throughout the team, on-boarding new team members or resolving blockers. We encourage everyone to pair and will also pair with our clients' internal engineers to help them level up their experience.