The foundations of our software development culture at Gearset
The foundations of our software development culture at Gearset
This set of values is how we think about engineering at Gearset. We wrote it to crystallize our development culture and create a shared understanding of what “good” looks like to us.
When we had 5 developers working back to back and all going for tea breaks at the same time, we didn’t need this written down — it was just how we worked. As the team has grown to multiples of that, we can’t rely on that shared understanding just happening by itself any more. So, we’ve distilled the things that we think make engineering at Gearset what it is. We hope it will help existing team members to know how to make decisions and prioritise work, and help new starters know what to expect and how we work. We also hope it helps keep us accountable to our own high standards!
These values will never be perfect. As with all of the process, rules, and principles that we follow at Gearset, these values are things that we use as guiding principles in the general case, but they’re not dogma. We attempt to be pragmatic in all that we do, and that means sometimes, if it benefits us, we will temporarily break from these guidelines. We should also be open to changing them if our values change — what works for 25 engineers might not work for 100 (or 500!)
As with our company values and mentoring guide, we want to open up our engineering values to the world. If they’re good enough to hold ourselves to then they’re good enough to share.
Our values can be split roughly into three categories:
Strong priorities are only useful if we keep an eye on them. We constantly evaluate our work against our priorities, and leave ourselves in a position to pivot if and when what we’re working on isn’t the highest priority thing any more. Being able to react quickly sets us apart from the competition and enables us to make sure we’re always working on the thing that will have the highest impact.
Perfect is the enemy of good. We try to slice our work into the smallest useful chunks possible so that we can deliver value to our users as soon as possible. This lets us pivot faster, lowers the risk of each release, and gets us feedback on all aspects of what we’re building as early as possible.
Sometimes it turns out that we were going in the wrong direction and we can course-correct without wasting any time. Sometimes it turns out what we’ve already built solves the problem well enough and we can move on to something else that’s higher impact. Sometimes we don’t agree completely on an approach but the one we’ve got is good enough for now.
We don’t always know the answers and we don’t always know how best to proceed. That’s okay — but we make sure that when we pick up a piece of work, we own it. That means pushing it forwards until it comes to a conclusion, even (and especially) if that means bringing in other members of the team or company to help. It doesn’t necessarily mean doing all of the work or solving all of the problems. In fact, sometimes owning a task means identifying that it isn’t worth doing at all any more.
Processes are necessary sometimes, but sometimes they just get in our way without providing much real benefit. We aim to strip out all process except where it’s absolutely necessary and provides real value. Where it is valuable, we ask how we can make it as easy as possible for everyone involved - can we make it optional? Can we automate it?
When a particular process stops being valuable, we’re not afraid to tweak it or remove it entirely, and we value open and honest feedback about what is and isn’t working. A process that made sense for a team of 8 people 2 years ago isn’t guaranteed to make sense for a team of 20 today.
Silos don’t help anyone. We believe that when knowledge and experience are spread widely, great things happen, and that when they’re concentrated in only one or two people we’re missing opportunities. Having three people that mostly know how a thing works is preferable to having one person that knows everything about that thing (examples of a “thing” include: a feature, a chunk of code, a technology, a user, a process.)
We’re not cookie cutter humans and we can’t all work on everything equally, but we actively encourage and empower each other to pick up work in any area. We seek out opportunities to work in an area if it helps to break down a silo, and encourage others to take those opportunities too.
There are loads of ways to do anything. Sometimes we’ll settle on something that isn’t what we’d all consider as the best approach, and often we’ll all have a different idea of what the best approach is, but we strive for consistency.
The benefit of consistency is flexibility: by following established and agreed upon patterns we lower the barriers for different people to work across any area of the app (see We fight silos). It makes implementing similar features and making cross-cutting changes simpler. That isn’t to say we’re dogmatic or inflexible - we should all be open to changing our approach where there’s a clear benefit - but when that happens we all need to be bought into changing as a team.
Candid, kind feedback is at the core of everything we do. It’s important that our feedback is honest, but it’s also important that it provides solutions or alternatives, not just problems. Providing reasons why an approach isn’t working is only of real value when we have another option that we can agree on. We strive to help our teammates find those solutions and move past obstacles. As reviewers of each others’ work we always share our opinions on how we think things could be improved, or point out things that we don’t think are quite right, but we recognize that not everything can or should be improved right now.
Bugs happen all the time. Even in production. When that happens, there’s no value in blaming individuals. There is value in spending time to understand the root cause and how we, as a team, can avoid the same thing happening again. Mistakes have value as long as we can learn from them.
We don’t blindly build features. We take the time to understand what problem we’re trying to solve and why, whether that means talking to the customer that was having a problem, or investigating the root cause of a bug thoroughly to make sure we understand it. We try to understand all of the reasons that a problem happens and examine at which level we should try to fix it.
Sometimes the problem isn’t in the code — perhaps the messaging or overall approach is wrong. Solving an issue without understanding it risks improving things for one or two customers but having a negative impact on the majority, or having little impact on anyone but increasing the complexity of the product we have to maintain.
It’s often a lot easier to fix a specific issue or bug by introducing a special case than finding a root cause. Unfortunately, these only ever build up over time, and eventually all of your problems become interactions between these edge cases which are very hard to reason about. Instead, we prefer to find the real root cause and fix that.
Having a crisp collective understanding of our domain and our code is fundamental to building systems that are reliable and are still easy to change years down the line. This applies to customer issues too — see We understand the problem before we try to solve it.
As software engineers we have to make trade-offs all the time — between different approaches to a problem, between time to get a job done and coming up with the best possible solution, and between different priorities of all kinds. Sometimes we have to decide that introducing some technical debt is the most pragmatic way forward, or that we’re going to support one customer workflow and not another.
Identifying these trade-offs and making sure we’re all aware of them is key to making sure we make good decisions, create good solutions, and spend our time effectively. We won’t choose the best trade-off 100% of the time, but knowing the compromises we’ve made helps us to correct our course later.