December 07, 2008

Exceptions

Before Object-Oriented programming (OOP), error conditions were commonly reported via a return code, an OS signal, or even by setting a global variable. One of the most useful notions introduced by OOP is that of an 'exception' because they drastically reduce the mental load of handling error cases.

In the old style of error handling, any time a function was called which could result in an error, the programmer had to remember to check whether an error occurred. If he forgot, the program would probably fail in some mysterious way at some point later down the line. There was no mechanism built into the language to aid the programmer in managing all the possible error conditions. This often meant that error handling was forgotten.

The situation is much improved with Exceptions, primarily because they offer a fool-proof way to ensure that error handling code is invoked (even if it is code for reporting an unhandled exception). This both makes it unnecessary to remember to check for errors, and it increases the cohesion of such code (i.e. it can be gathered into 'catch' blocks instead of mixed in with the logic of the function). Both of these help preserve the Unit Economy of the author and reader of the code.

Unfortunately, despite being such a useful innovation, exceptions are often abused. We've all seen situations where one must catch three different exceptions and do the same thing for each. We've all seen situations where only a single exception is thrown no matter what goes wrong, and it doesn't tell us anything about the problem. Both ends of the spectrum reflect a failure to use exceptions with the end user of the code in mind.

When throwing an exception, one should always keep two questions in mind: "Who is going to catch this?" and "What will they want to do with it?". With this in mind, here are a number of best practices I've seen:

Each library should have a superclass for its exceptions.

Very frequently, users of a library aren't going to be interested in what specific problem occured within the library; all they're going to want to know is that the library either did or didn't do its job. In the latter case, they will want the process of error handling to be as simple as possible. Having all exceptions in the library inherit from the same superclass makes that much easier.

Create a new subclass for each distinct outcome.

Most often, exception subclasses are created for each distinct problem which can arise. This makes a lot of sense to the author, but it usually doesn't match what the user of the code needs. Instead of creating an exception subclass for each problem, create one for each possible solution. This may mean having exceptions to represent: permanent errors, temporary errors, errors in input, etc. Try to consider what possible users of the component will want to do with the exception, not what the problem originally was.

Remember that exceptions can hold data.

In most languages, exceptions are full-fledged classes, and your subclasses can extend them like any other parent class. This means that you can add your own data to them. Whether it is an error code for the specific problem, the name of the resource which was missing, or a localization key for the error message, including specific data in the exception object itself often is an invaluable means for communicating data which would otherwise be inaccessible from the 'catch' block where the exception is handled.

What other tips and tricks would you suggest?

November 23, 2008

How Clean is Your Log?

Think back on the last time you urgently needed to figure out what a live, production piece of software was doing. It's very likely that nearly the first thing you did was to pull up it's log. It's also very likely that the majority of the content was either useless, unintelligible, or flat-out misleading.

The reason most logs fall into this state is that they are a piece-meal accumulation of statements which were thrown in over time. This is a problem because the only possible reason to waste the computer's resources producing them is to be as correct, intelligible, and useful as possible. In short, log files should be explicitly written for consumption by people.

As with anything else, clear writing requires the author to carefully select the content, and to create it with the reader in mind. In the case of log files, we know other engineers (or scripts they write) are going to read them, and they are going to want to know how the server has been behaving. Moreover, no one reads log files unless something is going wrong. Therefore, log files should be written to make it as easy as possible to debug why a server isn't behaving as it should. To that end, here are a few specific guidelines I've found most useful:


Make it easy to track down errors

It should go without saying, but it is imperative to make it as easy as possible to identify where - in the code - an error occurred. In Java this may be printing out the stack trace; in C/C++/Objective-C, this may be using the __FILE__ and __LINE__ macros. Most languages have some similar feature. Along these same lines, try to avoid having logging statements which look too much alike. This makes it easy to misinterpret the log, and completely identical statements make it impossible to know exactly where an error occurred.


Log all inputs and outputs

The fastest way to track down the reasons for strange output is to know whether the input was strange, too. This is especially true for a server (i.e. a malformed request), but it applies to other programs as well, provided you have a sufficiently broad view of "inputs" and "outputs".

