Sie sind auf Seite 1von 28

07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

13,881,958 members Sign in

Search for articles, questions, tips


articles Q&A forums stuff lounge ?

C# BAD PRACTICES: Learn how to make a good code by


bad example
Radosław Sadowski, 13 Apr 2016

   4.73 (498 votes) Rate this:

How to code properly

Introduction
My name is Radoslaw Sadowski and I'm a Microsoft Certified Software Developer. Since beginning of my career I was working with
Microsoft technologies.

After a few years of experience I saw so many badly written code that I could write a book showing all these dirty examples.
Those experiences made me a clean code freak.

A purpose of this article is to show how to write a clean, extendable and maintainable code by showing example of badly written class. I
will explain what troubles could it bring and present a way how to replace it by a better solution – using good practices and design
patterns.

First part is for every developer who knows C# language basics - it will show some basic mistakes and techniques how to make code
readable like a book. Advanced part is for developers who have at least basic understanding of design patterns – it will show completely
clean, unit testable code.

To understand this article you need to have at least basic knowledge of:

C# language
dependency injection, factory method and strategy design patterns

Example described in this article is a concrete, real world feature – I won't show examples like build pizza using decorator pattern or
implement calculator using strategy pattern :)

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 1/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

                                                 

As those theoretical examples are very good for explanation I found extremely difficult to use it in a real production applications.

We hear many times don't use this and use that instead. But why? I will try to explain it and prove that all the good practices and design
patterns are really saving our lives!

Note:

I will not explain C# language features and design patterns (it would make this article too long), there are so many good
theoretical examples in the network.  I will concentrate to show how to use it in our ever day work

Example is extremely simplified to highlight only described issues – I’ve found difficulties in understanding a general idea of
article when I was learning from examples which were containing tones of code.

I’m not saying that shown by me solutions for described below problems are the only one solutions, but for sure are working
and making from your code high quality solution.

I don't care in below code about error handling, logging etc. Code is written only to show solution for common programming
problems.

Let’s go to concretes...

So badly written class...


Our real world example will be below class:

Hide   Copy Code


public class Class1
{
public decimal Calculate(decimal amount, int type, int years)
{
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
if (type == 1)
{
result = amount;
}
else if (type == 2)
{
result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
}
else if (type == 3)
{
result = (0.7m * amount) - disc * (0.7m * amount);
}
else if (type == 4)
{
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 2/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
}
return result;
}
}

It is a really bad guy. Can we imagine what is the role of the above class? It is doing some wired calculating? That's all we can say about
it for now…

Now imagine that it is a DiscountManager class which is responsible for calculating a discount for the customer while he is buying
some product in online shop.

- Come on? Really?


- Unfortunately Yes!

It is completely unreadable, unmaintainable, unextendable and it is using many bad practices and anti-patterns.

What exact issues do we have here?

1. Naming – we can only guess what does this method calculate and what exactly is the input for these calculations. It is really hard
to extract calculation algorithm from this class.
Risks:
the most important thing in this case is – time wasting

                              
, if we will get enquiry from business to present them algorithm details or we will have a need to modify this piece of code it will
take us ages to understand logic of our Calculate method. And if we won’t document it or refactor the code, next time we/other
developer will spend the same time to figure out what exactly is happening there. We can make very easily mistake while
modifying it as well.

2. Magic numbers

                                                
In our example the type variable means - status of the customer account. Could you guess that? If-else if statements make a
choice how to calculate price of the product after discount.
Now we don’t have an idea what kind of account is 1,2,3 or 4. Let’s imagine now that you have to change algorithm of giving
discount for ValuableCustomer account You can try to figure out it from the rest of the code – what will take you a long time
but even though we can very easily make a mistake and modify algorithm of BasicCustomer account – numbers like 2 or 3
aren’t very descriptive. After our mistake customers will be very happy because they will be getting a discount of valuable
customers :)

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 3/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

3. Not obvious bug


Because our code is very dirty and unreadable we can easily miss very important thing. Imagine that there is a new customer
account status added to our system - GoldenCustomer. Now our method will return 0 as a final price for every product which
will be bought from a new kind of an account. Why? Because if none of our if-else if conditions will be satisfied (there will be
unhandled account status) method will always return 0. Our boss is not happy – he sold many products for free before
somebody realized that something is wrong.

4. Unreadable
We all have to agree that our code is extremely unreadable.
Unreadable = more time for understanding the code + increasing risk of mistake.

5. Magic numbers - again


Do we know what numbers like 0.1, 0.7, 0.5 mean? No we don't, but we should if we are owners of the code.
Let’s imagine that you have to change this line:
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));

as the method is completely unreadable you change only first 0.5 to 0.4 and leave second 0.5 as is. It can be a bug but it also can
be a fully proper modification. It’s because 0.5 does not tell us anything.
The same story we have in case of converting years variable to disc variable:
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
It is calculating discount for time of having account in our system in percentage. Ok, but what the hell is 5? It is a maximum
discount in percentage which customer can get for loyalty. Could you guess that?

