Engineering Principles at Glean
Our Engineering team follows five key principles. Read on to learn what they are...4 min read Published: 18 Apr 2023
At Glean, the Engineering team follows five key software engineering principles. We pride ourselves on following these principles, we work to ensure that they aren’t lip service. While this isn't an exhaustive list of all the things we value and practice, we feel these are especially important.
- Incremental Changes
At Glean, we care deeply about making sure our code is well tested. This unlocks a whole suite of benefits: we can practise continuous delivery, limit the need for out-of-hours production support, and can move fast without breaking things. Releasing our software is super low stress - a routine occurrence that happens multiple times a day.
We seek to maximise the value we get out of our automated tests. We want tests that provide a high level of confidence that a feature still works, but they should also execute quickly and without flakes. We can't always have both – when we can't, we'll try and strike a balance at different levels using the Testing Pyramid.
Developers typically author the end-to-end tests, and also ensure that manual exploratory testing is well supported (for example, by adding options in the debug menu, or "test helper" endpoints, to arrange test scenarios).
The greatest challenge in software development is managing complexity. Some complexity in software is inherent to the problem being tackled - any solution would have to deal with it in some form. But other complexity is "accidental complexity" - complexity introduced by the choices, the frameworks and libraries we use, the patterns and architectures we adopt, and the evolution of the system over time.
As complexity increases, it becomes harder to hold a mental model of the system in our heads, more challenging to understand any part of the system, and reduces our ability to predict all the consequences of a change. We start to go slower, and seemingly simple changes start to take longer.
There are a number of tactics that can help. Focusing on building "the simplest thing that could possibly work", rather than anticipating future needs that may or may not transpire (sometimes expressed as YAGNI - "you ain't gonna need it"). Good modularity is important - can we understand any class or function more-or-less in isolation, or do we have to reason about dozens of other pieces of code for it to start to make sense? Can we collaborate with Product on keeping product requirements simple - avoiding lots of intricate variations and edge cases? We also fight for time to address "technical debt", in order to keep our velocity high.
There are many good ideas, methodologies and best practices to draw from in software, and sometimes people can become ardent advocates for a particular way of doing things. At Glean, we realise everything is context-dependent - there are no absolute rules, just rules of thumb. We are opinionated, but not dogmatic - for example, we like immutability and default to that where possible, but we sometimes use mutation where there's good reason to. Every choice has advantages and disadvantages, and our job is to weigh them and pick a good tradeoff for any particular situation.
"Pragmatism" can sometimes be used as a justification to make unhealthy compromises - for example, taking on a large amount of technical debt in order to deliver a feature slightly faster. Instead, we seek to balance pragmatism with aspirations for technical excellence. If we want X, but X would be too hard or take too long, maybe we settle for Y - but at least we had a go at getting X.
We are willing to learn and change our mind, and are eager to explore new ideas - but maybe letting early adopters iron out the kinks first!
We make frequent small changes to our applications, and have these released into production as soon as we can (typically as soon as a pull request is merged). A small change is easy to code review and to explore with exploratory testing, and makes it simpler to identify the cause should a problem appear in production. It spreads out the risk of change, so we're never nervous about a "big bang" release to production.
A similar philosophy applies to how we deliver features - if we can deliver a basic feature quickly, users can begin to get value and we can learn from feedback about how best to improve it. We should always ask, what's the minimal viable version of this feature ("MVP") that we can deliver initially? In some cases, we can deliver features incrementally to users just by changing the app in small steps as we go, but sometimes it makes sense to use feature toggles to hide a new capability until it's all ready to be released. Either approach lets us break up a change into a series of small PRs.
We invest in ways in which we can work more efficiently, to try and use technology to our advantage and reduce cognitive load. We have an awesome Engineering Effectiveness squad that has a special focus on this, but it's everyone's responsibility to help out.
It's all too easy to end up spending time on "wasted" work - for example, spending a day struggling to get a local developer environment working correctly. It's not valuable to the business, and not interesting or fun for us either! We can't claim to eliminate these kinds of problems, but we do want to minimise them where we can. For example, we have tools to help set up and maintain the software stack that we need for development (Daktari), as well as scripts to diagnose problems with local environments. We share IDE and editor configuration via Git, and provide run configurations for common tasks.
Can we use tools to automate boring things for us, or help remember things we might otherwise forget? For example, if there's some trap or code smell that we want to avoid - can we get a linter to flag it? We use formatters to automatically format our code so we don't have to think about it (or get into debates about whitespace, because it's not all that interesting really!). We like type systems, because they document data shapes and can rule out potential bugs.
We also try to track useful developer metrics, with the aim of improving life for everyone - for example, are our CI builds getting slower over time? If so, we might need to spend a bit of time speeding it up. We might track progress on adopting a new code pattern, so we can ensure we eventually finish the migration.
More from Tech BlogView All
Exploratory Testing at Glean
Zaryoon shares what Exploratory Testing is, and how it can be implemented in your tech teams to gain a deeper understanding of how your features work.
Thinking Fast & Slow
Lynn shares how the Engineering team are applying the principles of "Thinking Fast & Slow" to their everyday work!
Maximising Engineer Value
Jonathan shares some practical tips on how you can maximise your Engineering teams value.