Wednesday, March 18, 2009

On inverting control

The term inversion of control is one that gets bandied around quite easily these days. Unfortunately most people react to the term with blank stares. In the hopes of preparing my explanation for the next time, here is my reflection on IoC. Consider the simple example:
class CustomerInfo {
}

interface NotificationService {
 void sendNotification(CustomerInfo info);
}
this separation is logical because the sending of customer info is not necessarily bound to the sending of some notice. It does however have a few problems: for one it forces each implementation of the notification service to know the details of the customer info. We can however consider some inversion on this:
class CustomerInfo {
 void sendTo(NotificationService notificationService);
}

interface NotificationService {
 void sendNotification(String message);
}
this would result in a send command that looks like this:
customerInfo.sendTo(notificationService);
which reads fairly well. This inversion has helped us to simplify the notification service, and allowed us to re-encapsulate the customer info. Now what happens if the notice has a complicated, transport-specific format? We can make the abstraction more transparent flexible:
interface NotificationService {
 NotificationMessage createMessage();
 void sendNotification(NotificationMessage message);
}

interface NotificationMessage {
 void setSubmiter(String submiter);
 void setSubject(String subject);
 void setMessage(String message);
}

class CustomerInfo {
 void sendTo(NotificationService notificationService) {
  NotificationMessage message = notificationService.createMessage();
  ...
  notificationService.sendNotification(message);
 }
}
Now this approach could allow us to implement an email-based notification or an instant-messaging-based notification service. The code is cleaner and safer, all without coupling the service to the source of the message. Not bad.

Saturday, March 14, 2009

On the smallest possible condition

Consider how 'if' statement logic can frequently obscure the behavior of the code.

Frequently a simple 'if' statement can contain two to three conditions. Sometimes more, rarely less.

This results in hard to read and maintain conditions. In some cases a condition can have two or more sets of logical conditions.

As an example, consider the following simple condition:

if (name != null && name.length < 20 && address.country == "CA") {
 ...
}

As programmers we may recognize that there are two intended conditions here. One deals with the validity of the name, while the other deals with the address being a domestic address (if you're in Canada). In trying to clean up this code we will frequently break this 'and' of two conditions into two 'if' statements like so:

if (name != null && name.length < 20) {
 if (address.country == "xyz") {
  ...
 }
}

this strikes me as no more clear than the first case. The dual 'if' statements correctly identify the two conditions, but their separation does not clarify the meaning of the code.

In my mind this clean up is heading in the wrong direction. I propose we take a step back to the original example.

If the conditions can be named in a descriptive way, then maybe we can clear this up a little. I propose that we create methods that will express the two conditions separately. I propose isNameValid() and isDomesticAddress().

This should result in the following condition:

boolean isNameValid() {
 return name != null && name.length < 20;
}

boolean isDomesticAddress() {
 return address.country == "xyz";
}

...
if (isNameValid() && isDomesticAddress()) {
 ...
}

This now speaks volumes more than either of the previous examples. Firstly in defines a clear logical combination of conditions.

Arguably we could have applied the same refactoring to the two-if structure, and it would have looked like this:

if (isNameValid()) {
 if (isDomesticAddress()) {
  ...
 }
}

I don't think this helps clarify things as much as the combined if statement.

In following with the idea of condition methods, the and-ed content of the condition in the third code snippet should be regrouped as:

boolean isValidForDomesticShipping() {
 return isNameValid() && isDomesticAddress();
}

and subsequently the conditional would look like this:

if (isValidForDomesticShipping()) {
 ...
}

Now the isValidForDomesticShipping can be easily tested on its own, and the if statement reads much better.

Negations

Now continuing along the road to conditional clarity, let us consider the impact of using negative conditions. In C-style languages such as C++, C# and Java, the negation of a boolean expression is done with the use of the exclamation point. Now the simplicity of this syntax is great, except that it really doesnt jump out when speed reading the code. Obviously Python doesnt suffer from this problem, the negation is a resounding three letter operator "not".

An if statements should not use the negative condition to inverse the behavior of the conditional method. Instead it is clearer to write a conditional method that represents the expressed inversion. This forces the method to say what the condition means.

isNotValidForShipping stands out more clearly than !isValidForShipping.

Help, I'm drowning in methods

As the number of conditional methods increases, you may find your classes getting a little cluttered. This may be a sign that the classes have too many members.

in order to unclutter a class, pushing some of its members and corresponding methods (including conditional methods) into smaller classes.

This means that instead of a string member called name, your Shipping slip may have a member of type CustomerName, and as well as a member of type Address.

An example of the above code with delegated responsibility could resemble this:

boolean isValidForDomesticShipping() {
 return customerName.isValid() && shippingAddress.isDomestic();
}

Now this is starting to make the code cleaner and clearer still.

Conclusion

Rules

  1. 'if' statements should not include conditions, only a call to a conditional method.
  2. As much as possible, an if statements should not use the negative condition to inverse the behavior of the conditional method. Instead it is clearer to write a conditional method that represents the negation in plain English (or whatever language you're programming in)
  3. A conditional method should use only one comparison operation (!=, >, <, ==, etc) or one combining operation (&&, ||). This forces combined conditions to use condition methods on the individual members.
  4. Conditional operations should never have 'if' statements themselves, they should be composed of logical operators and conditional method calls only.
  5. Complex condition methods should be broken into a number of conditional methods.

This approach to condition evaluation helps to make code self-documenting. It can also facilitate testing of conditional logic as it separates the evaluation of the condition from the if statement.

If I start to find that a class is accumulating too many conditional methods, then chances are that the class does too much, and some of its members need to be split off into new classes, the conditional methods will follow.