6. DRY – Don’t repeat yourself


It is not visible for the first look but there are many places in our method where the code is repeated.
For example:
disc * (amount - (0.1m * amount));

is the same logic as:


disc * (amount - (0.5m * amount))

there is only static variable which is making a difference – we can easily parametrize this variable.
If we won’t get rid of duplicated code we will come across situations where we will only do a part of task because we will not see
that we have to change in the same way for example 5 places in our code. Above logic is calculating a discount for years of being
a customer in our system. So if we will change this logic in 2 of 3 places our system will become inconsistent.

7. Multiple responsibilities per class


Our method has at least 3 responsibilities:
1. Choosing calculation algorithm,
2. Calculate a discount for account status,
3. Calculate a discount for years of being our customer.
It violates Single Responsibility Principle. What risk does it bring? If we will need to modify one of those 3 features it will affect
also 2 other. It means that it could break something in features which we didn’t want to touch. So we will have to test all class
again – waste of time.

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 4/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Refactoring...
In 9 below steps I will show you how we can avoid all described above risks and bad practices to achieve a clean, maintainable and unit
testable code which will be readable like a book.

I STEP – Naming, naming, naming


It's IMHO one of the most important aspect of a good code. We've only changed names of method, parameters and variables and now
we exactly know what below class is responsible for.

Hide   Shrink   Copy Code


public class DiscountManager
{
public decimal ApplyDiscount(decimal price, int accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 :
(decimal)timeOfHavingAccountInYears/100;
if (accountStatus == 1)
{
priceAfterDiscount = price;
}
else if (accountStatus == 2)
{
priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage
* (price - (0.1m * price)));
}
else if (accountStatus == 3)
{
priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
}
else if (accountStatus == 4)
{
priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage
* (price - (0.5m * price)));
}

return priceAfterDiscount;
}
}

However we still don't know what 1, 2, 3, 4 mean, let's do something with it!

II STEP – Magic numbers


One of techniques of avoiding magic numbers in C# is replacing it by enums. I prepared AccountStatus enum to replace our magic
numbers in if-else if statements:

Hide   Copy Code


public enum AccountStatus
{
NotRegistered = 1,
SimpleCustomer = 2,
ValuableCustomer = 3,
MostValuableCustomer = 4
}

Now look at our refactored class, we can easily say which algorithm of calculating discount is used for which account status. Risk of
mixing up Account Statuses decreased rapidly.

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 5/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Hide   Shrink   Copy Code


public class DiscountManager
{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 :
(decimal)timeOfHavingAccountInYears/100;

if (accountStatus == AccountStatus.NotRegistered)
{
priceAfterDiscount = price;
}
else if (accountStatus == AccountStatus.SimpleCustomer)
{
priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage
* (price - (0.1m * price)));
}
else if (accountStatus == AccountStatus.ValuableCustomer)
{
priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
}
else if (accountStatus == AccountStatus.MostValuableCustomer)
{
priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage
* (price - (0.5m * price)));
}
return priceAfterDiscount;
}
}

III STEP – more readable


In this step we will improve readability of our class by replacing if-else if statement with switch-case statement.

I also divided long one-line algorithms into two separate lines. We have now separated “calculation of discount for account status” from
“calculation of discount for years of having customer account”.

For example, line:


priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));

was replaced by:


