I've been paid to program since 1979 and for most of that time, I've been working with other people's code. At first it was “add this little feature to something we already have.” These days, it's “how can we be better” and “is this code worth keeping?” Reading code has always been a huge part of my job, and so I care a lot about the kind of code I (and the people I work with) write. Of course, I want it to be fast – I'm a C++ programmer after all. It also needs to be correct, yes. But there's more to it than those two things: I want code that's readable, understandable, sensible, and even pleasant.
I've put a lot of work into looking at code and seeing how it could be better. Often, I recommend making it better by using things - language keywords, library functionality, etc. - that we've added to C++ in this century or even this decade. I show people how to write code that does the same thing but is clearer, shorter, more transparent, or better encapsulated. Until recently, I didn't spend a lot of time thinking about why people wrote that code the way they did, even when there were things they could have done at the time that were clearly better. I just made the code better. In this article, I want to talk about that why factor, and about the humans who write the code you read and maintain.
What Are Emotions?
Because I mention Emotions in the title, it's probably a good idea to discuss what they are. I think of emotions as out-of-band interrupts. Emotions deliver a conclusion without all the supporting evidence being clearly listed. For instance, you're walking down the street, or negotiating to buy a car, or on a date, and suddenly your brain tells you something like:
- Get out!
- Trust this person.
- Smile, relax, everything's fine.
- Fight, yell, hit, and scream!
These reactions may be right - you may be talking to a wonderful person you can trust - or wrong - the sales rep may be trying to flatter you into paying too much for the car. But the point is that at the moment the message arrives, you don't have a nice clear list of reasons to feel that way. Your brain has done some pattern matching and delivered a conclusion. You can act on it or not.
Some people don't like it when other people take actions based on emotions. If you become angry and leave a situation, you may not be able to explain the precise details that led to your conclusion. You may not be able to prove that leaving was right. I've been told that relying on emotional reactions to make decisions is lazy, non-rigorous, or cutting corners.
No Emotions Allowed
People who feel that that emotional reactions are inappropriate are especially common in the field of software development. They ban the disruptive out-of-band interrupts that emotions are. They insist that you must win arguments with logic and not with feeling strongly about things. Just the pure crystalline logic of the 1s and 0s of the matrix. A lot of people really feel this way and try to suppress emotions in themselves and in others.
To a certain extent, they have a point. If we're arguing about how many parameters a function takes, I don't want to hear that you feel five is the right number: “It just makes me happy seeing it like that.” I want you to persuade me with logic. But for many cases, the quick overview conclusion delivered by emotions is super useful - not for winning arguments, but for doing my work. I look over an API, a whole collection of functions and all their parameters, and something in me says EEEWWWW. I don't know exactly what is so yucky, but it draws me in there to give a closer look and to see what I rationally think about that part of the code. I'm not going to say, “pay me to re-architect your whole system because it feels kind of gross and wrong,” but that first emotional response had great value in bringing my attention to a place that needed it. I've learned to value those signals a great deal.
But not everyone does. When I tell them “I don't know what specifically I dislike about this API right now; at first glance, my gut has a problem with it and I know it needs to be looked at so I'll write you a summary this afternoon,” they may reject that because there's nothing to argue with, they have to just trust me. They may reject it because they think I'm being weird or emotional instead of using my experience. (This is odd, because emotional and intuitive reactions to situations are how your experience generally shows itself to you.) Perhaps they may just be in the habit of telling other people not to feel emotions: not to get really happy about things, or really upset either. We have a whole strain of humor about this: “Oh, the humanity” and other memes where people are mocked for being upset over relatively small things or for being happy.
Emotional and intuitive reactions to situations are how your experience generally shows itself to you.
Mocking people for their “first-world problems” or replying “oh, the humanity” to them when they're getting worked up is our way of enforcing a social norm within the programming community, especially programming communities that have roots in the 20th century rather than the 21st. The social norm says “don't express emotions to me,” and ideally, don't even have emotions. But here's the thing: Programmers are human beings and human beings have emotions. Therefore, whether you like it or not, programmers have emotions.
In reality, emotions are a big part of software development. It's a lot more than writing and debugging code, and the not-code parts of it are FULL of emotion: getting users to tell you what they want instead of what they think they want, trusting your team, telling the truth about your limitations and dreams, being brave enough to go against the stream when you have to, keeping your integrity and values when you find yourself in a place that doesn't share them, and much more.
I'm not here to talk about any of that today. I want to focus in on just one myth, just one not-true thing that we all tell ourselves: There are no emotions in code. That there are no messy feelings when it comes to writing code, because code is purely logical.
Your Code Shows Emotions
In fact, code is full of emotions, right there on the page for anyone to read. Most people don't believe me, but seeing is believing. Let me show you some examples.
Fear
When you start asking why people do things, you can get some interesting answers. Take commented-out code as an example. You can't find anyone who likes it. We rant and rail that there is source control; people can take work notes and paste the deleted stuff in there to use later; it's just confusing when you're trying to read later, it messes up your searches; and so on. But people do it, they do it every day. Why? Because they're scared. “I might not be doing this right; I might need this later.”
Here's an example from real code, found in the middle of a long function.
//if (m_nCurrentX != g_nCurrentX
// || m_nCurrentABC != g_nCurrentABC) {
//}
That's a semi-tricky “if” comparing the member variable values of things to some global values with similar names. That's apparently no longer necessary, but the developer can't bring themself to throw it away. They aren't sure they could get it back if they needed it again. They feel timid and afraid about this code base, and about the consequences for not being able to find what they need later.
Another frustrating category of comments: the “don't blame me” comment. This developer is afraid they'll be caught doing something that another developer, or a manager, will disapprove of, so they leave a comment explaining that it wasn't their choice, someone told them to make this change. You'll see things like:
limit = InputLimit - 1; //as per Bill
This developer hasn't decided how to calculate the limit, isn't prepared to stand behind that calculation, is telling you “hey, if you have any issues with this line of code, don't talk to me, go and find Bill, it was all Bill's idea.” And it's not just that they aren't confident how to calculate the limit, it's also that they're worried someone is going to object to this line and they want to defend themselves against hostile reviews. Somehow the environment has left people unable to feel confident about even the simplest calculations.
Fear is also why people don't delete variables they're not using. They leave in lines of code calculating something that's never used after it's calculated. Functions that are never called. Member variables that are never set or used. “How can I be sure we won't need it?” the developer is clearly saying. Other times, they know they don't need it, but they still don't take the time to clean up. They don't have time for that. They're going to “get in trouble” for not getting things done quickly enough, for not getting enough things done each day, and deleting code that “isn't hurting anyone” just doesn't make it onto the priority list. Yes, perhaps some future developer will be slowed a little by trying to figure out what's happening, but this developer right here is trying to make a deadline or trying not to get fired, and so the old unneeded junk stays behind.
It takes bravery to divert from patterns you see in existing code, even when you know they're wrong, to stand up for the right way to do things. I see things like this next sample in almost every C++ codebase I meet:
int c, n;
int r1, r2, r3, r4;
double factor;
double pct1, pct2, pct3, v1, v2, v3, v4, v5;
double d1, d2, d3;
There's so much wrong here! None of these variables are being initialized. None are declared close to where they're used. And the names! I can guess what factor is, but what does r
stand for? I am pretty sure v
just stands for value and d
for double. There's no information at all in these names. But a scared developer, a developer worried about what code reviewers will say, a developer afraid they're putting bugs in working code by messing with it, that developer just adds d4
to the end of the list and feels pretty sure that's the safest thing to do today.
I also see code that checks conditions that don't need to be checked. Here's a C++ example:
if (pPolicy)
{
delete pPolicy;
}
Deleting a null pointer is a no-op. It's harmless. There is no need for this check but I see it all the time. (If the pointer was set to null
after the delete, or anything else was done inside those braces, then it would be fine, but that's not happening here.) I'm also likely to wonder why you're doing manual memory management here but ignore that for now and concentrate on the mindset of someone who keeps checking things “just in case,” someone who's paying in runtime performance every single time this application runs, because they feel tentative and unsure. Afraid.
It's easy to say that training people and doing code reviews will teach them that delete
on a null
pointer is a no-op. But what if the developer is afraid their coworkers will let them down? They check the same conditions repeatedly because they can't be sure the conditions are always met. “That was in a team-mate's code, they might have changed it without telling me.” Here's a place where a thorough test suite, and running those tests after every change, can improve your runtime performance. If you can confidently write the code knowing you're getting valid parameters from the other parts of the application, think how many of those runtime checks (make sure x
is positive, check that y
is not over the limit, and so on) could be dropped!
Sometimes fear is also why a developer does everything by hand instead of using a library. They got hurt by a library once and now they need to see it, step through it, watch it work. They can't trust anyone else's code and they're willing to take longer writing it all themselves, or write na?ve code that misses some edge and corner cases, out of the fear of what unknown library code might do.
Arrogance and Anger
Earlier, I showed a code snippet with variable names like r1
and v2
. I've actually asked a developer what those names meant and been asked “aren't you smart enough to figure out what these are?” When I ask people to explain obscure
function names, the responses sound an awful lot like “Why should I explain myself to people who can't understand it without an explanation?” You'll see this with deliberately opaque names like f()
, foo
, and bar
. Setting aside their origins in the dark humor of people facing wartime death, foo and bar mean “this doesn't have a name, it doesn't need a name, and your desire for it to have a name is wrong.” I think that's a terrible thing to leave in code for others to read later.
It may come from arrogance, from believing you're just better than everyone else and they don't deserve explanations, or not wanting to take the time to provide them. It can also come from someone who is angry about something else and showing that in their code.
Sometimes this sort of “I'm better than anyone else” is what drives developers to use raw loops instead of something from a library, to write their own containers, and so on. They perhaps ran into a performance problem a decade or two ago in one popular library and concluded that they would always be better than those library writers. Although “it ain't bragging if you can do it,” very few developers can actually outperform those who concentrate on a specific library all day long. Maybe they measured the performance of their hand-rolled solution with their unusual data against the library solution, but then again, maybe they didn't. It's worth checking.
You'd be surprised (I no longer am) how often I find sneering comments and names in live code. People who say lusers, PEBCAK, and RTFM in emails and Slack say it in their code too. They say it in their commit messages too: April Wensel found a pile of hits for “stupid user” in GitHub commit comments. Obviously, those comments are public (she found them). How do you imagine users would feel discovering a commit message that was nothing more than “Be nicer to the stupid user” when learning about a product they used? And that committer needs to try harder at “be nicer,” by the way, because that comment shows a distinct lack of nice. Steve Miller searches executables for swear words: Malware has more of them than non-malware does.
Malware has more swear words in the code than non-malware.
I've also seen function names and variable names that drip with disdain and contempt for the work being done. No, I don't mean calling a variable “dummy.” I mean things like putting a coworker's name in a function to show you think they're wrong. I came across something called UndoStevesNonsense()
once. Only it wasn't Steve and it wasn't nonsense; it was a cruder expression of disagreement. Apparently, Steve wrote some code that did things this developer didn't think should be done, so rather than settling the design as a group or going to an authority, this person just undid those steps and named the function accordingly. Again, if you're all about runtime performance, the thought of one developer doing steps A, B, and C (that all take time on every run) and another developer turning around and arranging for them to be undone, it's horrifying. But it's real in live code today.
Selfishness
Fear, arrogance, and anger don't explain all the bad code out there. Another huge group of developers are selfish. They don't take the time to clean up, refactor, rearrange, or rename as they go. You can hear them asking “Why should I spend my time making things easy for you?” Imagine you're given an hour to fix something. You get it fixed in 50 minutes. If you spend 20 minutes tidying up, subsequent fixes in this area will only take half an hour. You'll be even after only one fix in this area, and ahead for every fix after that. But if the team is measured on each fix, it's possible someone else will be saving that half hour, while the original developer is punished for the 20 minutes extra. So, unsurprisingly, that 20 minutes isn't taken.
Selfish code has short and opaque names because the developer didn't bother thinking of good ones. It uses magic numbers instead of constants with names. There are side effects and consequences everywhere, like using public variables because it's quicker, or mutable global state because it's quicker. Sure, it might be slower next time, but next time is someone's else problem, right?
Selfishness also leads to information hoarding. My job is safe if nobody else can do this. If I explain this to others, I'll be less valuable because I'll be replaceable. (As an outside consultant, my usual reaction on hearing that someone is irreplaceable is to change that first. It's not good for teams and it doesn't lead to good code in most cases.) This developer sees their coworkers as competitors and doesn't want to help them. That's not how good teams work.
Laziness
Not all programmers are selfish, of course. Some of them just can't be bothered. “Whatever; it works. Mostly. We have testers, right?” They don't use libraries because they resist learning new things or finding new ways to do things. They're busy typing code. Or copying and pasting code. “Abstraction? Sounds like work to me!” When you suggest that they add some testing, or build automation or scripts, you're likely to hear, “If you think that matters, you do it.” They don't show any kind of commitment to the quality of the code, the user's experience, deadlines, their own future, the success of the company, or the goals of the team. They just want to come in, type without thinking a lot, and go home, having been paid for the day regardless of what was actually accomplished. And it shows in the code!
It's not just that they haven't refactored, haven't spotted useful abstractions, haven't given things good names. You see repetition, really long functions, a mishmash of naming conventions - all things that are easy to clean up on a slow day when you don't want to be thinking about new code. But that time just isn't taken. You see bugs that would be easily caught if you turned up the warning level, because turning down the warning level is a classic lazy person's way of getting their code to compile and run. And you see disorganization and mess because the effort to think clearly about the problem and organize the code to communicate about the problem is more effort than this developer is willing to make.
Now, a warning. Some teams practice crunch. If you keep a team in crunch indefinitely, they will end up behaving in a way that is indistinguishable from laziness. They literally don't have time to find out how to do things more quickly. They live in fear of the drop-dead deadline, that if it's missed, they will be fired, the company will collapse, they will forever be associated with the failed project. They can't invest an hour to save a day or a week. Nobody is letting them have the hour. They already aren't sleeping enough and are keeping track of whose marriage has failed so far on a whiteboard in the common room. The code that comes out of crunch is rarely good code. Sometimes, that doesn't matter. When it does matter, good teams go back and clean up afterwards, when the deadline has been met. If no one has done that and the artifacts are still in the code, I know there's a lazy developer that's getting away with it, or a burned-out developer that no one looks after, or perhaps perennial crunch. The bad code points directly to bad management practices.
Why Does This Matter?
So yes, your code can show emotions to me. I can see fear, arrogance, selfishness, laziness, and more, right there on the screen. In some sense, it's true that code is only logic and has no emotions. You're given a rule like “if today's date is after the due date, then the item is overdue and this is how it gets processed.” There's no emotion to that. The person doesn't get a break on the overdue process because they're cute, or get extra fees charged because they're rude. But everything in the way you implement that rule, including the variable and function names you use, whether you make checking for overdue-ness a member function of something, what data type you use for dates and how you test “after,” all of that can and does carry emotion to someone who knows how to read it there.
Of course, one single-letter variable name does not a psychopath make. Sometimes calling a variable i
is the right thing to do. I see emotions in patterns, not in single instances. And when you see a pattern, and learn about the team and the developers, you don't go and find the code author and say “wow! I never knew before how scared you are all the time!” Seeing the emotional causes of bad code can give you empathy as you read and fix legacy code. I no longer yell out “what were you thinking?” as I read old code. I understand now that sometimes people were under time pressure, were being measured, were getting unpleasant code reviews, or were missing the tools we all rely on, and that put them in a mindset that produced this sort of code.
For me, gaining these insights also leads naturally to suggesting changes to a team or a workplace. That might mean that a particular team member should learn something, or that a particular management practice should be amended. Ask yourself, are your management practices causing runtime performance issues? They often do and knowing that may be enough to get them changed. It can also direct the way you try to get a particular developer to write better code. If they're writing bad code because they're afraid that they'll be fired if they miss a deadline, or don't close enough tickets each day, or get changes requested after a code review, then naturally the way to help them write better code is to reassure them about those fears. Yelling at them to get their code quality up is only going to increase their fear and will probably make the code worse. But if someone is writing bad code because they're selfish or arrogant and not interested in making things easier for the rest of the team, they don't need reassurance, they need reminders about what's important to their employer.
Are your management practices causing runtime performance issues?
I also keep the emotions my code demonstrates in mind when I'm writing - even one-page samples that can fit on a slide or in an article. People will copy what they see in your code base as they work on it. Do you want them to copy the fear or arrogance or selfishness they see there? Do you want them to believe that good developers use short and meaningless names?
Look Where You Want to Go
It's tempting to conclude that you should try to take the emotions out of your code. But I don't think it's possible. The bad code showing that the author was afraid is timid, overly cautious, self-protective. Take that fear away and you don't have neutral code, you have confident and capable code. The selfish code that hoards information, after you've done some refactoring so it explains itself and has clear names, isn't neutral: it's generous. Once you understand that everything you write is lighting up with good or bad emotions, why not, when you can, take the time and put in the work to show what you stand for and create code that inspires and helps others?
Confidence
You can show confidence in your code. Start by deleting things you don't need - old code, variables whose values aren't used, member variables that are never set or read. You have source control, after all. I take “work notes” as I tackle specific tasks, and if I rip out several lines of code that are now obsolete, I can paste them into those notes, where they'll generally be easier to find than if they stay in the code, but commented out. As a nice side effect, my code now appears less tentative, less worried about the future, less concerned with what a reviewer will think of me.
When you've just added a feature or fixed a bug or otherwise been working on some code, take the time to clean up afterwards. You'll never know more about this code than you do right now, and now is the time to record that knowledge in the code itself. Give things names that explain your thinking. Leave comments that guide people through the sharp edges if there are some. This might help you later or it might help someone else. Code like this says, “I know I'm right, let me show you.”
When you come across something obsolete, that can be done better using a new language feature, change it. (You have tests, right?) When you come across something hand-rolled, replace it with a known-good library approach. (Again, you have tests. You can do this.) When you find a raft of variable names like r1
, r2
, r3
, change them to something better. Let your code say, “I'm brave enough to stand up for doing things the right way.”
Humility
The opposite of arrogance is humility. Knowing that you're good is not the same as thinking you're the best at everything. Code acknowledging that the next person to read is likely to be a good developer who deserves an explanation is humble code. So use libraries, and include a link to the documentation somewhere. In C++, it's easy to add a comment on the line where you #include the header file. In other languages, you can find somewhere else to put that comment so that people copying and pasting later will paste the link as well as the code that uses the library.
Write gentle comments that tell why, not what. But don't rely only on comments. Where things aren't obvious, you want to leave some help for the next person, and names are always better than comments for functions, variables - everything. When you imagine the next reader of your code, don't imagine someone less than you; imagine someone better than you. After all future you is likely to be better than current you, and future you is the most likely next reader of this code.
Generosity and Hard Work
My eyes light up when I read code that's truly well done. When I see clean engineering that was done to make next time easier, well thought-out encapsulation, and that elusive appropriate level of abstraction. Someone has taken the time to clean up: to refactor, rearrange, and rename things so that the code makes sense and leads me through the process.
Information sharing is also generous, someone who's thinking “my job is safe if we can all do this.” Their comments are enlightening, they've chosen good names, and they've arranged the code in an order that makes sense to the reader. “First we prep the order, then ship it, then update the inventory. I get it.” That sort of clarity isn't easy, and it's generally not what you write the first time. Someone has put in the effort to make this code good.
Sometimes code just strikes you as brilliant. It's clearly and obviously right, and dramatically easy to grasp. I don't mean that it's clever. I mean that it's obvious. Consider this C#:
var creditScore = application.CreditScore;
switch (creditScore)
{
case int n when (n <= 50):
return false;
case int n when (n > 50):
return true;
default:
return false;
}
This code isn't wrong. It compiles without any warnings, and it implements the logic that's needed. But there are three cases (two ranges and a default) and lots of places to make mistakes. Someone might return true
in two of these return statements instead of one. Someone might change the 50 in the first case but forget to change it in the second. And so on. Most of all, it takes you some time to reason through this and figure out what the rule is as implemented here. Compare to this line:
return (application.CreditScore > 50);
It does exactly the same thing. It's much shorter, and it can't get inconsistent. There's only one 50 in there - if you change it, you've changed it in all the places you need to. You don't leave the reader looking at your default case trying to imagine an integer that doesn't meet either of the first two cases. And there's no local variable to trace through the code: That's one less moving part for the reader to hold in their head.
Something that's rare for me when I start to look through a team's existing code is that it simply compiles, links, runs, and passes the tests. Oh, it compiles, but there are warnings. Maybe hundreds of them. And the team members all know there are 417 and if ever there are 418, somebody needs to look into it. Or it runs, but you get an exception on startup, just hit Continue and don't worry about it. Or it leaves a few stray
files behind and you have to hand delete them before you run it again. Or it passes the tests, but there are only seven of them. When I meet code that compiles without warnings, runs smoothly without by-hand steps, and has complete and well documented tests, I feel really looked after. Here's someone who doesn't have to be asked to do it right. They aren't using tools for the sake of tools, or for fun, but to make things run smoothly.
I can see that sort of work ethic when I look inside the code, too. It uses modern constructs or libraries because the developers are always learning. It follows modern practices, not just churning out code. Code like this (and the scripts and tests that surround it) show a commitment to the future, to that developer's own ease and to the team's success. This is the code of a hard-working programmer who doesn't do just the minimum to skate by. Who doesn't just copy what was there before, including the bad patterns and the bad code. Who takes the time to see if now is the time to change that thing that sort of grew organically and has become unwieldly and almost unmaintainable.
Choose to Show Positive Emotions
So sure, your code could show fear, selfishness, laziness, and arrogance. But why not show confidence, generosity, humility, and how hard working you are? Your code will be easier to read and maintain. You'll enjoy reading and maintaining it more, and your reputation will improve as other people realize they can understand what you write, it's easy to change it when life changes, and it's generally better code. Even if the code isn't better, there's a lot to be gained from writing this way. But it probably will be better.
I want you to care about those who wrote the code you maintain and those who maintain the code you write. When you find crummy code, fix it. Show your confidence. Clean up. Make it right. Name things well. You're going to show emotions in your code and they might as well be positive ones!