Bank accounts with methods to transfer funds
$begingroup$
I recently took a programming exam for an interview and unfortunately didn't pass it. I wasn't offered any feedback about the results. I'm looking for help on how to improve my OOP skills.
The requirements for the program were:
- a
bank
has aname
- a
bank
has manyaccounts
transactions
are stored on theaccounts
.
- There are different types of
accounts
:savings
andchecking
.
Checking accounts
can have multiple types,money market
andindividual
.
Individual accounts
can't withdraw more than $1000 at a time.
- I need to demonstrate withdrawal, deposit, and transferring funds through unit tests.
The code currently compiles and the unit tests run successfully.
Here is the main program:
Bank.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace BankExcercise
{
public class Bank
{
public string name;
private List<Account> bankAccounts;
public Bank(string name)
{
this.name = name;
bankAccounts = new List<Account>();
}
public int OpenBankAccount(Type accountType, decimal startingBalance)
{
int newId = bankAccounts.Count();
bankAccounts.Add((Account)Activator.CreateInstance(accountType, newId, startingBalance));
return newId;
}
public Account GetAccount(int ownerId)
{
Account account = bankAccounts.Where(x => x.owner == ownerId).FirstOrDefault();
if(account == null)
{
throw new ApplicationException("no account exists with that id");
}
return account;
}
public bool TransferFunds(int fromAccountId, int toAccountId, decimal transferAmount)
{
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
Account fromAccount = GetAccount(fromAccountId);
Account toAccount = GetAccount(toAccountId);
if(fromAccount.balance < transferAmount)
{
throw new ApplicationException("insufficient funds");
}
fromAccount.Transfer(-1 * transferAmount, toAccountId);
toAccount.Transfer(transferAmount, fromAccountId);
return true;
}
}
}
I decided to abstract an Account
from which savings
and checking
would derive.
Account.cs
using BankExcercise.Transactions;
using System;
using System.Collections.Generic;
namespace BankExcercise
{
public abstract class Account
{
public int owner { get; set; }
public decimal balance { get; set; }
public List<Transaction> transactions { get; set; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
public virtual bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
public void Transfer(decimal transferAmount, int transferToId)
{
balance += transferAmount;
TransferTransaction newTransaction = new TransferTransaction(transferAmount, transferToId, owner);
transactions.Add(newTransaction);
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ApplicationException("invalid deposit amount");
}
balance += amount;
Transaction newTransaction = new DepositTransaction(amount);
transactions.Add(newTransaction);
}
}
}
SavingsAccount.cs
namespace BankExcercise.Accounts
{
public class SavingsAccount : Account
{
public SavingsAccount(int id, decimal newBalance) : base(id, newBalance) { }
}
}
I created a CheckingAccount
abstract class that would inherit from Account
CheckingAccount.cs
namespace BankExcercise.Accounts
{
public abstract class CheckingAccount : Account
{
public CheckingAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
IndividualAccount
and MoneyMarket
would inherit from CheckingAccount
IndividualAccount.cs
using BankExcercise.Transactions;
using System;
namespace BankExcercise.Accounts.Checking
{
public class IndividualAccount : CheckingAccount
{
public IndividualAccount(int owner, decimal balance) : base(owner, balance)
{
}
public override bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if(withdrawlAmount > 1000)
{
throw new ApplicationException("withdrawl limit exceeded");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
}
}
There were no explicit instructions to follow for MoneyMarket
MoneyMarket.cs
namespace BankExcercise.Accounts.Checking
{
public class MoneyMarketAccount : CheckingAccount
{
public MoneyMarketAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
Unit Tests
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BankExcercise;
using BankExcercise.Accounts.Checking;
namespace BankExcerciseTests
{
[TestClass]
public class IndividualAccountTests
{
public Bank bank;
public Account bankAccount;
public int accountId;
[TestInitialize]
public void AccountSetup()
{
bank = new Bank("USA BANK");
accountId = bank.OpenBankAccount(typeof(IndividualAccount), 10000);
bankAccount = bank.GetAccount(accountId);
}
[TestMethod]
public void AccountIdShouldMatchGivenValue()
{
Assert.AreEqual(bankAccount.owner, accountId);
}
[TestMethod]
public void CheckingBalanceShouldMatchAccountBalance()
{
Assert.AreEqual(bankAccount.balance, 10000);
}
[TestMethod]
public void DepositingShouldSucceed()
{
bank.GetAccount(accountId).Deposit(100);
Assert.AreEqual(bankAccount.balance, 10100);
}
[TestMethod]
public void WithdrawingShouldSucceed()
{
Assert.IsTrue(bankAccount.Withdrawl(999));
Assert.AreEqual(bankAccount.balance, 9001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void WithdrawlingMoreThanLimitShouldFail()
{
bankAccount.Withdrawl(1001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void OverdraftingShouldFail()
{
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
bankAccount.Withdrawl(1000);
}
}
}
Unit Tests
using BankExcercise;
using BankExcercise.Accounts;
using BankExcercise.Accounts.Checking;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace BankExcerciseTests
{
[TestClass]
public class TransactionTests
{
public Bank bank;
[TestInitialize]
public void BankSetup()
{
bank = new Bank("First Bank of America");
bank.OpenBankAccount(typeof(IndividualAccount), 1465181);
bank.OpenBankAccount(typeof(SavingsAccount), 100);
bank.OpenBankAccount(typeof(MoneyMarketAccount), 15000);
bank.OpenBankAccount(typeof(SavingsAccount), 2564);
bank.OpenBankAccount(typeof(IndividualAccount), 87945);
bank.OpenBankAccount(typeof(SavingsAccount), 1000000001);
}
[TestMethod]
public void TransferringFromAnAccountWithCorrectBalanceShouldSucceed()
{
Assert.IsTrue(bank.TransferFunds(0, 2, 1465181));
Assert.AreEqual(bank.GetAccount(0).balance, 0);
Assert.AreEqual(bank.GetAccount(2).balance, 1480181);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringFromAnAccountWithInsufficientFundsShouldFail()
{
bank.TransferFunds(4, 3, 88947);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidToAccountIdShouldFail()
{
bank.TransferFunds(1, -1, 100);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidFromAccountIdShouldFail()
{
bank.TransferFunds(1000, 4, 4658);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringInvalidAmountShouldFail()
{
bank.TransferFunds(1, 2, -594);
}
}
}
c# unit-testing interview-questions
$endgroup$
add a comment |
$begingroup$
I recently took a programming exam for an interview and unfortunately didn't pass it. I wasn't offered any feedback about the results. I'm looking for help on how to improve my OOP skills.
The requirements for the program were:
- a
bank
has aname
- a
bank
has manyaccounts
transactions
are stored on theaccounts
.
- There are different types of
accounts
:savings
andchecking
.
Checking accounts
can have multiple types,money market
andindividual
.
Individual accounts
can't withdraw more than $1000 at a time.
- I need to demonstrate withdrawal, deposit, and transferring funds through unit tests.
The code currently compiles and the unit tests run successfully.
Here is the main program:
Bank.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace BankExcercise
{
public class Bank
{
public string name;
private List<Account> bankAccounts;
public Bank(string name)
{
this.name = name;
bankAccounts = new List<Account>();
}
public int OpenBankAccount(Type accountType, decimal startingBalance)
{
int newId = bankAccounts.Count();
bankAccounts.Add((Account)Activator.CreateInstance(accountType, newId, startingBalance));
return newId;
}
public Account GetAccount(int ownerId)
{
Account account = bankAccounts.Where(x => x.owner == ownerId).FirstOrDefault();
if(account == null)
{
throw new ApplicationException("no account exists with that id");
}
return account;
}
public bool TransferFunds(int fromAccountId, int toAccountId, decimal transferAmount)
{
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
Account fromAccount = GetAccount(fromAccountId);
Account toAccount = GetAccount(toAccountId);
if(fromAccount.balance < transferAmount)
{
throw new ApplicationException("insufficient funds");
}
fromAccount.Transfer(-1 * transferAmount, toAccountId);
toAccount.Transfer(transferAmount, fromAccountId);
return true;
}
}
}
I decided to abstract an Account
from which savings
and checking
would derive.
Account.cs
using BankExcercise.Transactions;
using System;
using System.Collections.Generic;
namespace BankExcercise
{
public abstract class Account
{
public int owner { get; set; }
public decimal balance { get; set; }
public List<Transaction> transactions { get; set; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
public virtual bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
public void Transfer(decimal transferAmount, int transferToId)
{
balance += transferAmount;
TransferTransaction newTransaction = new TransferTransaction(transferAmount, transferToId, owner);
transactions.Add(newTransaction);
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ApplicationException("invalid deposit amount");
}
balance += amount;
Transaction newTransaction = new DepositTransaction(amount);
transactions.Add(newTransaction);
}
}
}
SavingsAccount.cs
namespace BankExcercise.Accounts
{
public class SavingsAccount : Account
{
public SavingsAccount(int id, decimal newBalance) : base(id, newBalance) { }
}
}
I created a CheckingAccount
abstract class that would inherit from Account
CheckingAccount.cs
namespace BankExcercise.Accounts
{
public abstract class CheckingAccount : Account
{
public CheckingAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
IndividualAccount
and MoneyMarket
would inherit from CheckingAccount
IndividualAccount.cs
using BankExcercise.Transactions;
using System;
namespace BankExcercise.Accounts.Checking
{
public class IndividualAccount : CheckingAccount
{
public IndividualAccount(int owner, decimal balance) : base(owner, balance)
{
}
public override bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if(withdrawlAmount > 1000)
{
throw new ApplicationException("withdrawl limit exceeded");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
}
}
There were no explicit instructions to follow for MoneyMarket
MoneyMarket.cs
namespace BankExcercise.Accounts.Checking
{
public class MoneyMarketAccount : CheckingAccount
{
public MoneyMarketAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
Unit Tests
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BankExcercise;
using BankExcercise.Accounts.Checking;
namespace BankExcerciseTests
{
[TestClass]
public class IndividualAccountTests
{
public Bank bank;
public Account bankAccount;
public int accountId;
[TestInitialize]
public void AccountSetup()
{
bank = new Bank("USA BANK");
accountId = bank.OpenBankAccount(typeof(IndividualAccount), 10000);
bankAccount = bank.GetAccount(accountId);
}
[TestMethod]
public void AccountIdShouldMatchGivenValue()
{
Assert.AreEqual(bankAccount.owner, accountId);
}
[TestMethod]
public void CheckingBalanceShouldMatchAccountBalance()
{
Assert.AreEqual(bankAccount.balance, 10000);
}
[TestMethod]
public void DepositingShouldSucceed()
{
bank.GetAccount(accountId).Deposit(100);
Assert.AreEqual(bankAccount.balance, 10100);
}
[TestMethod]
public void WithdrawingShouldSucceed()
{
Assert.IsTrue(bankAccount.Withdrawl(999));
Assert.AreEqual(bankAccount.balance, 9001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void WithdrawlingMoreThanLimitShouldFail()
{
bankAccount.Withdrawl(1001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void OverdraftingShouldFail()
{
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
bankAccount.Withdrawl(1000);
}
}
}
Unit Tests
using BankExcercise;
using BankExcercise.Accounts;
using BankExcercise.Accounts.Checking;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace BankExcerciseTests
{
[TestClass]
public class TransactionTests
{
public Bank bank;
[TestInitialize]
public void BankSetup()
{
bank = new Bank("First Bank of America");
bank.OpenBankAccount(typeof(IndividualAccount), 1465181);
bank.OpenBankAccount(typeof(SavingsAccount), 100);
bank.OpenBankAccount(typeof(MoneyMarketAccount), 15000);
bank.OpenBankAccount(typeof(SavingsAccount), 2564);
bank.OpenBankAccount(typeof(IndividualAccount), 87945);
bank.OpenBankAccount(typeof(SavingsAccount), 1000000001);
}
[TestMethod]
public void TransferringFromAnAccountWithCorrectBalanceShouldSucceed()
{
Assert.IsTrue(bank.TransferFunds(0, 2, 1465181));
Assert.AreEqual(bank.GetAccount(0).balance, 0);
Assert.AreEqual(bank.GetAccount(2).balance, 1480181);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringFromAnAccountWithInsufficientFundsShouldFail()
{
bank.TransferFunds(4, 3, 88947);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidToAccountIdShouldFail()
{
bank.TransferFunds(1, -1, 100);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidFromAccountIdShouldFail()
{
bank.TransferFunds(1000, 4, 4658);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringInvalidAmountShouldFail()
{
bank.TransferFunds(1, 2, -594);
}
}
}
c# unit-testing interview-questions
$endgroup$
2
$begingroup$
Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate.
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 30 '18 at 22:15
$begingroup$
Can you tell us whether you were applying for a junior or senior position? This is important because there are different expectations for candidates on each level. From your description I believe it was for a senior position and for that you made a lot of unforgivable mistakes that one could tolerate from a beginner. That's probably why you failed it.
$endgroup$
– t3chb0t
May 31 '18 at 14:25
add a comment |
$begingroup$
I recently took a programming exam for an interview and unfortunately didn't pass it. I wasn't offered any feedback about the results. I'm looking for help on how to improve my OOP skills.
The requirements for the program were:
- a
bank
has aname
- a
bank
has manyaccounts
transactions
are stored on theaccounts
.
- There are different types of
accounts
:savings
andchecking
.
Checking accounts
can have multiple types,money market
andindividual
.
Individual accounts
can't withdraw more than $1000 at a time.
- I need to demonstrate withdrawal, deposit, and transferring funds through unit tests.
The code currently compiles and the unit tests run successfully.
Here is the main program:
Bank.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace BankExcercise
{
public class Bank
{
public string name;
private List<Account> bankAccounts;
public Bank(string name)
{
this.name = name;
bankAccounts = new List<Account>();
}
public int OpenBankAccount(Type accountType, decimal startingBalance)
{
int newId = bankAccounts.Count();
bankAccounts.Add((Account)Activator.CreateInstance(accountType, newId, startingBalance));
return newId;
}
public Account GetAccount(int ownerId)
{
Account account = bankAccounts.Where(x => x.owner == ownerId).FirstOrDefault();
if(account == null)
{
throw new ApplicationException("no account exists with that id");
}
return account;
}
public bool TransferFunds(int fromAccountId, int toAccountId, decimal transferAmount)
{
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
Account fromAccount = GetAccount(fromAccountId);
Account toAccount = GetAccount(toAccountId);
if(fromAccount.balance < transferAmount)
{
throw new ApplicationException("insufficient funds");
}
fromAccount.Transfer(-1 * transferAmount, toAccountId);
toAccount.Transfer(transferAmount, fromAccountId);
return true;
}
}
}
I decided to abstract an Account
from which savings
and checking
would derive.
Account.cs
using BankExcercise.Transactions;
using System;
using System.Collections.Generic;
namespace BankExcercise
{
public abstract class Account
{
public int owner { get; set; }
public decimal balance { get; set; }
public List<Transaction> transactions { get; set; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
public virtual bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
public void Transfer(decimal transferAmount, int transferToId)
{
balance += transferAmount;
TransferTransaction newTransaction = new TransferTransaction(transferAmount, transferToId, owner);
transactions.Add(newTransaction);
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ApplicationException("invalid deposit amount");
}
balance += amount;
Transaction newTransaction = new DepositTransaction(amount);
transactions.Add(newTransaction);
}
}
}
SavingsAccount.cs
namespace BankExcercise.Accounts
{
public class SavingsAccount : Account
{
public SavingsAccount(int id, decimal newBalance) : base(id, newBalance) { }
}
}
I created a CheckingAccount
abstract class that would inherit from Account
CheckingAccount.cs
namespace BankExcercise.Accounts
{
public abstract class CheckingAccount : Account
{
public CheckingAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
IndividualAccount
and MoneyMarket
would inherit from CheckingAccount
IndividualAccount.cs
using BankExcercise.Transactions;
using System;
namespace BankExcercise.Accounts.Checking
{
public class IndividualAccount : CheckingAccount
{
public IndividualAccount(int owner, decimal balance) : base(owner, balance)
{
}
public override bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if(withdrawlAmount > 1000)
{
throw new ApplicationException("withdrawl limit exceeded");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
}
}
There were no explicit instructions to follow for MoneyMarket
MoneyMarket.cs
namespace BankExcercise.Accounts.Checking
{
public class MoneyMarketAccount : CheckingAccount
{
public MoneyMarketAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
Unit Tests
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BankExcercise;
using BankExcercise.Accounts.Checking;
namespace BankExcerciseTests
{
[TestClass]
public class IndividualAccountTests
{
public Bank bank;
public Account bankAccount;
public int accountId;
[TestInitialize]
public void AccountSetup()
{
bank = new Bank("USA BANK");
accountId = bank.OpenBankAccount(typeof(IndividualAccount), 10000);
bankAccount = bank.GetAccount(accountId);
}
[TestMethod]
public void AccountIdShouldMatchGivenValue()
{
Assert.AreEqual(bankAccount.owner, accountId);
}
[TestMethod]
public void CheckingBalanceShouldMatchAccountBalance()
{
Assert.AreEqual(bankAccount.balance, 10000);
}
[TestMethod]
public void DepositingShouldSucceed()
{
bank.GetAccount(accountId).Deposit(100);
Assert.AreEqual(bankAccount.balance, 10100);
}
[TestMethod]
public void WithdrawingShouldSucceed()
{
Assert.IsTrue(bankAccount.Withdrawl(999));
Assert.AreEqual(bankAccount.balance, 9001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void WithdrawlingMoreThanLimitShouldFail()
{
bankAccount.Withdrawl(1001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void OverdraftingShouldFail()
{
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
bankAccount.Withdrawl(1000);
}
}
}
Unit Tests
using BankExcercise;
using BankExcercise.Accounts;
using BankExcercise.Accounts.Checking;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace BankExcerciseTests
{
[TestClass]
public class TransactionTests
{
public Bank bank;
[TestInitialize]
public void BankSetup()
{
bank = new Bank("First Bank of America");
bank.OpenBankAccount(typeof(IndividualAccount), 1465181);
bank.OpenBankAccount(typeof(SavingsAccount), 100);
bank.OpenBankAccount(typeof(MoneyMarketAccount), 15000);
bank.OpenBankAccount(typeof(SavingsAccount), 2564);
bank.OpenBankAccount(typeof(IndividualAccount), 87945);
bank.OpenBankAccount(typeof(SavingsAccount), 1000000001);
}
[TestMethod]
public void TransferringFromAnAccountWithCorrectBalanceShouldSucceed()
{
Assert.IsTrue(bank.TransferFunds(0, 2, 1465181));
Assert.AreEqual(bank.GetAccount(0).balance, 0);
Assert.AreEqual(bank.GetAccount(2).balance, 1480181);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringFromAnAccountWithInsufficientFundsShouldFail()
{
bank.TransferFunds(4, 3, 88947);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidToAccountIdShouldFail()
{
bank.TransferFunds(1, -1, 100);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidFromAccountIdShouldFail()
{
bank.TransferFunds(1000, 4, 4658);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringInvalidAmountShouldFail()
{
bank.TransferFunds(1, 2, -594);
}
}
}
c# unit-testing interview-questions
$endgroup$
I recently took a programming exam for an interview and unfortunately didn't pass it. I wasn't offered any feedback about the results. I'm looking for help on how to improve my OOP skills.
The requirements for the program were:
- a
bank
has aname
- a
bank
has manyaccounts
transactions
are stored on theaccounts
.
- There are different types of
accounts
:savings
andchecking
.
Checking accounts
can have multiple types,money market
andindividual
.
Individual accounts
can't withdraw more than $1000 at a time.
- I need to demonstrate withdrawal, deposit, and transferring funds through unit tests.
The code currently compiles and the unit tests run successfully.
Here is the main program:
Bank.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace BankExcercise
{
public class Bank
{
public string name;
private List<Account> bankAccounts;
public Bank(string name)
{
this.name = name;
bankAccounts = new List<Account>();
}
public int OpenBankAccount(Type accountType, decimal startingBalance)
{
int newId = bankAccounts.Count();
bankAccounts.Add((Account)Activator.CreateInstance(accountType, newId, startingBalance));
return newId;
}
public Account GetAccount(int ownerId)
{
Account account = bankAccounts.Where(x => x.owner == ownerId).FirstOrDefault();
if(account == null)
{
throw new ApplicationException("no account exists with that id");
}
return account;
}
public bool TransferFunds(int fromAccountId, int toAccountId, decimal transferAmount)
{
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
Account fromAccount = GetAccount(fromAccountId);
Account toAccount = GetAccount(toAccountId);
if(fromAccount.balance < transferAmount)
{
throw new ApplicationException("insufficient funds");
}
fromAccount.Transfer(-1 * transferAmount, toAccountId);
toAccount.Transfer(transferAmount, fromAccountId);
return true;
}
}
}
I decided to abstract an Account
from which savings
and checking
would derive.
Account.cs
using BankExcercise.Transactions;
using System;
using System.Collections.Generic;
namespace BankExcercise
{
public abstract class Account
{
public int owner { get; set; }
public decimal balance { get; set; }
public List<Transaction> transactions { get; set; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
public virtual bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
public void Transfer(decimal transferAmount, int transferToId)
{
balance += transferAmount;
TransferTransaction newTransaction = new TransferTransaction(transferAmount, transferToId, owner);
transactions.Add(newTransaction);
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ApplicationException("invalid deposit amount");
}
balance += amount;
Transaction newTransaction = new DepositTransaction(amount);
transactions.Add(newTransaction);
}
}
}
SavingsAccount.cs
namespace BankExcercise.Accounts
{
public class SavingsAccount : Account
{
public SavingsAccount(int id, decimal newBalance) : base(id, newBalance) { }
}
}
I created a CheckingAccount
abstract class that would inherit from Account
CheckingAccount.cs
namespace BankExcercise.Accounts
{
public abstract class CheckingAccount : Account
{
public CheckingAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
IndividualAccount
and MoneyMarket
would inherit from CheckingAccount
IndividualAccount.cs
using BankExcercise.Transactions;
using System;
namespace BankExcercise.Accounts.Checking
{
public class IndividualAccount : CheckingAccount
{
public IndividualAccount(int owner, decimal balance) : base(owner, balance)
{
}
public override bool Withdrawl(decimal withdrawlAmount)
{
if (balance < withdrawlAmount)
{
throw new ApplicationException("insufficient funds");
}
else if(withdrawlAmount > 1000)
{
throw new ApplicationException("withdrawl limit exceeded");
}
else if (withdrawlAmount <= 0)
{
throw new ApplicationException("invalid withdrawl amount");
}
balance -= withdrawlAmount;
Transaction newTransaction = new WithdrawlTransaction(withdrawlAmount);
transactions.Add(newTransaction);
return true;
}
}
}
There were no explicit instructions to follow for MoneyMarket
MoneyMarket.cs
namespace BankExcercise.Accounts.Checking
{
public class MoneyMarketAccount : CheckingAccount
{
public MoneyMarketAccount(int owner, decimal balance) : base(owner, balance)
{
}
}
}
Unit Tests
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BankExcercise;
using BankExcercise.Accounts.Checking;
namespace BankExcerciseTests
{
[TestClass]
public class IndividualAccountTests
{
public Bank bank;
public Account bankAccount;
public int accountId;
[TestInitialize]
public void AccountSetup()
{
bank = new Bank("USA BANK");
accountId = bank.OpenBankAccount(typeof(IndividualAccount), 10000);
bankAccount = bank.GetAccount(accountId);
}
[TestMethod]
public void AccountIdShouldMatchGivenValue()
{
Assert.AreEqual(bankAccount.owner, accountId);
}
[TestMethod]
public void CheckingBalanceShouldMatchAccountBalance()
{
Assert.AreEqual(bankAccount.balance, 10000);
}
[TestMethod]
public void DepositingShouldSucceed()
{
bank.GetAccount(accountId).Deposit(100);
Assert.AreEqual(bankAccount.balance, 10100);
}
[TestMethod]
public void WithdrawingShouldSucceed()
{
Assert.IsTrue(bankAccount.Withdrawl(999));
Assert.AreEqual(bankAccount.balance, 9001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void WithdrawlingMoreThanLimitShouldFail()
{
bankAccount.Withdrawl(1001);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void OverdraftingShouldFail()
{
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
Assert.IsTrue(bankAccount.Withdrawl(1000));
bankAccount.Withdrawl(1000);
}
}
}
Unit Tests
using BankExcercise;
using BankExcercise.Accounts;
using BankExcercise.Accounts.Checking;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace BankExcerciseTests
{
[TestClass]
public class TransactionTests
{
public Bank bank;
[TestInitialize]
public void BankSetup()
{
bank = new Bank("First Bank of America");
bank.OpenBankAccount(typeof(IndividualAccount), 1465181);
bank.OpenBankAccount(typeof(SavingsAccount), 100);
bank.OpenBankAccount(typeof(MoneyMarketAccount), 15000);
bank.OpenBankAccount(typeof(SavingsAccount), 2564);
bank.OpenBankAccount(typeof(IndividualAccount), 87945);
bank.OpenBankAccount(typeof(SavingsAccount), 1000000001);
}
[TestMethod]
public void TransferringFromAnAccountWithCorrectBalanceShouldSucceed()
{
Assert.IsTrue(bank.TransferFunds(0, 2, 1465181));
Assert.AreEqual(bank.GetAccount(0).balance, 0);
Assert.AreEqual(bank.GetAccount(2).balance, 1480181);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringFromAnAccountWithInsufficientFundsShouldFail()
{
bank.TransferFunds(4, 3, 88947);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidToAccountIdShouldFail()
{
bank.TransferFunds(1, -1, 100);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringWithInvalidFromAccountIdShouldFail()
{
bank.TransferFunds(1000, 4, 4658);
}
[TestMethod]
[ExpectedException(typeof(ApplicationException))]
public void TransferringInvalidAmountShouldFail()
{
bank.TransferFunds(1, 2, -594);
}
}
}
c# unit-testing interview-questions
c# unit-testing interview-questions
edited May 31 '18 at 7:40
t3chb0t
35k752124
35k752124
asked May 30 '18 at 22:00
mlapagliamlapaglia
1336
1336
2
$begingroup$
Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate.
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 30 '18 at 22:15
$begingroup$
Can you tell us whether you were applying for a junior or senior position? This is important because there are different expectations for candidates on each level. From your description I believe it was for a senior position and for that you made a lot of unforgivable mistakes that one could tolerate from a beginner. That's probably why you failed it.
$endgroup$
– t3chb0t
May 31 '18 at 14:25
add a comment |
2
$begingroup$
Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate.
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 30 '18 at 22:15
$begingroup$
Can you tell us whether you were applying for a junior or senior position? This is important because there are different expectations for candidates on each level. From your description I believe it was for a senior position and for that you made a lot of unforgivable mistakes that one could tolerate from a beginner. That's probably why you failed it.
$endgroup$
– t3chb0t
May 31 '18 at 14:25
2
2
$begingroup$
Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate.
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 30 '18 at 22:15
$begingroup$
Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate.
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 30 '18 at 22:15
$begingroup$
Can you tell us whether you were applying for a junior or senior position? This is important because there are different expectations for candidates on each level. From your description I believe it was for a senior position and for that you made a lot of unforgivable mistakes that one could tolerate from a beginner. That's probably why you failed it.
$endgroup$
– t3chb0t
May 31 '18 at 14:25
$begingroup$
Can you tell us whether you were applying for a junior or senior position? This is important because there are different expectations for candidates on each level. From your description I believe it was for a senior position and for that you made a lot of unforgivable mistakes that one could tolerate from a beginner. That's probably why you failed it.
$endgroup$
– t3chb0t
May 31 '18 at 14:25
add a comment |
4 Answers
4
active
oldest
votes
$begingroup$
Coding points
Fields and Properties
In C#, properties should be PascalCased (Owner, Balance, Transaction, in Account).
I would push for no public fields (Name, in Bank), always expose properties rather than fields.
If a property should not be changed outside the class, it should have no public getter. If it should not be changed at all then it should be readonly (or have no setter, if a property)
public abstract class Account
{
public int Owner { get; private set; }
public decimal Balance { get; private set; }
public List<Transaction> Transactions { get; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
//...
Return values
We always return true
from Withdrawal
. Under what conditions can it return false? If it can never return false
, why do we have a return value.
If we do want/need the return value, why do Transfer
and Deposit
not have one?
Exceptions
This can be argued, but I would say that we should not be using exceptions for the various checks. 'Insufficient Funds' is not really an unexpected, out of the norm, problem. It is the sort of thing we should expect to occur and for which we should allow.
'Invalid Account', has better qualification for being an exception - if we have reached this part in the processing - somehow selecting an account and getting an account id - and then find that there is no account of that id, then that would be an unexpected, out of the norm occurrence.
Note:
The docs for ApplicationException
note that is should not be used. Don't inherit from it, don't throw it, don't catch unless you re-throw ApplicationException
Using Type for account creation
Using the Type
and the Activator
to create account instances seems (to me) a bit strange. I would say that an enum for account type (or a name for easier extension - arguable) and a Factory of some sort would be more usual. Yes, it can be argued that this is what using the Type
and the Activator
is doing but in an interview situation, showing that one knows about and understands the design pattern is good.
We have hard-wired the account creation into the Bank. If we wish to change how accounts are created then we need to open up and change the Bank. If we pass in a factory (as an interface) then we can change the way that accounts are created without needing to change the Bank.
Design Points
Transactions
The code for the different transaction types seems to be missing (or else I am just missing it). Either way, it seems overkill to have different classes for each transaction type.
At one extreme, we can create a single Transaction account that includes fields for all transaction types even though some fields (say, OtherAccount) will not be used.
Or, we can create classes for each set of data needed (say, `SingleAccountTransaction', 'TwoAccountTransaction'). OK, it saves only a single class (so far) but classes which are different only in name seems wasteful.
Again, a Factory to create the transactions would allow us to encapsulate the behaviour and change it if we desired.
Accounts
At the moment, we have the following account types
- Account
- SavingsAccount
- CheckingAccount
- MoneyMarketAccount
- IndividualAccount
All (as far as I can see) to implement the $1000 limit on withdrawals from the Individual account.
As with the Transactions above, this seems overkill. If the only difference between the accounts are the rules for withdrawals (or deposits or transfers or any other transaction, or any other rules), then we should encapsulate the rules and have a single Account class that can be instantiated with a set of rules (Factory to the rescue again).
At the moment, we not only have the proliferation of Accounts but the code in the Withdrawal()
ends up getting replicated in Account
and Individual
account because we need to override all the functionality - this will be a maintenance problem if we change core rules and need to update it through all the account types.
We can restructure things so that we put the common pieces in the base class and call them from the derived classes, putting only the specific pieces in the derived classes but this can get messy if the order of checks is important and varies between account type, or we have special cases (say we are allowed to have an overdraft on some checking accounts).
The more types of account we have, the more onerous it becomes to maintain a different class for each account type. If the only difference is the rules, the encapsulate the rules.
As a bonus, in an interview situation (at a certain level), many, if not most, of the answers offered will be polymorphic Account types. Being able to offer and defend a different answer will make one stand out. :D
Update:
Adding an if
block into the base class will work for this limited situation but is very cumbersome when the are lots of different account types with lots of different rules. The good part of the polymorphic accounts is that it removes the need for switch statements based upon the account type.
The intent of encapsulating the rules
is to have some mechanism for configuring the transactions and then have the factory inject into the Account
the specific configuration for each account type. We do not want any explicit checks for AccountType
in the Account
code.
We can configure the transactions in a few ways
- A set of rules for the transaction (like the balance and amount rules for withdrawals).
- Transaction objects which have the rules embedded within themselves - though if we are not careful, this can get as messy as the accounts themselves in terms of class proliferation
- Probably lots of other that I can't think of right now
There can be a lot of code and moving pieces in this, so for a few account types/transactions/rules it is probably overkill but as I said, in an interview, if one can show an understanding of why inheritance and polymorphism is not always the best answer, what the alternatives are and the pros and cons of these, it will probably make one stand out.
$endgroup$
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing theAccount
to a regular class, having an enum stating what type of class it is, and have a simple if block inwithdrawl
to handle anindividual
type account? Then make a factory that can generate anaccount
with the enum set to the appropriate type?
$endgroup$
– mlapaglia
May 31 '18 at 15:48
add a comment |
$begingroup$
I did not read it all, here is just my comments on what I noticed first:
- There are no transactions, in the account. That is the transactions are not stored. This will stop the code from being re-entrent, prevent logging/auditing/monthly statements etc. You were told that the account stores transactions, not a reduction (of value).
- Never use floating point for money. Well done for using Decimal over float, but still not good enough. You should use a very big int, and work in pence/cents.
$endgroup$
1
$begingroup$
There is a list of transactions in theAccount
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to usedecimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
1
$begingroup$
What's wrong withdecimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "Thedecimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision
$endgroup$
– Nick A
May 31 '18 at 14:09
add a comment |
$begingroup$
Do not use public
fields. Public fields are direct way to unexpected behavior of your application. Use properties instead of them:
private readonly string name;
public string Name
{
get { return name; }
}
Use readonly
for fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.
Do not throw from System.ApplicationException. Jeffery Richter in Framework Design Guidelines said:
System.ApplicationException is a class that should not be part of the .NET Framework. The original idea was that classes derived from SystemException would indicate exceptions thrown from the CLR (or system) itself, whereas non-CLR exceptions would be derived from ApplicationException. However, a lot of exception classes didn't follow this pattern. For example, TargetInvocationException (which is thrown by the CLR) is derived from ApplicationException. So, the ApplicationException class lost all meaning. The reason to derive from this base class is to allow some code higher up the call stack to catch the base class. It was no longer possible to catch all application exceptions.
Use predefined .NET exception types, for example:
if(transferAmount <= 0)
{
throw new ArgumentException("transfer amount must be positive");
}
Best practices for exceptions is Here.
Will never go through else if
statement:
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
$endgroup$
add a comment |
$begingroup$
This is dangerous
int newId = bankAccounts.Count();
Cannot assume that will be a valid newID. If this is a server application two clients could get the same newID before adding. An account can be removed.
Account should overide equals and Accounts should be a HashSet.
There is no facility for knowing what accounts belong to an individual.
There is no check of a person is allowed to withdraw from an account.
External should not be able to set balance.
public decimal balance { get; set; }
The balance could be changed and no transaction. No withdrawal.
Maybe they are not looking for that stuff. If the job was at a bank you have shown poor domain knowledge.
Name should be a property
public class Bank
{
public string Name { get; }
private List<Account> bankAccounts = new List<Account>();
public Bank(string name)
{
Name = name;
}
Transactions does not need a set.
public List<Transaction> transactions { get; } = new List<Transaction>();
Even with just a get;
transactions can be modified externally. It should probably be read only.
$endgroup$
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195513%2fbank-accounts-with-methods-to-transfer-funds%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Coding points
Fields and Properties
In C#, properties should be PascalCased (Owner, Balance, Transaction, in Account).
I would push for no public fields (Name, in Bank), always expose properties rather than fields.
If a property should not be changed outside the class, it should have no public getter. If it should not be changed at all then it should be readonly (or have no setter, if a property)
public abstract class Account
{
public int Owner { get; private set; }
public decimal Balance { get; private set; }
public List<Transaction> Transactions { get; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
//...
Return values
We always return true
from Withdrawal
. Under what conditions can it return false? If it can never return false
, why do we have a return value.
If we do want/need the return value, why do Transfer
and Deposit
not have one?
Exceptions
This can be argued, but I would say that we should not be using exceptions for the various checks. 'Insufficient Funds' is not really an unexpected, out of the norm, problem. It is the sort of thing we should expect to occur and for which we should allow.
'Invalid Account', has better qualification for being an exception - if we have reached this part in the processing - somehow selecting an account and getting an account id - and then find that there is no account of that id, then that would be an unexpected, out of the norm occurrence.
Note:
The docs for ApplicationException
note that is should not be used. Don't inherit from it, don't throw it, don't catch unless you re-throw ApplicationException
Using Type for account creation
Using the Type
and the Activator
to create account instances seems (to me) a bit strange. I would say that an enum for account type (or a name for easier extension - arguable) and a Factory of some sort would be more usual. Yes, it can be argued that this is what using the Type
and the Activator
is doing but in an interview situation, showing that one knows about and understands the design pattern is good.
We have hard-wired the account creation into the Bank. If we wish to change how accounts are created then we need to open up and change the Bank. If we pass in a factory (as an interface) then we can change the way that accounts are created without needing to change the Bank.
Design Points
Transactions
The code for the different transaction types seems to be missing (or else I am just missing it). Either way, it seems overkill to have different classes for each transaction type.
At one extreme, we can create a single Transaction account that includes fields for all transaction types even though some fields (say, OtherAccount) will not be used.
Or, we can create classes for each set of data needed (say, `SingleAccountTransaction', 'TwoAccountTransaction'). OK, it saves only a single class (so far) but classes which are different only in name seems wasteful.
Again, a Factory to create the transactions would allow us to encapsulate the behaviour and change it if we desired.
Accounts
At the moment, we have the following account types
- Account
- SavingsAccount
- CheckingAccount
- MoneyMarketAccount
- IndividualAccount
All (as far as I can see) to implement the $1000 limit on withdrawals from the Individual account.
As with the Transactions above, this seems overkill. If the only difference between the accounts are the rules for withdrawals (or deposits or transfers or any other transaction, or any other rules), then we should encapsulate the rules and have a single Account class that can be instantiated with a set of rules (Factory to the rescue again).
At the moment, we not only have the proliferation of Accounts but the code in the Withdrawal()
ends up getting replicated in Account
and Individual
account because we need to override all the functionality - this will be a maintenance problem if we change core rules and need to update it through all the account types.
We can restructure things so that we put the common pieces in the base class and call them from the derived classes, putting only the specific pieces in the derived classes but this can get messy if the order of checks is important and varies between account type, or we have special cases (say we are allowed to have an overdraft on some checking accounts).
The more types of account we have, the more onerous it becomes to maintain a different class for each account type. If the only difference is the rules, the encapsulate the rules.
As a bonus, in an interview situation (at a certain level), many, if not most, of the answers offered will be polymorphic Account types. Being able to offer and defend a different answer will make one stand out. :D
Update:
Adding an if
block into the base class will work for this limited situation but is very cumbersome when the are lots of different account types with lots of different rules. The good part of the polymorphic accounts is that it removes the need for switch statements based upon the account type.
The intent of encapsulating the rules
is to have some mechanism for configuring the transactions and then have the factory inject into the Account
the specific configuration for each account type. We do not want any explicit checks for AccountType
in the Account
code.
We can configure the transactions in a few ways
- A set of rules for the transaction (like the balance and amount rules for withdrawals).
- Transaction objects which have the rules embedded within themselves - though if we are not careful, this can get as messy as the accounts themselves in terms of class proliferation
- Probably lots of other that I can't think of right now
There can be a lot of code and moving pieces in this, so for a few account types/transactions/rules it is probably overkill but as I said, in an interview, if one can show an understanding of why inheritance and polymorphism is not always the best answer, what the alternatives are and the pros and cons of these, it will probably make one stand out.
$endgroup$
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing theAccount
to a regular class, having an enum stating what type of class it is, and have a simple if block inwithdrawl
to handle anindividual
type account? Then make a factory that can generate anaccount
with the enum set to the appropriate type?
$endgroup$
– mlapaglia
May 31 '18 at 15:48
add a comment |
$begingroup$
Coding points
Fields and Properties
In C#, properties should be PascalCased (Owner, Balance, Transaction, in Account).
I would push for no public fields (Name, in Bank), always expose properties rather than fields.
If a property should not be changed outside the class, it should have no public getter. If it should not be changed at all then it should be readonly (or have no setter, if a property)
public abstract class Account
{
public int Owner { get; private set; }
public decimal Balance { get; private set; }
public List<Transaction> Transactions { get; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
//...
Return values
We always return true
from Withdrawal
. Under what conditions can it return false? If it can never return false
, why do we have a return value.
If we do want/need the return value, why do Transfer
and Deposit
not have one?
Exceptions
This can be argued, but I would say that we should not be using exceptions for the various checks. 'Insufficient Funds' is not really an unexpected, out of the norm, problem. It is the sort of thing we should expect to occur and for which we should allow.
'Invalid Account', has better qualification for being an exception - if we have reached this part in the processing - somehow selecting an account and getting an account id - and then find that there is no account of that id, then that would be an unexpected, out of the norm occurrence.
Note:
The docs for ApplicationException
note that is should not be used. Don't inherit from it, don't throw it, don't catch unless you re-throw ApplicationException
Using Type for account creation
Using the Type
and the Activator
to create account instances seems (to me) a bit strange. I would say that an enum for account type (or a name for easier extension - arguable) and a Factory of some sort would be more usual. Yes, it can be argued that this is what using the Type
and the Activator
is doing but in an interview situation, showing that one knows about and understands the design pattern is good.
We have hard-wired the account creation into the Bank. If we wish to change how accounts are created then we need to open up and change the Bank. If we pass in a factory (as an interface) then we can change the way that accounts are created without needing to change the Bank.
Design Points
Transactions
The code for the different transaction types seems to be missing (or else I am just missing it). Either way, it seems overkill to have different classes for each transaction type.
At one extreme, we can create a single Transaction account that includes fields for all transaction types even though some fields (say, OtherAccount) will not be used.
Or, we can create classes for each set of data needed (say, `SingleAccountTransaction', 'TwoAccountTransaction'). OK, it saves only a single class (so far) but classes which are different only in name seems wasteful.
Again, a Factory to create the transactions would allow us to encapsulate the behaviour and change it if we desired.
Accounts
At the moment, we have the following account types
- Account
- SavingsAccount
- CheckingAccount
- MoneyMarketAccount
- IndividualAccount
All (as far as I can see) to implement the $1000 limit on withdrawals from the Individual account.
As with the Transactions above, this seems overkill. If the only difference between the accounts are the rules for withdrawals (or deposits or transfers or any other transaction, or any other rules), then we should encapsulate the rules and have a single Account class that can be instantiated with a set of rules (Factory to the rescue again).
At the moment, we not only have the proliferation of Accounts but the code in the Withdrawal()
ends up getting replicated in Account
and Individual
account because we need to override all the functionality - this will be a maintenance problem if we change core rules and need to update it through all the account types.
We can restructure things so that we put the common pieces in the base class and call them from the derived classes, putting only the specific pieces in the derived classes but this can get messy if the order of checks is important and varies between account type, or we have special cases (say we are allowed to have an overdraft on some checking accounts).
The more types of account we have, the more onerous it becomes to maintain a different class for each account type. If the only difference is the rules, the encapsulate the rules.
As a bonus, in an interview situation (at a certain level), many, if not most, of the answers offered will be polymorphic Account types. Being able to offer and defend a different answer will make one stand out. :D
Update:
Adding an if
block into the base class will work for this limited situation but is very cumbersome when the are lots of different account types with lots of different rules. The good part of the polymorphic accounts is that it removes the need for switch statements based upon the account type.
The intent of encapsulating the rules
is to have some mechanism for configuring the transactions and then have the factory inject into the Account
the specific configuration for each account type. We do not want any explicit checks for AccountType
in the Account
code.
We can configure the transactions in a few ways
- A set of rules for the transaction (like the balance and amount rules for withdrawals).
- Transaction objects which have the rules embedded within themselves - though if we are not careful, this can get as messy as the accounts themselves in terms of class proliferation
- Probably lots of other that I can't think of right now
There can be a lot of code and moving pieces in this, so for a few account types/transactions/rules it is probably overkill but as I said, in an interview, if one can show an understanding of why inheritance and polymorphism is not always the best answer, what the alternatives are and the pros and cons of these, it will probably make one stand out.
$endgroup$
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing theAccount
to a regular class, having an enum stating what type of class it is, and have a simple if block inwithdrawl
to handle anindividual
type account? Then make a factory that can generate anaccount
with the enum set to the appropriate type?
$endgroup$
– mlapaglia
May 31 '18 at 15:48
add a comment |
$begingroup$
Coding points
Fields and Properties
In C#, properties should be PascalCased (Owner, Balance, Transaction, in Account).
I would push for no public fields (Name, in Bank), always expose properties rather than fields.
If a property should not be changed outside the class, it should have no public getter. If it should not be changed at all then it should be readonly (or have no setter, if a property)
public abstract class Account
{
public int Owner { get; private set; }
public decimal Balance { get; private set; }
public List<Transaction> Transactions { get; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
//...
Return values
We always return true
from Withdrawal
. Under what conditions can it return false? If it can never return false
, why do we have a return value.
If we do want/need the return value, why do Transfer
and Deposit
not have one?
Exceptions
This can be argued, but I would say that we should not be using exceptions for the various checks. 'Insufficient Funds' is not really an unexpected, out of the norm, problem. It is the sort of thing we should expect to occur and for which we should allow.
'Invalid Account', has better qualification for being an exception - if we have reached this part in the processing - somehow selecting an account and getting an account id - and then find that there is no account of that id, then that would be an unexpected, out of the norm occurrence.
Note:
The docs for ApplicationException
note that is should not be used. Don't inherit from it, don't throw it, don't catch unless you re-throw ApplicationException
Using Type for account creation
Using the Type
and the Activator
to create account instances seems (to me) a bit strange. I would say that an enum for account type (or a name for easier extension - arguable) and a Factory of some sort would be more usual. Yes, it can be argued that this is what using the Type
and the Activator
is doing but in an interview situation, showing that one knows about and understands the design pattern is good.
We have hard-wired the account creation into the Bank. If we wish to change how accounts are created then we need to open up and change the Bank. If we pass in a factory (as an interface) then we can change the way that accounts are created without needing to change the Bank.
Design Points
Transactions
The code for the different transaction types seems to be missing (or else I am just missing it). Either way, it seems overkill to have different classes for each transaction type.
At one extreme, we can create a single Transaction account that includes fields for all transaction types even though some fields (say, OtherAccount) will not be used.
Or, we can create classes for each set of data needed (say, `SingleAccountTransaction', 'TwoAccountTransaction'). OK, it saves only a single class (so far) but classes which are different only in name seems wasteful.
Again, a Factory to create the transactions would allow us to encapsulate the behaviour and change it if we desired.
Accounts
At the moment, we have the following account types
- Account
- SavingsAccount
- CheckingAccount
- MoneyMarketAccount
- IndividualAccount
All (as far as I can see) to implement the $1000 limit on withdrawals from the Individual account.
As with the Transactions above, this seems overkill. If the only difference between the accounts are the rules for withdrawals (or deposits or transfers or any other transaction, or any other rules), then we should encapsulate the rules and have a single Account class that can be instantiated with a set of rules (Factory to the rescue again).
At the moment, we not only have the proliferation of Accounts but the code in the Withdrawal()
ends up getting replicated in Account
and Individual
account because we need to override all the functionality - this will be a maintenance problem if we change core rules and need to update it through all the account types.
We can restructure things so that we put the common pieces in the base class and call them from the derived classes, putting only the specific pieces in the derived classes but this can get messy if the order of checks is important and varies between account type, or we have special cases (say we are allowed to have an overdraft on some checking accounts).
The more types of account we have, the more onerous it becomes to maintain a different class for each account type. If the only difference is the rules, the encapsulate the rules.
As a bonus, in an interview situation (at a certain level), many, if not most, of the answers offered will be polymorphic Account types. Being able to offer and defend a different answer will make one stand out. :D
Update:
Adding an if
block into the base class will work for this limited situation but is very cumbersome when the are lots of different account types with lots of different rules. The good part of the polymorphic accounts is that it removes the need for switch statements based upon the account type.
The intent of encapsulating the rules
is to have some mechanism for configuring the transactions and then have the factory inject into the Account
the specific configuration for each account type. We do not want any explicit checks for AccountType
in the Account
code.
We can configure the transactions in a few ways
- A set of rules for the transaction (like the balance and amount rules for withdrawals).
- Transaction objects which have the rules embedded within themselves - though if we are not careful, this can get as messy as the accounts themselves in terms of class proliferation
- Probably lots of other that I can't think of right now
There can be a lot of code and moving pieces in this, so for a few account types/transactions/rules it is probably overkill but as I said, in an interview, if one can show an understanding of why inheritance and polymorphism is not always the best answer, what the alternatives are and the pros and cons of these, it will probably make one stand out.
$endgroup$
Coding points
Fields and Properties
In C#, properties should be PascalCased (Owner, Balance, Transaction, in Account).
I would push for no public fields (Name, in Bank), always expose properties rather than fields.
If a property should not be changed outside the class, it should have no public getter. If it should not be changed at all then it should be readonly (or have no setter, if a property)
public abstract class Account
{
public int Owner { get; private set; }
public decimal Balance { get; private set; }
public List<Transaction> Transactions { get; }
public Account(int owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
transactions = new List<Transaction>();
}
//...
Return values
We always return true
from Withdrawal
. Under what conditions can it return false? If it can never return false
, why do we have a return value.
If we do want/need the return value, why do Transfer
and Deposit
not have one?
Exceptions
This can be argued, but I would say that we should not be using exceptions for the various checks. 'Insufficient Funds' is not really an unexpected, out of the norm, problem. It is the sort of thing we should expect to occur and for which we should allow.
'Invalid Account', has better qualification for being an exception - if we have reached this part in the processing - somehow selecting an account and getting an account id - and then find that there is no account of that id, then that would be an unexpected, out of the norm occurrence.
Note:
The docs for ApplicationException
note that is should not be used. Don't inherit from it, don't throw it, don't catch unless you re-throw ApplicationException
Using Type for account creation
Using the Type
and the Activator
to create account instances seems (to me) a bit strange. I would say that an enum for account type (or a name for easier extension - arguable) and a Factory of some sort would be more usual. Yes, it can be argued that this is what using the Type
and the Activator
is doing but in an interview situation, showing that one knows about and understands the design pattern is good.
We have hard-wired the account creation into the Bank. If we wish to change how accounts are created then we need to open up and change the Bank. If we pass in a factory (as an interface) then we can change the way that accounts are created without needing to change the Bank.
Design Points
Transactions
The code for the different transaction types seems to be missing (or else I am just missing it). Either way, it seems overkill to have different classes for each transaction type.
At one extreme, we can create a single Transaction account that includes fields for all transaction types even though some fields (say, OtherAccount) will not be used.
Or, we can create classes for each set of data needed (say, `SingleAccountTransaction', 'TwoAccountTransaction'). OK, it saves only a single class (so far) but classes which are different only in name seems wasteful.
Again, a Factory to create the transactions would allow us to encapsulate the behaviour and change it if we desired.
Accounts
At the moment, we have the following account types
- Account
- SavingsAccount
- CheckingAccount
- MoneyMarketAccount
- IndividualAccount
All (as far as I can see) to implement the $1000 limit on withdrawals from the Individual account.
As with the Transactions above, this seems overkill. If the only difference between the accounts are the rules for withdrawals (or deposits or transfers or any other transaction, or any other rules), then we should encapsulate the rules and have a single Account class that can be instantiated with a set of rules (Factory to the rescue again).
At the moment, we not only have the proliferation of Accounts but the code in the Withdrawal()
ends up getting replicated in Account
and Individual
account because we need to override all the functionality - this will be a maintenance problem if we change core rules and need to update it through all the account types.
We can restructure things so that we put the common pieces in the base class and call them from the derived classes, putting only the specific pieces in the derived classes but this can get messy if the order of checks is important and varies between account type, or we have special cases (say we are allowed to have an overdraft on some checking accounts).
The more types of account we have, the more onerous it becomes to maintain a different class for each account type. If the only difference is the rules, the encapsulate the rules.
As a bonus, in an interview situation (at a certain level), many, if not most, of the answers offered will be polymorphic Account types. Being able to offer and defend a different answer will make one stand out. :D
Update:
Adding an if
block into the base class will work for this limited situation but is very cumbersome when the are lots of different account types with lots of different rules. The good part of the polymorphic accounts is that it removes the need for switch statements based upon the account type.
The intent of encapsulating the rules
is to have some mechanism for configuring the transactions and then have the factory inject into the Account
the specific configuration for each account type. We do not want any explicit checks for AccountType
in the Account
code.
We can configure the transactions in a few ways
- A set of rules for the transaction (like the balance and amount rules for withdrawals).
- Transaction objects which have the rules embedded within themselves - though if we are not careful, this can get as messy as the accounts themselves in terms of class proliferation
- Probably lots of other that I can't think of right now
There can be a lot of code and moving pieces in this, so for a few account types/transactions/rules it is probably overkill but as I said, in an interview, if one can show an understanding of why inheritance and polymorphism is not always the best answer, what the alternatives are and the pros and cons of these, it will probably make one stand out.
edited Jun 6 '18 at 8:12
answered May 31 '18 at 9:08
AlanTAlanT
3,0191015
3,0191015
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing theAccount
to a regular class, having an enum stating what type of class it is, and have a simple if block inwithdrawl
to handle anindividual
type account? Then make a factory that can generate anaccount
with the enum set to the appropriate type?
$endgroup$
– mlapaglia
May 31 '18 at 15:48
add a comment |
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing theAccount
to a regular class, having an enum stating what type of class it is, and have a simple if block inwithdrawl
to handle anindividual
type account? Then make a factory that can generate anaccount
with the enum set to the appropriate type?
$endgroup$
– mlapaglia
May 31 '18 at 15:48
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Well-written review, touched on everything I was going to mention....before I realized there were answers already xD
$endgroup$
– Shelby115
May 31 '18 at 12:35
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing the
Account
to a regular class, having an enum stating what type of class it is, and have a simple if block in withdrawl
to handle an individual
type account? Then make a factory that can generate an account
with the enum set to the appropriate type?$endgroup$
– mlapaglia
May 31 '18 at 15:48
$begingroup$
Very through response, thank you! By encapsulating the rules for the accounts, do you mean something as simple as changing the
Account
to a regular class, having an enum stating what type of class it is, and have a simple if block in withdrawl
to handle an individual
type account? Then make a factory that can generate an account
with the enum set to the appropriate type?$endgroup$
– mlapaglia
May 31 '18 at 15:48
add a comment |
$begingroup$
I did not read it all, here is just my comments on what I noticed first:
- There are no transactions, in the account. That is the transactions are not stored. This will stop the code from being re-entrent, prevent logging/auditing/monthly statements etc. You were told that the account stores transactions, not a reduction (of value).
- Never use floating point for money. Well done for using Decimal over float, but still not good enough. You should use a very big int, and work in pence/cents.
$endgroup$
1
$begingroup$
There is a list of transactions in theAccount
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to usedecimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
1
$begingroup$
What's wrong withdecimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "Thedecimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision
$endgroup$
– Nick A
May 31 '18 at 14:09
add a comment |
$begingroup$
I did not read it all, here is just my comments on what I noticed first:
- There are no transactions, in the account. That is the transactions are not stored. This will stop the code from being re-entrent, prevent logging/auditing/monthly statements etc. You were told that the account stores transactions, not a reduction (of value).
- Never use floating point for money. Well done for using Decimal over float, but still not good enough. You should use a very big int, and work in pence/cents.
$endgroup$
1
$begingroup$
There is a list of transactions in theAccount
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to usedecimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
1
$begingroup$
What's wrong withdecimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "Thedecimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision
$endgroup$
– Nick A
May 31 '18 at 14:09
add a comment |
$begingroup$
I did not read it all, here is just my comments on what I noticed first:
- There are no transactions, in the account. That is the transactions are not stored. This will stop the code from being re-entrent, prevent logging/auditing/monthly statements etc. You were told that the account stores transactions, not a reduction (of value).
- Never use floating point for money. Well done for using Decimal over float, but still not good enough. You should use a very big int, and work in pence/cents.
$endgroup$
I did not read it all, here is just my comments on what I noticed first:
- There are no transactions, in the account. That is the transactions are not stored. This will stop the code from being re-entrent, prevent logging/auditing/monthly statements etc. You were told that the account stores transactions, not a reduction (of value).
- Never use floating point for money. Well done for using Decimal over float, but still not good enough. You should use a very big int, and work in pence/cents.
answered May 31 '18 at 10:58
ctrl-alt-delorctrl-alt-delor
1094
1094
1
$begingroup$
There is a list of transactions in theAccount
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to usedecimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
1
$begingroup$
What's wrong withdecimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "Thedecimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision
$endgroup$
– Nick A
May 31 '18 at 14:09
add a comment |
1
$begingroup$
There is a list of transactions in theAccount
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to usedecimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
1
$begingroup$
What's wrong withdecimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "Thedecimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision
$endgroup$
– Nick A
May 31 '18 at 14:09
1
1
$begingroup$
There is a list of transactions in the
Account
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to use decimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
There is a list of transactions in the
Account
class and each operation adds the created transaction to this list. The recommendation from Microsoft is to use decimal
for financial and monetary transactions docs.microsoft.com/en-us/dotnet/csharp/language-reference/…$endgroup$
– AlanT
May 31 '18 at 11:19
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
$begingroup$
I see that you create something called a transaction (but no definition of it). It is does not appear to be a transaction, it may be a log. The balance should be a reduce function on all transactions. Your implementation if focused around balance, with transaction added as an after thought. Microsoft, is correct that decimal is better than float or double, as it has a much bigger range, and does not have the rounding errors that you will get e.g. with 30cents. But put checks in your code to ensure that it does not go out of range.
$endgroup$
– ctrl-alt-delor
May 31 '18 at 11:37
1
1
$begingroup$
What's wrong with
decimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "The decimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision$endgroup$
– Nick A
May 31 '18 at 14:09
$begingroup$
What's wrong with
decimal
for money? it offers exact representations of values to 28 or 29 digits. It was basically designed to be for money. The C# docs even specify that it's suitable for money: "The decimal
type is a 128-bit data type suitable for financial and monetary calculations.". And decimal doesn't have a bigger range (as you said in your last comment), it has a smaller range, that's how it can afford the extra precision$endgroup$
– Nick A
May 31 '18 at 14:09
add a comment |
$begingroup$
Do not use public
fields. Public fields are direct way to unexpected behavior of your application. Use properties instead of them:
private readonly string name;
public string Name
{
get { return name; }
}
Use readonly
for fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.
Do not throw from System.ApplicationException. Jeffery Richter in Framework Design Guidelines said:
System.ApplicationException is a class that should not be part of the .NET Framework. The original idea was that classes derived from SystemException would indicate exceptions thrown from the CLR (or system) itself, whereas non-CLR exceptions would be derived from ApplicationException. However, a lot of exception classes didn't follow this pattern. For example, TargetInvocationException (which is thrown by the CLR) is derived from ApplicationException. So, the ApplicationException class lost all meaning. The reason to derive from this base class is to allow some code higher up the call stack to catch the base class. It was no longer possible to catch all application exceptions.
Use predefined .NET exception types, for example:
if(transferAmount <= 0)
{
throw new ArgumentException("transfer amount must be positive");
}
Best practices for exceptions is Here.
Will never go through else if
statement:
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
$endgroup$
add a comment |
$begingroup$
Do not use public
fields. Public fields are direct way to unexpected behavior of your application. Use properties instead of them:
private readonly string name;
public string Name
{
get { return name; }
}
Use readonly
for fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.
Do not throw from System.ApplicationException. Jeffery Richter in Framework Design Guidelines said:
System.ApplicationException is a class that should not be part of the .NET Framework. The original idea was that classes derived from SystemException would indicate exceptions thrown from the CLR (or system) itself, whereas non-CLR exceptions would be derived from ApplicationException. However, a lot of exception classes didn't follow this pattern. For example, TargetInvocationException (which is thrown by the CLR) is derived from ApplicationException. So, the ApplicationException class lost all meaning. The reason to derive from this base class is to allow some code higher up the call stack to catch the base class. It was no longer possible to catch all application exceptions.
Use predefined .NET exception types, for example:
if(transferAmount <= 0)
{
throw new ArgumentException("transfer amount must be positive");
}
Best practices for exceptions is Here.
Will never go through else if
statement:
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
$endgroup$
add a comment |
$begingroup$
Do not use public
fields. Public fields are direct way to unexpected behavior of your application. Use properties instead of them:
private readonly string name;
public string Name
{
get { return name; }
}
Use readonly
for fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.
Do not throw from System.ApplicationException. Jeffery Richter in Framework Design Guidelines said:
System.ApplicationException is a class that should not be part of the .NET Framework. The original idea was that classes derived from SystemException would indicate exceptions thrown from the CLR (or system) itself, whereas non-CLR exceptions would be derived from ApplicationException. However, a lot of exception classes didn't follow this pattern. For example, TargetInvocationException (which is thrown by the CLR) is derived from ApplicationException. So, the ApplicationException class lost all meaning. The reason to derive from this base class is to allow some code higher up the call stack to catch the base class. It was no longer possible to catch all application exceptions.
Use predefined .NET exception types, for example:
if(transferAmount <= 0)
{
throw new ArgumentException("transfer amount must be positive");
}
Best practices for exceptions is Here.
Will never go through else if
statement:
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
$endgroup$
Do not use public
fields. Public fields are direct way to unexpected behavior of your application. Use properties instead of them:
private readonly string name;
public string Name
{
get { return name; }
}
Use readonly
for fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.
Do not throw from System.ApplicationException. Jeffery Richter in Framework Design Guidelines said:
System.ApplicationException is a class that should not be part of the .NET Framework. The original idea was that classes derived from SystemException would indicate exceptions thrown from the CLR (or system) itself, whereas non-CLR exceptions would be derived from ApplicationException. However, a lot of exception classes didn't follow this pattern. For example, TargetInvocationException (which is thrown by the CLR) is derived from ApplicationException. So, the ApplicationException class lost all meaning. The reason to derive from this base class is to allow some code higher up the call stack to catch the base class. It was no longer possible to catch all application exceptions.
Use predefined .NET exception types, for example:
if(transferAmount <= 0)
{
throw new ArgumentException("transfer amount must be positive");
}
Best practices for exceptions is Here.
Will never go through else if
statement:
if(transferAmount <= 0)
{
throw new ApplicationException("transfer amount must be positive");
}
else if (transferAmount == 0)
{
throw new ApplicationException("invalid transfer amount");
}
answered May 31 '18 at 12:14
HelloWorldHelloWorld
1759
1759
add a comment |
add a comment |
$begingroup$
This is dangerous
int newId = bankAccounts.Count();
Cannot assume that will be a valid newID. If this is a server application two clients could get the same newID before adding. An account can be removed.
Account should overide equals and Accounts should be a HashSet.
There is no facility for knowing what accounts belong to an individual.
There is no check of a person is allowed to withdraw from an account.
External should not be able to set balance.
public decimal balance { get; set; }
The balance could be changed and no transaction. No withdrawal.
Maybe they are not looking for that stuff. If the job was at a bank you have shown poor domain knowledge.
Name should be a property
public class Bank
{
public string Name { get; }
private List<Account> bankAccounts = new List<Account>();
public Bank(string name)
{
Name = name;
}
Transactions does not need a set.
public List<Transaction> transactions { get; } = new List<Transaction>();
Even with just a get;
transactions can be modified externally. It should probably be read only.
$endgroup$
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
add a comment |
$begingroup$
This is dangerous
int newId = bankAccounts.Count();
Cannot assume that will be a valid newID. If this is a server application two clients could get the same newID before adding. An account can be removed.
Account should overide equals and Accounts should be a HashSet.
There is no facility for knowing what accounts belong to an individual.
There is no check of a person is allowed to withdraw from an account.
External should not be able to set balance.
public decimal balance { get; set; }
The balance could be changed and no transaction. No withdrawal.
Maybe they are not looking for that stuff. If the job was at a bank you have shown poor domain knowledge.
Name should be a property
public class Bank
{
public string Name { get; }
private List<Account> bankAccounts = new List<Account>();
public Bank(string name)
{
Name = name;
}
Transactions does not need a set.
public List<Transaction> transactions { get; } = new List<Transaction>();
Even with just a get;
transactions can be modified externally. It should probably be read only.
$endgroup$
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
add a comment |
$begingroup$
This is dangerous
int newId = bankAccounts.Count();
Cannot assume that will be a valid newID. If this is a server application two clients could get the same newID before adding. An account can be removed.
Account should overide equals and Accounts should be a HashSet.
There is no facility for knowing what accounts belong to an individual.
There is no check of a person is allowed to withdraw from an account.
External should not be able to set balance.
public decimal balance { get; set; }
The balance could be changed and no transaction. No withdrawal.
Maybe they are not looking for that stuff. If the job was at a bank you have shown poor domain knowledge.
Name should be a property
public class Bank
{
public string Name { get; }
private List<Account> bankAccounts = new List<Account>();
public Bank(string name)
{
Name = name;
}
Transactions does not need a set.
public List<Transaction> transactions { get; } = new List<Transaction>();
Even with just a get;
transactions can be modified externally. It should probably be read only.
$endgroup$
This is dangerous
int newId = bankAccounts.Count();
Cannot assume that will be a valid newID. If this is a server application two clients could get the same newID before adding. An account can be removed.
Account should overide equals and Accounts should be a HashSet.
There is no facility for knowing what accounts belong to an individual.
There is no check of a person is allowed to withdraw from an account.
External should not be able to set balance.
public decimal balance { get; set; }
The balance could be changed and no transaction. No withdrawal.
Maybe they are not looking for that stuff. If the job was at a bank you have shown poor domain knowledge.
Name should be a property
public class Bank
{
public string Name { get; }
private List<Account> bankAccounts = new List<Account>();
public Bank(string name)
{
Name = name;
}
Transactions does not need a set.
public List<Transaction> transactions { get; } = new List<Transaction>();
Even with just a get;
transactions can be modified externally. It should probably be read only.
edited May 31 '18 at 12:29
answered May 31 '18 at 11:32
paparazzopaparazzo
5,0371934
5,0371934
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
add a comment |
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Do you mind adding an example of that last part? Curious as to what you mean when you say it can be modified externally if it only has a getter.
$endgroup$
– Shelby115
May 31 '18 at 12:37
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Uh, transactions.Clear();
$endgroup$
– paparazzo
May 31 '18 at 12:50
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
$begingroup$
Oh I see, you mean operations on the list instead of actually assignments. Does read-only really prevent these actions from occurring?
$endgroup$
– Shelby115
May 31 '18 at 12:56
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195513%2fbank-accounts-with-methods-to-transfer-funds%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
$begingroup$
Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate.
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 30 '18 at 22:15
$begingroup$
Can you tell us whether you were applying for a junior or senior position? This is important because there are different expectations for candidates on each level. From your description I believe it was for a senior position and for that you made a lot of unforgivable mistakes that one could tolerate from a beginner. That's probably why you failed it.
$endgroup$
– t3chb0t
May 31 '18 at 14:25