priceAfterDiscount = (price - (0.5m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);

Below code presents described changes:

Hide   Shrink   Copy Code

public class DiscountManager


{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 :
(decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatus.SimpleCustomer:
priceAfterDiscount = (price - (0.1m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.ValuableCustomer:

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 6/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
priceAfterDiscount = (0.7m * price);
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.MostValuableCustomer:
priceAfterDiscount = (price - (0.5m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
}
return priceAfterDiscount;
}
}

IV STEP - Not obvious bug


We finally got to our hidden bug!
As I mentioned before our method ApplyDiscount will return 0 as final price for every product which will be bought from new kind of
account. Sad but true..

How can we fix it? By throwing NotImplementedException!

                            

You will think - Isn't it exception driven development? No, it isn't!

When our method will get as parameter value of AccountStatus which we didn't support we want to be noticed immediately about this
fact and stop program flow not to make any unpredictable operations in our system.

This situation SHOULD NEVER HAPPEN so we have to throw exception if it will occur.

Below code was modified to throw an NotImplementedException if none conditions are satisfied – the default section of switch-case
statement:

Hide   Shrink   Copy Code

public class DiscountManager


{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 :
(decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatus.SimpleCustomer:
priceAfterDiscount = (price - (0.1m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.ValuableCustomer:
priceAfterDiscount = (0.7m * price);
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 7/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
case AccountStatus.MostValuableCustomer:
priceAfterDiscount = (price - (0.5m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
default:
throw new NotImplementedException();
}
return priceAfterDiscount;
}
}

V STEP - Lets analyse calculations


In our example we have two criteria of giving a discount for our customers:

1. Account status
2. Time in years of having an account in our system.

All algorithms look similar in case of discount for time of being a customer:
(discountForLoyaltyInPercentage * priceAfterDiscount)

,but there is one exception in case of calculating constant discount for account status:
0.7m * price

so let's change it to look the same as in other cases:


price - (0.3m * price)

Hide   Shrink   Copy Code

public class DiscountManager


{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 :
(decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatus.SimpleCustomer:
priceAfterDiscount = (price - (0.1m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.ValuableCustomer:
priceAfterDiscount = (price - (0.3m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.MostValuableCustomer:
priceAfterDiscount = (price - (0.5m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
default:
throw new NotImplementedException();
}
return priceAfterDiscount;
}
}

Now we have all the rules which are calculating a discount according to account status in one format:
price - ((static_discount_in_percentages/100) * price)

VI STEP - Get rid of magic numbers – another technique


https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 8/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Let's look at static variable which is a part of discount algorithm for account status:(static_discount_in_percentages/100)

and concrete instances of it:


0.1m
0.3m
0.5m

those numbers are very magic as well – they are not telling us anything about themselves.

We have the same situation in case of converting 'time in years of having an account' to discount a 'discount for loyalty:
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 :
(decimal)timeOfHavingAccountInYears/100;

number 5 is making our code very mysterious.

We have to do something with it to make it more descriptive!

I will use another technique of avoiding magic strings – which are constants (const keyword in C#). I strongly recommend to create one
static class for constants to have it in one place of our application.

For our example, I've created below class:

Hide   Copy Code

public static class Constants


{
public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5;
public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m;
public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m;
public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m;
}

and after modification our DiscountManager class will look as below:

Hide   Shrink   Copy Code

public class DiscountManager


{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears >
Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 :
(decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatus.SimpleCustomer:
priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.ValuableCustomer:
priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatus.MostValuableCustomer:
priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
default:
throw new NotImplementedException();
}
return priceAfterDiscount;
}
}

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 9/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

I hope you will agree that our method is now more self explanatory :)

STEP VII – Don't repeat yourself!

                     

Just to not duplicate our code we will move parts of our algorithms to separate methods.

We will use extension methods to do that.

Firstly we have to create 2 extension methods:

Hide   Copy Code

public static class PriceExtensions


{
public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize)
{
return price - (discountSize * price);
}

public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYe


ars)
{
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears >
Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 :
(decimal)timeOfHavingAccountInYears/100;
return price - (discountForLoyaltyInPercentage * price);
}
}

As names of our methods are very descriptive I don't have to explain what they are responsible for, now let's use the new code in our
example:

Hide   Shrink   Copy Code


public class DiscountManager
{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatus.SimpleCustomer:
priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS)
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
break;
case AccountStatus.ValuableCustomer:
priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
break;
case AccountStatus.MostValuableCustomer:
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 10/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTO
MERS)
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
break;
default:
throw new NotImplementedException();
}
return priceAfterDiscount;
}
}

Extension methods are very nice and can make your code simpler but at the end of the day are still static classes and can make your unit
testing very hard or even impossible. Because of that we will get rid of it in the last step. I've used it just to present you how they can
make our live easier, but I'm not a big fan of them.

Anyway, will you agree that our code looks a lot better now?

So let's jump to the next step!

STEP VIII – Remove few unnecessary lines...


We should write as short and simple code as it is possible. Shorter code = less possible bugs, shorter time of understanding the business
logic.
Let's simplify more our example then.

We can easily notice that we have the same mathod call for 3 kinds of customer account:
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);

Can't we do it once? No, we have exception for NotRegistered users because discount for years of being a registered customer does
not make any sense for unregistered customer. True, but what time of having account has unregistered user?

- 0 years

Discount in this case will always be 0, so we can safely add this discount also for unregistered users, let's do it!

Hide   Shrink   Copy Code

public class DiscountManager


{
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatus.SimpleCustomer:
priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS);
break;
case AccountStatus.ValuableCustomer:
priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
;
break;
case AccountStatus.MostValuableCustomer:
priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTO
MERS);
break;
default:
throw new NotImplementedException();
}
priceAfterDiscount = priceAfterDiscount.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears)
;
return priceAfterDiscount;
}
}

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 11/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

We were able to move this line outside the switch-case statement. Benefit – less code!

STEP IX – Advanced – Finally get clean code


All right! Now we can read our class like a book, but it isn't enough for us! We want super clean code now!

                                                                

Ok, so let's do some changes to finally achieve this goal. We will use dependency injection and strategy with a factory method design
patterns!

That's how our code will look at the end of the day:

Hide   Copy Code

public class DiscountManager


{
private readonly IAccountDiscountCalculatorFactory _factory;
private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator;

public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDisco


untCalculator)
{
_factory = factory;
_loyaltyDiscountCalculator = loyaltyDiscountCalculator;
}

public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)


{
decimal priceAfterDiscount = 0;
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountIn
Years);
return priceAfterDiscount;
}
}

Hide   Copy Code


