Sunday, 11 November 2012

Is Programming the Art of Making the Right Decision?

If you’ve read my previous blog on using Explicit Locking, you may remember that I wrote some sample code that transferred cash between two random bank accounts using the following algorithm:

IF fromAccount is locked THEN
    IF toAccount is locked THEN
        withDraw money from the fromAccount
        deposit money into the toAccount
    END IF
END IF

You may also remember that this was multithreaded code originally written to create a deadlock. In order to demonstrate Explicit Locking’s Lock interface and ReentrantLock implementation I needed to add a thread locking mechanism to my straight-forward Account POJO and so, the question was: how should I do this?

public class Account {

 
private final int number;

 
private int balance;

 
public Account(int number, int openingBalance) {
   
this.number = number;
   
this.balance = openingBalance;
 
}

 
public void withDrawAmount(int amount) throws OverdrawnException {

   
if (amount > balance) {
     
throw new OverdrawnException();
   
}

   
balance -= amount;
 
}

 
public void deposit(int amount) {

   
balance += amount;
 
}

 
public int getNumber() {
   
return number;
 
}

 
public int getBalance() {
   
return balance;
 
}
}

When you know that your code is going to be read by a good number of programmers, it focuses your mind on trying to provide the best solution possible that’ll demonstrate what you’re talking about1.

Many years ago, I had a team leader who was studying for a management qualification and he had the following questions permanently written on the whiteboard behind him:

  1. What is the problem?
  2. What are the solutions?
  3. What is the best possible solution?

Therefore, if the problem is how do you add thread locking to a POJO, then here are a few possible solutions:

Solution 1 - Using Inheritance

The idea here would be to make Account inherit from ReentrantLock.


What can you say about this idea? Firstly it will work as it exposes the methods that the example transfer code needs to use. However, if you take a closer look you’ll see that it breaks the ISA rule as you cannot say “an Account ISA ReentrantLock” - it just doesn’t work and shows a total lack of a basic understanding of object orientation.

Solution 2 Using Aggregation and a getter() Method



The second possible solution would be for the Account POJO to aggregate a ReentrantLock object and expose it using a getLock() method, but is this a good idea? Firstly, you can say that it will work and it does demonstrate the problem from my previous blog. It does, however, break the encapsulation of the Account object by unnecessarily exposing Account’s internal structure to the world at large. On top of that, it also breaks the Laws of Demeter. On a more practical level, if I employed this technique in my production code and, for some reason I had to make a change to that code, then I'll have to make changes all across the codebase, where ever the getLock() occurred making this a very risky strategy.

Solution 3 Account Implements the Lock Interface and Delegates to ReentrantLock



In this solution, the Account class implements the Lock interface and also HASA ReentrantLock instance. All calls to the Account class’s Lock interface are now delegated to the aggregated ReentrantLock instance.

What can you say about this idea? Well, again, it will work. The Account object’s internals are completely hidden from any caller, which is good as any future changes to the locking mechanism are more likely limited solely to the Account class and not spread throughout a large part of the codebase.

On the minus side, this idea takes slightly more code to implement than the previous two and the Account object’s interface is larger and therefore slightly clunkier.

The Best Possible Solution

Given the three choices above option one is a non-starter, and although I did very briefly consider option 2, I chose solution number three for the reasons outlined above and the code came out looking like this:

public class Account implements Lock {

 
private final int number;

 
private int balance;

 
private final ReentrantLock lock;

 
public Account(int number, int openingBalance) {
   
this.number = number;
   
this.balance = openingBalance;
   
this.lock = new ReentrantLock();
 
}

 
public void withDrawAmount(int amount) throws OverdrawnException {

   
if (amount > balance) {
     
throw new OverdrawnException();
   
}

   
balance -= amount;
 
}

 
public void deposit(int amount) {

   
balance += amount;
 
}

 
public int getNumber() {
   
return number;
 
}

 
public int getBalance() {
   
return balance;
 
}

 
// ------- Lock interface implementation

 
@Override
 
public void lock() {
   
lock.lock();
 
}

 
@Override
 
public void lockInterruptibly() throws InterruptedException {
   
lock.lockInterruptibly();
 
}

 
@Override
 
public Condition newCondition() {
   
return lock.newCondition();
 
}

 
@Override
 
public boolean tryLock() {
   
return lock.tryLock();
 
}

 
@Override
 
public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
   
return lock.tryLock(arg0, arg1);
 
}

 
@Override
 
public void unlock() {
   
if (lock.isHeldByCurrentThread()) {
     
lock.unlock();
   
}
  }

}

I don’t know about you, but it seems to me that a lot of programmers would simply write down the first thing that comes into their head without considering other options and I imagine that this is probably the result of how they’ve been taught to write code.

My daughter is an architecture student and from day one they have to stand up and talk about their designs in front of other students, lecturers and real architects. They have to justify their choices and stand by them; something that, so far as I know, doesn’t happen on computer science courses2.

Don’t get me wrong, it would be financially impossible for developers to stand up and justify the design of every class they write in front of a panel of peers and experts, but, due to the nature of programming, our work is usually hidden from plain site as the only people who can appreciate the art of programming are other programmers, which usually means that you can (and people often do) get away with murder.

So, is there a new anti-pattern to define here? I guess that it doesn’t rank with Big Ball of Mud or the The God Object, but “The first solution is the only and best solution” is probably the cause of a large number of problems.


1Maybe I don’t always get it write...
2If it does then please let me know...

No comments: