Empathy Driven Software Design
Made up term. Yet so important
Empathy for:
- your current team members
- the new members coming in to the team
- whoever will be working on the app 6 months from now. (could be still you)
- the people that will be tasked with adding features and maintaining your app even after the key players have left the project.
Building a complex piece of software to be developed by a large team requires a serious dose of empathy. Generally, software engineering skills in a team lie on a large spectrum. Maybe your team is lucky to have highly capable software engineers (heroes) that can handle any problems you can throw at them. That is not the case most of the time. It is cruel to expect everyone to rise to that amazing level.
Here are some of the “not so ideal” personas I’ve seen in my past experience:
- Kate has been working in QA for several years. She is familiar with scripting and is quite good at Perl. She is now going to be part of a modern UI development team.
- Jake is a mechanical engineer. He is interested in coding and thus takes a break and goes through coding camps. Jake is good at coding but his fundamentals and language skills are very narrow.
- Harmeet is a contractor. His consulting company sends him on assignment to various companies. Harmeet is a jack of all trades but master of none.
- Molly is a recent grad and an intern. She is quite new and requires quite a bit of help understanding concepts.
- Ronny is a single dad that juggles a lot of things. He likes to come in at 9, do his work and leave at 5. He is honest and a hard worker. He is not passionate about coding. He likes to know what needs to be done and he does it. (The “Steve Jobs” culture in Silicon Valley blurs the line between work and personal life. Unfortunately, that is the reality and I hope it changes sometime.)
- Vern’s workload needs to be managed closely with clearly identified tasks. He gets distracted easily and must be kept on track with Jira tickets and followed through.
- Sofie works from a remote location. Her feature development velocity is slow because of the turn around time in communication.
My success as a team lead or architect is defined by how well and how soon I can have everyone in my team participate and be contributing members to the project.
I will attempt to list a few guidelines that can infuse empathy in your design. Like my other articles, this is within the scope of developing Enterprise UI Apps using React.js.
Pick a mainstream framework with a small learning curve
At time of writing this article, there are only two that I can recommend. React.js or Vue.js. With the advent of hooks, React has become an even simpler framework to learn. JSX is still JavaScript and thus easier to learn than Vue’s DSLs. These frameworks are easy to get started with and learn incrementally.
I love React because of its small API. I can keep it in my head. JSX is nothing but JavaScript expressions. There is nothing new to learn.
Empathy factor: Getting started quickly is very important. This makes the process exciting and interesting. Because of the huge adoption of React and Vue.js, there are thousands of free articles and stack overflow answers. I can write React code without once referring to the docs. TypeScript, not so much.
Trim the framework to just the essentials
Just because it is there doesn’t mean you have to use it. For instance, our new React projects do not use classes. You don’t have to focus on the class component API and their related quirks any more. Also, a big chunk of TypeScript is not needed.
Easy to reason
This means that with minimal guidance, a beginner level software developer should be able to understand what your code does without doing a lot of mental gymnastics or a week long TOI session.
Software inherently is complex because it is in essence, a set of instructions for machines. Thus tacking more complexity in your design or tools is not a good idea. When possible, use established patterns instead of inventing your own abstractions.
Empathy factor: The more readable/reason-able your code is, the less chances of black boxing that is needed.
Keep it simple
No clever code. Keep it easy to read. Follow Uncle bob’s rules for writing clean code. This article by Martin Fowler is also a good read. Tuck away complex pieces of your software into folders or libraries as well abstracted, well tested libraries.
UI development is a complex domain. You have to deal with three distinct specifications such as JavaScript, CSS/SASS and HTML. These bring in their own set of complexities and quirks. Thus keeping it simple is quite imperative.
Empathy factor: Clever code and abstractions are tough to understand. Write in a style that can be followed by others.
Reduce the cognitive load
The more experienced you are, the simpler your programming style should become. Check this Blog from Kent Dodds for an example.
Now the trend is TypeScript. While I understand the value of static typing, code like this hurt the cause:
function pluck(o, propertyNames) {
return propertyNames.map(n => o[n]);
}// Typing this would yield:function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] {
return propertyNames.map(n => o[n]);
}
Simple TypeScript types, enums and interfaces are easy to follow. But generics can get hairy very quickly. Again, this is my opinion. I am looking after the new person who needs to be on boarded quickly into feature development or maintenance. If the goal of complex typing is to reduce errors, I’d consider unit tests. A junior engineer will find it easier to follow.
Empathy factor: To an entry level JavaScript programmer, TypeScript is a lot to take in. The more we understand the tools, the better code we write. Complex constructs in TypeScript are very non intuitive. It wastes time by having to do “how to type it” google searches.
Follow design principles and best practices
SRP — Single Responsibility Principle
Your software should be composed of modules that do one thing. This applies to atomic and larger composed modules.
- Keep your components pure.
- Move business logic to containers.
- Make helpers unit testable. Organize them nicely. A single file per function is not an overkill.
Empathy factor: You not only make it easy to test units, pure functions and components are easily repaired or replaced.
Separation of concerns
With hooks it is even more easy to do this in a React app. Concerns whether they are local or global in nature must be separated out.
YAGNI — You Ain’t Gonna Need It (now)
Over engineering is common among enthusiastic engineers. We end up with code that is future proof. Only, that future never arrives.
Tests
I’m not advocating TDD. It is a skill that takes years to hone. I struggle with it constantly. But having unit test and integration coverage are a must even when they are added in retroactively. It provides a certain comfort to those taking after you. Write testing patterns that are easy to duplicate across the app.
Empathy factor: Provide confidence to new developers taking on existing code that they won’t break something unintentionally. They can also follow your lead in writing new tests.
Strap tests and lint-ing to commits, pushes and bundling.
You can easily do this with husky.
Empathy factor: New devs will not commit imperfect code accidentally.
Make your features easily deletable
How you score in this will determine how maintainable your code is.
- Don’t store data local to a feature globally. Redux stores its data in a single global state tree. I’ve seen apps that use Redux to manage ALL the application state. Even when you divide it up among multiple reducers, it is still in the global context. Every action will trigger a state reduction via ALL the reducers. Use caution and consider point #2 below.
- Use React context to keep your data locally within your feature “pod”. Use Reducers using the new React hooks to manage state nicely.
- Use CSS in JS. I recommend CSS modules. CSS modules let you keep your css/sass in its own files. I was fascinated by styled components for a while but mixing JS and CSS via a rarely used template feature seems a bit wrong. Styled also is a bit heavier since it needs to include its own CSS parser. I’m not a fan of libraries that use camelCased CSS JSON.
- Co-locate your code. This includes components, containers, state, helpers and tests.
Empathy factor: Your code base will remain clean and lean. The chances of regression is less, resulting in more confident devs.
Tuck away and label your global state and mutations
State mutations are a necessary inconvenience. Strewing global mutations all over your code is a recipe for disaster. For example, the following can be organized into their own folders.
- Bootstrap code (auth, RBAC, i18n, error boundaries)
- Global state management.
Empathy factor: Debugging and maintenance becomes much more easier. Plus the devs are clear on where the regression bombs are and will exercise caution.
Provide recipes for reusable patterns
This is where things such as storybook come in handy. You can clearly show how to use a UI component or layout. No amount of documentation can match a working code recipe. I’m a big fan of CodeSandbox. But it is a challenge to demonstrate your code from inside the enterprise. I have not tried private repos yet.
I have even gone so far a creating “samples” folder within certain modules to help new devs get started.
Empathy factor: Learning is easy once you have a working example.
Make your app easy to run
Ideally, “npm install” and “npm start” is all a dev need to do to get a working version of your app running. This means that you may have to strap your app with a mock api server and some magic around authentication. Make sure that there is no tribal knowledge involved in getting your app working.
Empathy factor: The joy of being able to run the app without having to depend on tribal knowledge is tremendous. Think of the new hires who are already not very comfortable asking too many questions. Being able to have the app working so easily inspires confidence.
Keep your documentation small but updated
Ideally, check them into your repo along with your code as .md files. Organize them in your README.md file. There is no need to keep them in a separate Wiki. But it can be a pain to manage docs in git. If you have to have a separate Wiki, link them from your README file.
Empathy factor: Documentation is accessible immediately without someone having to send you a bunch of links.
Do detailed code reviews
This applies to everyone including senior devs. Code reviews can be a bit uncomfortable to those who are not familiar with code reviews. Explain the reasoning and the review process to the team. Coach developers on how to add comments without coming across as a jerk. Typed comments are one of the worst form on communication because it is faceless and devoid of emotion. Empathy should be provided in huge portions while writing comments. It is a learning experience and every organization will evolve a certain style and chemistry.
Code reviews are also your security. When your code breaks, it becomes a shared responsibility. Everyone in the team will own it.
Empathy factor: Learning. Shared ownership of the code.
Code along with your devs
Recently, during an on-boarding process, I sat with a group of frontend engineers and started coding with them. This helped me gauge and identify specific areas they needed help with. It revealed the bad (complex) parts in my design that were not communicating well. It also exposed the holes in the core documentation.
Empathy factor: It forces you to see your design from their shoes. It also humanizes you to the team.
To summarize, when mixed with empathy, you develop a work culture that is nurturing and devoid of jerks.