public interface ILoyaltyDiscountCalculator
{
decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears);
}

public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator


{
public decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears)
{
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears >
Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 :
(decimal)timeOfHavingAccountInYears/100;
return price - (discountForLoyaltyInPercentage * price);

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 12/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
}
}

Hide   Shrink   Copy Code

public interface IAccountDiscountCalculatorFactory


{
IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus);
}

public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory


{
public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus)
{
IAccountDiscountCalculator calculator;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
calculator = new NotRegisteredDiscountCalculator();
break;
case AccountStatus.SimpleCustomer:
calculator = new SimpleCustomerDiscountCalculator();
break;
case AccountStatus.ValuableCustomer:
calculator = new ValuableCustomerDiscountCalculator();
break;
case AccountStatus.MostValuableCustomer:
calculator = new MostValuableCustomerDiscountCalculator();
break;
default:
throw new NotImplementedException();
}

return calculator;
}
}

Hide   Shrink   Copy Code

public interface IAccountDiscountCalculator


{
decimal ApplyDiscount(decimal price);
}

public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator


{
public decimal ApplyDiscount(decimal price)
{
return price;
}
}

public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator


{
public decimal ApplyDiscount(decimal price)
{
return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price);
}
}

public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator


{
public decimal ApplyDiscount(decimal price)
{
return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price);
}
}

public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 13/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
{
public decimal ApplyDiscount(decimal price)
{
return price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price);
}
}

First of all we got rid of extension methods (read: static classes) because using them made caller class (DiscountManager) tightly
coupled with discount algorithms inside extension methods. If we would want to unit test our ApplyDiscount method it would not be
possible because we would be also testing PriceExtensions class.

To avoid it I've created DefaultLoyaltyDiscountCalculator class which contains logic of ApplyDiscountForTimeOfHavingAccount


extension method and hide it’s implementation behind abstraction (read: interface) ILoyaltyDiscountCalculator. Now when we want to
test our DiscountManager class we will be able to inject mock/fake object which implements ILoyaltyDiscountCalculator into our
DiscountManager class via constructor to test only DiscountManager implementation. We are using here Dependency Injection Design
Pattern.

                              
 

By doing it we have also moved responsibility of calculating a discount for loyalty to a different class, so if we will need to modify this
logic we will have to change only DefaultLoyaltyDiscountCalculator class and all other code will stay untouched – lower risk of
breaking something, less time for testing.

Below use of divided to separate class logic in our DiscountManager class:


priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);

In case of calculation discount for account status logic, I had to create something more complex. We had 2 responsibilities which we
wanted to move out from DiscountManager:

1. Which algorithm to use according to account status


2. Details of particular algorithm calculation

To move out first responsibility I’ve created a factory class (DefaultAccountDiscountCalculatorFactory) which is an implementation on
Factory Method Design Pattern and hid it behind abstraction - IAccountDiscountCalculatorFactory.

                            

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 14/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Our factory will decide which discount algorithm to choose. Finally we are injecting our factory into a DiscountManager class via
constructor using Dependency Injection Design Pattern.

Below use of the factory in our DiscountManager class:


priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);

Above line will return proper strategy for particular account status and will invoke ApplyDiscount method on it.

First responsibility divided so let’s talk about the second one.

Let's talk about strategies then…

                             

As a discount algorithm could be different for every account status we will have to use different strategies to implement it. It’s great
opportunity to use Strategy Design Pattern!

In our example we have now 3 strategies:


NotRegisteredDiscountCalculator
SimpleCustomerDiscountCalculator
MostValuableCustomerDiscountCalculator

They contain implementation of particular discount algorithms and are hidden behind abstraction:
IAccountDiscountCalculator.

It will allow our DiscountManager class to use proper strategy without knowledge of its implementation. DiscountManager only
knows that returned object implements IAccountDiscountCalculator interface which contains method ApplyDiscount.

NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculator, MostValuableCustomerDiscountCalculator classes contain


implementation of proper algorithm according to account status. As our 3 strategies look similar the only thing we could do more would
be to create one method for all 3 algorithms and call it from each strategy class with a different parameter. As it would make our
example to big I didn't decide to do that.

All right, so to sum up now we have a clean readable code and all our classes have only one responsibility – only one reason to change:
1. DiscountManager – manage code flow
2. DefaultLoyaltyDiscountCalculator – calculation of discount for loyalty
3. DefaultAccountDiscountCalculatorFactory – deciding which strategy of calculation account status discount to choose
4. NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculator, MostValuableCustomerDiscountCalculator – calculation
of discount for account status

Now compare method from beginning:

Hide   Copy Code

public class Class1


{
public decimal Calculate(decimal amount, int type, int years)
{
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100;

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 15/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
if (type == 1)
{
result = amount;
}
else if (type == 2)
{
result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
}
else if (type == 3)
{
result = (0.7m * amount) - disc * (0.7m * amount);
}
else if (type == 4)
{
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
}
return result;
}
}

to our new, refactored code:

Hide   Copy Code

