Saturday, December 29, 2007

Aristotelian Metaphysics and Software Complexity

Metaphysics... it's the study of all that stuff "beyond" physics, right? Or is it that New Age-y study of crystals? I can never remember. I looked it up this weekend: the term Metaphysics was created by a first century translator of Aristotle. It's the title he gave to the untitled chapter that came after the chapter on physics. I always suspected that metaphysics contained a large amount of b.s. Ah, vindication is sweet.

Anyway, metaphysics (for better or worse) is the study of those things which transcend scientific observation. Questions like "Why does the world exist?" and "Does the world exist outside our mind?" are both firmly metaphysical questions. Have you and a friend ever discussed the possibility of the Matrix being real? Congratulations, you are an amateur metaphysicist! Time to update the resume. Go ahead and do it now, I'll wait.

Considered one way, judging software complexity is certainly not a metaphysical endeavor. We have lines of code, we have cyclomatic complexity, we have function point analysis. All of these are valid, scientific measurements of software complexity. You may disagree, but I assure you, somewhere in the world someone is willing to defend these measurements.

So let's cut straight to the point and ask, "What would Aristotle do?" And (indirectly) what does metaphysics tell us about these complexity measurements? Well... Aristotle believed that everything has a telos, an inner goal it is meant to attain. A pine cone has an inner goal of a pine tree. A coffee bean has a telos of a double latte. It is what the coffee bean was meant to be. A "Hello World" example written in Lisp also has a telos:

(print "Hello World")
It is meant to print "Hello World" to the console. The Java version is slightly longer, but does have the same telos:
class HelloWorld {
public static void main(String args[]) {
System.out.println("Hello World");
}
}
On a larger scale, most software has a telos too. It might be meant to reduce the costs of your supply chain. Or maybe it is meant to allow you to shop for pet food from your computer. And the components within a system also have a telos. The Shopping Cart is meant to allow you to track ordered, but not purchased, items. It's not hard to argue that software components have a telos; it is considerably harder to do so about humans in general!

So the first question metaphysics asks is, "What's the point of all this?" Our answer is, "to print hello world!" Easy. The second question metaphysics asks is, "What makes this so?" What makes an elephant an elephant? What makes a duck a duck? Just as importantly, what makes an elephant not a duck? And just what makes our "Hello World example" a "Hello World example"?

Aristotle makes a distinction between two things: essential properties and accidental properties of an object. The essential properties are those traits that makes a thing that thing. A black elephant is still an elephant. A gray duck is still a duck. But if your elephant has feathers and a beak, then perhaps it's not really an elephant. Accidental properties, on the other hand, describe how a thing is rather than what it is. The color brown is accidental to a duck, but feathers and a beak are pretty essential.

And what of the Hello World examples?

What essential properties must a Hello World example contain? And what is just accidental? When you compare the two examples above, it seems that they both contain some sort of print/println command and the "Hello World" string. Those traits seem pretty essential. The rest is probably all just accidental properties: the weird parentheses required in Lisp, the class declaration and main() method required in Java.

Now, considering the two examples, what parts of the examples account for the most complexity? Isn't it the exact same list we created for the accidential properties? Lisp is complex (at first) because of all those danged parenthesis, and Java is complex (at first) because of all that danged OO cruft.

Let's consider another example and stop picking on Java. The example performs some simple math 40 - (5 + 10 * 2) and prints the result

In Lisp:
(print (- 40 (* 2 (+ 5 10))))
In Groovy:
println 40 - (5 + 10 * 2)
The Groovy version does seem more essential... it more closely models the way we are taught basic arithmetic. All those parentheses in Lisp are kinda tough to look at. They are the same thing though, right? They both print 10. Right? Ummm, actually, the Groovy version prints 15. Why? Java evaluates the * operator before the + operator, resulting in 40 - 25 instead of 40 - 30. I FULLY UNDERSTAND that println 40 - ((5 + 10) * 2) would have yielded 30, just like the Lisp version. The point is that the essential complexity of operator precedence is abbreviated in the Java/Groovy version, while it is visible in the Lisp version. Abbreviated to the point of hiding it within the language.

In this example, the Lisp version is superficially more complex. It shows all the essential properties of arithmetic while the Groovy/Java version abbreviates them away to nothingness. The Groovy/Java version is therefore more complex because it is missing essential features like operator precedence. Sometimes simplicity is more verbose than complexity, as this example shows, and sometimes complexity is more verbose, as the Hello World example shows.

How can this knowledge be applied to scientific methods of simplicity like lines of code, cyclometric complexity, and function point analysis?

Lines of code clearly has nothing to do with the amount of essential properties of a software component or system. This is a very poor measure of software complexity, and the definition of a complex system as one that contains many lines of code is a bit of circular logic. You are essentially saying, the software is complex because it is large and the software is large because it is complex.

Similarly, cyclometric complexity is also a poor measurement of software complexity because it does not address any of the essential properties of the system. Breaking down a component into small methods each with low complexity does not remove any of the essential complexity. You're left with a measurement of complexity with one and only one data point - the code you wrote. There is no way to compare your solution to a solution designed differently.

Function Point Analysis is probably the closest you'll come to measuring essential complexity. Yet, it would seem possible to make two wildly different implementations of a system with x number of function points, each with a wildly different accidental complexity. I have zero experience with function point analysis, but I'm suspect.

So what are we to do?

The software you write has a telos... it was meant to do something. Simplicity is modeling a solution that contains those traits essential to the telos. Complexity is a solution that contains only accidental traits. Hiding essential traits and showing accidental traits both lead to complexity.

As for how to measure complexity, isn't it a metaphysical and somewhat subjective activity best performed by groups in design and code reviews? Can anyone scientifically measure that which is essential and accidental to a software system? Sounds like software complexity really is all about metaphysics after all!

4 comments:

Hamlet D'Arcy said...

By golly... too many years of a liberal arts education has robbed me of the knowledge of order of operations. Because of it, I tend to read everything from left to right, so I assumed that 40 - (5 + 10 * 2) was the same as 40 - ((5 + 10) * 2), when in fact, multiplication is universally accepted to be applied before addition. So I picked a bad example (yet again, sigh).

I'll stand by the idea that hiding an essential property of a system makes that solution more complex. However, Java arithmetic operators are a bad example for anyone with a decent science education.

>> I don't think this sentence is
>> correct: "The Groovy/Java
>> version is therefore more
>> complex because it is missing
>> essential features like operator
>> precedence."
>>
>> Programming languages aside,
>> following basic arithmetic rules,
>> the following are equal because
>> of operator precedence:
>> 40 - (5 + 10 * 2)
>> 40 - (5 + (10 * 2))

Unknown said...

I'm always amazed how people lose the telos of a project or application. I suspect in the battle of good software versus evil development, telos will one day match up against feature-creep.

Unknown said...

Mmm... Are you aware Leibniz has been discussed in relation to software complexity? His metaphysics was a system of algorithms. He came up with it after inventing the computer and the infinitisimal calculus.

Hamlet D'Arcy said...

@alex I never heard of this, I'll check it out!