Poor Man’s Polymorphism or What’s so Wrong about If-Then-Else?

by Zoran Horvat

In this article we will attack the last fortress of code complexity – branching statements. There are several roles we assign to branching statements. Some of those are legitimate, like guard clauses which we are using when coding partial functions.

You can learn more about partial functions and guard clauses from article Why do We Need Guard Clauses? In this article we will focus on those more prominent and less legitimate uses of if-then-else statements.

Basic Example

Now straight to code. Imagine an e-commerce application in which every registered user had a money account where she could deposit money. Should there be insufficient money in the account, an attempt to buy an item would fail. However, some accounts let the application credit the some users and let them spend more money than they have deposited.

And here is the implementation of this feature. Below you can find complete listing of the CreditAccount class, which implements this special-purpose money account. The class is relatively complex, but I believe it will serve the purpose well. If you take a closer look, you will find that its implementation swarms with branching statements.

public class CreditAccount: AccountBase
{

    private readonly IList<MoneyTransaction> pendingTransactions = new List<MoneyTransaction>();

    private readonly bool strictTransactionOrder;

    public CreditAccount(bool strictTransactionsOrder)
    {
        this.strictTransactionOrder = strictTransactionsOrder;
    }

    public override decimal Balance
    {
        get
        {
            return base.Balance + this.pendingTransactions.Sum(trans => trans.Amount);
        }
    }

    public override Option<MoneyTransaction> TryWithdraw(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to withdraw must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(-amount);

        if (this.Balance >= amount)
            base.RegisterTransaction(transaction);
        else
            this.pendingTransactions.Add(transaction);

        return Option<MoneyTransaction>.Create(transaction);

    }

    public override MoneyTransaction Deposit(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to deposit must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(amount);
        base.RegisterTransaction(transaction);

        this.ProcessPendingWithdrawals();

        return transaction;

    }

    private void ProcessPendingWithdrawals()
    {

        Option<MoneyTransaction> option = Option<MoneyTransaction>.CreateEmpty();

        do
        {
            option = this.TrySelectPendingTransaction();
            ProcessPendingWithdrawal(option);
        }
        while (option.Any());

    }

    private Option<MoneyTransaction> TrySelectPendingTransaction()
    {
        if (this.strictTransactionOrder)
            return this.TrySelectFirstPendingTransaction();
        else
            return this.TrySelectConformingPendingTransaction();
    }

    private void ProcessPendingWithdrawal(Option<MoneyTransaction> option)
    {

        if (!option.Any())
            return;

        MoneyTransaction transaction = option.Single();

        base.RegisterTransaction(transaction);
        this.pendingTransactions.Remove(transaction);

    }

    private Option<MoneyTransaction> TrySelectFirstPendingTransaction()
    {

        if (!this.pendingTransactions.Any())
            return Option<MoneyTransaction>.CreateEmpty();

        MoneyTransaction candidate = this.pendingTransactions.First();

        if (base.Balance + candidate.Amount < 0)
            return Option<MoneyTransaction>.CreateEmpty();

        return Option<MoneyTransaction>.Create(candidate);

    }

    private Option<MoneyTransaction> TrySelectConformingPendingTransaction()
    {
        return
            this.pendingTransactions
                .Where(trans => base.Balance + trans.Amount >= 0)
                .Take(1)
                .Select(trans => Option<MoneyTransaction>.Create(trans))
                .DefaultIfEmpty(Option<MoneyTransaction>.CreateEmpty())
                .Single();
    }

}

This class derives from the AccountBase class, which provides some basic features common to all money accounts. This class is not shown in the listing because it is really not interesting for this analysis of branching statements.

Anyway, this class is typical example of coding based on branching instructions. We are grasping for if-then-else structures too lightly. Would you believe if I promised to refactor this class and bring it to a much simpler and more flat implementation?

