On Friday, Tom Crockett went to twitter to declare:
The demand that all code be readable by a beginner is the demand that all proofs be elementary. It limits our ability to use powerful tools— Tom Crockett (@pelotom) April 4, 2014
I think that’s a shame.
I feel this is an unfortunate and flawed way of thinking about readable code.
Complex != Powerful
Writing code that is readable does not in any way imply that the code must therefor be elementary, and writing complex code does not somehow indicate it’s power level. On the contrary; the more complex the concepts in the code the greater the need there is to provide an abstraction so that we can reason about it and therefor leverage its power to its greatest potential. The reason we can use the “powerful tools” that Tom mentions in the first place is because their complexity has been masked into a simpler abstraction; thus exposing their power as a tool.
Warning: Math Ahead
Let’s look at the math analogy a little deeper because it’s an interesting one.
Quick! Solve for ‘x’: (I won’t blame you if you skip ahead)
Don’t have the time? Ok what about this one?
Turns out those 2 equations are exactly the same. The latter is simplified. Did the simplification some how limit the power of the more complex version? Absurd. It simply made it easier to chew off.
Ok that’s factoring out complexity; what about abstraction?
That’s pretty awful. Can we make an abstraction? Yes.
Fine, you say, but that could be considered ‘elementary’. Not important. Ok what about this abstraction?
\[ \pi \]
That one is pretty important, with arguably complex math behind it. Did the fact that we abstracted that knowledge behind a symbol somehow make it not a powerful tool?
Code is simplified for exactly the same reason and purpose that math is simplified. To allow us to reason about concepts at a high level by reducing the amount of knowledge one must have to understand something at a glance. It is a waste of developer time to sit and figure out what complex code is doing just because the author couldn’t be bothered to factor out the complexity into manageable parts.
Recently I wrote code for a sorting problem. At the time I didn’t realize it but it’s a called the “Dutch Flag Problem” and Dijkstra came up with it back in 1976 in his book “A Discipline of Programming”.
The problem is as follows:
Given an array known to contain only the numbers 0, 1 and 2 sort it in O(n).
So for example if you had the input:
Then the output would be:
The time complexity requirement rules out the possibility of using an untuned general sort from most languages standard library (most sorts of that sort are O(n log n) or thereabouts).
There are actually 2 ways of solving the problem.
The first method of solving it is sort of cheating but is simpler; Just iterate the array and maintain a hash of (digit => count). This will get you the information you’re after but you didn’t really sort the data. (In a real world scenario this is probably sufficient though since you probably aren’t going to actually want to iterate the same digit N times if you know there are N of them in the array.)
The 2nd method of solving the problem is the more interesting one; Given that we have known constraints on the incoming data we can develop an algorithm specifically tuned for the situation. The problem becomes one of partitioning:
1 def sort(data) 2 return nil if !data 3 answer = data.dup 4 return answer if answer.length <= 1 5 6 left = 0 7 mid = 0 8 right = answer.length - 1 9 10 while mid <= right 11 suspect = answer[mid] 12 if suspect == 0 13 answer[mid], answer[left] = answer[left], answer[mid] 14 left += 1 15 mid += 1 16 elsif suspect == 2 17 answer[mid], answer[right] = answer[right], answer[mid] 18 right -= 1 19 else 20 mid += 1 21 end 22 end 23 24 answer 25 end
The general idea here is that we iterate through the array once and during that iteration if the current element under inspection is a 0 then we kick it to the left just beyond where a 0 was last placed. Otherwise if it’s a 2 then kick it to the right just before where a 2 was last placed. In the end you get the partitioned data.
Tonight was the monthly Seattle.rb meetup, held monthly on the first tuesday of the month at Substantial’s office in Seattle’s Capitol Hill neighborhood. Something occurred to me while watching tonight’s talks; It seems obvious now but the talks that I took the most away from were generally the ones that used comedy to educate.
I come from the .NET world (I’m new to the Ruby world) and one of the louder figures from that world is Scott Hanselman and he definitely uses comedy to make his talks more interesting. I’ve met Scott a few times at local Nerd Dinner meetups and his comedy is actually just part of his personality. I suspect others who give talks don’t have this intrinsic comedy personality but try their best to include it because it really is a great way to keep your audience engaged.
It’s 2013 so I hope most of you by now believe in having clear method/class/function/variable names, but in case not I present to you the following arguments.
Why Is Naming Important?
The point of naming is abstraction for humans. Naming is not abstraction for compilers because the compiler isn’t going to care about names at all. Naming is not just some procedure invented by language designers to make you box up your code. I’ll say it again: The point of naming is abstraction for humans.
Let’s say someone on your team submits a code review to you and you open it up and find this method call:
Your teammate probably didn’t give this line of code a 2nd thought. Why would he? To him it’s entirely obvious what’s going on because he’s the one who wrote the code. But is it obvious to you? Do you know what ‘process’ means in this case? What are we doing to ‘foo’? What is being done to the system with our ‘foo’ that we passed in?
The problem with the above code is that it failed to abstract the inner concepts from humans. The practical implication of this poorly named method is that you and every other programmer who comes across it will have to dive inside it to understand what it’s doing. The method name wasn’t clear so you don’t know what you’re getting into when you call it. It’s as if there is no difference to having no method at all and just inlining it’s contents.
What Great Names Do
What if the above code instead looked like this:
Do you need to dive into the method to understand it now? No, because the abstraction is clear. You don’t need to know the specifics of the internals of the method because the person who wrote it wanted you to understand what was going on by expressing it in a clear name. Time is saved because you now have less surface area of the codebase to grok and the implementation of that method is free to be refactored so long as it upholds the contract.
Lot’s of people bandy around this idea of Technical Debt which sounds great because it lines up to the metaphor of using credit wisely. It’s become such a common phrase that we hardly even think much about its true meaning and expect everyone to nod in agreement at its wisdom. But what does technical debt really mean? Does it mean that it’s ok to write a bunch of crappy code and add a bunch of complexity because “we’ll clean it up later” ? That’s the commonly accepted meaning. I think that’s wrong.
Let’s look at the analogy of credit usage again. When you use your credit card, do you just spend it irresponsibly on a bunch of stuff you ought not to? Do you charge that on your credit card do you just say “I’ll clean this up later” ? Well you might if you’re used to making poor financial decisions, but if you are smart you make sure that you are using your credit for wise purchases and that you’ll actually be able to pay that debt off quickly (because you know carrying a balance forward is really bad for your credit score). From the time you incur the debt until the time its payed off you are extremely diligent about paying it to zero, and you have an active plan for doing so.
Are there parallels that we can see in software? I think so. If it’s not OK for the person who has maxed out their credit cards on bad purchases, is it ok for you to do the same with your code? I don’t think so. All you’ll end up doing is bankrupting your code base (and who knows, as a startup potentially your business).
(c) 2014 Ben Lakey
The words here do not reflect those of my employer.