Tuesday, May 26, 2020

Coding in Anger

The most skilled senior developer I worked with when I was still a pretty junior developer had a couple stock phrases that I think ended up teaching me a lot about the right way to write code. One of those phrases was "coding in anger."

When he was asked to give his opinion on a new tool or library, he would often say, "I don't know yet. I've just played with it for a couple days, but I haven't really used it in anger yet."

This, to me, is one of the most fundamentally human lessons of programming. Good patterns are the ones you reach for when you are exasperated with your code base. Good libraries are the ones you reach for when you are infuriated with the extant solution to a particular problem. You learn to write code well by writing it in anger.

Your code ends up being personal, opinionated, and judgmental.

If you are not putting your personality and your judgment into your code, you haven't figured out what you're doing yet. When I'm working in a codebase that has been maintained by a small enough team that was maintained by a bunch of bad developers, I can sometimes infer who did what without looking at git history just by the smell. But this is an artifact of small team size.

Oh, this query is pointlessly and inefficiently over-complicated and uses CTEs everywhere instead of temp tables even though temp tables are much more efficient in this case. I know who wrote it. But that only works if I'm on a team with only one bad developer whose form of badness is taking pride in making things pointlessly overcomplicated. If there are two or more of them, they feed off each other in their attempts to write ever more  elaborate code.

There are about eight approaches to bad development that I've seen.

1) The person who only knows the minimum set of knowledge required to write something, who turns every screw into a nail because the hammer is the only tool they have in their toolbelt.
2) The person who uses the most elaborate technique possible to solve anything to prove that they know esoteric things even when those things are completely irrelevant and even counterproductive to the problem at hand.
3) The person who experiments with a different library every project.
4) The person who just doesn't care and takes no pride in anything and has no consistency. They might mix different forms of capitalization for things that aren't case sensitive (SQL, yuck!) or mix tabs and spaces or put no thought into where their line breaks are supposed to go, and have no consistency of whether they put the operators at the start of the new line or the end of the old one. etc. 
5) The person who actually can't code and just does crazy stuff by trial and error until it works.
6) The person who thinks verbose, defensive code is optimal and writes as many try/catch statements as possible and includes extra branches in their code that end up doing the same thing just to prove that they considered that branch too.
7) The person who copies and pastes spaghetti code from elsewhere in the repository and strings it out further instead of integrating anything.
8) The person who constantly googles everything and copy-pastes from stack overflow.

When there are two or more people on a team who do any one of these things, everything gets worse at a rate that scales superlinearly with the number of problem children, and it becomes impossible to tell who wrote what. All of these different approaches illustrate a lack of opinion about what good code looks like. The code that reflects the personality rather than the values of the author.

In contrast, when you are working with a really good developer, and you read code that only they wrote, you know exactly who wrote it, and you feel judged for not writing all of your code the same way they write theirs. There are forms of consistency you didn't realize could exist. All classes are always instantiated by passing in arguments as kwargs, and functions and methods besides constructors are always invoked by passing in any arguments that can be passed in positionally, positionally. You eventually realize that there's a reason for this pattern, and unless you have a similarly esoteric pattern that is equally well-thought out, you adopt it. There's partial convergence among good developers, but they all come with their own opinions too.

There are only so many personalities, but there are endlessly many different sets of values.

As long as two or more really good developers are on the same team working closely together, they are able to negotiate a truce. They stake out their respective hills to die on, and they figure out where and how they can agree to disagree, and they assert themselves in the places where no one else has defined a standard.

One says metaclasses are good and codegen is bad. The other says the opposite. The compromise is that neither metaclasses nor codegen will be used because they both think that this is a hill to die on. They have different opinions about how linebreaks should be handled in a function's signature, and they each write it their own way because it's ultimately not that important. (But, if one of them has to take over the other's code and become the primary maintainer, the line breaks get changed every time a function's signature needs to be updated, until finally all the remaining functions also need to have their line breaks updated "for consistency.")

When they cease working closely together, they become rivals. Their code diverges in ways that make them disapprove of each other's approach.

This is just what happens because they are both opinionated and both write code in anger. And it's fine. Actually, it's good. This is what's supposed to happen. It helps them keep a clean separation between their duties. Neither of them want to touch the other's code base anymore, and if they are going to collaborate with another new developer, they will find someone junior and impressionable who has a lot of raw talent but hasn't figured out that good developers code in anger yet, and life goes on with each team developing its own culture and its own ways. It motivates them to write better code to prove that their approach is the superior approach.

The same thing happens in music. Collaborators can come together and form their own style. Bands can sound like they belong together. The Beatles and The Velvet Underground both always sounded like coherent bands. But when Paul McCartney goes off and does his own thing and John Lennon does his own thing, there's no common ground between them anymore. Same thing with John Cale and Lou Reed. They might get back together for a concert in Central Park and find that they can recreate the magic they once shared for one night, or they might be able to set aside their difference permanently for the sake of collaboration. Most likely, they've become too unique though, at this point.

There's a widespread notion that goodness converges, and all the really smart people will find common ground they can agree on if they get together. But that's not what really happens. Goodness diverges and all the really smart people define their own world with its own culture where the set of practices only make sense in light of all the other practices.

Some of their disagreements stem from their choice of editor. Some of their disagreements stem from their preferred line width. Some of their disagreements come from the patterns of the languages that they use on the side to solve the problems that for whatever reason, their primarily language isn't particularly well-suited to address. Etc. And this whole ecosystem with all of its difference keeps growing and keeps diverging.

And this is good. This is what's supposed to happen. This creates the kind of diversity or opinion and skillset that is actually valuable instead of the arbitrary divisions created by pointless separations of duties.

So go forth, and code in anger.

No comments:

Post a Comment