In the remainder of this article, we will first analyze different appearances of branching instructions that can be seen in this class. Then, we will try to remove branching instructions one after the other. As you will see, most of the if-then-elses will be removed in one way or the other.

But some will remain intact – namely, guard clauses will remain. The reason why they exist lies with existence of partial functions (see Why do We Need Guard Clauses? for details). Since all partial functions in this class will remain partial after refactoring, guard clauses will remain as well. Other branchings will hopefully be factored out.

Full If-Then-Else Instruction

TryWithdraw method is of great importance to the CreditAccount class:

public class CreditAccount: AccountBase
{

    private readonly IList<MoneyTransaction> pendingTransactions = new List<MoneyTransaction>();
    ...
    public override Option<MoneyTransaction> TryWithdraw(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to withdraw must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(-amount);

        if (this.Balance >= amount)
            base.RegisterTransaction(transaction);
        else
            this.pendingTransactions.Add(transaction);

        return Option<MoneyTransaction>.Create(transaction);

    }
    ...
}

Although this method returns Option of MoneyTransaction, all requests to withdraw are acceptable. TryWithdraw method overrides the abstract method from the base class. Some derived classes will not allow withdrawals if there is not enough money on the account, and that is why the base class specifies that this method should return an option. If derived class decides to reject withdrawal request, it would return an empty option. You can learn more about the Option<T> type from one of the previous articles, titled Understanding the Option (Maybe) Functional Type .

Since CreditAccount never rejects a withdraw request, it Withdraw method will always return an option containing a valid money transaction. Only, there is this list named pendingTransactions, which is there to catch all transactions that cannot be executed right now due to low balance.

That is what the if-then-else instruction is used for in the TryWithdraw method. If balance is sufficient, it registers a transaction, effectively withdrawing money. Mind the slight complication in control flow caused by this branching instruction. Otherwise, transaction is added to the list of pending transactions, to be executed later, after a sufficient deposit is made.

This kind of if-then-else is probably what we use the most frequently. It is sometimes very hard to come up with a replacement to this branching instruction. Generally, there is nothing wrong with this kind of branching, as long as we are ready to accept more complicated control flow it causes. If there is the way to remove the branching and replace it with a flat, uniform control flow, then that would be good news to hear. Less complex code is easier to manage, easier to understand and maintain later.

So we have identified the first important form of the branching instruction. That is the full-blown if-then-else, i.e. two-way branching with “then” and “else” block. Characteristic of this kind of branching is that it is performed over a dynamic condition which is not the same in all executions of one object, and also cannot be predicted when the surrounding method is entered.

If-Then-Else To Replace Polymorphism

Deposit method in the CreditAccount class registers a deposit transaction and then tries to process some of the pending transactions.

public class CreditAccount: AccountBase
{
    ...
    public override MoneyTransaction Deposit(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to deposit must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(amount);
        base.RegisterTransaction(transaction);

        this.ProcessPendingWithdrawals();

        return transaction;

    }
    ...
}

ProcessPendingWithdrawals method is interesting in its own right. Its purpose is to fetch as many pending transactions as possible, having current balance on mind, and then to execute them. This will make past dues effective after sufficient deposit was made to cover them. Here is the implementation of this method and methods it depends on – once again, implementation swarms with branching instructions:

public class CreditAccount: AccountBase
{
    ...
    private void ProcessPendingWithdrawals()
    {

        Option<MoneyTransaction> option = Option<MoneyTransaction>.CreateEmpty();

        do
        {
            option = this.TrySelectPendingTransaction();
            ProcessPendingWithdrawal(option);
        }
        while (option.Any());

    }

    private Option<MoneyTransaction> TrySelectPendingTransaction()
    {
        if (this.strictTransactionOrder)
            return this.TrySelectFirstPendingTransaction();
        else
            return this.TrySelectConformingPendingTransaction();
    }
    ...
}

There is quite a long sequence of methods that will be called to first select transactions that can be executed, and then to effectively execute them and remove them from the list of pending transactions. These methods are not of immediate importance in this analysis. In this section, we will focus on the TrySelectPendingTransaction method.

