|
 |
Home > Topics > Test & Evaluation > Detail: The Accidental Complexity of Logic
 | |  |  The Accidental Complexity of Logic
 By Kevlin Henney

  
 Summary: Much code complexity and no small number of program defects can be traced back to confusion over logical expressions and the expression of logic. Find out how you can get that complexity under control. |  |  |
|
 | Logic lies at the heart of computation. Boolean algebra guides much of the course of a program's flow. It is the stuff of programming. It is also, as Kernighan and Plauger noted in The Elements of Programming Style, the source of occasional confusion: "Boolean algebra is not used nearly as widely as ordinary arithmetic, so we must write logical expressions more carefully lest we confuse the reader." They wrote this thirty years ago, but our ability to be confused has not lessened any in that time.
Much code complexity, many programming thinkos, and no small number of program defects can be traced back to confusion over logical expressions and the expression of logic. In spite of our best aspirations and the relative simplicity of Boolean algebra, this is not the kind of thinking that comes instinctively to humans--even programmers.
In many ways this state of affairs can be considered ironic. In formulating the calculus that bears his name, George Boole was pursuing an ambitious goal, as indicated in the title and opening of his 1853 work, An Investigation into the Laws of Thought: "The design of the following treatise is to investigate the fundamental laws of those operations of the mind by which reasoning is performed."
In practice, although we use Boolean logic correctly and effectively much of the time, the shortfall between the way we think and the needs of code presents sufficient opportunity for incorrect and unruly code to creep in.
Dead Code Walking
Consider the fragment in listing 1a, which is based on some code I saw a few years ago.
Because the body of the loop is unreachable, this becomes a rather longwinded way of writing the fragment in listing 1b.
Given the chaos and low quality of the project as a whole, it is exceedingly unlikely that the illusory, lines-of-code productivity boost that the dead code gives was intentional.
Omitting a not, or including a surplus one, or using an or where an and was meant, or vice versa, are common examples of errors that take code down the wrong path. Unit tests can be a great help in such situations: Staring at a piece of code for too long, you may possibly fool yourself into thinking that your inverted logic is correct, but a simple assertion is less easily duped. Of course, in the project in question there was no coherent form of testing--manual or otherwise--at any level, and because of the application's high coupling, unit tests would have been pretty much impossible. Code review is a complementary way of uncovering such defects and would have been a suitable alternative. Code reviews do not have to be formal, and an informal pair walkthrough is often less time consuming and less threatening than walking through code in a meeting with many others.
Seeing the Forest for the Trees
Simple thinkos are one source of error, but another comes from accidental complexity--making things more complex than necessary so that it becomes difficult to see what is going on. Consider the fragment in listing 2a, which is based on code I ran across (well, tripped over) many years ago.
The code didn't start out this way. It probably made sense originally--and even after a change or two--but eventually an addition led to a contradiction, which means the code reduces to the snippet in listing 2b.
Or, more concisely, to the code in listing 2c.
Signal to Noise Ratio
But unclear logic is not just about defects and dead code. Code can be functionally correct but developmentally poor. Clumsy logic also introduces accidental complexity, making the process of reading code more labored than it should be. The code in listing 3a includes a redundant check.
In this particular case, another programmer suggested the "simplification" shown in listing 3b, but it is not exactly an improvement.
Instead, for such a simple case, group conditions that have a common result, as shown in listing 3c.
Should the condition become longer than is either reasonable or readable, extract and name a method that represents the whole condition rather than disassembling the logic into an if else tree.
Boolean-to-Boolean Converters
One particularly common and noisy kind of construct is what a conference attendee I once met referred to as the "Boolean-to-Boolean converter," a name that nicely sums up the redundancy of such constructs. These are often characterized by extensive use of true and false literals and control flow structures. They all can be reduced to much simpler expressions. Here is a typical example of an identity converter:
if(found)
return true;
else
return false;
Which is nothing more than:
return found;
And here is an elaborate negation:
if(enabled)
enabled = false;
else
enabled = true;
Which is sometimes written slightly more compactly as:
enabled = enabled ? false : true;
This still reduces to something simpler and more direct:
enabled = !enabled;
Listing 4a is a slightly more nested example.
Its jagged formation still reduces to something briefer and more direct, as shown in listing 4b.
Sometimes a truth is established on one line, only to be restated using literals in repetitive form across subsequent lines as shown in listing 5a.
Listing 5b demonstrates that both the decision structure and the use of the Boolean literals are unnecessary.
Perhaps one of the most pervasive examples of a Boolean-to-Boolean converter is the explicit comparison of Boolean results against Boolean literals:
if(checkCacheExists() == true)
...
The result of explicitly comparing a Boolean against true makes it no truer than it already was. Such tautological phrasing is often a consequence (or a cause) of imperative names that should follow a more predicate-like naming style. Rather than naming the action, name the truth that is being
acted on:
if(cacheExists())
...
Names beginning with check, validate, and verify are typical candidates for such renaming.
Flag Waiving
Overreliance on flags leads to code with a lot of raw Boolean literals--often an indicator that logic can be revisited and simplified. Consider the example in listing 6a.
It simplifies to listing 6b.
Similarly, listing 7a represents an impressive attempt to mask a one liner.
The separation of declaration from initialization introduces
more noise on top of the flag-driven logic. From all this we can extract the delightfully brief listing 7b.
Conclusion
In each of these cases it is worth keeping in mind that it is not necessarily ignorance or sloppiness that has led to unnecessary or incorrect logic, and it has nothing to do with the programming language--the examples may have been presented in Java, but they were based on published and production code in a variety of languages. Nevertheless, to paraphrase the author Martin Amis, it is terrible to see a programmer being beaten up by a programming language. Revisit, review, and revise. Truth (or falsehood) will out. {end}
What examples of logic have you seen that, on
revisiting, have proven to be either incorrect or reducible to something far simpler?
Join the conversation below or start a new one in the Member Comments section.
About the Author Kevlin Henney is an independent consultant and trainer based in the UK. He provides consultancy and training in programming techniques, software architecture, and development process. He is co-author of two recent books on patterns, A Pattern Language for Distributed Computing and On Patterns and Pattern Languages.
Back to Top
|
|
|
|
|
|
|
Testing Training Courses
Software Testing Certification, Systematic Software Testing, Test Management, Mastering Test Design, Just-in-time Testing
Software Engineering Training
Mastering the Requirements Process, Requirements Modeling, Introduction to the Capability Maturity Model Integration, Business-Driven Software Measurement
Agile Software Development Training
Scrum Master Implementation Workshop, User Stories and Estimation in Agile Development, Design Patterns Explained, Practical Test-Driven Development
|
|
|
|
 |
| 
 | Census: Web-based Bug Tracking and Defect Tracking Track software bugs, defects, enhancements, support calls, and more. Issue tracking software that is scaleable, fully customizable and integrated with VSS. Includes e-mail notifications, role-based workflow, change history, and Crystal reporting.

|  | Web based bug tracking - AdminiTrack.com AdminiTrack offers an effective web-based bug tracking system designed for professional software development teams.

|  | Check Out IT Certification Preparation Materials Sign Up With SkillSoft & Get Access to Training Materials for Over 50 Professional Certifications.

|  | The Very Best in Pairwise Test Testcover.com - Compare our test case efficiency, response time, and ease of use. Simply the best!

|  | Bug Tracking On-Demand Looking for the reliable, convenient, secure and completely web-based issue tracking system? BUGtrack allows unlimited number of users, projects and bugs as well as unlimited customer support for a low flat rate.

|
|  | Get your product or service listed here.
|  |
|
|
|
|
 |