It turns out I was wrong in the last post, at least if the early returns from the second experiment are to be believed. Luckily, the scientific method allows for wrongness and is even so kind as to provide a means for correcting it. I hypothesized that time to comprehend would vary at a higher order with cyclomatic complexity than with lines of code. This appears not to be the case. Hey, that’s why we are running the experiments, right?
By the way, as always, you can join the experiment if you want.
You don’t need to have participated from the beginning by any stretch, and you can opt in or out for any given experiment as suits your schedule.
Results of the First Experiment
Recall that the first experiment asked people to record time to comprehend for a series of methods that varied by number of lines of code. To keep the signal to noise ratio as high as possible, the methods were simply sequential arithmetic operations, operating on an input and eventually returning a transformed output. There were no local variables or class level fields, no control flow statements, no method invocations, and no reaching into global state. Here is a graph of the results from doing this on 3 methods, with 1, 5, and 10 logical lines of code.
So as not to overburden anyone with work, and because it’s still early, the experiment contained three methods, yielding three points. Because this looked loosely quadratic, I used the three points to generate a quadratic formula, which turned out to be this.
It’s far from perfect, but this gives us our first crack at shaping time to comprehend as something experimental, rather than purely hypothetical. Let’s take a look at how to do this using NDepend in Visual Studio. Recall all the way back in the second post in this series that I defined a metric for time to comprehend. It was essentially a placeholder for the concept, pending experimental results.
1 2 |
// Seconds to Comprehend a Method JustMyCode.Methods.Select(m => new { m, m.Name, Seconds = m.NbLinesOfCode }) |
All we’re doing is setting the unit we’ve defined, “Seconds,” equal to the number of lines of code in a method. But hey, now that we’ve got some actual data, let’s go with it! The code for this metric now looks like this.
1 2 3 4 5 6 7 8 9 10 |
// Seconds to Comprehend a Method JustMyCode.Methods.Select( m => new { m, m.Name, m.NbLinesOfCode, Seconds = (1.729 * m.NbLinesOfCode * m.NbLinesOfCode + .2267 * m.NbLinesOfCode + 6.644) } ) |
I’ve spread on multiple lines for the sake of readability and with a nod to the notion that this will grow as time goes by. Also to note is that I’ve included, for now, the number of logical lines of code as a handy reference point.
Exploring CQLinq Functionality
This is all fine, but it’s a little hard to read. As long as we’re here, let’s do a brief foray into NDepend’s functionality. I’m talking specifically about CQLinq syntax. If you’re going to get as much mileage as humanly possible out of this tool, you need to become familiar with CQLinq. It’s what will let you define your own custom ways of looking at and reasoning about your code.
I’ve made no secret that I prefer fluent/expression Linq syntax over the operator syntax, but there are times when the former isn’t your best bet. This is one of those times, because I want to take advantage of the “let” keyword to define some things up front for readability. Here’s the metric converted to the operator syntax.
1 2 3 4 5 6 7 8 9 |
// Seconds to Comprehend a Method from m in JustMyCode.Methods select new { m, m.Name, m.NbLinesOfCode, Seconds = (1.729 * m.NbLinesOfCode * m.NbLinesOfCode + .2267 * m.NbLinesOfCode + 6.644) } |
With that in place, let’s get rid of the cumbersome repetition of “m.NbLinesOfCode” by using the let keyword. And, while we’re at it, let’s give NbLinesOfCode a different name. Here’s what that looks like in CQLinq.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Seconds to Comprehend a Method from m in JustMyCode.Methods let loc = m.NbLinesOfCode select new { m, m.Name, Lines = loc, Seconds = (1.729 * loc * loc + .2267 * loc + 6.644) } |
That looks a lot more readable, huh? It’s now something at least resembling the equation pictured above. But there are a few more tweaks we can make here to really clean this thing up, and they just so happen to demonstrate slightly more advanced CQLinq functionality. We’ll use the let keyword to define a function instead of a simple assignment, and then we’ll expand the names out a bit to boot. Here’s the result.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Seconds to Comprehend a Method from method in JustMyCode.Methods let loc = method.NbLinesOfCode let LengthFactor = new Func<IMethod,double?>(m => 1.729 * loc * loc + .2267 * loc + 6.644) select new { method, method.Name, Lines = loc, Seconds = LengthFactor(method) } |
Pretty darned readable, if I do say so myself! It’s particularly nice the way seconds is now expressed — as a function of our LengthFactor equation. As we incorporate more results, this approach will allow this thing to scale better with readability, as you’ll be able to see how each consideration contributes to the seconds.
So, what does it look like? Check it out.
Now we can examine the code base and get a nice readout of our (extremely rudimentary) calculation of how long a given method will take to understand. And you know what else is cool? The data points of 8.6 seconds for the 1 LLOC method and 51 for the 5 LLOC method. Those are cool because those were the experimental averages, and seeing them in the IDE means that I did the math right. 🙂
So we finally have some experimental progress and there’s some good learning about CQLinq here. Stay tuned for next time!
Comments:
Comments are closed.