Code Kata #1: Bank Account

The Problem

Write a class named BankAccount that implements the following public interface:


public interface BankAccount
{
    void deposit(int amount)
    void withdraw(int amount)
    void printStatement()
}
                                        

(Note you can do this exercise in any programming language, translate the above code as appropriate)

Example statement

When you call the ‘printStatement’ method, something like the following is printed on standard output:


Date       || Amount || Balance
2012-01-14 || -500   || 2500
2012-01-13 || 2000   || 3000
2012-01-10 || 1000   || 1000
                                        

This example statement shows one withdrawal on 14th January 2012, and two deposits on 13th and 10th January respectively.

Notes

* You cannot change the public interface of the BankAccount
* We’re using ints to represent money, which in general may not be the best idea. In a real system, we would always use a datatype with guaranteed arbitrary precision, but doing so here would distract from the main purpose of the exercise.
* Don’t worry about matching the exact formatting of the bank statement, the important thing is to print a table that has column headings and which orders transactions by date.

The thinking....

Since we already have an interface we will start with that. We will use python to solve the problem.


class IBankAccount(ABC):

    @abstractmethod
    def deposit(self, amount: int):
        pass

    @abstractmethod
    def withdraw(self, amount: int):
        pass

    @abstractmethod
    def print_statement(self):
        pass
                                        

To hold the data of each transaction, we can use a simple dataclass that will have a date and the amount. It is not necessary to have a dataclass but makes the whole thing more organized.


@dataclass
class Transaction:
    date: str
    amount: int
                                        

Now we can create the BankAccount class that will implement our interface. We have to make sure that the amount in withdraw and deposit methods won't be a negative number.

It is important to hold the history of the transactions. So, we will introduce a list with the name history that will hold all the transactions.


class BankAccount(IBankAccount):

    def __init__(self):
        self.history = []

    def deposit(self, amount: int):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.history.append(Transaction(datetime.today().strftime('%Y-%m-%d %H:%M'), amount))

    def withdraw(self, amount: int):
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        self.history.append(Transaction(datetime.today().strftime('%Y-%m-%d %H:%M'), -amount ))

    def print_statement(self):
        pass

                                        

The last part of the problem is to write the method that will print the transaction history on screen.


    def print_statement(self):
        header = "Date             ||          Amount ||         Balance ||"
        separator = "-" * 57
        print(header)
        print(separator)

        total_balance = 0
        for transaction in self.history:
            total_balance += transaction.amount
            print(f"{transaction.date} || {transaction.amount:>15} || {total_balance:>15} ||")
                                        

The final file will look like this:


from abc import ABC, abstractmethod
from datetime import datetime
from dataclasses import dataclass


@dataclass
class Transaction:
    date: str
    amount: int


class IBankAccount(ABC):

    @abstractmethod
    def deposit(self, amount: int):
        pass

    @abstractmethod
    def withdraw(self, amount: int):
        pass

    @abstractmethod
    def print_statement(self):
        pass


class BankAccount(IBankAccount):

    def __init__(self):
        self.history = []

    def deposit(self, amount: int):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.history.append(Transaction(datetime.today().strftime('%Y-%m-%d %H:%M'), amount))

    def withdraw(self, amount: int):
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        self.history.append(Transaction(datetime.today().strftime('%Y-%m-%d %H:%M'), -amount ))

    def print_statement(self):
        header = "Date             ||          Amount ||         Balance ||"
        separator = "-" * 57
        print(header)
        print(separator)

        total_balance = 0
        for transaction in self.history:
            total_balance += transaction.amount
            print(f"{transaction.date} || {transaction.amount:>15} || {total_balance:>15} ||")
                                        

Improvements

The most obvious improvement that we could do, but we are restricted by the requisite of the problem (use the given interface) could be to separate the print statement from the bank account class for SOLID reasons.