If you pay close attention to implementation of this method, you will notice the use of this strictTransactionOrder Boolean flag, which was originally supplied through the constructor and it tells whether pending withdrawals should be executed in strict order of arrival, or we could pick smaller requests first and wait for more deposits to pay larger bills.

This if-then-else statement in the TrySelectPendingTransaction method is of the worst kind. You should definitely try not to have any such branching statement in your code. This branching instruction is executed over a Boolean condition which will never change its value during the lifetime of a holding object.

If you think this situation through, you will notice that it acts as a replacement for polymorphism. There should definitely be two classes, one implementing one selection algorithm and the other implementing the other algorithm. These two algorithms should definitely not be placed in the same class and then controlled by a Boolean flag.

Use of branching instruction shown above is what I call poor man’s polymorphism. It is much better approach to rely on derived classes and then to have only one selection algorithm and not to perform any branching.

If-Then-Return as Guard Clause

The next method that is very interesting is this TrySelectFirstPendingTransaction.

public class CreditAccount: AccountBase
{
    ...
    private Option<MoneyTransaction> TrySelectFirstPendingTransaction()
    {

        if (!this.pendingTransactions.Any())
            return Option<MoneyTransaction>.CreateEmpty();

        MoneyTransaction candidate = this.pendingTransactions.First();

        if (base.Balance + candidate.Amount < 0)
            return Option<MoneyTransaction>.CreateEmpty();

        return Option<MoneyTransaction>.Create(candidate);

    }
    ...
}

The purpose of this method is to pick the first transaction from the list of pending transactions and, if any, see if that transaction can be paid from current balance. The method begins with the if-then-return instruction straight away. This form of branching instruction is called guard clause. It guards the method from executing under conditions to which this method does not apply.

Guard clause is quite different from proper if-then-else instruction we could see earlier. It doesn’t have an else branch. And that is a fundamental shift. I’ll give you a hint about guard clauses. They are the programmatic implementation of what is known as partial functions in mathematics. You can learn more about partial functions in article Why do We Need Guard Clauses? When a function does not apply to certain subset of its inputs, then we need a guard clause to give up early and skip function body execution.

As you may already suspect, guard clauses cannot be removed from code like other forms of branching might. As long as the method is not applicable to subset of its input values, it must be guarded.

If-Then-Throw as Guard Clause

There is one more form of branching instruction in this class. Public methods, TryWithdraw and Deposit, begin with a very specific form of guard clause.

public class CreditAccount: AccountBase
{
    ...
    public override Option<MoneyTransaction> TryWithdraw(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Amount to withdraw must be positive.", nameof(amount));
        ...
    }

    public override MoneyTransaction Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Amount to deposit must be positive.", nameof(amount));
        ...
    }
    ...
}

This is the so-called if-then-throw guard clause. You will also hear the term if-then-throw design pattern. This guard clause is applied to public interface of the class. Certain values or combinations of input parameters are unacceptable to an object. Typically, null references, empty strings, non-positive values like in these money-related methods.

If-then-throw guard clause is there to throw an exception should the caller attempt to place a call that cannot be executed. Deposit with negative amount could be turned into a withdrawal and be done with it. But CreditAccount class is not entitled to make such decisions. All decisions must be explicit. Deposit method must deposit money. Therefore, Deposit method throws an exception when faced with non-positive input. And so does the TryWithdraw method.

If-then-throw guard clauses, like their close cousin if-then-return guard clauses, cannot be removed from code unless the class interface is redesigned so that its methods are fully applicable to all possible values of their arguments. Having null references, empty strings, negative or zero numbers on the table, this is not likely to happen in any realistic application.

Techniques to Remove Branching Instructions