public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)


{
decimal priceAfterDiscount = 0;
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYe
ars);
return priceAfterDiscount;
}

Conclusion
Presented in this article code is extremely simplified to make explanation of used techniques and patterns easier. It shows how common
programming problems can be resolved in a dirty way and what are benefits of resolving it in a proper, clean way using good practices
and design patterns.

In my work experience I saw many times highlighted in this article bad practices. They obviously exist in many places of application not
in one class as in my example, which makes finding it even more difficult as they are hidden between proper code. People who are
writing this kind of code always argue that they are following Keep It Simple Stupid rule. Unfortunately almost always systems are
growing up and becoming very complex. Then every modification in this simple, unextendable code is very though and is bringing huge
risk of breaking something.

Bare in mind that your code will live in a production environment for a long time and will be modified on every business requirement
change. So writing too simple, unextendable code will have serious consequences very soon. And finally be nice for developers, who will
maintain your code after yourself :)

If you have some questions according to article don't hesitate to contact me!

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 16/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

                              

License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author


Radosław Sadowski
Ireland

Passionate, Microsoft Certified Software Developer(Senior),


having Masters Degree in Information Technology.

You may also be interested in...


A Solution Blueprint for DevOps Transform Clipboard Contents Between Copy and
Paste

C# BAD PRACTICES: Learn how to make a good MAME.NET


code by bad example – Part 2

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 17/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

C# BAD PRACTICES: Learn How to Make a Good C++ Exceptions: Pros and Cons
Code by Bad Example – Part 5

Comments and Discussions


 

You must Sign In to use this message board.

Search Comments

First Prev Next

Good Article
stefanro2000 6-Aug-18 9:52

Who doesn't get the point that this is an instructional article and not a code solve should read different articles. Also if anybody
has better ideas about writing instructional articles should go ahead and do it. Good luck!

Keep coding my friends!

Sign In · View Thread 5.00/5 (1 vote)  

I'm sorry, but this is very bad programming


J.Ignacio Tel 12-Apr-18 9:25

Overkill and Bloatware are not the way to go

This case, the whole code can be reduced to ONE SINGLE LINE OF CODE:
Hide   Copy Code
amountAfterDiscount = AmountAfterDiscount(amount, levelofdiscount, years);
---
static decimal AmountAfterDiscount(decimal amount, int levelofdiscount, int years)
{
return amount
* ( 1 - Math.Max( 0, 0.1m + ( Math.Min( levelofdiscount, MAXIMUM_LEVELOFDISCOUNT ) - 2 )
* 0.2m ) )
* ( 1 - Math.Min( years, MAXIMUM_YEARS_TOCONSIDER4DISCOUNT ) / 100m );
}

Though probably a more clearer alternative would be:


Hide   Copy Code
amountAfterDiscount = amount * KDiscount_Years(years) * KDiscount_LevelOfDiscount(levelofdiscount);
---
static decimal KDiscount_Years(int years) => 1 - Math.Min( years, MAXIMUM_YEARS_TOCONSIDER4DISCOUNT
) / 100m;
static decimal KDiscount_LevelOfDiscount(int levelofdiscount) => 1 - Math.Max( 0, 0.1m + ( Math.Min(
levelofdiscount, MAXIMUM_LEVELOFDISCOUNT ) - 2 ) * 0.2m );
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 18/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

And that's all you need.

Honestly, I fail to understand such praise for an article that teaches to refactorize to the point of having 5 additional classes, 3
additional interfaces and 2 local dependencies... when it can just be expressed in one line of code and zero local dependencies.

Changing the level of discount (or int type) for the AccountStatus enum is a bad idea

The code just calculates a percentage of discount based in a number of years and a level of discount that ranges from 1 to 4.
And that's all it needs to know to do the job. The four key words in programming are simplicity, encapsulation, and simplicity,
and encapsulation. Any additional information which is not needed to do the job just increases coupling, goes against
separation of concerns and creates unnecessary problems long term.

If the algorithm doesn't need to know how much you value your customers, just don't give it that information. That will create
unnecessary complications further on.

A few examples:

● What if we want to give coupons that give a temporal level of discount for a shopping? It could be quickly solved with:
Hide   Copy Code
amountAfterDiscount = amount * KDiscount_Years(years) *
KDiscount_LevelOfDiscount(Math.Max(client_levelofdiscount, coupon_levelofdiscount));

However, if you're passing AccountStatus information, how do you solve it? Do you change the AccountStatus for just one
shopping? Do you pass a higher (and fake) AccountStatus pretending that the customer has a better status that it has?

● What if we want to increase the level of discount because of Low Season? It quickly solved with:
Hide   Copy Code
amountAfterDiscount = amount * KDiscount_Years(years) * KDiscount_LevelOfDiscount(levelofdiscount +
isLowSeason ? 1 : 0);

Again, if you're passing AccountStatus information, do you increase the AccountStatus temporally during the Low Season? And
then you decrease it when the Low Season ends? That could create all kind of bugs, like a customer with the highest status, not
being increased in Low Season because he already has the maximum, and then being decreased when the Low Season finishes.

