The engineering leadership team at Carbon Five has embarked on an effort to improve our leveling criteria and bring consistency to hiring and promotion criteria across offices. As an initial step, we decided to articulate to staff internally, candidates externally, and to our current and potential future clients what our values are as an engineering organization.
I was heavily involved in composing this document, including writing the first draft. What follows is our final work, which was a joint effort from the whole engineering leadership team. While this was a group effort, I can think of no other document that better articulates the values and priorities that I bring to my work and so I am publishing it on my web site as well. My coauthors were Christian Nelson, Rob Pak, Matt Bricston, and Wil Wade.
- Build the simplest thing that works. This encourages us to avoid assumptions about future requirements, helps us deliver more quickly which tightens the feedback loop, and prevents over-engineering. We can always revisit a solution later as requirements change or evolve.
- Pick the right solution for the problem at hand. Not every situation is the same and there often isn’t a one-size-fits-all answer. We should choose solutions that match the circumstances (e.g. company and product stage, number of users, budget constraints, etc). The same feature on two different projects may be built quite differently.
- Make technical decisions with our clients in mind. We are only temporarily stewards of the code we work on; ultimately we hand off to our client and we want them to be able to build upon our work and be happy with the decisions made.
- Empathize with users and be product-minded. We do our best work when we make decisions with users in mind. Being user and product minded helps us build things that are a better fit for those who use what we create.
- Consider ethical implications. The products we create impact others and we must consider the ethical implications of what we’re building. We adopt the General Principles from the ACM Code of Ethics (section 1, pages 4-6) and should speak up if we have concerns about what we have been asked to build.
- Work as a team. We work as a team on a project, and it’s the team that is or is not successful. Each team member has an obligation to the rest of the team to be dependable and share responsibility for the success of the project, regardless of role or discipline.
- Follow the team’s process. Process helps team members coordinate with one another in predictable ways, and allows the efforts of individuals to dovetail nicely with those of others on the team.
- Collaborate across disciplines. Building a product is a cross-disciplinary exercise. Engineers, designers, product managers, and stakeholders all provide essential expertise in building a great product. We work best when we’re all working collaboratively to realize a shared product vision rather than working in silos with intermittent communication.
- Encourage collective ownership of code. While there’s a tendency for people to gravitate towards parts of the product or codebase, teams should share ownership of product and its code. Collective ownership encourages learning and reduces risk (i.e. the bus factor).
- Nurture a safe, learning environment. To learn and grow, people have to feel comfortable saying “I don’t know.” To build new and innovative products requires taking risks, such as suggesting unconventional solutions. Psychological safety is essential for both. Facilitate conversations so that everyone who wants to participate is able to, regardless of communication styles and personalities. Be supportive and respectful of your teammates, Carbon Five and client alike.
- Be flexible and open minded. Having different ideas on a team is an asset when the team can work through their differences. Listen, have an open mind about what you’re hearing, and be flexible. When necessary, disagree and commit.
- Listen first. Slow down and listen to those around you. Be certain you understand what’s being expressed and not jumping to conclusions. Ask clarifying questions when uncertain so that everyone is on the same page.
- Communicate in ways accessible to your audience. Communicating effectively with cross-functional collaborators and stakeholders is critical to the success of a product team. Jargon is often an impediment to this effective communication, especially when talking to non-technical collaborators and stakeholders.
- Prefer high bandwidth, low friction communication. Complex topics and hard conversations are handled more effectively (and efficiently) using high bandwidth communication, with multiple senses. Choose the best medium for the situation, and don’t hesitate to switch to a higher bandwidth mode if it’s not working. Written text < Call < Video Call < Face to Face
- Always look for risk, communicate it proactively, and bring potential solutions. Identifying and addressing risk early, and in an ongoing manner is critical to the success of difficult projects.
- Advocate for predictability in decision-making. Healthy teams have a shared understanding of how decisions are made. This prevents churn in the decision-making process and allows the whole team to know when a decision has been finalized.
- Sell technical decisions. Discussing technical decisions with a clear explanation of costs and trade offs will build trust and confidence. Depending on the situation and audience, this may include high-fidelity verbalization of low-level details of a programming language or high level details of architecture. Create a shared understanding of the proposed technical decisions.
- Prioritize continuously and ruthlessly. Every day we should be working on the most important things, so that over the duration of a project, we have delivered the most value.
- Deliver quickly and early. Delivering value quickly is a critical part of building trust with our clients, and building trust with our clients is critical to successful projects.
- Maintain high productivity. Productivity isn’t just about an individual’s output, it is about the team as a whole. Productivity allows us to charge a premium for our services. It also gives us capital to work with when addressing other issues that may arise on a project.
- Always be unblocking. We try to tee things up so we’re not blocked, but there will always be questions, challenges, or other things that come up and block us. It’s our responsibility to always be unblocking, so that we can deliver value even when it’s difficult. Similarly, we want to be sure we’re not blocking others on the team.
- Proactively seek what you need to succeed. Often there are questions, clarifications, or other things we need to stay productive. It’s your responsibility to seek these out to keep momentum, even when it might not feel like it should be your responsibility.
- Adapt to the circumstances. Projects have different needs and priorities, and they’re different depending on the size, organization, and personalities. It’s important to communicate about these differences and factor them in to our process and working agreements. Pragmatism over dogma.
- Shorten feedback loops. The sooner we know we are off course, the easier it is to correct and get back on course. When feedback loops are long, we spend considerably more time correcting our mistakes. That’s valuable time that could have been spent delivering more user value.
Sustainable and Maintainable
- Work at a sustainable pace. We focus on delivering the right stuff rather than the most: less but better. We work a normal work day and generally discourage working off hours. We do our best work when we’re well-rested, enthusiastic, and ready to build good products with a clear mind.
- Write code with strong consideration for other engineers. Leave code that you touch in a better place than when you started. Utilize good naming and code structure to tell an understandable story to other engineers about what your code is doing. Minimize the conceptual surface area of your codebase and avoid using overly-clever approaches to minimize the effort required for other developers to understand and modify your code.
- Use less and more-boring technology. Add libraries when the requirements justify their usage and the cost they will bring to future maintainability. Prefer convention over novelty so that new team members can understand the codebase more easily. Avoid bleeding-edge libraries and frameworks. Well-established and “boring” technology is more stable, easier to staff, and its usage less likely to be controversial.
- Write tests pragmatically. Codebases can be both under-tested and over-tested. Strive for a test suite that prevents regressions and adequately drives development, but doesn’t test beyond the point of diminishing returns. Quality over quantity.
- Manage technical debt proactively. All software incurs technical debt; it is an inherent part of the software development process. Like other kinds of debt, some technical debt can compound: becoming more difficult to pay down the longer you wait to address it. Paying down technical debt incrementally and regularly is healthier than letting it accumulate to the point where big rewrites or pausing all feature development becomes necessary.