Bottom line is that we have seen four kinds of branching instructions in this class:

  • If-then-else instruction which navigates the execution around dynamically calculated condition.
  • If-then-else instruction that navigates execution around condition which is static and never changes during the lifetime of an object.
  • If-then-return guard clause, which silently terminates execution of a method when the method is not applicable to current input.
  • If-then-throw guard clause which is the most invasive form of branching in terms that it throws an exception when its condition is satisfied.

All these branching instructions are affecting control flow. Some of them are affecting it in ways that should not find its place in well-designed classes.

So in the remainder of this article, we will tackle branching instructions one after the other and see if we can remove all of them. As you will see, only the proper guard clauses will remain, and even that due to more theoretical reasons, rather than our inability to come up with a cleaner design.

Removing If-Then-Else with Dynamic Condition

Probably the most frequent situation in which branching instruction is used in practice is when we are splitting the control flow into two separate control flows depending on a run-time condition. This is needless complication more often than not. In so many applications we can flatten the flow by unifying several flows into one.

In this particular method, TryWithdraw, which is repeated below, we are testing whether the account contains sufficient amount of money before withdrawing it.

public class CreditAccount: AccountBase
{
    ...
    public override Option<MoneyTransaction> TryWithdraw(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to withdraw must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(-amount);

        if (this.Balance >= amount)
            base.RegisterTransaction(transaction);
        else
            this.pendingTransactions.Add(transaction);

        return Option<MoneyTransaction>.Create(transaction);

    }

    public override MoneyTransaction Deposit(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to deposit must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(amount);
        base.RegisterTransaction(transaction);

        this.ProcessPendingWithdrawals();

        return transaction;

    }
    ...
}

This partial listing also shows the Deposit method, which is there to contrast implementations of the two operations. Deposit is executed unconditionally – all deposits with positive amount are valid and happily accepted. But withdrawal splits into one of the two possible control flows.

But we don’t really have to branch. It takes a bit of thinking to figure that we already have a mechanism for executing withdrawals in this class. There is the mechanism for processing pending transactions, which was used from the Deposit method to complete pending withdrawals after the user has added more money. Now, why not employ the same control flow in the TryWithdraw method, like this:

public class CreditAccount: AccountBase
{
    ...
    public override Option<MoneyTransaction> TryWithdraw(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to withdraw must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(-amount);

        this.pendingTransactions.Add(transaction);
        this.ProcessPendingWithdrawals();

        return Option<MoneyTransaction>.Create(transaction);

    }

    public override MoneyTransaction Deposit(decimal amount)
    {

        if (amount <= 0)
            throw new ArgumentException("Amount to deposit must be positive.", nameof(amount));

        MoneyTransaction transaction = new MoneyTransaction(amount);
        base.RegisterTransaction(transaction);

        this.ProcessPendingWithdrawals();

        return transaction;

    }
    ...
}

Changes like this one require deep understanding of the application. This implementation is not doing the same thing as the previous one. Be careful when changing flow in your own applications. I could put it the other way: This change is not a refactoring. It is a design change.

But it pays to invest in the design which avoids branching instructions. If-then-else that has just been removed from implementation was a pure distraction. It used to complicate the control flow, and code without branching is still doing the same thing. But new implementation is not doing it in the same way as before. Instead, now it does it in the same way as the other operation, Deposit, is doing it.

I have essentially unified parts of control flows for these two features. Try to unite control flows whenever you can. There are many benefits from doing so. Less branching is one. Less work to do when the feature has to be changed is even greater motive. For example, I could even choose to unite the TryWithdraw and Deposit methods into a single implementation:

public class CreditAccount: AccountBase
{
    ...
    public override Option<MoneyTransaction> TryWithdraw(decimal amount)
    {
        MoneyTransaction transaction = this.AcceptTransaction(-amount, (a) => a < 0, this.pendingTransactions.Add);
        return Option<MoneyTransaction>.Create(transaction);
    }

    public override MoneyTransaction Deposit(decimal amount)
    {
        return this.AcceptTransaction(amount, (a) => a > 0, base.RegisterTransaction);
    }

