Design principles for building great code interfaces and developer experiences
Improving product development is my current focus at Stripe. There's:
Developer experience (DX), how developers interact with software, is always top of mind for me. I've developed a few design principles about great DX that I'd like to share.
Audience analysis is really important to predictably providing value. As such it is important to tangibly talk about the insights at your disposal here.
The levels of understanding your customers:
We can roughly map these to common audiences:
The more fidelity you have, the more flexibility you have to meet people's needs. A greater fidelity also implies narrow, more situated problems where it is easier to make assumptions. For example, within a single company you can say, "I will provide data consistency guarantees using [common company database]." Whereas with open-source, more often than not interfaces must be written agnostic to choice of persistence layer.
Choosing a lens through which you will serve users is the most important aspect to designing an interface.
There is so much a developer is expected to read. It's better to design interfaces like documentation doesn't exist. Definitely write documentation, but don't use documentation as a crutch instead of investing time in making an interface more intuitive.
It's important to remember, especially as you get super invested and deep into a problem space, that no one else should be expected to think that critically. Developers at any level copy-and-paste working code and twist it to fit their use case. A good interface design acknowledges this and ensures a good outcome regardless.
Such is life. Tests have a cost just like any other piece of code. What you believe to be worth testing about using your interface won't align with people using it. Don't count on the tests of your consumers to give you confidence about your interface.
Definitely write code that can be tested for those who do. Lower the cost of testing by providing test helpers. Orient code in such a way that even if people don't write tests now, when it comes time to debug or pin functionality down they can. No matter how simple or slick an interface, if it can't be tested before production it is a nightmare.
It's okay to not provide a general purpose tool. Especially when you're serving a single company, you don't need to be thinking about your interfaces like POSIX or duck tape. Not supporting the unforeseen is not only fine, it's advantageous.
I can't count the number of times that I've prevented bad outcomes just by not supporting them.
Limiting possibilities introduces friction into other engineers' work and brings the discussion to you. While the quote may not have been Ford's, "If I had asked people what they wanted, they would have said faster horses" resonates with me strongly. Having the discussions, asking "what are you really trying to do?" gets to the heart of the problems developers are facing. Better solutions follow.
A more open interface just lets each engineer that uses it re-invent in their own bespoke way how to solve the problem at hand. These problems more often than not could have a well-defined interface and documentation around it.
This sounds like forcing user research and it very much is. To understand your customers' use cases is the best way to meet them.
There's a counterweight to providing a really great developer experience, which is you need to think about the maintainer experience as well. If a public interface comes at the cost of internal changes being very costly, either:
As the change paralysis sets in internally, external uses also inevitably become instant tech debt that will need to be ported over to something else some day.
Take the time to understand what a maintainer may want:
A good interface that doesn't deliver is worse than a clunky one that always does. A great interface can evolve over time to keep up with everything else around it.
Depending on the problem space, there are extents to which things can go very wrong. Providing an interface around a problem, you want to be thinking about all outcomes, not just the good ones.
Engineers building on top of your interface won't be writing project estimates and deadlines around all the internal failure modes of your system. If there's opportunity to introduce more friction so they are aware of these concerns, you have choices of:
Remember, we're talking about developer experience. Live issues are strictly more stressful to deal with than issues that come up in tests before the code is even merged.
I've done a lot of interface design. Open source interfaces, where there's so many assumptions you shouldn't make, I've found that simply scratching my own itch and designing an interface that if it were made by someone else I would use is good enough for most small projects. Certainly, open source is supposed to be fun and boxing yourself in prematurely can be daunting.
When it becomes business, however, I've definitely seen great results thinking about the meta-game of interface design. And different businesses have different risk appetites. Certainly my focus is more on sustainable growth and preventing bad outcomes because of the stage Stripe is at. If you're just trying to get something working at all, by all means cherry pick!
Thanks for reading!