Thursday, March 20, 2008

Cohesion & Coupling

In a banking application, I would want the CheckingAccount class to have methods of deposit() and withdraw() because those are widely known and understood functions of this type of financial institution's account. A function that may not belong in this class is transferFunds(IAccount anotherAccount) for several reasons.

First, and most obvious, when I write a check to another person, my checking account is not responsible for completing the transaction; that is a service the institution provides so, having the transferFunds() method in the CheckingAccount class lowers the class's cohesion. In other words, the software representation of the business component does not reflect the true functionality required of the business component.

Second, the act of transferring funds may have many implementations and having this functionality in CheckingAccount gives the class another axis of change (violating the Single Responsibility Principle). Since the instance passed to transferFunds() implements IAccount, it could conceivably be a brokerage account, a line of credit account, a mortgage account or a checking account at any other institution. The notification of Federal authorities for transfers above $10,000 is just one concern of the transferFunds() method.

Googling this topic, there are some questionable examples of BankAccount, this being one of them - http://www.artima.com/weblogs/viewpost.jsp?thread=78614 and the follow-up - http://www.artima.com/weblogs/viewpost.jsp?thread=80350. It is fairly well known that financial institutions generally have rules regarding the payment of interest on savings and checking accounts but, in the code shown, the account ignores that fact and a method accepts an interest rate and increments the balance by the calculated amount. Worse, the Account has getBalance() and setBalance() methods, essentially removing the encapsulation around the balance field.

In that example, the BankAccount class has low cohesion because (1.) it has the additional responsiblity of interest calculations and (2.) because it lacks the repsonsiblities it should have, of being responsble for its own balance; the getter and the setter remove the encapsulation around the account balance. Removing the encapsulation in this way would allow one account's balance to be arbitrarily set to negative $1,000,000 and there's probably a business rule around that.

Finally, having the object responsible for its use in threads gives the class orthogonal responsibilities it shouldn't have, further lowering its cohesion.

It seems to me that several important ideas in OOAD are almost never stated. Because these ideas aren't stated, it shouldn't be a surprise that there is so much variability in OO designs. OO is not an end unto itself; in the domain model, OO should be used to model business concepts. This misunderstanding has lead to an impedence in OOAD that is a greater problem than the object-relational impedence we hear so much about. We have an application layer with which to address object-relational impedence but the impedence between the business and the software that models the business has to be dealt with in the design. The impedence between a bank account and the software design of the BankAccount lead to some pretty bad code like that referenced above but, as it was code by the author of the O'Reilly C++ Cookbook, many might hold it in high regard.

The lesson is that despite being able to get working software from almost any random combination of verbs and nouns, design is important and, if design is important, then being able to evolve the design is critical. One should be able to start with a design that has errors (because they all do) and refactor it. One refactoring would probably occur when the rules around the interest calculation made work with the BankAccount class onerous. Another refactoring would probably occur when the regulations around money transfers were implemented.

No software is perfect; it doesn't need to be. Software does need to evolve. It is easiest to modify code when it is complete with tests, concise and layered according to orthogonal concerns.