    private MoneyTransaction AcceptTransaction(decimal arbitraryAmount, Func<decimal, bool> guard,
                                               Action<MoneyTransaction> registerTransaction)
    {

        if (!guard(arbitraryAmount))
            throw new ArgumentException("Amount must be positive.");

        MoneyTransaction transaction = new MoneyTransaction(arbitraryAmount);
        registerTransaction(transaction);

        this.ProcessPendingWithdrawals();

        return transaction;

    }
    ...
}

In this implementation, there is only one method which deals with transactions. This method is capable of accepting both deposits and withdrawals. TryWithdraw and Deposit methods, which were mandatory public methods of this class, and which are overriding abstract methods of the base class, are now one-liners. They are not doing anything for real, just preparing arguments to the unified AcceptTransaction method which are appropriate for current money-related operation: withdrawal or deposit.

This kind of optimization is aimed at reducing code duplication and reducing code size in general. It is useful to think about such design changes because they reduce number of defects and reduce subsequent maintenance efforts.

Replacing Static If-Then-Else with Proper Polymorphism

Now look at the TrySelectPendingTransaction method, listed below. It is a gateway to one of the two distinct implementations of the feature.

public class CreditAccount: AccountBase
{
    ...
    private readonly bool strictTransactionOrder;

    public CreditAccount(bool strictTransactionsOrder)
    {
        this.strictTransactionOrder = strictTransactionsOrder;
    }
    ...
    private void ProcessPendingWithdrawals()
    {

        Option<MoneyTransaction> option = Option<MoneyTransaction>.CreateEmpty();

        do
        {
            option = this.TrySelectPendingTransaction();
            ProcessPendingWithdrawal(option);
        }
        while (option.Any());

    }

    private Option<MoneyTransaction> TrySelectPendingTransaction()
    {
        if (this.strictTransactionOrder)
            return this.TrySelectFirstPendingTransaction();
        else
            return this.TrySelectConformingPendingTransaction();
    }
    ...
}

The first implementation, TrySelectFirstPendingTransaction method, picks the oldest pending transaction and returns empty option if that transaction cannot be paid. The other implementation, TrySelectConformingPendingTransaction method, walks through the transactions, from oldest to youngest and picks the first one that can be paid.

These are the two selection strategies supported by this class. The problem should be obvious from the way I said it: Two selection strategies. This class supports two implementations of the same feature. And the Boolean flag passed through the constructor is used to select one or the other.

I already called this instruction the worst possible use of if-then-else. And I will add insult to injury. Using branching statements to select implementation is a poor man’s polymorphism. Object method gives us a direct solution to the problem: Dynamic binding. Polymorphic calls. We should use the object method.

public abstract class CreditAccount: AccountBase
{
    ...
    private void ProcessPendingWithdrawals()
    {

        Option<MoneyTransaction> option = Option<MoneyTransaction>.CreateEmpty();

        do
        {
            option = this.TrySelectPendingTransaction();
            ProcessPendingWithdrawal(option);
        }
        while (option.Any());

    }

    protected abstract Option<MoneyTransaction> TrySelectPendingTransaction();
    ...
}

This is the polymorphic approach. By deferring implementation of the method, we are letting the client call one implementation or the other based purely on run-time conditions determined by having an object of one derived class or the other.

Boolean flag which was used to drive the decision is now gone. The type of the object is the selection flag, and it is even not limited to only two options. We could have as many derived classes as we like, without affecting the control flow in the base class.

This is the example of applying the Template Method design pattern. Base class describes a placeholder for a feature, and then lets derived classes implement it. Template Method comes with a drawback. It is fully determined at compile time. Another design pattern helps there: Strategy. We can turn the selection algorithm into an object, and then supply one object or the other at run time. We could define an interface like this:

public interface ITransactionSelector
{
    Option<MoneyTransaction> TrySelectOne(IEnumerable<MoneyTransaction> transactions,
                                          decimal maxAmount);
}

