Is Code a Language?
Whether we like it or not, the code we write today will be read by someone else tomorrow, next week, in a year… in ten years? Heck, you may even have to read it again yourself! Does the code we write do what it's supposed to do? If yes, excellent! Does the code we write communicate what it does? Even better!
Clearly, code has to do something, it must solve a problem, else why would we write it? Equally, code must communicate! It must communicate with everyone who'll ever need to read and understand it in the future. If we write code purely from the perspective of functionality, we'll find hard to read, understand and, most importantly, change.
The age-old adage, "code is read many more times than it is written" is true. Writing code that reads well (that not only works but tells the story) is an art that can be practised. In the same way we, as humans, gradually master language as we grow, our vocabulary expands and we find new ways of stringing words together to express ourselves… and so, I believe, it is the same with the code we write.
How can we approach writing code that communicates?
Call a spade a spade
"There are only two hard things in computer science: cache invalidation and naming things." Phil Karlton
My favourite variation on this quote is of course…
"There are only two hard things in computer science: cache invalidation, naming things and off-by-one errors."
So much has already been said on the topic of naming things but it bears repeating… file, directory, class, function and method names are all important. However, let's focus for a moment on variable naming: data is never just data… what is the data? What does it represent? A serialized object? An array of key/value pairs? What actually do the key/value pairs represent? Find the name!
Thinking time is so underrated in this regard. We must allow ourselves the liberty to think: try different names, ask ourselves which name best expresses the underlying meaning of the thing?
If I really can't settle on a name for something, (it is hard after all) then I will give it a temporary name (and add a todo), oftentimes it will become clear as I work on the feature and it becomes obvious. Colleagues can also help. We often find ourselves chatting about how to achieve something in code but let's not forget to include naming in those discussions!
An obvious point (and closely related to naming things), if a spade becomes a shovel, it's no longer a spade. Avoid mutation. Not only can it cause some tricky-to-debug issues, but it causes confusion and increases the mental load on the reader.
Express intent with abstractions
Abstractions can be helpful providing they're commonly understood. Sometimes that's hard to gauge. In programming, we have a rich library of well-documented design patterns that we can draw from to help express intent in our code. Why are design patterns useful? Not only because they're patterns set out in order to achieve some outcome but because they're recognisable and commonly understood. We can have a conversation using a shared vocabulary of terminology without ambiguity.
I recently came across code that took some JSON as its input and, treating it as a string, removed the first two and last two characters from the JSON as follows…
Pretty simple code, right? If you can remember the parameter order of str_replace(), you're good, perhaps your IDE helps there. And then there's the mutation, kill it. Kill it with fire!
Joking aside, what's going on here? Ignoring the fact that square brackets represent an array in JSON, so calling json_decode() without removing the square brackets would be absolutely fine, let's take a moment to think about it…
So there's a token, which is inside two, matched square brackets.
Are we trimming the string? Kind of, but to trim something usually means trimming just one end of the thing… think haircut.
Are we unwrapping? Kind of. There's a token in the middle, wrapped in two square brackets. Maybe we're unwrapping the token.
There's something about the square brackets acting as delimiters of the start and end of the token…
We're removing the outermost characters, perhaps we're peeling the token? Disregard…
I'm beginning to think about food, about vegetable preparation… carrots specifically. You top-and-tail them, cutting off both ends of the carrot before boiling them… does that express what's going on here? Is it a good abstraction? Hmm, to top-and-tail is also used to refer to a sleeping arrangement…
You get the point, let's go with the idea of unwrapping the token, now taking this a step further, how could we express this intent in PHP code?
Well, a comment could work, right?
// Unwrap the token by removing the square bracket delimiters.
Or, we could use a closure…
Then, when we need to unwrap a token, it's clear what the intent is…
I find the use of closures useful for expressing intent, even if I end up removing the closure later. I find it helps to give a name to a thing, a process, an operation, or something I am doing in my code.
I hope this article has given you some food for thought. I'd love to hear about how you express intent in your code in the comments. If you think I should get out more, let me know!
Would you use closure to express intent? Would you use rtrim() and ltrim(), perhaps even substr() in the code example above? Let's talk!