I consider an input/output to be any data which enters (or leaves) the program's runtime context. This includes: user input (mouse/keyboard events and the like), files read/written to disk, data sent/received on a socket, or requests/results from a database query. I recommend at least logging something every time such an input or output occurs, and if reasonable, the actual content itself.

Logging inputs and outputs provides a number of major benefits. First, you know exactly what the system was asked to do. This allows you to immediately know whether a problem is in your program or outside of it. Second, it gives you a pretty clear indication of what will reproduce a problem (if there is one). Finally, it allows you to report errors to the owners of the "other" system in case you received an unexpected input.


Record how long it takes

Errors are frequently just that a system is taking too long to respond. Being able to quantify the time required for each request is an extremely valuable way to isolate the problem. However, it is not sufficient to simply record how long your system took. You must also record timings for each request you make outside of your application's runtime context. That way, you'll know right away if your program appears slow because it's waiting for something else: whether it's a web service call or reading a file from disk.


Don't log the expected case

If something is supposed to happen, you generally don't need to know about in the log (unless it's one of the things mentioned above). Bear in mind that no one is interested in reading a program's log unless the program is misbehaving. At that point, the person wants to read as little of the log as possible. Including a lot of "business as usual" statements in the log only makes the process more difficult.


Use log levels to indicate action required

Since a log file's purpose is to assist in figuring out why a program isn't running correctly, it's extremely valuable to use log levels to indicate how "badly" things are going. I recommend thinking of the action required as a guide to what log level to use:

  • FATAL: page someone immediately
  • ERROR: send an email immediately
  • WARN: send in a daily report
  • INFO: include for later debugging purposes

Depending upon your logging system, the various levels often have different names, but these should map to whatever you use. Even if you don't actually have an automated system to page people based upon your log file, it's extremely clarifying to think about each level in these terms.


Make it easy to parse

Finally, bear in mind that most uses of a log file involve searching or parsing them using automated tools. Be sure to include standard fields in each line so that these tools can easily extract information from them. Here are some examples of what fields you may want to include:

  • time (down to milliseconds and time zone)
  • the number of the line in the log statement (to ensure each line is unique)
  • the current thread
  • the current user ID / IP address / other client-related identifier
  • the log level
  • the file/class


There's a lot more to be said on logging, but if every log I've read implemented these few principles, my life would have been a lot easier. If there's some piece of wisdom that's too good to be missed, feel free to add it in the comments!

November 16, 2008

The Right Size for a Method

One occasionally hears of some programming zealot who swears up and down that methods should be kept to 'n' lines or less. The actual value may vary, but the arbitrary nature of the claim remains. However, there is a kernel of truth there, and it has to do with preserving Unit Economy.

We all know that the longer a method is, the more we have to keep in our minds to understand it. There are likely to be more local variables, more conditional statements, more exceptions caught and thrown, and more side-effects of all those lines of code. Furthermore, the problem grows faster and faster as there are more lines of code since they all potentially can have an impact on one another. Keeping methods short has a disproportionately large benefit.

Of course, claiming that there's some absolute "correct" number is clearly nonsensical. The same number of lines in C, Lisp, Java, Assembler, or Ruby will accomplish radically different things, and even how one legitimately counts lines will change dramatically. What does not change, though, is the need for the reader (and author) of the code to understand it as a whole. To this end, one should strive to keep the number of discrete tasks a method accomplishes to within the range of what people generally can remember at once: between one and six.

Each task within a method may have several lines of code of its own; how many tends to vary widely. Consider the process of reading a series of rows from a database. There may be a task to establish a database connection, another to create the query, another to read the values, and perhaps one more to close everything down. Each of these may be composed of anywhere from one to many lines of code.

Tasks may even have subtasks. Consider the example of building a login dialog. At some point, there is likely to be some code which creates a variety of controls, and places them on the screen (e.g. an image control for the company logo, a text field to capture the user name, etc). In the method which does this, one may consider the process of creating the components a single task which has a number of subtasks: one for each component.