This interface exposes a single method, and that method is the selection strategy. Now, the method has become equal to the object that holds it. When it comes to using the strategy object, we have to inject it to the client through constructor:

public class CreditAccount: AccountBase
{
    ...
    private readonly ITransactionSelector pendingTransactionSelectionStrategy;

    public CreditAccount(ITransactionSelector transactionSelectionStrategy)
    {

        if (transactionSelectionStrategy == null)
            throw new ArgumentNullException(nameof(transactionSelectionStrategy));

        this.pendingTransactionSelectionStrategy = transactionSelectionStrategy;

    }
    ...
    private Option<MoneyTransaction> TrySelectPendingTransaction()
    {
        return
            this.pendingTransactionSelectionStrategy
            .TrySelectOne(this.pendingTransactions, base.Balance);
    }
    ...
}

Later on, when the time comes to effectively select the next transaction to execute in the TrySelectPendingTransaction method, we are simply invoking the strategy. As you can see, the branching is now gone. And not only that there is no branching, but we have also opened the solution for new selection strategies, which might not be invented yet.

Note that this solution is still polymorphic. TrySelectOne method in strategy implementations is virtual, since it comes from the interface declaration. Only this time, actual implementation of the method is moved to a separate inheritance hierarchy – that is the distinction compared to Template Method design pattern.

In C# we can also use Func delegates to save some typing, but that often yields a less readable code. This would be the signature of the selection strategy when implemented as a delegate:

public class CreditAccount: AccountBase
{
    private Func<IEnumerable<MoneyTransaction>, decimal, Option<MoneyTransaction>> strategy;
    ...
}

In many cases it is preferable to add a new type to the code base and in that way simplify the client. Choose your side, all effects are the same – there will be no branching in either case.

Living with Guard Clauses

Implementation which used to rely on if-then-else instructions was complicated and probably hard to maintain. It gets much better when two-way branching is removed entirely, and replaced with unified flow – either directly, or by introducing an external implementation in form of a class implementing the Strategy design pattern.

After making these design changes, we are hitting the guard clauses. Guard clauses remained the last refuge of complicated control flow.

Unfortunately, we can never remove guard clauses with a reasonable alternative. For example, all reference types come with null as a valid element in the set of objects of that type. And most of the methods we write will refuse to acknowledge null as a valid input.

It gets a little bit better with non-null unacceptable objects. We could, for instance, define a type named NonEmptyString. Any non-null reference to a NonEmptyString would come with guarantee that the contained string is not an empty string. This guarantee would be implemented in the NonEmptyString’s constructor, which would of course contain that wretched if-then-throw condition which would test whether the incoming string is null or empty and then fail if it is.

But just try to imagine endless list of custom classes that come with built-in guarantees: PositiveInt, NonNegativeInt, PositiveDecimal, NonEmptyString, NonEmptyOrWhiteSpaceString, StringShorterThan50, and so on. It is hard to imagine that any realistic project would incorporate such classes. Therefore, it looks more probable that all or most of the public methods we write will incorporate if-then-throw instructions to guard against invalid input.

Other kinds of guards, namely if-then-return instructions that we normally use in non-public methods, could sometimes be replaced with strategies. Such instructions can be regarded as one-way strategies, where the other way, the missing else branch, is replaced with a do-nothing implementation. This could be turned into application of a Null Object pattern. You can learn more about Null Object from article How to Reduce Cyclomatic Complexity Part 1: Null Object Pattern .

There is another related article - How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern . Special Case is a bigger brother to Null Object in terms that it provides some behavior, but it can still be applied successfully to replace missing else branches in if-then-return guard clauses.

If-Then-Throw Guard Clauses and Code Contracts

Another option to removing if-then-throw guard clauses is to use Code Contracts. If you are familiar with Design by Contract method, you know that guards can be expressed in terms of method preconditions. These are the Boolean conditions that must be fulfilled before the method is called.