● What if we need to classify customers status for something else AND the classification is not the same?. Let's imagine we need
to establish priority for customer service. That could be easily and clearly solve with customers having both fields
LevelOfDiscount and PriorityForCustomerService describing exactly that. What about using AccountStatus?. Then the same
customer could be labeled as AccountStatus.ValuableCustomer with regard to discount but AccountStatus.SimpleCustomer with
regard to customer service. So, is he a valuable customer or is he a simple customer? That's a mess, and mess is a recipe for
bugs.

Sign In · View Thread  

Re: I'm sorry, but this is very bad programming


Radosław Sadowski 25-Apr-18 14:35

Yes, your comment reminds me that there will be always people who think that the earth is flat

Sign In · View Thread  

Re: I'm sorry, but this is very bad programming


J.Ignacio Tel 26-Apr-18 1:18
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 19/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Actually, it's exactly the opposite.

If you wanted to develop a serious flat earth theory, you'd have to include all kind of artifacts to explain empirical
observations. On the other hand, round earth is a simple, elegant and straight-forward solution. It happened the same
with the Ptolemaic solar system, which had to pile up orbit over orbit over orbit and then more, to explain observations.
While the sun in the center and elliptic orbits are a simple, beautiful and straight-forward solution.

Simplicity is the key. Occam said it. Feynman said it. And the KISS principles said it.

If you can solve the problem using a pure function, one line long, with no states... what's the point in using several
classes, interfaces and mutables?

Sign In · View Thread  

Re: I'm sorry, but this is very bad programming


Radosław Sadowski 26-Apr-18 1:24

You didn't get the idea of the article, but don't worry Maybe reading again the article and comments will help

Artice says:
Example is extremely simplified to highlight only described issues – I’ve found difficulties in understanding a
general idea of article when I was learning from examples which were containing tons of code.

It's not about the refactoring super simple class. It's about the approach to take in a complex system - shown on
simple example.

Yes, follow KISS and violate SRP, OCP and other principles.

Ok, that was my last response as it doesn't make sense to continue this discussion.

Thanks!

Sign In · View Thread  

Re: I'm sorry, but this is very bad programming


J.Ignacio Tel 26-Apr-18 3:08

Aha.

So let's see it. My approach is that you should always go towards more simplicity, less states and less coupling.
You don't need to go full functional programming, not everybody likes it, but as a general rule even for OOP, the
less complexity and less states, the better.

And with that approach, you can solve the problem with a very short pure function, no states, and which doesn't
need to know anything about the rest of the code.

Your approach creates more complexity, more states, and even more coupling, since it requires the rest of the
code to declare how valuable customers are. For some reason, you think that approach is better, maybe not here,
this is a simple case after all, but sure in more complex cases.

Problem is: the goal of design should be to split complex code into simple isolated pieces, at least as much as
possible. The simpler, and the more isolated, the better. If some approach overcomplicates simple things, then I'm
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 20/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

sorry, but that's a bad approach. You can't make the code simple and readable if by default you're
overcomplicating what's simple and readable.

Sign In · View Thread  

I would not be so optimistic


Vladimir Vorobiev 22-Jan-18 21:57

the original
Hide   Copy Code
public class Class1
{
public decimal Calculate(decimal amount, int type, int years)
{
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
if (type == 1)
{
result = amount;
}
else if (type == 2)
{
result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
}
else if (type == 3)
{
result = (0.7m * amount) - disc * (0.7m * amount);
}
else if (type == 4)
{
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
}
return result;
}
}

... and the result

Hide   Expand   Copy Code


public interface IAccountDiscountCalculatorFactory
{
IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus);
}

public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory


