There’s a common misconception among new software professionals that the best engineers are the best at writing code. In other words, if we get really, really good at writing code, we’ll become really, really good software engineers. Accolades, equity, and glamorous talks at big conferences ensue.
But our job isn’t to write code. Our job is to find and solve problems that move the business forward. Writing great code is a necessary but insufficient skill for doing that job.
When software projects fail, or take 2x to 10x as long as they should to get done, it’s almost always because the implementer diligently built something that either didn’t solve the problem or didn’t function well, and they had to throw their code away and start over.
At Lob, we coach engineers to become great problem solvers by decomposing the process into three steps: Understand, Design, Build. Projects led by engineers who follow this framework involve less backtracking, are less prone to interruptions, are simpler, incur less toil and operational burden, and are more likely to succeed.
- What’s the business outcome we want?
- What’s preventing us from achieving that outcome today?
- What already exists today in our codebase or infrastructure that helps or hinders change?
Starting implementation without a solid understanding of the business problem commonly results in two outcomes:
- We frequently need to stop what you’re doing to ask others for more information. Frequent interrupts are costly; they introduce context changes, usually for more than one individual. Nathan Yergler writes about the cost of interrupts here.
- We end up building something that doesn’t solve the problem.
When PMs or project leads circulate design docs or hold project kickoff meetings, they are trying to jam as much understanding of the business problem as possible into the heads of everyone involved. The more context we can cache locally, the fewer retrievals we’re forced to make.
Engineers also need to understand the existing technical landscape to gauge how easy or hard the problem will be to solve. Have similar problems been solved before? Can existing infrastructure be reused to solve the problem? Our estimations will be orders of magnitude off without some familiarity with what’s there already.
- What are some different approaches we could use to solve the problem?
- What are the benefits, risks, and mitigations of those approaches?
I’ve got some bad news and some good news. The bad news is that as companies mature, fewer and fewer problems have an obvious solution; the easy ones have been solved already! The good news is that we enjoy a tremendous amount of freedom and choice in our approach to problem-solving, and for most of us, this elevates our craft from pure implementation to fulfilling, creative work.
Since there are usually several design options, it’s not super likely that the first one that comes to mind is the best one. By spending the time to research more than one option, we get a better sense of the trade-offs we’d be making by committing to each.
In practice, this could take the form of a whiteboarding exercise, a design doc or one-pager, or a full-blown technical design review. The latter is just an institutionalized way to ensure that reasonable alternatives are considered and the implications of each are thoroughly explored.
I’m a big fan of the Benefits, Risks, Mitigations framework described in this article on First Round Review.
Remember when your high school English teacher made you outline your essay before writing it? After the heavy lift of figuring out what you’re actually trying to say, the rest is just connecting the dots.
Writing code is the easiest part when we’ve already made the hard design decisions. It’s faster, more educational, and more fun when we’re pretty sure that what we’re trying to do is going to work. We have the headspace to think about crafting excellent domain logic, handling edge cases, and writing tests when we’re not winging difficult design decisions along the way.
Putting UDB in action
I’ve found this framework to be most useful in three situations:
- When mentoring interns and entry-level engineers
- When approaching gnarly problems that we don’t know how to solve right away
- When diagnosing why a project isn’t going well
UDB is a really simple tool for helping less experienced engineers slow down and think about the work they’re doing. Because it’s so easy to teach, we offer this framework to intern mentors and first-time engineering managers so they can help their mentees think rigorously about problem-solving. Checking in after each step of this framework gives you x-ray vision into new engineers’ reasoning. It makes it really easy to help illuminate blind spots, suggest new directions, and avoid rabbit holes early on.
Most senior engineers intuitively converge on something more or less like this framework through practice. But it’s still a useful way to break down super hairy problems into components. Open-ended projects for which there is no solution known to the engineering team are good candidates for applying UDB—especially when the proposed designs change our architecture or are otherwise hard to reverse. Being really clear about which part of the process we’re in also gives us some structure for staying on topic while collaborating with other engineers and stakeholders.
Finally, UDB comes in handy as a diagnostic when projects don’t seem to be going well. When projects are taking radically longer than expected, or when there’s significant backtracking, it’s a pretty good sign that either the core business problem isn’t well-understood by the implementing team, or that the design isn’t fully baked. Knowing how to pinpoint what’s going wrong gives managers, mentors, and technical leaders an entry point to provide help.
Lob’s engineering and data teams are poised to double in size in 2019. Making mental frameworks like UDB available to everybody helps us decentralize decision-making as we scale up, keeping it where it belongs: in the hands of the engineers who are solving problems autonomously, and the teams that support them in getting good work done.