In .NET applications we can use the Code Contracts library which comes with code rewriter. This tool modifies the source code before compiler gets the chance to build the binaries. Rewritten code will incorporate if-then-throw or similar instructions, based on Code Contracts settings. This will simplify your custom code a bit. There will be no branching instructions.

Unfortunately, due to dynamic nature of guard clauses, it is impossible to implement guards as attributes. That would probably be more appropriate, but again, Intermediate Language doesn’t work that way and dynamic throw conditions must be implemented as proper instructions placed at the beginning of the method body.

It would be out of scope of this article to discuss Code Contracts in .NET. I will just show the Deposit method with Contract clause which replaces the previous if-then-throw implementation:

public class CreditAccount: AccountBase
{
    ...
    public override MoneyTransaction Deposit(decimal amount)
    {

        Contract.Requires<ArgumentException>(amount > 0, "Amount to deposit must be positive.");

        MoneyTransaction transaction = new MoneyTransaction(amount);
        base.RegisterTransaction(transaction);

        this.ProcessPendingWithdrawals();

        return transaction;

    }
    ...
}

The first line of code in this method is self-explanatory. It states that the Deposit method requires positive value of the amount argument. Should this condition be violated, the method should fail with ArgumentException.

This line of code looks better than traditional if-then-throw implementation. It is more compact and easier to read, thanks to class and method names that are suggesting their purpose. But this implementation comes with one additional benefit. Code Contracts behavior can be affected by project properties. Once again, this topic is way out of bounds of this article, so I will just mention that it is possible to select among wide range of behaviors of the code rewriter and thus affect actual code which would be injected in place of call to the Requires method. One basic selection is to choose whether the failed precondition will cause ArgumentException as specified here or it should cause a more violent form of failure – system-level assertion.

Summary

In this article we have walked through implementation of one non-trivial domain class which was coded in traditional imperative style. We have seen that this kind of coding comes with many branching instructions. It is a problem to have many branching instructions because they make code more complicated, harder to read and test, harder to maintain.

It gets much better if we can remove branching instructions and make code simpler, make its control flow flat. We have seen several methods of removing branching from this complicated class.

If-then-else instructions over dynamically calculated conditions are hardest to manage. We cannot remove them because we don’t know the condition outcome up-front. But sometimes we can redesign the overall control flow of the class and make the condition superfluous. We may sometimes unify two or more control flows into a single one, injecting strategies and similar methods in different stages of control flow execution. In that way, we could yield a much better design and, as a direct consequence, remove branching instructions that used to create a false image of distinct control flows in similar operations.

The other kind of full-blown if-then-else statements appeared when branching was made over a static condition, condition which never changes during the lifetime of the object. Everything that determines one or the other execution branch is known up-front in the constructor. Such if-then-else statements are entirely synthetic and can and should be removed. We have seen Template Method and Strategy patterns that can be applied to remove this false polymorphism and replace it with proper polymorphism. Instead of selecting one execution or the other, we choose one concrete class or the other to instantiate at run time and in that way select actual implementation of the feature.

The last form of branching were guard clauses. These were caused by inability of the methods to cope with some values that could be passed to them at run time. Therefore, guard clauses remain as integral part of any class implementation. But even there, we could come up with a better solution. If-then-return guard clauses can sometimes be replaced with Template Method or Strategy, just like mentioned previously. The missing else branch can be replaced with empty implementation – Null Object or Special Case pattern fit well in this case. Remaining guard clauses, if-then-throw clauses, which used to fail when method preconditions were violated, could be replaced with Code Contracts segments which are serving the same purpose. In that way, obligation to throw an exception is moved to an infrastructure library, relieving the custom code from having to deal with precondition violations.


If you wish to learn more, please watch my latest video courses

About

Zoran Horvat

Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.

  1. Pluralsight
  2. Udemy
  3. Twitter
  4. YouTube
  5. LinkedIn
  6. GitHub