Given at DogFoodCon 2016 in Columbus, Ohio
As developers, most of our time is spent working on existing software – even if it’s just the software we wrote ourselves, yesterday. And over time, software rots. If were 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 “ its preventive maintenance. In this session, Steve will cover some common places to look for signs of degradation in existing applications, and describe the steps we can take to improve our code. Examples will use C# and primarily ASP.NET.
5. Technical Debt
• Low quality code and shortcuts in our applications
• Technical debt, like real debt, has direct cost to pay off as well as interest
http://deviq.com/technical-debt/
10. “
”
You don’t need permission to practice basic hygiene
when you write software.
http://ardalis.com/when-should-you-refactor/
Make cleaning up your code something you do as part of writing code.
13. The Refactoring Process
• Verify existing behavior
• Write CharacterizationTests if none exist
• Find test points
• Break dependencies
• Apply Refactoring
• Confirm existing behavior is preserved
14. CharacterizationTests
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 now pass
15.
16. S O L I DPrinciples
http://flickr.com/photos/kevinkemmerer/2772526725/
21. Common Refactorings
• Replace Magic Number/String
• Parameterize Method
• Pull Up Field
• Pull Up Method
• Replace Conditional With Polymorphism
• Introduce Method
22. Common Source of Repetition: 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
}
23. Visual Studio Code Clones
• Find similar blocks of code in your projects/solution
• Can detect matches that are similar but vary in small ways (like variable
names)
• Available inVS2015 Premium and Ultimate
24.
25. 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
26. What is a responsibility?
“My CustomerManager class is only responsible for
anything to do with a Customer.That follows SRP, right?”
27. Examples of Responsibilities
• Persistence
• Validation
• Notification
• Error Handling
• Logging
• Class Selection / Construction
• Formatting
• Parsing
• Mapping
28. Dependency and Coupling
• Excessive coupling makes changing legacy software difficult
• Breaking apart responsibilities and dependencies is a large part of working
with existing code
36. 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
37. 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, ObjectOrientedSoftwareConstruction
42. OCP OK
private IEnumerable<ICustomerRule> _rules;
public bool IsSpecialCustomer(Customer customer)
{
foreach(var rule in _rules)
{
if(rule.Evaluate(customer) == false) return false;
}
return true;
}
43.
44. 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.
48. Nulls Break Polymorphism
foreach(var employee in employees)
{
if(employee == null)
{
// print not found message
break;
}
Helpers.PrintEmployee(employee);
} http://ardalis.com/nulls-break-polymorphism
49.
50. 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
55. ISP OK (i.e. to support CQRS)
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);
}
Existing implementations of
IRepository<T> are
unaffected by pulling out
smaller interfaces!
No existing code breaks!
56.
57. 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#
58. Dependency Inversion Principle
• Depend on Abstractions
• Interfaces, not concrete types
• Inject Dependencies into Classes
• Structure Solution so Dependencies FlowToward Core
• Onion Architecture (a.k.a. Ports and Adapters, a.k.a. Hexagonal Architecture)
60. 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
61. Common Refactorings
• Extract Class
• Extract Interface / Apply Strategy Pattern
• Extract Method
• Introduce Service Locator / Container
63. Hidden Dependencies
• Checkout Depends on an available SMTP server, but the class doesn’t reflect
this
• Follow the Explicit Dependencies Principle
• http://deviq.com/explicit-dependencies-principle/
69. 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?
74. • New Coworking Space in Hudson,Ohio
• Private offices / Flex desk spaces
• Fiber internet
• Complimentary coffee / tea / water
• Great development community
• @techhubhudson
• Website online soon
Tech Hub Hudson
75. • Includes images like the ones in this presentation
• Kickstarter Successful!
• Available November 2016
• Order now:
store.DevIQ.com
2017 Software Craftsmanship Calendar
76. 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
Over time, neglect can turn software from well-oiled machinery into heaps of useless junk.
Typically the low quality code was left because of the need to meet deadlines, but once in place, the justification for having put it there no longer matters.
Whenever you are forced to work around the hacks, bad code, etc, in your application, the decrease in your productivity represents interest on technical debt. If you spend time refactoring the code to improve its quality, you are paying down the principal of the technical debt (and thus reducing future interest costs).
Accidental vs. Deliberate decision to incur the debt.
Reckless vs. Prudent kinds of debt incurred.
Should we spend an iteration or a month just trying to improve the quality of the system, and pay down technical debt?
What would you think if you went to your favorite restaurant, only to find that it was closed for a month, with a sign explaining that it needed to clean up things that had gotten out of hand due to neglect. Would you feel as confident about eating the product of that kitchen, knowing it had let things go so wrong that it now needed a month without producing food just to clean up the mess it had created. How do you think our customers should feel if we request time to clean up our code instead of fixing bugs or delivering new features?
By definition, refactoring attempts to improve the quality of a software application without changing its behavior. If you’re changing behavior, you’re not refactoring, you’re fixing a bug or adding a feature (hopefully – or adding a bug).
Only refactor code while your tests are passing. Make sure they still pass when you are done. Break large refactorings into small steps and test after each step.
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.
Principles provide guidance for refactoring. They provide a consistent target to aim toward.
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.
Demo GildedRose.
Using Excel
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)
Open-Closed Principle
Software can only be open to extension in a few different axes, and each additional bit of flexibility adds complexity to the software. Thus, this flexibility should be added only when needed, not simply anticipated.
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.
Nulls break polymorphism and violate LSP just as much as other subtypes that aren’t substitutable! Try to avoid the possibility of dealing with nulls in polymorphic code like foreach loops – consider using Null Object Pattern to address.
This is an extemely 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.
“Legacy” N-Tier Logical Architecture
Hidden Dependencies
Extract interface
Implement interface with tightly coupled original code