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 Smile | :)
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 ammount, int type, int years)
{
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
if (type == 1)
{
result = ammount;
}
else if (type == 2)
{
result = (ammount - (0.1m * ammount)) - disc * (ammount - (0.1m * ammount));
}
else if (type == 3)
{
result = (0.7m * ammount) - disc * (0.7m * ammount);
}
else if (type == 4)
{
result = (ammount - (0.5m * ammount)) - disc * (ammount - (0.5m * ammount));
}
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?
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.
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 Smile | :)
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.
Unreadable
We all have to agree that our code is extremely unreadable.
Unreadable = more time for understanding the code + increasing risk of mistake.
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 = (ammount - (0.5m * ammount)) - disc * (ammount - (0.5m * ammount));
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?
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 * (ammount - (0.1m * ammount));
is the same logic as:
disc * (ammount - (0.5m * ammount))
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.
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.
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 AddDiscount(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 AccountStatuses enum to replace our magic numbers in if-else if statements:
Hide Copy Code
public enum AccountStatuses
{
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.
Hide Shrink Copy Code
public class DiscountManager
{
public decimal AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
if (accountStatus == AccountStatuses.NotRegistered)
{
priceAfterDiscount = price;
}
else if (accountStatus == AccountStatuses.SimpleCustomer)
{
priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
}
else if (accountStatus == AccountStatuses.ValuableCustomer)
{
priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
}
else if (accountStatus == AccountStatuses.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 AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatuses.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatuses.SimpleCustomer:
priceAfterDiscount = (price - (0.1m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.ValuableCustomer:
priceAfterDiscount = (0.7m * price);
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.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 AddDiscount 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 NOT HAPPEN NEVER 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 AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatuses.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatuses.SimpleCustomer:
priceAfterDiscount = (price - (0.1m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.ValuableCustomer:
priceAfterDiscount = (0.7m * price);
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.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:
Account status
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 AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
switch (accountStatus)
{
case AccountStatuses.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatuses.SimpleCustomer:
priceAfterDiscount = (price - (0.1m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.ValuableCustomer:
priceAfterDiscount = (price - (0.3m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.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
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 AddDiscount(decimal price, AccountStatuses 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 AccountStatuses.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatuses.SimpleCustomer:
priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.ValuableCustomer:
priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
case AccountStatuses.MostValuableCustomer:
priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
break;
default:
throw new NotImplementedException();
}
return priceAfterDiscount;
}
}
I hope you will agree that our method is now more self explanatory Smile | :)
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 AddDiscountForAccountStatus(this decimal price, decimal discountSize)
{
return price - (discountSize * price);
}
public static decimal AddDiscountForTimeOfHavingAccount(this 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);
}
}
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 AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
switch (accountStatus)
{
case AccountStatuses.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatuses.SimpleCustomer:
priceAfterDiscount = price.AddDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS)
.AddDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
break;
case AccountStatuses.ValuableCustomer:
priceAfterDiscount = price.AddDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
.AddDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
break;
case AccountStatuses.MostValuableCustomer:
priceAfterDiscount = price.AddDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS)
.AddDiscountForTimeOfHavingAccount(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:
.AddDiscountForTimeOfHavingAccount(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 AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
switch (accountStatus)
{
case AccountStatuses.NotRegistered:
priceAfterDiscount = price;
break;
case AccountStatuses.SimpleCustomer:
priceAfterDiscount = price.AddDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS);
break;
case AccountStatuses.ValuableCustomer:
priceAfterDiscount = price.AddDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS);
break;
case AccountStatuses.MostValuableCustomer:
priceAfterDiscount = price.AddDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS);
break;
default:
throw new NotImplementedException();
}
priceAfterDiscount = priceAfterDiscount.AddDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
return priceAfterDiscount;
}
}
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 loyaltyDiscountCalculator)
{
_factory = factory;
_loyaltyDiscountCalculator = loyaltyDiscountCalculator;
}
public decimal AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).AddDiscount(price);
priceAfterDiscount = _loyaltyDiscountCalculator.AddDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
return priceAfterDiscount;
}
}
Hide Copy Code
public interface ILoyaltyDiscountCalculator
{
decimal AddDiscount(decimal price, int timeOfHavingAccountInYears);
}
public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator
{
public decimal AddDiscount(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);
}
}
Hide Shrink Copy Code
public interface IAccountDiscountCalculatorFactory
{
IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatuses accountStatus);
}
public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory
{
public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatuses accountStatus)
{
IAccountDiscountCalculator calculator;
switch (accountStatus)
{
case AccountStatuses.NotRegistered:
calculator = new NotRegisteredDiscountCalculator();
break;
case AccountStatuses.SimpleCustomer:
calculator = new SimpleCustomerDiscountCalculator();
break;
case AccountStatuses.ValuableCustomer:
calculator = new ValuableCustomerDiscountCalculator();
break;
case AccountStatuses.MostValuableCustomer:
calculator = new MostValuableCustomerDiscountCalculator();
break;
default:
throw new NotImplementedException();
}
return calculator;
}
}
Hide Shrink Copy Code
public interface IAccountDiscountCalculator
{
decimal AddDiscount(decimal price);
}
public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator
{
public decimal AddDiscount(decimal price)
{
return price;
}
}
public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator
{
public decimal AddDiscount(decimal price)
{
return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price);
}
}
public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator
{
public decimal AddDiscount(decimal price)
{
return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price);
}
}
public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator
{
public decimal AddDiscount(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 AddDiscount 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 AddDiscountForTimeOfHavingAccount 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.AddDiscount(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:
Which algorithm to use according to account status
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.
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).AddDiscount(price);
Above line will return proper strategy for particular account status and will invoke AddDiscount 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 AddDiscount.
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 ammount, int type, int years)
{
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100;
if (type == 1)
{
result = ammount;
}
else if (type == 2)
{
result = (ammount - (0.1m * ammount)) - disc * (ammount - (0.1m * ammount));
}
else if (type == 3)
{
result = (0.7m * ammount) - disc * (0.7m * ammount);
}
else if (type == 4)
{
result = (ammount - (0.5m * ammount)) - disc * (ammount - (0.5m * ammount));
}
return result;
}
}
to our new, refactored code:
Hide Copy Code
public decimal AddDiscount(decimal price, AccountStatuses accountStatus, int timeOfHavingAccountInYears)
{
decimal priceAfterDiscount = 0;
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).AddDiscount(price);
priceAfterDiscount = _loyaltyDiscountCalculator.AddDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
return priceAfterDiscount;
}
Source: http://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code
Best tutorials, tips, tricks and news about application development and testing with SQL, ASP &... using only C# and dotnet tools & technologies
Categories
- .Net Core (2)
- C# (6)
- Debugging (1)
- Gaming (1)
- Interveiws (1)
- News (7)
- ORM (2)
- software engineering (3)
- Tips&Tricks (8)
- Tools (9)
- UWP (1)
Saturday, March 12, 2016
Experimental .NET Core Debugging in VS Code
oday we are releasing our first experimental preview of debugging for the new ASP.NET Core CLI toolset in Visual Studio Code. Before I continue it’s important to note a few things:
This support is for the new prerelease .NET Core CLI toolset; it does not work for the existing DNX experience (see the ASP.NET teams Q&A blog post to understand the changes).
VS Code’s IntelliSense does not yet work with the .NET CLI tools. So if you want to try out debugging, IntelliSense won’t be available for the CLI project type.
With this first release you get breakpoints, stepping, variable inspection, and call stacks.
vsc
However, given the very early stage of .NET Core and the debugging experience there will be some features you might be used to in the Visual Studio IDE that we don’t have in VS Code yet. These include:
No separate console window is created when you start debugging, all program output appears in the Debug Console of VS Code. This means that to use Console.ReadLine in a console application during debugging you have to start the application with dotnet run outside of VS Code and then attach the debugger.
No Edit & Continue (the ability to modify code and have the changes applied while debugging)
Cannot edit the value of variables during debugging
No TracePoints or Conditional breakpoints
Set Next Statement is not supported
We are very interested in your feedback to help us prioritize addressing these as covered at the end of this post.
Getting started
To get started you will need to do a few things (see our GitHub page for complete instructions)
Install (or upgrade to) Visual Studio Code version 0.10.10 or greater
Install the .NET CLI tools
Install the C# extension
Open any .cs file in VS Code to load the extension and complete the installation (view progress in the Output pane)
Install mono
Things you get while debugging
Breakpoints – set breakpoints with one click (or via F9)from the editor
Watches – define expressions that you want to keep an eye on
Locals – see the value of local variables automatically
Call Stack – find information on method calls on the stack
Debug Console – shows you all debug messages (show and hide this pane using Ctrl+Shift+Y or Cmd+Shift+Y on a Mac)
Current values – hover over variables to see the value
All panes show coordinated values – whenever you click an entry in the Call Stack you automatically jump to the corresponding line in the editor and watches and locals are updated
What else can you configure for debugging?
Once you install and configure the extension you will have the basic debugging experience describe above, but there is much more you can configure using the launch.json file.
Stop at Entry
Setting the stopAtEntry flag will cause the debugger to break immediately on the entry point to the application. This allows you to start stepping through your code without setting breakpoints.
Configure debugging for ASP.NET applications
Besides the default debug configuration there’s a configuration called .NET Core Launch (web) which is a good starting point for debugging ASP.NET applications. It contains an additional section “launchBrowser” that is used to open up a WebBrowser on your development machine whenever you start debugging. Per default it is configured to use the default browser but you can specify alternative browsers if you modify the commands in file accordingly. To get started and to give ASP.NET a try just download the ASP.NET CLI samples from Github .
Here’s what a configuration of launch.json would look like if you configure it to open a page with Opera.
…
"osx": {
"command" : "open",
"args" : "-a opera ${auto-detect-url}"
}
…
Modify launch URL
The URL the browser navigates to is configurable as well. If you stick to the defaults the debugger figures out the URL automatically and targets to the default URL the server listens to. ${auto-detect-url} serves as placeholder to find the URL automatically and you can use it anywhere in the configuration. For a scenario where you’d like to always debug a certain URL path like http://localhost:5000/api/customers/42 , you can overwrite the default value to avoid the manual navigation process.
Attach scenarios
For attach scenarios a third configuration section is integrated. All you have to do here is specify the process name of the application you want to debug. In our case this would be HelloWorld. If you spin up your application from the command line with dotnet run make sure you select the .NET Core Attach configuration first before starting the debugger.
If you have multiple processes running with the same name you can also specify processId instead of the processName. Just make sure you don’t use both properties at the same time.
top
processid
Hidden configuration properties
There are currently three additional properties that don’t show up in the default configuration. If you wonder how to figure out those properties aside from reading the documentation, here’s a tip: Start typing “o in launch.json (make sure you also type the leading quotation mark) and you’ll get an auto-completion that shows you a list of properties containing an “o”.
typeo
While this is very handy when you’re already editing, you might want always up to date documentation. As always the truth is in the code, and if you want to dig a little deeper you can take a look at the installation folder of the extension where you will find a file called package.json . Consider this file as the master template for valid configurations. It gives you a very good overview about configurations possible on your machine for your current version of the extension in easy to understand and human-readable json.
pathtopackagejson
json
symbolPath
You can use “symbolPath” to specify an array of paths to your debugging symbols. This can be very helpful if your symbols are located on a central server and when you are working across multiple operating systems. Symbols generated on one specific operation system also work cross platform.
A valid configuration of the symbol path is shown below.
“symbolPath”:”[ \”/Volumes/symbols\” , \”/OtherVolume/otherSymbols\” ]”
justMyCode
When you are in a debug session you might not be interested in stepping into framework code or into code of components you haven’t written. This is the reason why we came up with justMyCode. This feature is enabled per default as it seems to be the preferred setting for most debugging situations. However, if you want to debug framework code or external components you can enable this by setting it explicitly to false.
“justMyCode”:false
sourceFileMap
If you want to point the debugger to the corresponding source files for your debugging session you can specify a mapping table using the sourceFileMap property. This property can have any number of entries and will make sure the debugger works with the source code files you want it to work with. A configuration could look like this.
“sourceFileMap”: {
“C:\foo\bar”:”/home/me/foo/bar”
}
Source: https://blogs.msdn.microsoft.com/visualstudioalm/2016/03/10/experimental-net-core-debugging-in-vs-code/
This support is for the new prerelease .NET Core CLI toolset; it does not work for the existing DNX experience (see the ASP.NET teams Q&A blog post to understand the changes).
VS Code’s IntelliSense does not yet work with the .NET CLI tools. So if you want to try out debugging, IntelliSense won’t be available for the CLI project type.
With this first release you get breakpoints, stepping, variable inspection, and call stacks.
vsc
However, given the very early stage of .NET Core and the debugging experience there will be some features you might be used to in the Visual Studio IDE that we don’t have in VS Code yet. These include:
No separate console window is created when you start debugging, all program output appears in the Debug Console of VS Code. This means that to use Console.ReadLine in a console application during debugging you have to start the application with dotnet run outside of VS Code and then attach the debugger.
No Edit & Continue (the ability to modify code and have the changes applied while debugging)
Cannot edit the value of variables during debugging
No TracePoints or Conditional breakpoints
Set Next Statement is not supported
We are very interested in your feedback to help us prioritize addressing these as covered at the end of this post.
Getting started
To get started you will need to do a few things (see our GitHub page for complete instructions)
Install (or upgrade to) Visual Studio Code version 0.10.10 or greater
Install the .NET CLI tools
Install the C# extension
Open any .cs file in VS Code to load the extension and complete the installation (view progress in the Output pane)
Install mono
Things you get while debugging
Breakpoints – set breakpoints with one click (or via F9)from the editor
Watches – define expressions that you want to keep an eye on
Locals – see the value of local variables automatically
Call Stack – find information on method calls on the stack
Debug Console – shows you all debug messages (show and hide this pane using Ctrl+Shift+Y or Cmd+Shift+Y on a Mac)
Current values – hover over variables to see the value
All panes show coordinated values – whenever you click an entry in the Call Stack you automatically jump to the corresponding line in the editor and watches and locals are updated
What else can you configure for debugging?
Once you install and configure the extension you will have the basic debugging experience describe above, but there is much more you can configure using the launch.json file.
Stop at Entry
Setting the stopAtEntry flag will cause the debugger to break immediately on the entry point to the application. This allows you to start stepping through your code without setting breakpoints.
Configure debugging for ASP.NET applications
Besides the default debug configuration there’s a configuration called .NET Core Launch (web) which is a good starting point for debugging ASP.NET applications. It contains an additional section “launchBrowser” that is used to open up a WebBrowser on your development machine whenever you start debugging. Per default it is configured to use the default browser but you can specify alternative browsers if you modify the commands in file accordingly. To get started and to give ASP.NET a try just download the ASP.NET CLI samples from Github .
Here’s what a configuration of launch.json would look like if you configure it to open a page with Opera.
…
"osx": {
"command" : "open",
"args" : "-a opera ${auto-detect-url}"
}
…
Modify launch URL
The URL the browser navigates to is configurable as well. If you stick to the defaults the debugger figures out the URL automatically and targets to the default URL the server listens to. ${auto-detect-url} serves as placeholder to find the URL automatically and you can use it anywhere in the configuration. For a scenario where you’d like to always debug a certain URL path like http://localhost:5000/api/customers/42 , you can overwrite the default value to avoid the manual navigation process.
Attach scenarios
For attach scenarios a third configuration section is integrated. All you have to do here is specify the process name of the application you want to debug. In our case this would be HelloWorld. If you spin up your application from the command line with dotnet run make sure you select the .NET Core Attach configuration first before starting the debugger.
If you have multiple processes running with the same name you can also specify processId instead of the processName. Just make sure you don’t use both properties at the same time.
top
processid
Hidden configuration properties
There are currently three additional properties that don’t show up in the default configuration. If you wonder how to figure out those properties aside from reading the documentation, here’s a tip: Start typing “o in launch.json (make sure you also type the leading quotation mark) and you’ll get an auto-completion that shows you a list of properties containing an “o”.
typeo
While this is very handy when you’re already editing, you might want always up to date documentation. As always the truth is in the code, and if you want to dig a little deeper you can take a look at the installation folder of the extension where you will find a file called package.json . Consider this file as the master template for valid configurations. It gives you a very good overview about configurations possible on your machine for your current version of the extension in easy to understand and human-readable json.
pathtopackagejson
json
symbolPath
You can use “symbolPath” to specify an array of paths to your debugging symbols. This can be very helpful if your symbols are located on a central server and when you are working across multiple operating systems. Symbols generated on one specific operation system also work cross platform.
A valid configuration of the symbol path is shown below.
“symbolPath”:”[ \”/Volumes/symbols\” , \”/OtherVolume/otherSymbols\” ]”
justMyCode
When you are in a debug session you might not be interested in stepping into framework code or into code of components you haven’t written. This is the reason why we came up with justMyCode. This feature is enabled per default as it seems to be the preferred setting for most debugging situations. However, if you want to debug framework code or external components you can enable this by setting it explicitly to false.
“justMyCode”:false
sourceFileMap
If you want to point the debugger to the corresponding source files for your debugging session you can specify a mapping table using the sourceFileMap property. This property can have any number of entries and will make sure the debugger works with the source code files you want it to work with. A configuration could look like this.
“sourceFileMap”: {
“C:\foo\bar”:”/home/me/foo/bar”
}
Source: https://blogs.msdn.microsoft.com/visualstudioalm/2016/03/10/experimental-net-core-debugging-in-vs-code/
Tuesday, February 23, 2016
Porting MSBuild to .NET Core
This post was written by Daniel Plaisted, a software engineer on the .NET Team.
Click here to read the main post on MSDN
Click here to read the main post on MSDN
Saturday, August 29, 2015
Unity : Developing Your First Game with Unity and C#
As a software architect, I’ve written many systems,
reverse-engineered native code malware, and generally could figure
things out on the code side. When it came to making games, though, I was
a bit lost as to where to start. I had done some native code graphics
programming in the early Windows days, and it wasn’t a fun experience. I
then started on DirectX development but realized that, although it was
extremely powerful, it seemed like too much code for what I wanted to
do.
Then, one day, I decided to experiment with Unity, and I saw it
could do some amazing things. This is the first article in a four-part
series that will cover the basics and architecture of Unity. I’ll show
how to create 2D and 3D games and, finally, how to build for the Windows
platforms.
What Unity Is
Unity is a 2D/3D engine and framework that gives you a system
for designing game or app scenes for 2D, 2.5D and 3D. I say games and
apps because I’ve seen not just games, but training simulators,
first-responder applications, and other business-focused applications
developed with Unity that need to interact with 2D/3D space. Unity
allows you to interact with them via not only code, but also visual
components, and export them to every major mobile platform and a whole
lot more—for free. (There’s also a pro version that’s very nice, but it
isn’t free. You can do an impressive amount with the free version.)
Unity supports all major 3D applications and many audio formats, and
even understands the Photoshop .psd format so you can just drop a .psd
file into a Unity project. Unity allows you to import and assemble
assets, write code to interact with your objects, create or import
animations for use with an advanced animation system, and much more.
As Figure 1 indicates, Unity has done work
to ensure cross-platform support, and you can change platforms literally
with one click, although to be fair, there’s typically some minimal
effort required, such as integrating with each store for in-app
purchases.
Figure 1 Platforms Supported by Unity
Perhaps the most powerful part of Unity is the Unity Asset
Store, arguably the best asset marketplace in the gaming market. In it
you can find all of your game component needs, such as artwork, 3D
models, animation files for your 3D models (see Mixamo’s content in the
store for more than 10,000 motions), audio effects and full tracks,
plug-ins—including those like the MultiPlatform toolkit that can help
with multiple platform support—visual scripting systems such as
PlayMaker and Behave, advanced shaders, textures, particle effects, and
more. The Unity interface is fully scriptable, allowing many third-party
plug-ins to integrate right into the Unity GUI. Most, if not all,
professional game developers use a number of packages from the asset
store, and if you have something decent to offer, you can publish it
there as well.
What Unity Isn’t
I hesitate to describe anything Unity isn’t as people
challenge that all the time. However, Unity by default isn’t a system in
which to design your 2D assets and 3D models (except for terrains). You
can bring a bunch of zombies into a scene and control them, but you
wouldn’t create zombies in the Unity default tooling. In that sense,
Unity isn’t an asset-creation tool like Autodesk Maya or 3DSMax, Blender
or even Adobe Photoshop. There’s at least one third-party modeling
plug-in (ProBuilder), though, that allows you to model 3D components
right inside of Unity; there are 2D world builder plug-ins such as the
2D Terrain Editor for creating 2D tiled environments, and you can also
design terrains from within Unity using their Terrain Tools to create
amazing landscapes with trees, grass, mountains, and more. So, again, I
hesitate to suggest any limits on what Unity can do.
Where does Microsoft fit into this? Microsoft and Unity work
closely together to ensure great platform support across the Microsoft
stack. Unity supports Windows standalone executables, Windows Phone,
Windows Store applications, Xbox 360 and Xbox One.
Getting Started
Download the latest version of Unity and get yourself a
two-button mouse with a clickable scroll wheel. There’s a single
download that can be licensed for free mode or pro. You can see the
differences between the versions at unity3d.com/unity/licenses. The Editor, which is the main Unity interface, runs on Windows (including Surface Pro), Linux and OS X.
I’ll get into real game development with Unity in the next
article, but, first, I’ll explore the Unity interface, project structure
and architecture.
Architecture and Compilation
Unity is a native C++-based game engine. You write code in
C#, JavaScript (UnityScript) or, less frequently, Boo. Your code, not
the Unity engine code, runs on Mono or the Microsoft .NET Framework,
which is Just-in-Time (JIT) compiled (except for iOS, which doesn’t
allow JIT code and is compiled by Mono to native code using
Ahead-of-Time [AOT] compilation).
Unity lets you test your game in the IDE without having to
perform any kind of export or build. When you run code in Unity, you’re
using Mono version 3.5, which has API compatibility roughly on par with
that of the .NET Framework 3.5/CLR 2.0.
You edit your code in Unity by double-clicking on a code file
in the project view, which opens the default cross-platform editor,
MonoDevelop. If you prefer, you can configure Visual Studio as your
editor.
You debug with MonoDevelop or use a third-party plug-in for
Visual Studio, UnityVS. You can’t use Visual Studio as a debugger
without UnityVS because when you debug your game, you aren’t debugging
Unity.exe, you’re debugging a virtual environment inside of Unity, using
a soft debugger that’s issued commands and performs actions.
To debug, you launch MonoDevelop from Unity. MonoDevelop has a
plug-in that opens a connection back to the Unity debugger and issues
commands to it after you Debug | Attach to Process in MonoDevelop. With
UnityVS, you connect the Visual Studio debugger back to Unity instead.
When you open Unity for the first time, you see the project dialog shown in Figure 2.
Figure 2 The Unity Project Wizard
In the project dialog, you specify the name and location for
your project (1). You can import any packages into your project (2),
though you don’t have to check anything off here; the list is provided
only as a convenience. You can also import a package later. A package is
a .unitypackage file that contains prepackaged resources—models, code,
scenes, plug-ins—anything in Unity you can package up—and you can reuse
or distribute them easily. Don’t check something off here if you don’t
know what it is, though; your project size will grow, sometimes
considerably. Finally, you can choose either 2D or 3D (3). This dropdown
is relatively new to Unity, which didn’t have significant 2D game
tooling until fairly recently. When set to 3D, the defaults favor a 3D
project—typical Unity behavior as it’s been for ages, so it doesn’t need
any special mention. When 2D is chosen, Unity changes a few seemingly
small—but major—things, which I’ll cover in the 2D article later in this
series.
This list is populated from .unitypackage files in certain
locations on your system; Unity provides a handful on install. Anything
you download from the Unity asset store also comes as a .unitypackage
file and is cached locally on your system in
C:\Users\<you>\AppData\Roaming\Unity\Asset Store. As such, it
will show up in this list once it exists on your system. You could just
double-click on any .unitypackage file and it would be imported into
your project.
Continuing with the Unity interface, I’ll go forward from clicking Create in the dialog in Figure 2 so a new project is created. The default Unity window layout is shown in Figure 3.
Figure 3 The Default Unity Window
Here’s what you’ll see:
- Project: All the files in your project. You can drag and drop from Explorer into Unity to add files to your project.
- Scene: The currently open scene.
- Hierarchy: All the game objects in the scene. Note the use of the term GameObjects and the GameObjects dropdown menu.
- Inspector: The components (properties) of the selected object in the scene.
- Toolbar: To the far left are Pan, Move, Rotate, Scale and in the center Play, Pause, Advance Frame. Clicking Play plays the game near instantly without having to perform separate builds. Pause pauses the game, and advance frame runs it one frame at a time, giving you very tight debugging control.
- Console: This window can become somewhat hidden, but it shows output from your compile, errors, warnings and so forth. It also shows debug messages from code; for example, Debug.Log will show its output here.
Of important mention is the Game tab next to the Scene tab.
This tab activates when you click play and your game starts to run in
this window. This is called play mode and it gives you a playground for
testing your game, and even allows you to make live changes to the game
by switching back to the Scene tab. Be very careful here, though. While
the play button is highlighted, you’re in play mode and when you leave
it, any changes you made while in play mode will be lost. I, along with
just about every Unity developer I’ve ever spoken with, have lost work
this way, so I change my Editor’s color to make it obvious when I’m in
play mode via Edit | Preferences | Colors | Playmode tint.
About Scenes
Everything that runs in your game exists in a scene. When you
package your game for a platform, the resulting game is a collection of
one or more scenes, plus any platform-dependent code you add. You can
have as many scenes as you want in a project. A scene can be thought of
as a level in a game, though you can have multiple levels in one scene
file by just moving the player/camera to different points in the scene.
When you download third-party packages or even sample games from the
asset store, you typically must look for the scene files in your project
to open. A scene file is a single file that contains all sorts of
metadata about the resources used in the project for the current scene
and its properties. It’s important to save a scene often by pressing
Ctrl+S during development, just as with any other tool.
Typically, Unity opens the last scene you’ve been working on,
although sometimes when Unity opens a project it creates a new empty
scene and you have to go find the scene in your project explorer. This
can be pretty confusing for new users, but it’s important to remember if
you happen to open up your last project and wonder where all your work
went! Relax, you’ll find the work in a scene file you saved in your
project. You can search for all the scenes in your project by clicking
the icon indicated in Figure 4 and filtering on Scene.
Figure 4 Filtering Scenes in the Project
In a scene, you can’t see anything without a camera and you
can’t hear anything without an Audio Listener component attached to some
GameObject. Notice, however, that in any new scene, Unity always
creates a camera that has an Audio Listener component already on it.
Project Structure and Importing Assets
Unity projects aren’t like Visual Studio projects. You don’t
open a project file or even a solution file, because it doesn’t exist.
You point Unity to a folder structure and it opens the folder as a
project. Projects contain Assets, Library, ProjectSettings, and Temp
folders, but the only one that shows up in the interface is the Assets
folder, which you can see in Figure 4.
The Assets folder contains all your assets—art, code, audio;
every single file you bring into your project goes here. This is always
the top-level folder in the Unity Editor. But make changes only in the
Unity interface, never through the file system.
The Library folder is the local cache for imported assets; it
holds all metadata for assets. The ProjectSettings folder stores
settings you configure from Edit | Project Settings. The Temp folder is
used for temporary files from Mono and Unity during the build process.
I want to stress the importance of making changes only
through the Unity interface and not the file system directly. This
includes even simple copy and paste. Unity tracks metadata for your
objects through the editor, so use the editor to make changes (outside
of a few fringe cases). You can drag and drop from your file system into
Unity, though; that works just fine.
The All-Important GameObject
Virtually everything in your scene is a GameObject. Think of
System.Object in the .NET Framework. Almost all types derive from it.
The same concept goes for GameObject. It’s the base class for all
objects in your Unity scene. All of the objects shown in Figure 5 (and many more) derive from a GameObject.
Figure 5 GameObjects in Unity
A GameObject is pretty simple as it pertains to the Inspector window. You can see in Figure 6
that an empty GameObject was added to the scene; note its properties in
the Inspector. GameObjects by default have no visual properties except
the widget Unity shows when you highlight the object. At this point,
it’s simply a fairly empty object.
Figure 6 A Simple GameObject
A GameObject has a Name, a Tag (similar to a text tag you’d
assign via a FrameworkElement.Tag in XAML or a tag in Windows Forms), a
Layer and the Transform (probably the most important property of all).
The Transform property is simply the position, rotation and
scale of any GameObject. Unity uses the left-hand coordinate system, in
which you think of the coordinates of your computer screen as X
(horizontal), Y (vertical) and Z (depth, that is, coming in or going out
of the screen).
In game development, it’s quite common to use vectors, which
I’ll cover a bit more in future articles. For now, it’s sufficient to
know that Transform.Position and Transform.Scale are both Vector3
objects. A Vector3 is simply a three-dimensional vector; in other words,
it’s nothing more than three points—just X, Y and Z. Through these
three simple values, you can set an object’s location and even move an
object in the direction of a vector.
Components
You add functionality to GameObjects by adding Components.
Everything you add is a Component and they all show up in the Inspector
window. There are MeshRender and SpriteRender Components; Components for
audio and camera functionality; physics-related Components (colliders
and rigidbodies), particle systems, path-finding systems, third-party
custom Components, and more. You use a script Component to assign code
to an object. Components are what bring your GameObjects to life by
adding functionality, akin to thedecorator pattern in software
development, only much cooler.
I’ll assign some code to a new GameObject, in this case a
simple cube you can create via GameObject | Create Other | Cube. I
renamed the cube Enemy and then created another to have two cubes. You
can see in Figure 7 I moved one cube about -15 units
away from the other, which you can do by using the move tool on the
toolbar or the W key once an object is highlighted.
Figure 7 Current Project with Two Cubes
The code is a simple class that finds a player and moves its
owner toward it. You typically do movement operations via one of two
approaches: Either you move an object to a new position every frame by
changing its Transform.Position properties, or you apply a physics force
to it and let Unity take care of the rest.
Doing things per frame involves a slightly different way of
thinking than saying “move to this point.” For this example, I’m going
to move the object a little bit every frame so I have exact control over
where it moves. If you’d rather not adjust every frame, there are
libraries to do single function call movements, such as the freely
available iTween library.
The first thing I do is right-click in the Project window to
create a new C# script called EnemyAI. To assign this script to an
object, I simply drag the script file from the project view to the
object in the Scene view or the Hierarchy and the code is assigned to
the object. Unity takes care of the rest. It’s that easy.
Figure 8 shows the Enemy cube with the script assigned to it.
Figure 8 The Enemy with a Script Assigned to It
Take a look at the code in Figure 9 and note the public variable. If you look in the Editor, you can see that my public variable appears with an option to override the default values at run time. This is pretty cool. You can change defaults in the GUI for primitive types, and you can also expose public variables (not properties, though) of many different object types. If I drag and drop this code onto another GameObject, a completely separate instance of that code component gets instantiated. This is a basic example and it can be made more efficient by, say, adding a RigidBody component to this object, but I’ll keep it simple here.
Figure 9 The EnemyAI Script
public class EnemyAI : MonoBehavior
{
// These values will appear in the editor,
full properties will not.
public float Speed = 50;
private Transform _playerTransform;
private Transform _ myTransform;
// Called on startup of the GameObject it's
assigned to.
void Start()
{
// Find some gameobject that has the text
tag "Player" assigned to it.
// This is startup code, shouldn't query
the player object every
// frame. Store a ref to it.
var player = GameObject.FindGameObjectWithTag
("Player");
if (!player)
{
Debug.LogError(
"Could not find the main player.
Ensure it has the player tag set.");
}
else
{
// Grab a reference to its transform for
use later (saves on managed
// code to native code calls).
_playerTransform = player.transform;
}
// Grab a reference to our transform for use
later.
_myTransform = this.transform;
}
// Called every frame. The frame rate varies
every second.
void Update()
{
// I am setting how fast I should move toward
the "player"
// per second. In Unity, one unit is a meter.
// Time.deltaTime gives the amount of time since
the last frame.
// If you're running 60 FPS (frames per second)
this is 1/60 = 0.0167,
// so w/Speed=2 and frame rate of 60 FPS (frame
rate always varies
// per second), I have a movement amount of
2*0.0167 = .033 units
// per frame. This is 2 units.
var moveAmount = Speed * Time.deltaTime;
// Update the position, move toward the
player's position by moveAmount.
_myTransform.position = Vector3.MoveTowards
(_myTransform.position,
_playerTransform.position, moveAmount);
}
}
public class EnemyHealth : MonoBehavior
private EnemyAI _enemyAI;
// Use this for initialization.
void Start () {
// Get a ref to the EnemyAI script component
on this game object.
var enemyAI = this.GetComponent<EnemyAI>();
}
// Update is called once per frame.
void Update () {
_enemyAI.MoveTowardsPlayer();
}
Writing Code
In the prior code example, there are two methods, Start and Update, and the class EnemyHealth inherits from the MonoBehavior base class, which lets you simply assign that class to a GameObject. There’s a lot of functionality in that base class you’ll use, and typically a few methods and properties. The main methods are those Unity will call if they exist in your class. There are a handful of methods that can get called (see bit.ly/1jeA3UM). Though there are many methods, just as with the ASP.NET Web Forms Page Lifecycle, you typically use only a few. Here are the most common code methods to implement in your classes, which relate to the sequence of events for MonoBehavior-derived classes:Awake: This method is called once per object when the object is first initialized. Other components may not yet be initialized, so this method is typically used to initialize the current GameObject. You should always use this method to initialize a MonoBehavior-derived class, not a constructor. And don’t try to query for other objects in your scene here, as they may not be initialized yet.
Start: This method is called during the first frame of the object’s lifetime but before any Update methods. It may seem very similar to Awake, but with Start, you know the other objects have been initialized via Awake and exist in your scene and, therefore, you can query other objects in code easily, like so:
// Returns the first EnemyAI script component instance
it finds on any game object.
// This type is EnemyAI (a component), not a
GameObject.
var enemyAI = GameObject.FindObjectOfType<EnemyAI>();
// I'll actually get a ref to its top-level
GameObject.
var enemyGameObject = enemyAI.gameObject;
// Want the enemy’s position?
var position = enemyGameObject.transform.position;

Figure 10 Getting Stats
FixedUpdate: This method is called a fixed number of times a second, independent of the frame rate. Because Update is called a varying number of times a second and isn’t in sync with the physics engine, it’s typically best to use FixedUpdate when you want to provide a force or some other physics-related functions on an object. FixedUpdate by default is called every .02 seconds, meaning Unity also performs physics calculations every .02 seconds (this interval is called the Fixed Timestep and is developer-adjustable), which, again, is independent of frame rate.
Unity-Generated Code Projects
Once you have code in your project, Unity creates one or more project files in your root folder (which isn’t visible in the Unity interface). These are not the Unity engine binaries, but instead the projects for Visual Studio or MonoDevelop in which you’ll edit and compile your code. Unity can create what might seem like a lot of separate projects, as Figure 11 shows, although each one has a an important purpose.
Figure 11 Unity-Created Projects
If you have a simple Unity project, you won’t see all of these files. They get created only when you have code put into various special folders. The projects shown in Figure 11 are broken out by only three types:
- Assembly-CSharp.csproj
- Assembly-CSharp-Editor.csproj
- Assembly-CSharp-firstpass.csproj
The other projects serve the same purpose but have CSharp replaced with UnityScript. These are simply the JavaScript (UnityScript) versions of the projects, which will exist only if you use JavaScript in your Unity game and only if you have your scripts in the folders that trigger these projects to be created.
Now that you’ve seen what projects get created, I’ll explore the folders that trigger these projects and show you what their purposes are. Every folder path assumes it’s underneath the /Assets root folder in your project view. Assets is always the root folder and contains all of your asset files underneath it. For example, Standard Assets is actually /Assets/Standard Assets. The build process for your scripts runs through four phases to generate assemblies. Objects compiled in Phase 1 can’t see those in Phase 2 because they haven’t yet been compiled. This is important to know when you’re mixing UnityScript and C# in the same project. If you want to reference a C# class from UnityScript, you need to make sure it compiles in an earlier phase.
Phase 1 consists of runtime scripts in the Standard Assets, Pro Standard Assets and Plug-ins folders, all located under/Assets. This phase creates the Assembly-CSharp-firstpass.csproj project.
Phase 2 scripts are in the Standard Assets/Editor, Pro Standard Assets/Editor and Plug-ins/Editor folders. The last folder is meant for scripts that interact with the Unity Editor API for design-time functionality (think of a Visual Studio plug-in and how it enhances the GUI, only this runs in the Unity Editor). This phase creates the Assembly-CSharp-Editor-firstpass.csproj project.
Phase 3 comprises all other scripts that aren’t inside an Editor folder. This phase creates the Assembly-CSharp-Editor.csproj project.
Phase 4 consists of all remaining scripts (those inside any other folder called Editor, such as /Assets/Editor or /Assets/Foo/Editor). This phase creates the Assembly-CSharp.csproj project.
There are a couple other less-used folders that aren’t covered here, such as Resources. And there is the pending question of what the compiler is using. Is it .NET? Is it Mono? Is it .NET for the Windows Runtime (WinRT)? Is it .NET for Windows Phone Runtime? Figure 12 lists the defaults used for compilation. This is important to know, especially for WinRT-based applications because the APIs available per platform vary.
Figure 12 Compilation Variations
| |||||||||||||||||
- Bring in your assets (artwork, audio and so on). Use the asset store. Write your own. Hire an artist. Note that Unity does have native support for Maya, Cheetah3d, Blender and 3dsMax, in some cases requiring that software be installed to work with those native 3D formats, and it works with .obj and .fbx common file formats, as well.
- Write code in C#, JavaScript/UnityScript, or Boo, to control your objects, scenes, and implement game logic.
- Test in Unity. Export to a platform.
- Test on that platform. Deploy.
Subscribe to:
Posts (Atom)