5. Refactoring Process
• Verify existing behavior
• Write Characterization Tests if none exist
– Find test points
– Break dependencies
• Apply Refactoring
• Confirm existing behavior is preserved
Follow me at twitter.com/ardalis
6. Characterization Tests
Process
1. Write a test you know will fail
2. Use the output of the failing test to
determine the existing behavior to assert
3. Update the test with the new
value/behavior
4. Run the test again – it should pass
Follow me at twitter.com/ardalis
10. Principles of OO Design
0. Don’t Repeat Yourself (DRY)
1. Single Responsibility
2. Open/Closed
3. Liskov Substitution
4. Interface Segregation
5. Dependency Inversion
Follow me at twitter.com/ardalis
11.
12. Don’t Repeat
Repeat Yourself
• Duplication in logic calls for abstraction
• Duplication in process calls for
automation
Follow me at twitter.com/ardalis
13. Common Refactorings
• Replace Magic Number/String
• Parameterize Method
• Pull Up Field
• Pull Up Method
• Replace Conditional With Polymorphism
• Introduce Method
Follow me at twitter.com/ardalis
14. Role Checks
if(user.IsInRole(“Admins”)
{
// allow access to resource
}
// favor privileges over role checks
// ardalis.com/Favor-Privileges-over-Role-Checks
var priv = new ContentPrivilege(user, article);
if(priv.CanEdit())
{
// allow access
}
Follow me at twitter.com/ardalis
15.
16. Single Responsibility Principle
The Single Responsibility Principle states that every object
should have a single responsibility, and that
responsibility should be entirely encapsulated by the
class.
Wikipedia
There should never be more than one reason for a class to
change.
Robert C. “Uncle Bob” Martin
Follow me at twitter.com/ardalis
17. Example Responsibilities
• Persistence
• Validation
• Notification
• Error Handling
• Logging
• Class Selection / Construction
• Formatting
• Parsing
• Mapping
Follow me at twitter.com/ardalis
18. Dependency and Coupling
• Excessive coupling makes changing
legacy software difficult
• Breaking apart responsibilities and
dependencies is a large part of working
with existing code
Follow me at twitter.com/ardalis
19. Common Refactorings
• Extract Class
• Move Method
Follow me at twitter.com/ardalis
20. Heuristics and Code Smells
• Visual Studio Metrics
Follow me at twitter.com/ardalis
21. Code Smell: Regions
More on Regions: http://ardalis.com/regional-differences
Follow me at twitter.com/ardalis
22.
23. Open / Closed Principle
The Open / Closed Principle states that software entities
(classes, modules, functions, etc.) should be open for
extension, but closed for modification.
Wikipedia
Follow me at twitter.com/ardalis
24. Open / Closed Principle
Open to Extension
New behavior can be added in the future
Closed to Modification
Changes to source or binary code are not required
Dr. Bertrand Meyer originated the OCP term in his 1988
book, Object Oriented Software Construction
Follow me at twitter.com/ardalis
25. Common Refactorings
• Extract Interface / Apply Strategy Pattern
• Parameterize Method
• Form Template Method
Follow me at twitter.com/ardalis
28. OCP OK
private IEnumerable<ICustomerRule> _rules;
public bool IsSpecialCustomer(Customer c)
{
foreach(var rule in _rules)
{
if(rule.Evaluate(c) == false) return false;
}
return true;
}
Follow me at twitter.com/ardalis
29.
30. Liskov Substitution Principle
The Liskov Substitution Principle states that
Subtypes must be substitutable for their
base types.
Agile Principles, Patterns, and Practices in
C#
Named for Barbara Liskov, who first
described the principle in 1988.
Follow me at twitter.com/ardalis
31. Common Refactorings
• Collapse Hierarchy
• Pull Up / Push Down Field
• Pull Up / Push Down Method
Follow me at twitter.com/ardalis
32. Liskov Substitution Fail
foreach(var employee in employees)
{
if(employee is Manager)
{
Helpers.PrintManager(employee as Manager);
break;
}
Helpers.PrintEmployee(employee);
}
Follow me at twitter.com/ardalis
33. Liskov Substitution OK
foreach(var employee in employees)
{
employee.Print();
// or
Helpers.PrintEmployee(employee);
}
Follow me at twitter.com/ardalis
34.
35. Interface Segregation Principle
The Interface Segregation Principle states that
Clients should not be forced to depend on
methods they do not use.
Agile Principles, Patterns, and Practices in C#
Corollary:
Prefer small, cohesive interfaces to “fat” interfaces
Follow me at twitter.com/ardalis
37. ISP Fail (sometimes)
public IRepository<T>
{
T GetById(int id);
IEnumerable<T> List();
void Create(T item);
void Update(T item);
void Delete(T item);
}
Follow me at twitter.com/ardalis
38. ISP OK (for CQRS for example)
public IRepository<T> : IReadRepository<T>,
IWriteRepository<T>
{ }
public IReadRepository<T>
{
T GetById(int id);
IEnumerable<T> List();
}
public IWriteRepository<T>
void Create(T item);
void Update(T item);
void Delete(T item);
}
Follow me at twitter.com/ardalis
39.
40. Dependency Inversion Principle
High-level modules should not depend on low-level
modules. Both should depend on abstractions.
Abstractions should not depend on details. Details
should depend on abstractions.
Agile Principles, Patterns, and Practices in C#
Follow me at twitter.com/ardalis
41. Dependency Inversion Principle
• Depend on Abstractions
– Interfaces, not concrete types
• Inject Dependencies into Classes
• Structure Solution so Dependencies Flow
Toward Core
– Onion Architecture
Follow me at twitter.com/ardalis
43. Compile Time
Data Access Evolution Runtime
No separation of concerns:
User Interface
Data access logic baked directly into UI
ASP.NET Data Source Controls
Classic ASP scripts
Data access logic in UI layer via codebehind
ASP.NET Page_Load event
ASP.NET Button_Click event
Database
Follow me at twitter.com/ardalis
44. Data Access : Helper Compile Time
Classes Runtime
Calls to data made through a
utility
User Interface
Example: Data Access
Application Block (SqlHelper)
Helper Class
Logic may still live in UI layer
Or a Business Logic Layer may
make calls to a Data Access
Layer which might then call the
helper Database
Follow me at twitter.com/ardalis
45. What’s Missing? Compile Time
Abstraction! Runtime
No way to abstract away
data access User Interface
Tight coupling
Leads to Big Ball of Mud Core Infrastructure
system IFooRepository SqlFooRepository
Solution:
Depend on interfaces, not
concrete implementations
What should we call such Database
interfaces? Repositories!
Follow me at twitter.com/ardalis
47. Common Dependencies
• Framework See also responsibilities:
• Third Party Libraries • Persistence
• Validation
• Database • Notification
• File System • Error Handling
• Email • Logging
• Web Services • Class Selection /
Construction
• System Resources (Clock) • Formatting
• Configuration • Parsing
• The new Keyword • Mapping
• Static methods
• Thread.Sleep
• Random
Follow me at twitter.com/ardalis
48. Common Refactorings
• Extract Class
• Extract Interface / Apply Strategy Pattern
• Extract Method
• Introduce Service Locator / Container
Follow me at twitter.com/ardalis
Note that characterization tests, though they should be automated, are often not what we would think of as unit tests, or perhaps even integration tests. For instance, you could dump a log file showing the relevant state of the application, and then use that as the basis for your characterization test by comparing against it after your changes.
Avoid creating a big ball of mud system, where tracing through your code and its dependencies is like trying to unwind a tangled mess of spaghetti.
A very common source of repetition of code is role checks. These often describe different scenarios in different circumstances. For instance, maybe administrators can do anything, but managers can access resources within their division, etc. Encapsulating the logic of CanView, CanCreate, CanEdit, etc. in privilege objects makes these rules explicit, easier to test, and gives them a single location to live in the application.
Visual Studio can quickly analyze a project and show statistics for the classes and methods in the project. The maintainability index, cyclomatic complexity, and lines of code are all great metrics to pay attention to. The ideal maintainability index is 100, but don’t expect to hit that with any code that’s doing real work. However, you should certainly able to keep it above 50.
I’m not a fan of regions. They mainly exist because at one time they were a reasonable means of hiding generated code, before we had support for partial classes and other language features to deal with this. The worst offense with regions is when they’re used within a method, like this:(click)They’re also bad when used at the class level for “standard” formatting of code, making it impossible to actually see what the code does, like this:(click)Can someone tell me what this class does?(click)I have a whole article devoted to why using regions is a bad habit, anti-pattern, code smell, whatever you prefer. It includes some survey results on the most common ways people use them as well. (click)
What happens when we need to add another country?What happens when we must add another rule?How can we refactor this so this method no longer needs to change?
Define a type to describe a rule. Move each rule into its own type. Create a collection of rules to apply and apply them.Pass the set of rules into the IsSpecialCustomer() method’s class (or even the method itself).
Any time you find that you need to check the type of an object within a polymorphic block of code (such as a foreach), this is a sign that you are breaking LSP.
This is anextemely common example of the Repository design pattern. In fact, I use this exact pattern in quite a few production applications today. There’s nothing inherently wrong with this implementation on its own. However, sometimes it does violate ISP if you need to separate Commands from Queries
You can create small interfaces and compose the larger interfaces from the smaller ones if you control all of the code and you can’t simply do away with the larger interfaces. In this case, the separation of interfaces would allow us to do something like implement caching only on the read operations, and implement delayed writes using some kind of queue or message bus for the write operations.
Extract interfaceImplement interface with tightly coupled original code