In both cases, the important consideration is how organizing the method into tasks and subtasks helps preserve Unit Economy. By creating tasks which have strong Cohesion (i.e. you can name what that group of code does) and loose Coupling (i.e. you can actually separate that group of lines from the others), you give the reader ready-made abstractions within your method. In the first example, the reader can readily tell that there's a section for setting up the connection, and be able to mentally file that away as one unit without the need to remember each line of code in it. In the latter example, the reader can categorize the series of UI element creation subtasks as a single "build all the UI components" task, and again be able to abstract the entire thing away under a single unit. Even if there are a dozen or more individual components, it still can be considered a single task, that is, a single mental unit.

This ability to create abstractions within a single method is why there is no absolute "right" size for a method. Since grouping like things into tasks and subtasks preserves the reader's (and author's) Unit Economy, it is quite possible to have a method which is very long in absolute terms, and still quite comprehensible. It also implies that a fairly short method can be "too long" if it fails to provide this kind of mental structure. The proper length will always be determined by the amount of units (tasks) which one has to keep in mind, and the complexity of how those tasks are interrelated.

November 09, 2008

Dependency Injection

In my last post, I spent a good deal of space ranting against the Singleton pattern, and at the end, I promised an alternative. The one I had in mind was Dependency Injection (if you're not familiar with the term, I highly suggest you read the linked Wikipedia article before proceeding).

There are a large number of benefits to Dependency Injection (DI), but I'm going to focus on a few which fall under the categories of improved Cohesion, and improved Coupling.

Improved Cohesion

The strongest cohesive benefit of DI is that it greatly encourages thinking of objects as self-sufficient, stand-alone, software components. Each one must explicitly declare what settings it can accept (i.e. its configurable attributes), what other objects it interacts with (i.e. its dependencies), and what services it provides (i.e. its public methods). Since objects must be configured from the outside, they must provide all the necessary attributes and methods to do so. This requires the author to consider exactly what the function of the object is, and to consider what its public interface should be. The thinking process involved in DI leads to stronger cohesion in the individual objects.

Another major cohesive benefit is that objects don't need to include code to configure themselves. A substantial amount of code in non-DI objects tends to be for looking up configuration values (key-value pairs from files, structured data from a database, object references from a JNDI store, etc). The parsing and handling of this information is generally not the purpose of the class; it is merely an incidental price to make the object sufficiently flexible. By injecting such values from the outside, the class becomes much more focused on its real function, and less on the incidentals of configuration.

Improved Coupling

The crucial and most significant benefit of DI is that objects truly only know about one another's interfaces. In non-DI code, each object may be written in terms of an interface, but somewhere it needed to create or look up an instance using some hard-coded value. This could be as simple as calling a constructor, or as complex as reading a key from a file and looking up the actual object from JNDI. In either case, the object is tied to the actual implementation of that class. In a DI class, there is no connection to the actual implementation class in any way, thereby reducing coupling back to only the shared interface.

The second benefit is simply an implication of the first: that the number and arrangement of objects becomes tremendously more flexible. Since no object ties itself to any other instance, any number of objects can be created and configured to use any combination of dependencies. Perhaps multiple instances will all share the same reference to a dependency; perhaps they will each have their own instance. It may be that one instance of a dependency has been given a version which caches responses while another does not. The fact that every object is given its dependencies (instead of creating them) provides radically looser coupling. As such, the possibilities for arranging classes is dramatically increased.

I've only scratched the surface of the value of Dependency Injection as a design pattern. If you have worked with it before, you undoubtedly already understand the tremendous benefits it provides. If not, I strongly recommend you check out a Dependency Injection framework for your next project. Once you wrap your head around it, you'll never want to go back.

November 03, 2008

Singletons Deemed Dangerous

If you ask a software developer to name a few common design patterns, nine times out of ten, you'll hear about the venerable Singleton pattern. After all, it's one of the simplest design patterns, and one of the half-dozen or so creator patterns in the Gang of Four book. Sadly, this is an incredibly over-used pattern: to the point of qualifying as an anti-pattern.

I say anti-pattern because it is most commonly used as a way of creating an object-oriented global variable. This commonly occurs when the author of the code imagines that only one of a specific resource should ever be available within the system, and he wishes to make sure there is only one instance to match. Other times, the object is needed in several places which don't have a convenient way to pass an instance around, and a singleton is used to make sure the object is always available. In any case, the net result is the introduction of what is effectively a global variable into the code.

Once in place, a Singleton causes a number of insidious problems. The most difficult is that it is impossible to change how many objects of that class there are. It is frequently the case that it seems like there should only be one instance of an object at first, but that requirements change down the road, and multiple objects are needed later on. One example of this is a database connection. Since many different parts of a program may rely on a database, and most programs only address a single database, it is tempting to make the database connection pool a Singleton. Unfortunately, this doesn't allow for a situation where multiple databases must be addressed at once, or if some of the data moves behind a remote service call instead of the database.

A second major dilemma produced by Singletons is that it is impossible to substitute an alternate implementation. Either the behavior of the Singleton is changed for everyone, or not at all. This can lead to an explosion of complexity both within the Singleton (as it is adapted and configured to suit more and more separate and conflicting needs), and around it (as other classes need to provide more and more context). In ordinary circumstances, it would be easy enough to provide a group of subclasses with a shared interface to manage this complexity. Since the nature of Singletons makes this impossible, one is stuck with more complex means of altering the class's behavior.

A third problem is that Singletons make code which uses them very hard to unit test. Since every class which uses a Singleton fetches it at will from a global, static location, it is nearly impossible to test that class without also stubbing out everything which the Singleton interacts with (in addition to whatever stubs were necessary for the class itself). In effect, one must consider the functionality of the Singleton as being part of every class which uses it, since the two are so tightly Coupled as to be inseparable.

Finally, Singletons are not at all friendly to a programmer's Unit Economy. In order to successfully alter or maintain any class, one must bear in mind how it is used by every known class. By using layers and avoiding tight Coupling, one can make this a fairly manageable task. However, a Singleton destroys this effort completely. It is inherently available to any object of any layer, and it is inherently tightly coupled to any class which uses it. Both of these things mean that modifying a Singleton is a much larger undertaking than any other sort of class because of the potentially enormous mental context required to understand the ramifications of doing so.

So, what's the solution? There really are cases where everything in the system should refer to the same instance of an object, and there really are situations where an object is needed in a lot of places that aren't really connected to one another. Fair enough... I'll discuss one excellent alternative next time.

October 26, 2008

Fail Early, Fail Loudly

One easy step to help simplify your programs is to follow the adage "fail early and fail loudly". By failing early, I mean that your code should actively look for problems and stop as soon as something wrong is encountered. By failing loudly, I mean that your code should raise the alarm in a way that makes it obvious to other parts of the system (and people reading the code) that something unusual has just occurred.

Naturally, this doesn't mean that your code should explode whenever the slightest thing is wrong. On the contrary, it means that each object/package/component in your system should demonstrate good cohesion by refusing to operate in a situation it shouldn't have enough context to do so. Instead, each component should fail when encountering something that it isn't supposed to be able to handle, and delegate responsibility for handling that situation to the caller. The caller can then decide whether it has sufficient context to handle the situation, or to pass along the responsibility to another component.

Failing early is a benefit because you can then build whole sections of your program which don't have to consider some particular error case. For example, consider building a web service which accepts objects encoded in XML as input. If all the necessary error checking is performed up front (e.g. the XML is validated against the DTD, required elements needed in the API are found to be present, and various values are confirmed to be within acceptable ranges), the remainder of the web service call can be written to assume that the request was perfectly valid. The notion of Short Circuit statements I discussed recently is another variation on failing early. All of them reduce the mental load of reading (and writing) the code which follows them.

Failing loudly is a benefit because it reduces the mental effort required to follow up on errors which occur in other parts of the system. If a component fails loudly, a caller must ensure that they correctly respond to error conditions in order to be correct themselves. The best example of this is a checked exception. In Java, any method which can possibly throw a checked exception must explicitly declare it in its signature. Calling methods are forced - by the compiler - to either to catch the exception or declare that they throw the same exception themselves. This completely radically reduces the mental effort required to follow up on the error, and provides a built-in mechanism to remind you when you've forgotten to do so.

(The fact that some API's abuse checked exceptions horrendously is a topic for another time.)

There are a number of cases where these two ideas apply, but they are especially true around where data enters a program's runtime environment. Some examples include: reading from a database, accepting user input from the mouse/keyboard, accepting an incoming TCP/HTTP connection, or simply reading data from a file. In all of these cases, doing all of your error checking as soon as the data has entered the runtime environment will allow subsequent code to assume all the data was correct.

Failing early and failing loudly can also be seen as corollaries of Cohesion and Coupling. If a component is well-integrated to a single purpose, it will not attempt to handle conditions which are outside of that purpose. Instead, it will fail as soon as it is asked to cope with such a situation. In order for a component to offer loose coupling, it must reduce the amount of knowledge needed to interact with it. One aspect of this is to make it very clear what to expect in case the component has problems. Both of these concepts lead back to preserving the Unit Economy of the reader (and author!) of the code.

Questions, feedback, suggestions? I'd love to hear it.

October 19, 2008

Class Names

Coming up with good names is one of the most challenging aspects of Object-Oriented programming. So much of the mental context required to understand a program can be greatly simplified by a well-chosen metaphor, or obfuscated completely by one which doesn't quite fit. A poorly named class can muddle an entire API by confusing which pieces of functionality belong to what class, or creating the wrong mental model in the mind of the reader. So, what are some good rules to follow when naming classes?

Think of a definition statement

In order to ensure good Cohesion, think of what purpose the class serves. If you are able to come up with a single, short, clear statement of what the class should be, it means the candidate class likely has good cohesion. If not, then you may need to merge the class into another, or split it into multiple classes. Once you have a definition statement, choose the shortest noun phrase which captures the essence of it; this is your class's name. The benefit of this approach is not only to come up with a good name; it also provides excellent documentation for the class. In addition, as you and others work with the class in the future, you can refer to the definition to decide whether some new functionality belongs in that class or in another.

As an example, consider the definitional statement for the java.io.LineNumberReader class in the Java Standard Library: "A buffered character-input stream that keeps track of line numbers." This definition draws a very clear boundary around the function of this class, and very neatly boils down to the name of the class itself.

Reuse vocabulary

Keeping in mind the principle of Unit Economy, reuse vocabulary to reduce the number of concepts in the system. When choosing class names, consider what other classes in the system are related, serve a similar function, or fit into the application's architecture in the same way. Subclasses of the same parent class often should repeat the parent class's name with a new adjective to differentiate them from other subclasses (consider the whole java.io package). Classes which play a part in a standard design pattern often should include the name of the part they play (Apple's Cocoa API uses Model, View, Controller frequently in class names). Classes responsible for validating user input may all contain the word, "Validator", to explicitly spell out its role. Reusing this common vocabulary conveys a wealth of information to the future reader by explicitly telling him how the class fits into the system as a whole.

Stick with the end user's language

Nearly all programming is done with some end user in mind, and that end user invariably has a whole set of vocabulary around the problem your program is trying to solve. Take advantage of that built-in set of concepts for naming your classes. If you're writing a program to deal with a user's digital image, name the class "Photo". If you need a collection of them with some meta-data, name the class "Album". Take advantage of the real-world metaphors which already exist to reduce the effort required to understand your code.

Sticking to the user's own language becomes especially important when dealing with more technical areas where the end user's vocabulary may be somewhat foreign to you as a programmer (e.g. finance, mechanical engineering, or medicine). In this case, the existing terminology generally has an exact definition in the user's own problem space, and by sticking with that language, you carry all that meaning over into the program itself. This makes the mental job of translating between what the user says to what the program does much easier in everything from initial requirements, to bug reports, to enhancement requests. By understanding the user's problem domain, one can gain a lot of information about how the program works.

Naturally, there are a ton more suggestions, guidelines, and principles to apply when naming classes. What are some of your favorites that I haven't included?

October 12, 2008

Short Circuit Statements

As promised, it's finally time to move away from theory into a more specific topic about coding. My goal is to make most posts in the future more like this one, now that the most important concepts have been laid out. I'll refer back to them often, so if you haven't read the prior posts, you'll probably want to do that before going further.

What is a short-circuit statement? In this case, I'm not talking about the language feature related to boolean comparisons, but instead I'm talking about statements which cause a method to return as soon as some conclusion has definitely been reached. Here's an example from a very simple compareTo method in Java:

public int compareTo(Foobar that) {
if (that == null) return -1;
if (this._value < that._value) return -1;
if (this._value > that._value) return +1;
return 0;
}


In this example, each line (except the last one) would qualify as a short circuit statement; that is, they all return as soon as a definite answer is determined. If we weren't using short circuit statements, then the code may look like this:

public int compareTo(Foobar that) {
int result = 0;
if (that == null) {
if (this._value == that._value) {
if (this._value < that._value) {
result = -1;
} else {
result = +1;
}
}
}
return result;
}


For something this simple, there isn't a huge difference in the complexity between the two functions, but it still demonstrates the point. Many people ardently recommend always having a single return statement in any function, and would strongly advocate using the second example over the first. However, I would argue that the first is superior because it better respects the Unit Economy of the reader.

Short circuit statements support Unit Economy because they allow a reader to take certain facts for granted for the remainder of a method. In the first example after reading the first line of the method the reader knows that they will never have to worry about the that variable having a null value for the rest of the method. In the second example, the reader will have to carry the context of whether he is still reading code within the first if statement. Every short circuit statement removes one item from the set of things one must consider while reading the remainder of the method.

Naturally, this example is pretty simplistic, and it's a stretch to claim that either method is more complicated than the other. However, consider if this weren't a simple example. If this were a formula to compute an amortization table for a home mortgage, then the first few lines may look like this:

public AmortizationSchedule computeSchedule(
int principle, float rate, int term) {
if (principle <= 0) throw new IllegalArgumentException(
"Principle must be greater than zero");
if (rate < 0.0) throw new IllegalArgumentException(
"Rate cannot be less than zero");
if (term <= 0) throw new IllegalArgumentException(
"Term must be greater than zero");

// Here is the 20 or so lines to compute the schedule...
}


In this case, there may be a substantial amount of code following this brief preamble, and none of it has to consider what may happen if these invariants are broken. This greatly simplifies the act of writing the code, the logic of the code itself, and the process of maintaining the code later on.

Thoughts? Comments? I'd love to hear them.

October 05, 2008

Coupling & Cohesion, Part III

Last time, I described how Cohesion applies to everything from writing a single line of code all the way up to designing a remote service. Now, let's consider the same thing for Coupling.

Recall that Coupling is the mental load required to understand how a particular component relates to another compoent. If we take a line of code as a single component, then what defines how it is connected to the lines around it? For a start: the local variables it uses, methods it calls, conditional statements it is part of, the method it is contained in, and exceptions it catches or throws. The more of these things a single line of code involves, the more coupled it is to the rest of the system.

As an example, consider a line of code which uses a few local variables to call a method and store the result. This could be more or less coupled depending upon a number of factors. How many local variables are needed? Are any of the variables static or global variables? Is the method call private to the class, a public method on another class, or a static method defined somewhere? Is the result being stored in a local variable, an instance variable, or a static/global variable? Depending upon the answers to these questions, that one line may be more or less coupled to the other lines around it.

The implication of having Coupling which is too tight for a single line of code is that you have to understand a lot of other lines in order to understand that one. If it uses global variables, then you have to also understand what other code modifies the state of those variables. If it uses many local variables, then you have to understand the code which sets their values. If it calls a method on another object, then you have to understand what impact that method call will have. All of these things increase the amount of information you need to keep in mind to understand that line of code.

Now, consider what Coupling would mean for a remote service which is part of a large distributed system (e.g. Amazon.com). The connections such a service has are defined by the API it offers, the other services it consumes, and how their APIs are defined. For the service's own API, consider the following: Does the API respond to many service calls or just a few? Do the service calls require a lot of structured data to be passed in? How easy is it for a caller to obtain all the necessary information? How much is the service's internal implementation attached to the API it presents? How common is the communication protocol clients must implement? For the other services it consumes, consider: How many other services does it use? How are their APIs defined (considering the questions above)? Just as with a single line of code, the answers to these questions will define how tightly coupled a service is to the rest of the system around it.

Having Coupling which is too tight for a remote service carries troubles, too. Changes to downstream systems may force the service to need an update. Any change to the API may require upstream services to change as well. It may be impossible to change the service's implementation if it is too tightly coupled to its own API. Finally, it may be difficult to break the service into separate services as it grows in scope. It can be a costly and painful mistake down the road to allow too much coupling between services in a distributed environment.

Okay... enough theory! Next time, on to a more specific subject: Short Circuit statements.

September 28, 2008

Coupling & Cohesion, Part II

The concepts of coupling and cohesion apply at all levels of programming from writing a single method all the way up to planning the architecture of Amazon.com. As you build each piece (an individual line of code, a method, an object, or an entire remote service), you have to make sure it has strong cohesion and loose coupling. Does the component do exactly one thing which is easy to describe and conceptualize? Does it have relatively few, easy-to-understand connections to the other components around it?

Consider what it means to have strong cohesion for a single line of code. To have good cohesion, it would need to produce a single clear outcome. On the other hand, a line of code with poor cohesion will tend to have multiple side effects, or calculate many values at once:


int balance = priorBalances[balanceIndex++] -
withdrawals[withdrawalIndex++];

float gravitation = UNIVERSAL_G * (bodyA.mass * KG_PER_LB) *
(bodyB.mass * KG_PER_LB) /
((bodyA.position.x - bodyB.position.x) *
(bodyA.position.x - bodyB.position.x) +
(bodyA.position.y - bodyB.position.y) *
(bodyA.position.y - bodyB.position.y));


In both of these cases, the code is doing multiple things at once, and in order to understand what is going on, you have to mentally pull it apart, understand each piece, and then integrate them back together. Both of these lines can easily be re-written as several lines which each demonstrate much better cohesion:


balanceIndex++;
withdrawalIndex++;
int balance = priorBalances[balanceIndex] - widthdrawals[withdrawalIndex];

float massA = bodyA.mass * KG_PER_LB;
float massB = bodyB.mass * KG_PER_LB;
float xRadiusPart = bodyA.position.x - bodyB.position.y;
float yRadiusPart = bodyA.position.y - bodyB.position.y;
float radiusSquared = xRadiusPart * xRadiusPart + yRadiusPart *
yRadiusPart;
float gravitation = UNIVERSAL_G * massA * massB /
radiusSquared;


Each of the re-written examples has statements which are simpler, easier to understand, and clearly accomplish a single result.

At the far other end of the size spectrum, consider what strong cohesion means for a single service in a massively distributed system (e.g. Amazon.com). For a long while, there was a single, central piece of software, called Obidos, which was responsible for everything from presenting HTML to calculating the cost of an order, to contacting the UPS server to find out where a package was. This ultimately resulted in single program which constantly broke down, was impossible to understand fully, and nearly impossible to actually compile. The crux of the problem is that Obidos tried to do too much, and wound up with terrible cohesion. There was no way anyone could get their head around the essential functions it performed without dropping all kinds of important information.

That was many years ago, and since then, Amazon has considerably improved its situation. As an example, there is now a single service whose sole purpose is to compose the totals and subtotals for an order. It communicates with other services which each compute individual charges (e.g. shipping charges, tax, etc), and all it does is put them together in the right order. This new service is much easier to understand, far easier to describe, and much, much easier to work with on a daily basis.

Next time, I'll talk about how coupling applies across all levels, too.

September 21, 2008

Coupling & Cohesion

In my previous post, I discussed how the mind is naturally limited in the number of things it can consider at once, and how we create abstractions to increase the range of our thinking. By using abstractions, we can hide the details of how something works, thereby allowing ourselves to handle more information and still only have to keep in mind a small number of discrete items. This concept is called Unit Economy.

A consequence of this limitation is that we naturally design complex systems by breaking them down into simpler pieces. If any one piece is still too complex to build, then we break that piece down even further. The act of breaking a system into pieces serves the same function in engineering that creating abstractions does in thinking. Both allow us to ignore the details how a part of the system works, and just keep in mind the overall notion of what it does.

In order for this decomposition to work, however, we must follow two principles: Coupling and Cohesion.

Coupling is the extent to which two components are interconnected. This connection can be defined in terms of actual connections in the final design. But, for our purposes, consider it in terms of how much one has to know about one component in order to understand the function of the other. The crucial point is that Coupling describes the mental load required to understand the relationship between the two components.

To take some examples in the physical world, consider a toaster and a gas stove. A toaster is loosely coupled to the rest of the kitchen. It has a single plug, which is an industry standard, and which is shared by nearly every other electrical appliance in the kitchen. On the other hand, a gas stove is tightly coupled to the rest of the kitchen. It requires a gas main, a vent to be installed above it, an exhaust pipe, and it must be mounted flush with the rest of the cabinetry. When installing a toaster, you simply have to find a flat surface near a plug. When installing a gas stove, you need to understand quite a bit about the structure of the whole kitchen. The mental effort required to understand how a toaster is connected to the rest of the kitchen is far less than that required for the stove.

Cohesion is the extent to which all the parts of a component serve a unified purpose. For the purposes of computing mental load, we measure this by how easily we can come up with a single sentence which describes the essence of what the component does, and by whether each part of the component is needed to accomplish that task. The crucial point in terms of Unit Economy is that we are able to come up with a simple abstraction for the component which allows us to ignore the details of how the component works.

For some examples of strong and weak cohesion, consider a television set and a swiss army knife. In the television set, the description of what it does is pretty simple: "A television set converts a TV signal into a visible picture". On the other hand, describing a swiss army knife isn't nearly so simple. Attempting to come up with a similar statement gets pretty awkward: "A swiss army knife is a multi-function device which provides the ability to conveniently store and reveal tools to: cut things in a variety of ways, drive screws of various kinds, etc." When considering building some kind of system with these things, it's much easier to keep in mind a simple definition (like the TV set) than a rambling, complex one (like the swiss army knife).

So, what does this have to do with programming? More on that next time.

September 13, 2008

Crow Epistemology

Our brain is an amazing organ, capable of truly astounding feats of abstraction and generalization, particularly when compared to a computer. On the other hand, it measures up pretty poorly when it comes down to managing a lot of information at once.  Ayn Rand, a 20th century philosopher, described this phenomenon as Crow Epistemology with the following story (paraphrased).
Imagine there are a bunch of crows sitting in the tree-tops at the edge of a forest. A pair of hunters pass them on their way into the forest, and all the crows get really quiet. One hunter comes out alone, but the crows stay quiet because they know there's still another one in the forest. At little while later, the second hunter comes out, and as soon as he's out of sight, the crows relax and start cawing again.

Now, imagine that a group of 20 hunters goes into the forest. The crows get all quiet, just like before. After a while, 15 hunters come out again. As soon as they're out of sight, the crows start up with their cawing again. The crows could keep track of two hunters, but 20 was just too many for them to track by sight.
Of course, humans have the same problem. To address this, we create abstractions (like numbers) which allow us to group things together and keep track of them as a single unit. In our example, a boy sitting at the edge of the forest could simply count the hunters, and just remember one thing (the number 20). That way, he could easily know whether all the hunters had left the woods or not.

It turns out, programming is a lot harder than counting, and to do it, we need to keep all kinds of information stuffed in our heads. Naturally, the rules are no different, so we, as programmers, use lots of abstractions to keep everything straight. No one could possibly think about all the electrical signals racing around in a computer while they were designing a game. Even when designing a simple game, we need to break the program up into pieces, and complete each piece one at a time, and then stitch the parts together.

This process of using higher and higher abstractions to manage complexity is known as Unit Economy.  By grouping complex things together into a single unit, we can forget about how it works, and just remember what it does.  You don't have to remember how a transistor works to understand what it does, just as you don't need to remember how to implement a hash table to understand what it does.

The concept of Unit Economy is behind everything we do in our daily work as programmers.  Not too surprisingly, abusing a reader's Unit Economy is the foremost way to make your code unreadable.  More to come on this topic next time.

Introduction

I got interested in writing a blog through observing the daily complaint by programmers everywhere that there's a lot of pretty bad code out there. Now, there's probably a huge number of reasons for that, but one I personally keep running across is that the author of the code doesn't seem to know how to write code to be read by another human being.

The focus of this blog will be on what we can do in writing our code to help future readers of our code (including ourselves) understand what it is supposed to do. Like any discussion of coding style, a good deal of it will be based upon my own opinion of what makes something clearer. Wherever possible, though, I'd like to justify my own opinions by referencing how the mind works, and providing objective reasons for doing things one way over another.

Hopefully, I'll be able to share some of the things I've picked up in my own work, and learn something in the process.  Whether you agree or disagree, I'd love to hear your take on any of the topics I address!