Over the last two Let’s Build a Metric installments of this series, I’ve talked about different ways to count lines of code and about ways to count different paths through your code. So far, I’ve offered up the hypotheses that more statements/lines in a method means more time to comprehend, and that more paths through the code mean more time to comprehend. I’ll further offer the hypothesis that comprehension time varies more strongly with complexity than it does with lines of code.
I do have results in for the first hypothesis, but will hold off for one more installment before posting those. Everyone on the mailing list will soon receive the second experiment, around complexity, so I’ll post the results there in an installment or two, when I circle back to modifying the composite metric. If you haven’t yet signed up for the experiment, please do so here.
More Parameters Means Harder to Read?
In this post, I’d like to address another consideration that I hypothesize will directly correlate with time to comprehend a method: parameter count. Now, unlike these last two posts, parameter count offers no hidden surprises. Unlike lines of code, I don’t know of several different ways that one might approach counting method parameters, and unlike cyclomatic complexity, there’s no slick term for this that involves exponential growth vectors. This is just a matter of tabulating the number of arguments to your methods.
Instead of offering some cool new fact for geek water-cooler trivia, I’ll offer a relatively strong opinion about method parameters. Don’t have a lot of them. In fact, don’t have more than 3, and even 3 is pushing it. Do I have your attention? Good.
Whenever I’m working with and coaching teams, this point inevitably gets push-back. If you’ve been around a while like me, you might be thinking back on man pages and OS primitive calls that involved a dozen ref parameters. Hey, if the guys writing OS did it, it’s good enough for us, right? Or maybe you’re thinking of a Java or C# API that you’ve dealt with. Whatever the case may be, we’ve made and survived calls like this no worse for the wear. So, what’s the harm?
Well, if I’m right in my hypothesis, the harm is that high parameter counts increase time to comprehend and thus, at a minimum, cost extra money in developer salary. (High time to comprehend, I suspect, also leads to higher defect rates since developers may be impatient and act before really understanding). And there’s an intuitive understanding of the world around us that makes this at least seem plausible.
Thinking about Methods Expressed in English
If you’ve never done so before, think of the OOP, and thus C#, world in terms of nouns and verbs. Classes are nouns, and methods are verbs. Further, and without muddying the waters too much, consider that, in English, at least, there are different categories of noun: subject, direct object, indirect object. The subject uses the verb to do things to the object. In the phrase, “Erik started the car,” “Erik” is the subject, “started” is the verb, and “car” is the object.
When we construct classes and methods, we’re actually constructing implied sentences. _dataAccessObject.Insert(customer) maps in our world to “the data access object inserts a customer.” Now, to make things sufficiently murky, it’s not always clear whether our classes are the subject, such as in that sentence, or the object, such as in the case of _calculator.PushButton(1) where the subject is whoever invokes the method. But, notwithstanding that ambiguity, we naturally map method calls to English-y sentences.
The examples from the last paragraph are pretty straightforward, and I’d argue that this is because they map nicely to natural language sentences. But what starts to happen as we add more parameters? What if we have _recordManager.Merge(record1, record2)? Well, not that big of a deal. The record manager merges the first record and the second record. And in the case of _recordManager.Sort(record1, record2, record3) we can just say that the record manager sorts record 1 and record 2 and record 3. Still understandable, but notice the complexity starting to creep in? Notice how this starts to sound more and more stilted when said aloud?
Let’s make a jump now to this: _recordProcessor.Process(record1, record1.LineCount, record2, record2.LineCount, record3, record3.LineCount, true, false, -1). Now, I know what you’re thinking — you need to see the method signature to understand those boolean and integer flags. For the sake of discussion, let’s say that they’re flags for “merge the records and store in the database,” “log processing results” and “default record count or -1 if you don’t want default.” Do you want to go ahead and try to write this as a sentence in English? I sure don’t. And if this seems contrived, ask yourself how often you talk in plain language and have a subject or object that’s a compound clause with 9 different components.
If I can’t begin to describe what the method does in English, you can bet that understanding what it does is going to take a while. I’ll close out this installment of the series with a video on two different ways to look at method parameter counts in your code base using NDepend. Stay tuned for an upcoming experiment that involves the impact of method parameters on time to comprehend.