Showing posts with label philosophy. Show all posts
Showing posts with label philosophy. Show all posts

Friday, March 14, 2008

Fallacy of the Excluded Middle

I spent a lot of time with arguments; mostly reading others', frequently discussing them with coworkers, and periodically posting my own. Closures, statics, inner classes, design patterns... little bits of evidence are sprinkled around the Internet, and I enjoy tracking them down, compiling them, and merging them with others. If I consider enough arguments for or against a topic, say inner classes or statics, and I work to gain experience in that area, eventually I can abstract out a general rule or perhaps some design paradigms to follow. I sometimes call this "insight", or sometimes just say "learning".

After reading so many articles about software, have you ever stepped back from the facts and opinions presented and instead studied how an author argues a point?

Which leads to another meta-question: can you reject an author's thesis based on how it is argued, while completely ignoring the facts presented?

Consider "P ∨ ¬P". This is one way to argue a point. “P” is a premise, a statement, a conjecture. Closure complexity will ruin Java. The static keyword makes testing impossible. Inner classes overload the charter of the enclosing class. “¬P” means not-P. Closures complexity will not ruin Java. The static keyword does not make testing impossible. Inner classes do not overload the charter of the enclosing class. And the “∨” is just or. So "P ∨ ¬P" means P or not-P, that some statement is either true or not true. This is the law of the excluded middle and traces back to Aristotle.

This is how I argue against socialized medicine. “Do we want the people in charge of Hurricane Katrina or the Walter Reed hospital scandal in charge of the entire health care system?” My P is “Government incompetence leads to failure”, and my argument is that this statement is either true or not true. Either you accept the statement and are against nationalized health care, or you reject the statement and are completely naïve.

This is also how I argue against using the static keyword in Java. I recently told some people never to use the static keyword because “the static keyword destroys testability”. I believed this so much that I ended up creating instances of classes holding read-only variables that never change over the history of our business. Public constants anyone? No way, that would mean using static! In hindsight, my design was pretty silly.

Arguing against over-use of inner classes is another place I employ this logic. I encountered a few classes that were, in my opinion, entirely too long, and each had several inner classes inside of them. The rationale I was given was that classes should be inner if they are only ever used within that enclosing instance. My response was that the result of applying this rule would transform any Java application into a single .java source file with thousands of inner classes. "P ∨ ¬P". Either the rule is true or not true.

The truth about these three scenarios lays somewhere in the middle. The government is rife with incompetence, but that doesn't doom a national health plan (in and of itself, that is). The static keyword is a barrier to testability, but that doesn't mean it isn't without its uses. Inner classes too serve a purpose, but knowing when and where to use them is a bit of a fine art.

In general, arguments that rely on the law of the excluded middle are tenuous. The author may be making a true statement, but it's probably not true by virtue of the reasons given. You can't reject the author's thesis, but you probably can reject the reasons given. You can be on the lookout for this line of reasoning in your readings, but for me the real insight comes when you're looking at your own thoughts. What are the things you hold to be true that come from reason like this?

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!