Tech Mist - Programming as an empathic profession
Have you been bitten by a tetchy-whizz kid? The tetchy whizz-kid [or "TWK"] is a very talented technologist and therefore presumed to be a very talented software developer (note this distinction - we'll be coming back to this). They are so talented that it's highly likely they have written critical parts of your project that are some of the most optimised and least prone to failure. However, these parts of the project are also likely to be utterly inscrutable – and since nobody wants to be the one to break it, nobody except the TWK dares touch it. There's probably a handful of times when the best efforts of the rest of your team failed to fully solve a problem but then the TWK rolled in and fixed it all in a single three-day rewrite. It's these kinds of super-powers that incline everyone to ignore the other side of the TWK. The TWK doesn't want to talk to you. They don't want to justify anything to you. Of course they don't need your help! Did you fail to understand their very fast and complex explanation of something that you're unfamiliar with? You must be an idiot if you don't get it. Did they fail to understand your explanation? You must be an idiot if you think it made sense. Did you have the audacity to question or attempt to build on their proposed solution? This probably caused wildly disproportionate embarrassment and might be met with immediate vitriol. Or maybe not - maybe they'll just wait for you to make a minor error whereupon they'll ensure everyone knows about it and why it indicates how stupid you are.
If you've worked in enough teams or with enough software developers, especially in larger projects, there's a good chance you've had an encounter with a TWK. One of the reasons that they are able to get away with such antisocial behaviour is because they are living up to a stereotype that has become a trope in popular culture: the trope of the bespectacled, fast-talking, brainiac hacker who barges into police station in his hoody speaking in acronyms while everyone else in the room shrugs and asks the occasion question only to be shut down with sass. The trope is used to convey a specific brand of the "idiot savant" - we are supposed to interpret their lack of social skills, respect for authority, even dress sense as signals that they are a true master of their craft. That somehow their lack of ability to interface with people belies an unmatched ability to interface with computers. It's possible that this has some basis at the high end of lone-wolf hacking however working in a software development team or a multi-disciplinary team requires a more rounded skill set.
I want to break down the prevalent heuristic connection between the undesirable behaviours that characterise this stereotype and the perception of competence of software developers. These behaviours have a visible negative effect on team dynamics but the assumed benefit to the code might actually be a detriment as we will consider.
Code has two audiences
Once of the challenges of writing code is that you're writing a story for two different audiences: the machine and the future developer. These audiences not only speak different languages but they also understand the world in profoundly different ways, and they're reading the code for very different reasons. The machine is reading the code in order to execute instructions at a very high speed, whereas the future developer is reading the code in order to fully understand how to change those instructions without breaking anything. The machine cares very much about syntax, punctuation and spelling but has no insight into your intentions, so it also takes thing frustratingly literally. The future developer has a very high tolerance for errors and is able to bridge knowledge gaps with real world experience, knowledge of convention etc.
It might be tempting to prioritise writing for the machine over writing for the future developer; if the machine can't read the code, it doesn't run. But I urge you to think of it like this: in a team context, writing for the machine ensures that the code will run now, but writing for the future developer ensures that the code will run later. Code that works but is inscrutable leads to tech debt, which leads to rewrites or major refactoring, which wastes time. This is an important point so I'm going to reiterate in large type:
Code understandable by machines will work now but code understandable by other developers will work later too.
The main problem with the TWK is that they are usually writing with only one audience in mind: the machine. This is the instinct of the purist technologist. The machine is an easier audience for the TWK to appease because it gives feedback that is direct, easy to understand (usually) and instant. The machine doesn't require empathy. A technologist is, in my opinion, only partway to being a software developer; a software developer has more rounded skillset and it better able to write for both audiences effectively. This skillset allows software developers to limit tech debt and make code more maintainable which, in the long run, is usually more important than landing on the right initial solution in the first place. An easy-to-read mistake is usually easy to fix but a hard to read feature is difficult to extend.
How and why empathy leads to better code
Complex solutions to tricky problems have a way of feeling obvious in hindsight. It often takes an active effort to realise that the solution we've arrived at isn't going to be obvious for the next person. Consider philosophers like Friedrich Nietzsche. Nietzsche believed that the highest purpose in life is to prepare the world for the übermensch. But he did not simply assert this conclusion despite the fact that by the time he had reached it, it would have felt completely obvious and natural to him. He remembered the process by which he arrived at that conclusion and he takes his readers all the way along the cognitive road that he travelled. In order to do this he needed to be able to connect with his previous state of mind - a surprisingly difficult thing to do. It can be hard to understand why someone needs a lot of convincing or instruction to understand something that's obvious to you, even if it wasn't at all obvious to you for most of your life. Guiding your audience along your story not only makes your point more convincing by showing them how you reached your conclusion, it also gives them a much more solid foundation on which to build upon your conclusions and body of work.
Naturally the conclusion of the software engineering process is the running code and the running code speaks its message with perfect eloquence to the machine. The future developer, however, needs more than the running code; they need to understand the journey to the solution so that they know the precise moment at which the journey needs to change in order to take them to the new destination, aka., running code serving its new, modified purpose. Simply knowing that the code works is nowhere near as helpful as understanding exactly how and why it works in the specific way that it does. Empathy on the part of the part of the original developer is the key to bridging that gap in understanding in absentia.
The code is the conclusion but sometimes a future developer needs the full story.
How can we code more empathically?
Feel free to skip to "What about the tetchy whizz-kid?" if you're not a software developer.
This is going to be the same old stuff you've heard over and over but I'm beginning to think the reason why this doesn't always sink in is because the examples tend to be too extreme to be helpful. I'll try again in case these examples are different enough to make sense.
Meaningful variable names
Ok, everyone knows that "naming is hard" and you shouldn't call your variable something nondescript like "t" (although we've all seen something like that in a wild at least a few times). There's a lot of grey area between "t" and the apex of nomenclative clarity, so let's run through a more nuanced example:
The first variable to be christened was endowed with some noun related to whatever was being done, say "container". Now a bunch of "elements" are put into "container" and then "container" is put into something called "parent" (note that what this is the parent of is not specified). Further down the line, the elements had to host lists of data, so they end up with a child called "elementContainer". That's all it takes; confusion has been introduced! The machine understands but the future developer makes the reasonable assumption that the elementContainer is the container FOR the elements, not IN the elements. Yes, the code would ideally be structured in such a way that this isn't ambiguous but you can't take that for granted. Generic names aren't going to be descriptive enough as your feature grows more complex. Say what it does and, better yet, have a set of standards. Also remember that there's nothing wrong with minor refactors to make the code more readable to humans. If the scope is clearly contained, change the name. If you find yourself having to pick a different name because the one you wanted to use is taken, that's a great prompt to change both because the purpose of the variable is obviously somewhat ambiguous.
Simple structure
Simplifying your code's structure is easy. Look, we all push it a little bit sometimes - that single line for loop is tempting and honestly, probably not that bad if it's just calling a function. It can be hard to know when you've made a single line too complex for others to read, especially if you're more experienced (and harder to confuse) than others on your team. As a general rule, if you're using any kind of shorthand (e.g., ternary operators, fat arrow functions, AND/OR operators as short-circuits), don't compound them.
You could do this, but you shouldn't:
const confusion = x ? y || z : t.map(e => !!e.x && !e.z);
Comment your code
I'm perplexed by the fact that this is controversial. I have even heard developers say that commenting code is bad because well-written code always speaks for itself. I couldn't disagree more. Sometimes optimisation calls for code to be compressed in a way that reduces legibility quite sharply. Sometimes you try something in what feels like the most obvious way but it only gets you 90% of the way to your solution so you have to rewrite it in an unintuitive way. This can lead to another developer seeing your confusing solution, imagining the same obvious solution you initially tried, and then "helping you out" by refactoring it back to the wrong solution. Maybe you just really needed to get something over the line before midnight and you're not proud of your work but you need to draw the line. All sorts of strange, counter-intuitive situations can come up in code - that's how you know you're not reinventing the wheel. All these are potential sources of confusion that can be at least partially elucidated with a brief comment.
As a general rule, I include comments if:
- I tried something that felt obvious and it didn't work so I tried something else. I often start these comments with "https:// I know what you're thinking but let me explain..."
- I'm doing something that's kind of happening in multiple places and I can't work out how to force them together without too much compromise to the overall structure or readability of the code. An example might be building a large dynamic query over several lines and then iterating over it and constructing meaningful objects lower down. A note there might say "https:// Don't fetch data here because it cause an n+1. Add it to the query in ClassName.buildQuery()."
- I'm working in code, by me or anyone else, that I didn't understand the first time. I can only assume that someone else might make the same mistake (and it's not like commenting hurts anyone who did understand it). Make sure your comment isn't snide if it's in someone else's code.
- I do something that I know is suboptimal or I knowingly take an expedient shortcut. It happens. A mea culpa comment is no bad thing "https:// I know that there's probably a way to combine these two loops but I can't make it work right now."
Although this is a point of personal style, I try to keep my comments somewhat conversational. There's nothing wrong with adding a little bit of levity to the highly regimented world of code with a casual remark to a future developer. The machines can't hear us.
Be patient when you need to explain your code
Always remember that what's obvious to you is not obvious to the person asking how your code works. Every line came from the connections between your neurons so a part of it still lives in you. No part of it lives in the mind of your fellow team member. They're asking you not because they want to waste your time but because they want to understand. They're likely to want to understand the "why not" as well as the "why." Don't take this as a challenge to your solution; if you solution is robust then it's a good chance to contrast it favourably with an inferior alternative. If it's not, thank your co-worker for helping you to improve your solution.
On-boarding juniors is where this is most essential. They're already overwhelmed and they're already going home every night feeling like they'll never understand it all. You were like that too at some point. Be the person in the team who makes them feel like it's going to be ok and that it's fine if they don't get it all right now because it's all going to fall into place soon. I've seen a lack of understanding in a junior interpreted as a criticism of the code a few times which is not a helpful mindset for anyone.
What about the tetchy whizz-kid?
The tetchy whizz-kid is a challenge. There's no denying that they're a valuable addition to the team. While their talent should be acknowledged, recognising what's lacking in their skill set is equally important for the happiness of your team, the happiness of the TWK themselves and the productivity of the team as a whole. The most important thing is to fight the urge to promote the TWK into a management or mentoring role. They might, on some level, want the seniority that comes with such titles but they won't be happy with their increased exposure to co-workers and their co-workers won't be happy either if they're routinely subjected to impatience and a lack of empathy. Find a job title that reflects their career advancement but doesn't force them into people management when machine management is where they excel. A feeling of stagnation is a risk here, but subjecting an entire team to the pain of unempathetic leadership is not the answer to a talent retention problem.
It is also important to call out bad behaviour. Sometimes it's a fine line between being acceptably but unpleasantly moody and being abusive in a way that makes people unnecessarily uncomfortable. "That's just what they're like" is a dangerous phrase that should ring alarm bells. "Soft skills" (such as interpersonal and communication skills) aren't everybody's forte but a minimum standard of demeanour needs to be maintained. Raising one's voice, levelling insults (event mild ones) or making physical displays of anger (banging fists into desks or stomping around) are behaviours that can cause stress to permeate into an entire office. Expecting these moments to remain contained is perfectly reasonable. People also notice what their co-workers get away with and an office culture of open aggression and anger is a toxic one.
Takeaways
Code has two audiences: the machine and the future developer (that future developer might even be you).
Machine-legible code ensures working code in the immediate term while human-legible code ensures working code in the future.
Highly talented technologists who show a lack of empathy are likely baking that lack of empathy into their code in a way that will cause maintenance and tech debt challenges.
Don't subject employees to unempathetic leadership just because a management promotion feels like the next logical step for a technologist.
Make sure a culture of hostile temperament is not fostered by failing to manage antisocial behaviour from talented individuals.
Code empathetically.
Software Engineer - Node, React, AWS, TypeScript, PHP, Drupal
2 年I like what you're saying and how you're saying it, Jared! Re: _Code understandable by machines will work now, but code understandable by other developers will work later too._ Another way I've heard this expressed is: _Code is written once, but read many times._ Regarding the "tetchy whizz-kid (TWK)"... in more general terms this echoes the behaviour of the *prima-donna*, a person who believes they are so brilliant that without them the show cannot go live. One way to curtail the TWK is by relying more on process, rather than on individuals. For instance, the development manager can insist that all code be reviewed to company standards by peer reviewers. The TWK can be physically prevented from committed any code to the repository (eg through github) until it has been reviewed and approved by at least 1 or 2 team members (a typical github policy). Peer reviews should take both clear code and clear code comments into account. Alternatively, if the TWK has somehow convinced management that the company cannot survive without them (unlikely), then an option may be to move the TWK out of the development team and create a special role for them, "Chief.. Something-Meaningless", where they don't touch the code.
Software Engineer | Tech Lead | AWS Consultant
3 年Love your writing. I particularly approve of your 'general rules of commenting' which tend to mirror my own approach.
Gold. Keep this up man, great stuff!