{
public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus)
{
IAccountDiscountCalculator calculator;
switch (accountStatus)
{
case AccountStatus.NotRegistered:
calculator = new NotRegisteredDiscountCalculator();
break;
case AccountStatus.SimpleCustomer:
calculator = new SimpleCustomerDiscountCalculator();
break;
case AccountStatus.ValuableCustomer:
calculator = new ValuableCustomerDiscountCalculator();
break;
case AccountStatus.MostValuableCustomer:
calculator = new MostValuableCustomerDiscountCalculator();

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 21/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
break;
default:
throw new NotImplementedException();
}

return calculator;

Sign In · View Thread  

Re: I would not be so optimistic


Radosław Sadowski 25-Apr-18 14:32

Yes, it's not about the number of lines of code, but about the readability, extend-ability and maintainability.

And yeah, who cares these days about the SRP (sarcasm)

Sign In · View Thread  

Re: I would not be so optimistic


StewBarks 6-Jun-18 6:41

I'm sorry but the 1st version is much quicker and easier to read. Also, normally all these other classes and interfaces
will be all over the project in different folders, possibly in different projects. Looking for all the related classes and
interfaces it becomes a nightmare to understand how anything works. Granted it makes it more robust and extensible
but I don't agree that it's easier to read.

Sign In · View Thread  

Re: I would not be so optimistic


Radosław Sadowski 6-Jun-18 6:45

Hi,
Do you know that it isn't a course how to do refactoring of super simple class? It's just a presentation of the
approach which should be applied in a complex system to follow main best practices and achieve good maintainable
code.
Thanks

Sign In · View Thread  

Great refactoring, but possibly overkill.


David Schiffer 28-Nov-17 0:29

The class was very small, not much to go crazy with design patterns on. I mean if this was a larger system, then yes. But the class
indicates we are talking about a small webshop. Refactoring the class alone without adding additional complexity to it using
design patterns would be the optimal solution. Add some constants to the class and voila, you have explained the .7 and.5
values also. It would be plenty for a system like that. If you're on the other hand on a larger system, we need to fall in line with
the overall architecture of that legacy code. If that code is even savable, it does not look like it. And if not, refactor the whole
code to implement a sound separation of concern and a moderate level of abstraction instead. Or if there is a lot of code like
that, rewrite the whole thing. It's usually faster.

When refactoring, of course best practices,but we also need to think pragmatically, a small webshop does not need DI unless it
already exists in the code, than of course, hook up to it. And yes I know, the company might become successful and grow and
have millions of customers one day. That is possible, not likely but possible. If the shop grows rapidly, then refactor up to those
https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 22/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

standards otherwise, your code will cost so much, your clients puny webshop, may fail before it even takes of.

Now I am just being thorough here. You did a great job, ideally, this is how to do it. But refactoring is also about business and in
business pragmatism is key, too little can lead to catastrophe, too much cost your client more and he'll be dissatisfied or you
chose to give a discount that will kill your business. Just right, is the way to go.

modified 28-Nov-17 5:35am.

Sign In · View Thread 5.00/5 (1 vote)  

Re: Great refactoring, but possibly overkill.


Radosław Sadowski 25-Apr-18 14:23

Hi David,
as I mentioned in the article:

Article says:
Example is extremely simplified to highlight only described issues – I’ve found difficulties in understanding a
general idea of article when I was learning from examples which were containing tones of code.

Thanks

Sign In · View Thread  

Good Article
吴朝阳 20-Nov-17 15:15

Thanks for your article

Sign In · View Thread  

Re: Good Article


Radosław Sadowski 20-Nov-17 22:48

Thank you for reading!

Sign In · View Thread  

Re: Good Article


吴朝阳 21-Nov-17 16:02

Dear:
I dont konw if i can translate this series into Chinese ,It's just a plan

Sign In · View Thread  

Re: Good Article


https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 23/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Radosław Sadowski 22-Nov-17 0:17

Hi, I appreciate it, but I want all of my articles to stay only on codeproject website in the original form - in
English.

Thank you,
Radoslaw

Sign In · View Thread  

Re: Good Article


吴朝阳 22-Nov-17 16:44

I get it,and Thanks for the series,Expect more

Sign In · View Thread  

Dependancy Injection, Factory Method and Strategy Design


Lawliet0727 15-Nov-17 16:02

Hi Good Day Sir Radosław Sadowski

I have just read your article C# Bad Practices part 1, and I want to say that is amazing article that can truly help developer like
me to improve our skill and to produce better code. I'am a c# developer for almost 1 year I created some software in my
company but i'am not satisfied at all event it is in production now because my code here does not follow any Design Pattern.
Now I just want to ask you, do you have already a clear and fine article or example for c# design pattern, factory method and
Dependancy Injection? I know that is show in your article but I want to have the deep understanding about those 3. Thank You
in advance....

--All reply are really much appreciated;

Alvin Bernardo
From Philippines

Sign In · View Thread  

A simplified & hopefully more robust solution...


John Bevan 11-Nov-17 0:49

Great article; really like your approach to teaching by bad example; works very well.

How do you feel about the tweaks?

Hide   Expand   Copy Code

public static class Constants


{
public const int MAXIMUM_DISCOUNT_LOYALTY_YEARS = 5; //tweaked the name since really this is
the number of years rather than the discount
}

public class AccountStatus


{
public static readonly AccountStatus NotRegistered = new AccountStatus(1, "Not Registered",
0m);

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 24/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject
public static readonly AccountStatus SimpleCustomer = new AccountStatus(2, "Simple Customer",
0.1m);
public static readonly AccountStatus ValuableCustomer = new AccountStatus(3, "Valuable
Customer", 0.3m);
public static readonly AccountStatus MostValuableCustomer = new AccountStatus(4, "Most
Valuable Customer", 0.5m);

AccountStatus (int id, string name, decimal discountPercentageOff)


{
Id = id;
Name = name;
DiscountPercentageOff = discountPercentageOff;
}

public int Id {get; private set;}


public string Name {get; private set;}
public decimal DiscountPercentageOff {get; private set;} //by storing this here and having one
private constructor, we can't add a new account status without supplying a new value for it's
discount

It avoids the need to throw errors by forcing the programmer (i.e. via compiler) to populate the relevant discount percentages
when they add a new account status. It also means that the relationship between the former magic numbers, and their related
account statuses is now clear.
Finally, it allows new types of discounts to be added without modifying the existing class.

Sign In · View Thread  

Minor nit-pick
Craig Wagner 6-Nov-17 5:42

When you throw your NotImplementedException you should probably include a message containing the value that was
provided to the method. In the interest of not wasting time this would help the developer identify the value that was provided
and perhaps narrow down how it got into the system.

Also, your comparison at the very end of the article is a little disingenuous as it hides the factory and implementation classes, so
the refactor isn't really the huge reduction in code that it implies.

Sign In · View Thread  

Going further
Boris Modylevsky 16-Jul-17 12:22

Thank you Radosław for such a detailed article describing the refactoring process towards readable, maintainable and testable
code. Some readers suggested to stop after a few steps, while I would like to ask why not going further. Let me explain.
Currently adding a new discount type requires changes in three places: add enum value, add case in factory and add a class that
calculates that discount. Sounds like pretty heavy cost. I suggest adding a boolean method ShouldApply to interface

Hide   Copy Code


public interface IAccountDiscountCalculator
{
bool ShouldApply(AccountStatus accountStatus);
decimal ApplyDiscount(decimal price);
}

So each class that implements that interface will return true for his own type. For example:
Hide   Copy Code

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 25/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator


{

public bool ShouldApply(AccountStatus accountStatus)


{
return accountStatus == AccountStatus.NotRegistered;
}

public decimal ApplyDiscount(decimal price)


{
return price;
}
}

Then I would modify the factory to the following code with fewer lines of code, which means fewere possibility for bugs:
Hide   Copy Code
public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory
{
private readonly IList<IAccountDiscountCalculator> _calculators;

public DefaultAccountDiscountCalculatorFactory(IList<IAccountDiscountCalculator> calculators)


{
_calculators = calculators;
}

public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus)


{
var calculator = _calculators.FirstOrDefault(c => c.ShouldApply(accountStatus));

if ( calculator == null )
{
throw new NotImplementedException();
}

return calculator;
}
}

The above factory can be initialized using Dependency Injection Container by scanning automatically for types that implement
IAccountDiscountCalculator interface. After applying suggested refactoring adding a new discount type will require only adding
an enum value and a class. In order to reduce this, we can change enum to string. It will allow us adding a new discount type by
just adding a calculator class.

I would like to hear your opinion about my suggestion.

Thank you very much.

Boris

Sign In · View Thread  

Re: Going further


Radosław Sadowski 19-Jul-17 3:52

Hi Boris,
that's obviously a good idea and I already wrote a long article, which describes how to go further
And what are pros and cons of different approaches:

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 26/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

C# BAD PRACTICES: Learn how to make a good code by bad example – Part 2[^]

which is a continuation of Part 1

Thank you for your comment,


Radoslaw

Sign In · View Thread  

Focus on different stuff first


Member 11040051 23-Apr-17 13:04

First of all, thanks for this article. It's really interesting!

But, don't you think it would be better to focus on the little things first, instead of greatly increasing the complexity? I'll give you
two examples:

First, instead of:


Hide   Copy Code
return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price);

do:
Hide   Copy Code
return price * (1 - Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS);

This, to me, is much more natural. We take the price, and then multiply it by the percentage, reduced by the discount. When you
go to a shop and they have a sale, in your head, do you really first multiply the discount percentage by the product price and
then substitute this from the product price? I don't think so

Second, before introducing the strategy pattern, introduce a utility method for the discount calculation so that there arn't copy-
and-pasted pieces of code around. Something like:
Hide   Copy Code
public decimal ApplyDiscount(decimal price, decimal discount)
{
return price * (1 - discount);
}

From the original code, it doesn't really look like there's going to be a different kind of logic added for the customer type
discount computation in the future. So why not keep it simple?

Sign In · View Thread  

Class factory to do ladder logic?


pseudogrammaton 3-Apr-17 10:46

Deeply nested case/if logic is bad, but object oriented developers need to ask themselves if their attempts at refactoring are
creating a new problem.

modified 3-Apr-17 16:16pm.

Sign In · View Thread 5.00/5 (2 votes)  

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 27/28
07/03/2019 C# BAD PRACTICES: Learn how to make a good code by bad example - CodeProject

Re: Class factory to do ladder logic?


pseudogrammaton 3-Apr-17 10:59

or
An F# rewrite of a fully refactored C# Clean Code example | Functional Software .NET[^]

Sign In · View Thread  

Refresh 1 2 3 4 5 6 7 8 9 10 11 Next »

General    News    Suggestion    Question    Bug    Answer    Joke    Praise    Rant    Admin   

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile


Selecione o idioma ▼
Web04 | 2.8.190306.1 | Last Updated 14 Apr 2016
Layout: fixed | fluid Article Copyright 2016 by Radosław Sadowski
Everything else Copyright © CodeProject, 1999-2019

https://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code 28/28

Das könnte Ihnen auch gefallen