improving the quality of existing software
DESCRIPTION
Presented at DevReach 2013. As developers, most of our time is spent working on existing software. Sure, occasionally we get the chance to fire up a new Solution in Visual Studio, and that can be exciting. But after the first day, we find ourselves once more having to deal with the work we did yesterday. And today, we know more than we did yesterday, so there are things we’d do differently, if we had it to do over. Over time, software rots. If we’re not diligent, our beautiful code can degrade into a worthless mess. Keeping our code in working condition is no different than changing the oil in our car – it’s preventive maintenance. In this session, Steve will look at some common places to look for signs of degradation in existing applications, and steps to take to improve the code. Examples will use C# and primarily ASP.NET.TRANSCRIPT
Improving the Quality of Existing Software
Steve SmithTelerikardalis.com @ardalis
Software Rots
Technical Debt
• Low quality code and shortcuts in our applications
• Technical debt, like real debt, has direct cost and interest
http://www.jimhighsmith.com/
Preventive Maintenance
• Refactoring– Eliminate Duplication– Simplify Design
• Automated Tests– Verify correctness– Avoid regressions– Increase Confidence
When should you refactor?
• While delivering value
Refactoring Should Not Change System Behavior
Refactoring Process
• Verify existing behavior• Write Characterization Tests if none
exist– Find test points– Break dependencies
• Apply Refactoring• Confirm existing behavior is
preserved
Characterization Tests
Process1. Write a test you know will fail2. 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
S O L I DPrinciples
http://flickr.com/photos/kevinkemmerer/2772526725/
Principles of OO Design
0. Don’t Repeat Yourself (DRY)
1.Single Responsibility2.Open/Closed3.Liskov Substitution4.Interface Segregation5.Dependency Inversion
Don’t RepeatRepeat Yourself
• Duplication in logic calls for abstraction
• Duplication in process calls for automation
Common Refactorings
• Replace Magic Number/String• Parameterize Method• Pull Up Field• Pull Up Method• Replace Conditional With
Polymorphism• Introduce Method
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}
Single Responsibility PrincipleThe 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
Example Responsibilities
• Persistence• Validation• Notification• Error Handling• Logging• Class Selection / Construction• Formatting• Parsing• Mapping
Dependency and Coupling
• Excessive coupling makes changing legacy software difficult
• Breaking apart responsibilities and dependencies is a large part of working with existing code
Common Refactorings
• Extract Class• Extract Method• Move Method
Heuristics and Code Smells
• Visual Studio Metrics
Code Smell: Regions
?More on Regions: http://ardalis.com/regional-differences
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
Open / Closed Principle
Open to ExtensionNew behavior can be added in the future
Closed to ModificationChanges to source or binary code are not required
Dr. Bertrand Meyer originated the OCP term in his 1988 book, Object Oriented Software Construction
Common Refactorings
• Extract Interface / Apply Strategy Pattern
• Parameterize Method• Form Template Method
OCP Fail
OCP OK
OCP Fail
public bool IsSpecialCustomer(Customer c){ if(c.Country == “US” && c.Balance < 50) return false; if(c.Country == “DE” && c.Balance < 25) return false; if(c.Country == “UK” && c.Balance < 35) return false; if(c.Country == “FR” && c.Balance < 27) return false; if(c.Country == “BG” && c.Balance < 29) return false;
if(c.Age < 18 || c.Age > 65) return false; if(c.Income < 50000 && c.Age < 30) return false; return true;}
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;}
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.
Common Refactorings
• Collapse Hierarchy• Pull Up / Push Down Field• Pull Up / Push Down Method
Liskov Substitution Fail
foreach(var employee in employees){ if(employee is Manager) { Helpers.PrintManager(employee as Manager); break; } Helpers.PrintEmployee(employee);}
Liskov Substitution OK
foreach(var employee in employees){ employee.Print(); // or Helpers.PrintEmployee(employee);}
Interface Segregation PrincipleThe 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
Common Refactorings
• Extract Interface
Keep Interfaces Small and Focused
Membership Provider
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);}
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);}
Dependency Inversion PrincipleHigh-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#
Dependency Inversion Principle• Depend on Abstractions– Interfaces, not concrete types
• Inject Dependencies into Classes
• Structure Solution so Dependencies Flow Toward Core– Onion Architecture (a.k.a. Ports and
Adapters)
Application Layers
Data Access EvolutionNo separation of concerns:
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
User Interface
Database
Compile Time
Runtime
Data Access : Helper Classes Calls to data made through
a utility
Example: Data Access Application Block (SqlHelper)
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
User Interface
Database
Compile Time
Runtime
Helper Class
What’s Missing? Abstraction! No way to abstract
away data access
Tight coupling
Leads to Big Ball of Mud system
Solution: Depend on interfaces, not
concrete implementations What should we call such
interfaces? Repositories!
User Interface
Database
Compile Time
Runtime
CoreIFooRepository
InfrastructureSqlFooRepository
DIP Architecture (aka Ports and Adapters)
Common Dependencies
• Framework• Third Party Libraries• Database• File System• Email• Web Services• System Resources (Clock)• Configuration• The new Keyword• Static methods• Thread.Sleep• Random
See also responsibilities:• Persistence• Validation• Notification• Error Handling• Logging• Class Selection /
Construction• Formatting• Parsing• Mapping
Common Refactorings
• Extract Class• Extract Interface / Apply Strategy
Pattern• Extract Method• Introduce Service Locator / Container
DIP Fail
Some Improvement (Façade)
DIP OK (Strategy)
DIP OK (Strategy)
Self-Improvement and Quality• How fast can you produce:– Code you believe to be of high quality– Code that maybe gets the job done, but you
believe to be of low quality
• Which one can you produce more quickly?• Why?• How can we develop our skills and our
tools so that building quality is natural and easier than not doing so?
Week 1 Week 2 Week 3 Week 4 Week 5 Week 6 Week 7 Week 8 Week 90
2
4
6
8
10
12
14
16
User Stories Completed
High Quality Low Quality Column1
Week 1 Week 2 Week 3 Week 4 Week 5 Week 6 Week 7 Week 8 Week 90
2
4
6
8
10
12
14
16
18
20
User Stories Completed
High Quality Low Quality Column1
Summary
• Maintain / Improve Application Code• Follow DRY/SOLID Principles• Use Characterization Tests to “fix”
behavior• Apply Common Refactorings• Re-run Tests After (and during)
Refactorings• Train and Practice to Write Better
Code Faster
References
http://bit.ly/SFkpmq
Refactoring Catalog http://www.refactoring.com/catalog/index.html
Onion Architecture http://jeffreypalermo.com/blog/the-onion-architecture-pa
rt-1/
Books
Refactoring http://amzn.to/110tscA
Refactoring to Patterns http://amzn.to/Vq5Rj2
Working Effectively with Legacy Code http://amzn.to/VFFYbn
Code Complete http://amzn.to/Vq5YLv
Clean Code http://amzn.to/YjUDI0
Thank you!
@ardalis
ardalis.com
http://facebook.com/stevenandrewsmith