Hey, I’ll admit it: surprises can be fun! Christmases, birthdays, even gender reveals. You can expect and look forward to a surprise at each of these.
When you’re programming? Not so much. When it comes to code, more often than not, surprises tend to be unpleasant.
Managers: this post starts off a bit technical, so if you’d rather jump to the practical points, skip down to the next heading for some more general advice.
Here’s a personal example: a few days ago I was sitting back in my comfy office chair, reading through some Python code I’d received from a friend. Everything was going well, until I saw this:
setattr(obj, "some_attribute", value)
To explain, the
setattr function is used to set an attribute on an object, and takes a string as the second argument - the name of the value to set.
This is useful if you need to set an attribute’s value based on a dynamic attribute name. Something like this would work:
for suffix in list_of_suffixes: setattr(obj, "attr_" + suffix, value)
Now if you’re not doing anything dynamic, then why would you use
setattr? As opposed to something more idiomatic, like:
obj.some_attribute = value
That’s the question I found myself asking after seeing the code in the first example. In my experience there should be no difference between the two methods. But then I reminded myself: I don’t know the ins and outs of every single Python class. Perhaps the developer had a reason for doing it that way. Or perhaps they didn’t know what they’d done wrong. Regardless, they hadn’t left any comments or explanations for why they’d included such a nonstandard implementation, so I was left guessing.
Whatever the reason, I then felt compelled to take some time to follow up with them and see what was up, hoping that they’d either “fix” it or add a comment explaining the weirdness. (For what it’s worth, in this case there was no particular reason for using
setattr. The code was changed back to the “normal” way.)
End of technical stuff – managers read on!
The point is that because of an unexpected surprise in the code I was reading, I was struck with uncertainty and had to spend some time poking around to find out why the code was written the way it was – time that I could have spent on other problems, or on reading more pieces of code. It broke my workflow, and occupied my headspace. Hardly an earth-shattering problem, but that’s the thing about small surprises – run into enough of them, and over time, they can really add up.
A car analogy
Like many software problems, this can be reduced to a car analogy.
Let’s say that every day you go to get in your car. You pull out your keys, put them in the lock, then turn it. The door unlocks, and you get in and happily drive away.
One day your friend who drives the same type of car hitches a ride with you. She watches you get in and then says, “Hey, you know you can just unlock it with a remote, right? That way, you don’t have to fiddle around with the keyhole.”
Maybe you really didn’t know and can then start using the remote; alternatively, you can explain to your friend (with a comment) that the batteries are flat, so this is the only way to do it with your car.
What causes surprises?
The causes of a lot of surprises is that you don’t know what you don’t know.
You might do things that seem to work OK, but without in-depth investigation or help from someone else with more specific knowledge, you may very well never know if there’s a better way. Even experienced developers run into issues like this.
Another cause can be incomplete refactoring. Perhaps you’re working on some code that used to do things a certain way, but you’ve found a better (more idiomatic?) way. At some point, maybe you got distracted or just plain forgot, causing you to refactor the start of the code, but not the end… leaving readers to wonder why, without being able to look into your brain and see your full reasoning.
The upsides of surprises, and avoiding them anyway
Now, there are upsides to these sorts of nuggets of code; while they do shake you out of your headspace, they might also prompt you to go looking at code changes that you’d never looked at before. Maybe you’ve just seen one of those better ways, and the subsequent documentation trawl has left you with a whole new set of tricks and techniques to incorporate into your code. Or maybe spotting someone else’s mistake has helped you to discover possible reasons why your own code isn’t working. On that basis, can’t surprises be helpful?
Well, yes and no. The issue isn’t whether or not you learn, it’s the uncertainty involved in the process. Did the other dev use a package or method that you’ve never heard of, or was this just a typo? Are there good and solid reasons for them to have made that choice, or did they not know what they didn’t know? Having to try and get into the other dev’s head is a process that can take hours, hours that don’t need to be spent that way.
The fact is that while you can learn something from these surprises, you can learn faster if they aren’t as surprising. The right comment in the right place can explain whether a string of code is the result of refactoring or updates or a new technique that you’ve just read about and tried experimenting with. If someone knows that, they can figure out whether it’s something to look into themselves, or something that can be passed over.
Is it possible to create a surprise-free environment?
Mmmmmmmaybe? The fact is that while you can preach best practices to all and sundry until your tongue falls out, there’s no good way to force everyone to apply those practices. That being said, you can reduce the number of surprises that you leave other people, and suggest that other developers do the same. Here are some of the better tricks for doing so:
Be cognisant of anything “magic” or “hacky” that you’re working with. Think if there might be a less-magical way of achieving the same result.
Comment your code. If you’re forced to do something weird, make sure to comment it to explain yourself. Even if it’s only to explain it to yourself! I can’t count the number of times I’ve gone over my old code and asked, “What was I thinking?” Make it easier to understand for both you and everyone else.
Track your workflow. If you’re refactoring your code to reflect some new changes, updates or newly-discovered techniques, go through beforehand and leave comments in your code at the points where you need to perform refactoring. It’ll make it easier to avoid missing a spot, and if you still do miss something, the next dev to look it over will at least know why the code is strange there.
Perform code reviews. Get your peers to review what you’re doing and let you know if anything is surprising. That’s how I found the example code that prompted this post. If something’s clear to you but not one of your peers, they should let you know to fix or comment it.
setattr stupid tricks
When researching this article, I wondered about use cases for
setattr without dynamic attribute names; for example, stupid characters that wouldn’t normally be allowed. For example, this implementation is illegal:
obj.attribute-with-dashes = value
Whereas this is legal:
setattr(obj, "attribute-with-dashes", value)
Of course, doing so means that you’ll then need to access the
value = obj.attribute-with-dashes # not valid value = getattr(obj, "attribute-with-dashes") # you have to do this
You can also set attributes with spaces – even empty strings are valid!
setattr(obj, " ", value) # works setattr(obj, "", value) # works too!
Note: don’t do this. Sure, it works, but it’s just bad code. Even with a comment, most devs (including me) will decline your pull request if they see this.
Like death and taxes, surprises are inevitable. But if you’re careful about your code and think about how it’s going to be read, you can cut down your surprise count and make life easier for yourself and those around you. This approach, combined with peer reviews of code, can bring about a smoother environment for you and your team.