patterncsharpMinor
Gift aid calculator task
Viewed 0 times
giftcalculatoraidtask
Problem
I have recently taken a small technical test in C# and the following were expected:
Please find my code in GitHub Gift Aid Calculator. The story for the test can be found in the read me section of GitHub.
Program.cs
```
using System;
using GiftAidCalculator.TestConsole.BusinessObject;
using GiftAidCalculator.TestConsole.Helper;
using GiftAidCalculator.TestConsole.Interface;
using GiftAidCalculator.TestConsole.Repositry;
namespace GiftAidCalculator.TestConsole
{
class Program
{
static void Main()
{
IConfigRepositry config = new ConfigRepositry();
Console.WriteLine("Please enter the tax rate:");
decimal taxRate;
if (ValidateHelper.ValidateDecimal(Console.ReadLine(), out taxRate))
{
config.TaxRate = taxRate;
Console.WriteLine("** Promotion ");
Console.WriteLine(" 5% Supplement added on running ");
Console.WriteLine(" 3% Supplement added on swimming ");
Console.WriteLine("** Promotion \n");
Console.WriteLine("\n Input 1 for Running, 2 for Swimming and 3 for other events.");
int eventInput;
if (ValidateHelper.ValidateInteger(Console.ReadLine(), out eventInput))
{
Console.WriteLine("\n Please Enter donation amount:");
decimal donationAmount;
if (ValidateHelper.ValidateDecimal(Console.ReadLine(), out donationAmount))
{
var calculateTax = new TaxC
- All stories to be completed with an appropriate level of testing.
- No actual database implementation is required, feel free to stub it out.
- Your code should trend towards being SOLID.
- Make sure you write mock objects and do good unit testing with this.
- Make sure that your tests actually pass
- Do not over engineer the test and make sure there is no repetition of code.
Please find my code in GitHub Gift Aid Calculator. The story for the test can be found in the read me section of GitHub.
Program.cs
```
using System;
using GiftAidCalculator.TestConsole.BusinessObject;
using GiftAidCalculator.TestConsole.Helper;
using GiftAidCalculator.TestConsole.Interface;
using GiftAidCalculator.TestConsole.Repositry;
namespace GiftAidCalculator.TestConsole
{
class Program
{
static void Main()
{
IConfigRepositry config = new ConfigRepositry();
Console.WriteLine("Please enter the tax rate:");
decimal taxRate;
if (ValidateHelper.ValidateDecimal(Console.ReadLine(), out taxRate))
{
config.TaxRate = taxRate;
Console.WriteLine("** Promotion ");
Console.WriteLine(" 5% Supplement added on running ");
Console.WriteLine(" 3% Supplement added on swimming ");
Console.WriteLine("** Promotion \n");
Console.WriteLine("\n Input 1 for Running, 2 for Swimming and 3 for other events.");
int eventInput;
if (ValidateHelper.ValidateInteger(Console.ReadLine(), out eventInput))
{
Console.WriteLine("\n Please Enter donation amount:");
decimal donationAmount;
if (ValidateHelper.ValidateDecimal(Console.ReadLine(), out donationAmount))
{
var calculateTax = new TaxC
Solution
class Program.
That's a class. A very high-level abstract notion of something with an entry point - a
Writing SOLID code implies SRP, OCP, LSP, ISP and DIP.
Uh-oh. Red flag.
I'm only addressing the naming here though.
The main issue I'm seeing, is that the abstraction level at the entry point is very low, but in a SOLID app it's very high.
And the ending isn't very elegant either, and pretty much fails at DRY:
Try writing your application top-down. Take the time to put your mindset at the abstraction level that's right for the code you're writing. With the wrong abstraction levels, it's hard to make units for unit testing.
SRP
The
OCP
Being
LSP
Code against abstractions, code against the interface of objects - don't assume you know what the actual runtime type is going to be (see the "typical violation" section here). In other words,
ISP
Interface Segregation boils down to the fact that modifying an interface is a breaking change. Observe the KISS principle here: the interface of a
DIP
Dependency Inversion means that instead of
In order to run the
By building the app top-down by writing the abstractions first (actually with TDD, you'll first write a failing unit test) and peeling off abstraction levels as you dive into the low-level
That's a class. A very high-level abstract notion of something with an entry point - a
Main method. "Program" could be literally anything.Writing SOLID code implies SRP, OCP, LSP, ISP and DIP.
public class ValidateHelperUh-oh. Red flag.
Helper smells. Let's see...public static bool ValidateDecimal(string strInput, out decimal dOutput)public static bool ValidateInteger(string strInput, out int dOutput)strInput and dOutput ring the Hungarian Notation bell. And there's a Copy+Paste error, I'm guessing dOutput in ValidateInteger was meant to be named iOutput... How about this?public static bool ValidateDecimal(string value, out decimal result)public static bool ValidateInteger(string value, out int result)I'm only addressing the naming here though.
The main issue I'm seeing, is that the abstraction level at the entry point is very low, but in a SOLID app it's very high.
And the ending isn't very elegant either, and pretty much fails at DRY:
Console.WriteLine("\n Press any key to exit.");
Console.ReadLine();
}
else
{
Console.WriteLine("Invalid Input");
Console.ReadLine();
}
}
else
{
Console.WriteLine("Invalid Input");
Console.ReadLine();
}
}
else
{
Console.WriteLine("Invalid Input");
Console.ReadLine();
}Try writing your application top-down. Take the time to put your mindset at the abstraction level that's right for the code you're writing. With the wrong abstraction levels, it's hard to make units for unit testing.
class Program
{
static void Main()
{
IDataService dataService = new SomeDataService();
IUserInteractionService interactionService = new ConsoleInteractionService();
var app = new CalculatorApp(dataService, interactionService);
app.Execute();
}
}SRP
The
Main() method does one thing: it composes the application, and runs it. The composition is done with Poor Man's DI instead of with an IoC container, but refactoring to use one would be very easy.OCP
Being
static, the Main() method cannot be extended. The more code we write here, the more reasons to change we introduce, the further we stray from being open for extension, closed for modification. Writing code at the proper abstraction level helps succeeding here.LSP
Code against abstractions, code against the interface of objects - don't assume you know what the actual runtime type is going to be (see the "typical violation" section here). In other words,
SomeDataService could very well be fetching its data from async calls to a web service that returns JSON or XML data - Program doesn't need to know that much (abstraction levels again).ISP
Interface Segregation boils down to the fact that modifying an interface is a breaking change. Observe the KISS principle here: the interface of a
CalculatorApp object only exposes a parameterless Execute method. It's unlikely that anything else is ever going to be needed, because all the entry point ever needs to be doing is everything it takes to run this Execute method.DIP
Dependency Inversion means that instead of
newing up dependencies, a class will be injected with implementations for its dependencies, preferably through its constructor. Because CalculatorApp requires implementations for IDataService and IUserInteractionService, you already know that this class' responsibility will be to coordinate the interactions between these services, implementing the business logic.In order to run the
Execute method, Main has to instantiate a CalculatorApp. Because I'm injecting abstractions, I can write a unit test suite to cover everything the business rules dictate, where I'm free to inject a mock implementation for every dependency and verify that method X or Y is called N times on the IDataService, or whatever.By building the app top-down by writing the abstractions first (actually with TDD, you'll first write a failing unit test) and peeling off abstraction levels as you dive into the low-level
Console.WriteLine calls, you decide how much abstraction is enough abstraction, and it gets easier to realize how much abstraction is outright over-engineered: write only the code you need to write for tests to pass. Writing the app bottom-up in Main and refactoring towards SOLID makes this much harder than expanding the idea as you go from one level of abstraction to the next.Code Snippets
public class ValidateHelperpublic static bool ValidateDecimal(string strInput, out decimal dOutput)public static bool ValidateInteger(string strInput, out int dOutput)public static bool ValidateDecimal(string value, out decimal result)public static bool ValidateInteger(string value, out int result)Context
StackExchange Code Review Q#48695, answer score: 3
Revisions (0)
No revisions yet.