With my background in freelance web development, I’ve found myself wearing many hats — salesperson, designer, developer, and everything in-between. When these disciplines overlap, there are often substantial lessons to be learned.
You might be wondering how user empathy applies to a developer. We developers are often building a product based on requirements laid out by a designer. And while we collaborate with great designers (or cooperate with poor ones), we are often developing strictly with the user requirements in mind. To be thoughtful developers, I believe it is our responsibility to do more than just craft code that consistently produces the expected output for a particular input.
I am a huge proponent of Test-Driven Development (TDD), which at its core is based around the practice of not writing any new production code until a new automated test is written (and failing). Code is then written until the test passes, and finally cleaned up to remove any duplication. This is commonly referred to as “red, green, refactor” (for failing test, passing test, cleanup). This approach meshes very nicely with a set of design thinking principles as taught by the d.school at Stanford, founded by David Kelley, the founder of the design firm IDEO.
These principles, listed below, describe a methodological approach to a truly human-centric thought process. The ultimate goal is to deliberately put users at the center of the development process.
We need to be kind to more than just the future users of our software. We need to be kind to the users of our codebase. This imperative is the underlying motivation for this entire article. Every developer has at one point inherited code (often from themselves!) that solved a set of problems, but is a nightmare to interpret or refactor. The next person to work in that code, even if it’s ourselves, should be a part of our thought process before we commit a change. Is the new code sufficiently decoupled to help avoid growing pains? Was I honest with my testing, or was something too difficult to properly isolate? Did I refactor sufficiently, and cleanly rename any tests that may have changed?
Mindful of Process
When I think of being mindful in my daily development work, I gravitate toward scope. Scope can often be a dirty word in software, and requires a certain amount of focus. How large of a development change one makes to a project in a single iteration directly impacts the risk of that change. Compare implementing the following behavior(s) in individual red, green, refactor cycles:
- This component will fetch data from a service, render it if it matches specific criteria, then allow for user input
- This component should fetch data from a service
- This component should render data if it matches specific criteria
- This component should allow for user input after rendering
The first objective carries a broad scope, many implementation requirements, and a larger mental load. The second set, however, covers the exact same behaviors but breaks it down so that the implementation and functional requirements I must test are obvious at any given point. Working in this way, I can address each facet individually and give more focus to the specific behavior. This reduces the feedback loop while red-green testing, as I can confidently build each segment in layers, without worrying that I have regressed at any point. With reduced scope at each step, the code I write is likely more straight-forward to read, test, and interpret in the future. Be mindful of scope, and know when and how to reduce it.
Culture of Prototyping
At first glance, most developers would say a prototyping culture is inherent to our work. Yet, as an application’s lifecycle progresses you may find yourself at an impasse — there is a substantial change that must be made, and accompanying that are are plenty of unknowns. Our original prototype was not a perfect fit for the problem and must be repurposed and redesigned.
We ought to be prepared for these situations. Writing thoughtful tests is only the beginning, as they allow you to spot when and where these new pieces don’t quite fit. Avoiding coupling is another crucial component as it allows you to swap out pieces of your architecture while retaining a codebase with proper code coverage.
Highly coupled code implies permanence, whereas a prototype should be able to change constantly and quickly.
Bias Toward Action
Red, green, refactor! Succinctly define your expected outcomes, get immediate feedback while you’re developing, then give yourself peace of mind as you clean up. Best of all, you get to keep writing code throughout. Keep it clean, keep it simple. Too much time spent planning for a future feature can just as quickly harm as help. When you are writing only what is necessary, there are fewer layers to peel back when those future features are eventually implemented in an unforeseen way.
Show Don’t Tell
By and large, my favorite part of TDD is inheriting a codebase with mindfully written tests. They serve as living documentation of how the code should perform and are an excellent introduction to working in a new project. There is a certain directness of purpose that tends to accompany well-tested code, and an immeasurable bump in readability. It is our duty as professionals to write prescriptive tests, with clear indications of what is performing the action, and what the outcome should be. A well-written test will not just tell you what you are testing. It will show you why.
Be open and inclusive. When you have a problem, pair with someone else. When you disagree, respect that opinions are based on something, and they likely see something you don’t (or would appreciate seeing what you do). Work with your team to develop the best possible product for both you and the user. Attend those product meetings so you can understand not just the expectations but the intents, and give more directed feedback or insight. By working with a strong project manager or designer, you can help to redefine requirements in a way that provides a leaner, cleaner solution by reducing scope. And most crucially, don’t just cooperate — collaborate. You never know what sort of thought processes those types of interactions may inspire.
By combining TDD with the principles of design thinking, you’ll find a few benefits:
- Better estimation due to reduced scope and technical debt
- Cleaner code, which leads to less painful resolution of bugs and more frequent releases
- Easier collaboration, since smaller scoped technical conversations require much less context
You don’t have to be the end user to deserve an honest attempt at empathy. Bear these principles in mind the next time you are working on a tough problem, and ask yourself this question: Is this truly the most considerate course of action I could reasonably take?