Money is critical, and we must deliver an awesome infrastructure that enables our clients to provide the best financial experience to their customers.
To meet this requirement, we invest in quality code by making it a priority and building it into our processes. We don't like getting bogged down by technical debt and anyone in the future should be able to understand the previous technical decisions taken to build a service, this greatly increases maintenance productivity and avoid repeating past mistakes.
This means we:
Making the right decisions in terms of ensuring our code is modular, well-tested, and reusable enables the code to adapt to future needs. We emphasize quality code because it makes everyone’s life easier: Engineers have an easier time reusing code, and customers using a bug-free product.
We periodically review our code organization and data structures, and devote time to DevOps and refactoring projects. We don’t like getting bogged down by technical debt. Is this class a service or a repository? Should this method be a in a trait or in a parent class? You’ll have team members who care about answering those questions.
1 Open Positions
We intend to build a company that will last decades. Our product, funding, and market position make a long-term orientation for our engineering team not only possible, but necessary. For one thing, our product has enough fundamental complexity that adding incidental complexity by taking on too much technical debt would cripple our velocity much sooner than a company with a more straightforward, boilerplate product.
Additionally, we have a product that stands in a well-differentiated category of its own. This gives us breathing room to build durable competitive advantage by innovating, rather than constantly racing to eke out a razor-thin marginal advantage over similar competitor products. Our focus on code quality grows from deep roots in the nature of our business; it’s not just a pet preoccupation of some engineers on our team.
To this end, we embrace the usual repertoire of quality software development practices: code review, linting and static type checking, unit and integration testing, continuous integration, manual QA, deployment automation, issue tracking, and a thorough style guide that focuses on practices that improve code clarity without too much bikeshedding. These practices aren't engraved on stone tablets; we're always discussing how to do better, and ideas can be raised by anyone from our newest hire to our cofounder CTO.
At a higher level, we have a culture of circulating ideas in design documents well in advance of implementing them. When done right, a design review process does not slow down development. Good teams are perpetually bursting with ideas. If people are encouraged to share those ideas early, then features or changes can be proposed, discussed, and refined long before the team has bandwidth to implement them. Reflecting on design or implementation approaches as a team leads to greater simplicity and clarity. Building the simpler, clearer thing nearly always leads to greater development velocity, even in the relatively short term.
That said, we recognize that there are times when fast turnaround is imperative. Sometimes a defect that affects users must be fixed urgently, and the fix is clear, and in such cases we prioritize appropriately. And sometimes the best way to deliver quality in the long term is to put early iterations of a feature in contact with users so we can learn from their feedback. In these cases, we strategically deploy well-contained technical debt. (One tactic for containing debt in these situations is to ensure that decisions made in this mode are highly reversible. For example, exposing a limited beta of an Airtable block to a small audience constrains future development much less than a public UI or API in the core product.)
12 Open Positions
We strongly believe that having a quality code base is the foundation for fast iteration in a safe environment. Our commitment to quality code is not just lip service either. We have CI tests, follow a style guide, and do all of our code reviews at the design stage. Any non-trivial changes require a design engineering doc to outline and discuss how you’d approach the problem and what tradeoff you may have to make. For those who refer to the Joel Test, we are 12 out of 12 on his list. Of course, we are not 100% perfect and cut corners at times, but we always fully understand the consequences before committing to it.
Traditional wisdom would advise against a full rewrite of our code base, but GoodNotes 5 which we launched in January was actually a full rewrite. We did this because we needed to lay down a new foundation for conflict-free collaboration and cross platform development.
Find awesome products designed by independent artists.
San Francisco, CA and Melbourne, Australia
We invest in quality code by making it a priority and building it into our process, even setting aside a dedicated 25% of time for engineers to address technical concerns. At every planning meeting, we prioritize engineering work in addition to our product work. Our process emphasizes code quality through pair programming, and continuous refactoring as we add features. Throughout our interview process, we look for candidates that care about writing clear, maintainable code, and ask questions about their approach to testing and refactoring. We take pride in keeping things transparent around here with regularly scheduled Showcases with key members talking about features, releases, and QBRs (Quarterly Business Reviews).
We define syntax and pattern standards as a group, use code linters accordingly and respect our rules throughout the codebase. We pay more attention to quality than speed because we are constantly thinking about what the next engineer would think. Maintainability and developer happiness is always more valuable than short-term efficiency and self-rewarding productivity. We constantly improve our codebase quality by cleaning up old code and coming up with better ways. Every day, we apply our favorite mantra and company value: Leave it better.
We’re currently at 96% unit test coverage of our entire codebase. Testing is fundamental to our culture, and engineers are expected to not only write tests for all their code, but also manually test each feature they develop. We run lean when it comes to manual QA testers, as this isn’t scalable.
We call this workflow "pragmatic craftsmanship". Pragmatic craftsmanship means knowing when to invest vs when to ship. As a result, the codebase has engineering quality proportional to its impact. We also believe that product quality is a key differentiator for Asana. Thus we are willing to invest more to build the best experience.
Beyond our commitment to pragmatic craftsmanship, we heavily invest in new hire onboarding. This onboarding process allows us to teach developers the best patterns and practices. The process also enables developers to be valuable members of the team faster.
There’s no exact algorithm, but a guiding question Louis Bennett, our VP of Engineering, asks is, “What is the expiration date of this code? Think through the lifetime of said code to determine the amount of time spent on it.” Knowing when to invest in - quality versus speed - requires good judgement. We challenge every engineer at VSCO to identify this balance in each situation before deciding what the most appropriate course of action is.
We adhere to code guidelines and our code review process involves small cross-vertical teams. We work to make sure engineers aren’t blocked and have the standardization in place to keep quality top of mind. Our relatively small engineering org supports millions of daily active users and we take pride in producing scalable solutions that are written in clean, readable, and maintainable code.
Non-engineering teams are considerate of engineering timelines and make sure engineers have ample time to complete tasks in a thorough and methodical fashion. We also have a dedicated QA team and release process to ensure that our code quality remains high.
We don’t care just about “if it works.” It’s easy to make something work by hacking it together, but to write code that another person stepping in a year later can read and improve upon is much harder. We enforce best practices and avoid shortcuts, and our goal is to constantly think about scalability and maintainability.
When enforcing code styling we use Prettier TypeScript to keep the codebase more organized. By using more strict rules with TypeScript, it helps us maintain more consistency. We also use CI/CD to encourage small PRs and easier reviews, and we’re constantly deploying and testing our own code. We always schedule time to pay off tech debt. There’s a small amount of tech debt that you can have before it slows down your entire team/product/process, so we are always fighting against it.
It can be hard to find the perfect balance of all of these factors, but one thing we particularly care about at LightStep is good instrumentation. A high-quality code base that's easy to understand and diagnose is what makes a great work environment.
To that end, we take code reviews extremely seriously. It's an opportunity for shared learning, so we spend a non-trivial amount of time understanding each other's code. We use data and metrics to track software quality, and discuss these metrics at our weekly meeting. We also have a streamlined process for prioritizing and triaging all reported bugs; they are automatically assigned an owner so none fall through the cracks.
In our previous work as engineers at big companies like Google and Amazon, we were responsible for maintaining code quality and writing our own automated tests. This practice is something we brought to LightStep - we do not have a separate QA team, so we are accountable for the quality of the code we write, and are on-call for issues that arise in production. This motivates us to build robust monitoring, comprehensive alerts, and automated CI/CD pipelines.
Choosing how to tune the knob between expediency and elegance is one of the toughest (and most fun!) types of day-to-day decision that engineers face at a fast-paced early startup. However, we believe that this tradeoff is correctly achieved by compromising on the requirements of a problem or the generality of a solution, not in the quality of a system.
We have a high bar for robustness and have no problem prioritizing and working on highly technical tasks, or tasks that non-engineers would have difficulty understanding. We are constantly challenging each other to write better code, improve our processes, and generally improve ourselves and the codebase. We are intentional about what we build and spend lots of time up front designing solutions and choosing abstractions before we dive into a project. As a result, we have a clean codebase, have very healthy coding practices, and spend very little time fighting technical fires.
In our early days (circa 2011), we shipped code with the sole goal of meeting customers’ needs as quickly as possible. Back then, we had no choice but to ship one-week versions of features even when they had loads of technical debt. We focused on surviving to the next day and racing against declining bank balances. However, TeamUp now has the luxury of profitability. With founders and directors who code, we are deliberate about what technical debt we take on, and are always thoughtful about budgeting sufficient time to pay it off.
We still ship as quickly and as often as we can today, but we balance that with specs and research, comprehensive unit testing, internal feedback loops, and code reviews. While we’ve matured out of working out of desperation, we’ve maintained our sense of urgency. We use Github Flow and do thorough code reviews, and we’re not afraid to prototype something in code, discuss in a pull request, then throw it out to start over again.
We see our own engineering skills and processes as a WIP. We prioritize learning, refactoring, and cleaning up technical debt. We think long term about the engineer who will read our code a year from now. We’re always improving our documentation and testing skills. We want to hire engineers who prioritize those things too. We want to hire people who get excited about writing code that someone else will be delighted to read.
We refactor frequently whenever we encounter technical debt, and like to be on the bleeding edge of technologies and coding practices. It makes our engineers happier and allows us to ship faster!
Our stack consists of Koa.js, MongoDB, and Angular – because they were the hottest things a few years ago. These days, we’re playing with React, React Native, Flow.js, and Postgres – because Postgres is cool again :)
That said, we don’t just follow trends; we adopt new technologies only if it can significantly increase the quality of our code base. So while we adopt a micro-service architecture, we don’t overdo it and use our best judgement and apply the 80/20 rule to balance perfect with scrappy. Fun fact: our core algorithm is written in Common Lisp!
All code gets peer-reviewed before it goes into production. We strive for 99% test coverage on all the code we ship. This makes refactoring easier too!
We use planning and estimation to help clients understand what feature set fits within their budget, not what level of quality they can afford. We closely collaborate on user stories and make sure we have as much information about current and upcoming work. This helps us make the right design decisions that balance immediate needs with long-term maintenance.
When we program, we prioritize quality in a number of ways. We make extensive use of pair programming, code review, and test-driven development to create an environment where design is collaborative and the codebase is collectively owned. Since we have many long-term projects with clients, we spend a lot of time working on code we wrote months or even years ago. This helps us have a long-term outlook when writing features or fixing bugs instead of rushing to ship as much as possible every week.
Our technical screen is a short coding exercise and we emphasize to our candidates that completion is not a criterion for success, and we instead look for insight into a developers process: Do they ask questions if something’s ambiguous? Do they use tests? Do they document assumptions? What is their design process?
Not only is great code a joy to work with, it’s the only way to move quickly over the long-run. We take pride in our work, do thorough code reviews, and leave time every week to work on technical debt. Within the engineering team we review everything that goes into production, both to improve the quality of our code and to share knowledge between team members. We automate the checking of style so that code reviews can focus more on architecture and maintainability.
Our coding exercise during the interview process is primarily about assessing an engineer’s craft, or level of care that they put into their code. We prefer simple, clean, well-structured code over flash or speed. That said, we still value moving quickly, so we put time parameters on the exercise to give us a relative sense of what can be produced in a given amount of time. We hope to learn as much about you and your code quality during your interview as you do about us and our code quality.
Increasing code quality is a high priority for us. We are given the capacity to do things right, even if it takes longer. Everything is a trade-off: if we can spend a week fixing technical debt now to save us time in the future, then we’ll do it. We’re striving to increase test coverage across our codebase and we’ve increased coverage from 36% in November 2018 to 51% in March 2019.
By using feature flags inside of the product, we can ensure that new customers use the latest and greatest features, while leaving older customers on stable grounds without changing things. Our Customer Success team actively works to migrate customers to the latest version of features so they can be deprecated and removed from the code. Examples: hub1 -> hub2 migration; new API explorer and auto generation of SSL certificates.
As our customers often require similar functionality, we help them save money by reusing code from our existing codebase, that we call the Atlas Framework. The code that is part of our shared codebase is well written, tested, and maintained because we have many projects using it (any bugs are found and addressed quite quickly). When a customer comes to us asking for some new functionality that we don't already have in our shared codebase, we make a decision on whether to try and separate this new functionality out into something that can be reused, or to bake it into their application.
To help ensure that our code remains high quality, we always ensure that at least two developers work on each project and don't have anyone working in isolation. If a developer ever sees any issues with anything that has been written, we often see who wrote that code and go back to them with some feedback to help them write better code in the future. When we work on a software project we also factor in extensive software testing by our dedicated team of International Software Testing Quality Board (ISTQB) qualified software testers and we use our comprehensive software testing plan documents.
Article I of our Engineering Constitution is titled: “Never Compromise Quality.” We have a strong bias towards clarity over complexity, simplicity over cleverness when writing code and collaborative, constructive code reviews; from continuous integration to blue/green deploys; and a preference for care over speed when it comes to production. Our platform powers our partners’ day to day operations, and as a result, we strive to build our engineering culture on a bedrock of quality.
Engineers have wide latitude to make sweeping changes across the entire codebase to eliminate lava antipatterns, and we always attempt to fix entire classes of bugs at a time. When mistakes do occur, we practice a no-blame culture and follow the five whys during postmortem.
Want to List Your Company?
Submit a team profile!
Select 8 Values
Contact me (Lynne 👋)
Qualify Your Values
Reach Thousands of Devs
Find Value-Aligned Candidates
Have Meaningful Initial Conversations
Don't Fill Roles, Hire Teammates
You can post as many job openings as you want.