I’ve really come to loath if-statements. It developed over time. At the very beginning they are great as they give you the power to put some logic into you application. Usually a small program in one of your first programming classes. Well, over time I realized the hard way that they clutter your code and make it harder to read and hence understand and maintain. In OO languages you have far more powerful ways to put logic into your application without using if’s. However, this requires a good skill in programming and lots of practice. I’ve come to realize that you can identify a good programmer by her if-avoidance skill. Well, after some time in acceptance but dislike I now try to avoid them at any price and so should you. By the way, I consider switch-statements to be nested if’s.
Frederick Brooks coined once the terms ‘Essential Complexity’ and ‘Accidental Complexity’. The first describes the real complexity which comes from the domain and the problem to solve. The second one is manmade i.e. it is the complexity we get into by our own making like which OS, which language, which framework, … we choose. Using this point of view, there are essential if-statements and accidental if-statements. The accidental ones, what a fitting name, are the ones which need to be eliminated.
Essential if’s
There are things you need to check. Can I save the file, is there enough free disk space.
if (diskHasEngoughFreeSpace()) {
document.save();
}
Those decision are driven from external forces and are out of our control and so we need to check them every time we save.
Accidental if’s
Those buggers creep up everywhere in you code.
if (date == null) {
date = new Date(); // this is called defensive programming, one of the worst things you can do
}
or
if (date != null) {
uiWidget.setText(date.toString());
} else {
uiWidget.setText(“”);
}
Before you get creative in finding a workaround you should analyze whether date actually can be null. If yes, how? Is it a field of the class, a parameter of the method, a return value from a called method. All these questions and their answers provide you with a multitude of options.
/**
* @param date of the document to be printed; must not be null
*/
public void printHeader(Date date) {
// Usually I put those checks into a utility class and a checkParam method.
// i.e. Check.checkNotNull(date, “date”);
if (date == null) throw new IllegalArgumentException(“invalid parameter: date cannot be null. Good bye!”);
// once we are here we know that all parameter are valid
date.doSomething();
…
}
It is a good practice to have an object to be in well pre-defined state after creation.
class DocumentPrinter {
private Date date; // this is the same as private Date date = null;
…
}
It is better to do it this way
class DocumentPrinter {
private Date date = Date();
// if this is not sufficient then have the date passed in as parameter with the constructor and make the default constructor private
…
}
or even better use the Null-Object pattern
class Document Printer {
private Date date = new NullDate();
…
}
class NullDate extends Date {
public void toString() {
return “not set”;
}
From now on you can use date anywhere in your class without the need to check for null.
Now lets look at a return value from a called method. First I would study the documentation of the method and see whether the method actually can return null objects. In important cases I would even go so far as to quickly write a bunch of tests. With those tests you have clear documentation if the method can return null and under which conditions.
public void doSometing() {
…
Date date = getDateOfTransaction(transaction);
…
}
Knowing, that date might be null we need to see how the variable is being used subsequently. If it is being used for some simple display purposes we can wrap the method and return a NullDate in case the return value is null. If there is some more serious work with the object down the road then ‘Houston we have a problem’! Creating a fake date will not work and invite some big risks. Bypassing the date accesses even more so:
…
doThis();
doThat();
if (date != null) {
rebate = calculateRebate(date);
bookTransaction(rebate);
}
…
Sure, we could move the bookTransaction-method out of the block and guarantee that it will be always booked. But, assuming that we are dealing with a preferred customer who always gets a 10% discount. In this case we will get bunch of calls to our customer service folks which would have to deal with an upset customer. We don’t want that. Depending on whether you have access to the source code of used method you can go in fix the problem if not then you have a problem which you should encapsulate. The encapsulated problem offers a clear separation between working and broken smelly workaround code. Also it adds a lot the readability of the source code and you would have only one place to go and do the fix.
But now let’s look at another piece of code. Assuming we have so transmit data via a selectable protocol. The transmission must be encrypted under specific circumstances. So the code could look like this
public void send(String text, String transmission, String encryption) {
String sendText = “”;
if (encryption.equals(“RSA128”)) {
encryption = new RSA128();
sendText = encryption.encrypt(text);
} else if (encryption.equals(“RSA1024”)) {
encryption = new RSA128();
sendText = encryption.encrypt(text);
} else { // no encryption
sendText = text;
}
if (transimission.equals(“ftp”)) {
FTPTransmitter transmitter = new FTPTransmitter();
transmitter.transmit(sendText);
} else if (transmission.equals(“ssh”)) {
SSHTransmitter transmitter = new SSHTransmitter();
transmitter.transmit(sendText);
}
}
How about this
public void send(String text, String transmission, String encryption) {
Transmitter transmitter = Factory.createTransmitter(transmission, encryption);
Transmitter.transmit(text);
}
First, we extracted a Transmitter interface or if feasible an abstract class. The transmitter internally uses a strategy pattern to encrypt the text. The encryption is injected during construction time of the Transmitter. Again, there is an abstraction for the encryption. The factory could be a own creation or you could use a framework like Spring.
class Factory {
public static Transmitter createTransmitter(String transmission, String encryption) {
Encryption encryptionObj = createEncryption(encryption);
return createTransmitter(transmission, encryptionObbj);
}
public static Transmitter createTransmitter(String transmission, Encryption encryption) {
Transmitter transmitter = createTransmitter(transmission);
transmitter.setEncryption(encryption);
return transmitter;
}
…
}
Sure, if you were following correctly I would expect a ‘but now we have the if-statements in the conveniently not shown createEnryption and createTransmitter method’. Yes, your are right. However, I consider those if-statements at this location to be essential. Also, if you use Spring the condition checking will be externalized into the bean descriptor file.
Also, since the factory is driven by Strings, these could easily come from the UI. In that regard the UI would drive the behaviour without adding any conditional checking and would be easily extendable to other means of transmission and encryption without the need to modify it. And yes, the code will much easier to test. MockObjects anyone ;)
PS If anyone can give a me hint to how show source code in a nice and readable using Blogger. This would be highly appreciated.