Refactoring Refactoring is a step in the software design process Refactoring – Defined • (What) Code refactoring is the process of changing a computer program's internal structure without modifying its external functional behavior. • (Why) The purpose of refactoring is to make existing code easier to understand, modify and extend. • The term ‘refactoring’ is both a noun and a verb. There are catalogs of refactorings (noun) that simplify the process of refactoring (verb) code. • Refactoring reduces technical debt (the “principle” part). Example ... if (i != min) { int temp = num[i]; num[i] = num[min]; num[min] = temp; } ... Program behavior doesn’t change but the resulting code is easier to read and understand. Extract Method ... if (i != min) { swap(ref num[i], ref num[min]); } ... void swap(ref int a, ref int b) { int temp = a; a = b; b = temp; } When to Refactor • During new development. Writing well-designed code in one pass is not easy. Solution: divide and conquer; write code in two steps. Step 1: make it work. Step 2: make it better; refactor the code until the design is satisfactory. This is standard practice in test-driven development (TDD). • During maintenance. Making changes to existing code can be risky. Minimize the risk by making changes in two steps. Step 1: refactor the code to a state that will more easily accept the planned changes. Step 2: make the changes. • In between maintenance changes. Routine changes to software gradually increase software complexity (aka entropy or technical debt) unless extra effort is devoted to maintaining or reducing complexity. (This tendency is Layman’s second law of software maintenance.) When turnaround time for a change is short, there isn’t always time to maintain or reduce system complexity with each change. Refactoring between code changes reverses the adverse effects of quick fixes and makes future maintenance easier. Systematic Refactoring is more than just “cleaning up code” • Refactoring is more than just “cleaning up code” the same way an accounting audit is more than just “looking over the books”. • The term refactoring is sometimes used to refer to any change that improves code. • Here the term is being used to refer to systematic code changes that address reoccurring or identifiable problems. Both the problem and the systematic change have well-known names. • There are catalogs of refactorings that provide routine solutions to reoccurring problems found in existing code just as there are catalogs of design patterns that provide routine solutions to reoccurring problems faced during design. Like catalogs of design patterns, catalogs of refactoring also provide a vocabulary for discussing possible code changes. Refactoring makes software development more routine • Catalogs of refactorings serve as another handbook for software engineers. They offer routine solutions to reoccurring problems found in code. • Use of these handbooks makes software development and maintenance more routine, systematic and predictable. • Documented refactorings reduces the portion of the development process that still depends on inspiration and creativity. Routine Design Is your code ready to be refactored? • Step 0: make sure you have object-oriented code. • Most refactorings assume OO code. • A more serious condition—one that should be addressed before you refactor—is not having object-oriented code to start with. • Code is object-oriented when you have classes that encapsulate data and these classes have methods that act on the data. The objects created from these classes correspond to significant abstractions in the problem and/or solution domain. Refactorings are Transformations • Refactorings are transformations on code A B • They transform existing code that is poorly structured and difficult to understand and maintain into code with improved structure that is easier to understand and maintain. • Refactorings have names like: • Rename method • Extract superclass • Separate Query from Modifier Example Transformation/Refactoring Move Method Knowing when to apply a refactoring • If you have a clear understanding of the transformation a refactoring performs, you might be able to use your intuition to recognize when it applies. For example, if you find a cryptic method name in your code, rename method seems like an obvious choice. • There are many instances when a refactoring can be applied. (For example, you can rename any method.) How do you know when a particular refactoring should be applied? Knowing when to apply a refactoring [Cont] • Could just leave it up to your intuition to recognize when a refactoring is needed. However, if you are just starting out, your intuition might not be developed. • What is needed are guidelines for when to apply a refactoring that are a little more tangible than “this code just doesn’t feel right”. Code Smells • Code smells or pathologies are symptoms in code that suggest a deeper problem. Most code smells are paired with one or more corresponding refactorings for dealing with the problem. • The presence of symptoms doesn't always imply a change is needed. Design often involves tradeoffs among competing priorities. The code might be the way it is for a reason. • Code smells and their corresponding refactorings are rules of thumb not absolutes. Candidate Refacotorings Associated with Certain Code Smells More Code Smells • Too many parameters • Inappropriate intimacy – “a class that has dependencies on implementation details of another class.” • Contrived complexity – forcing the use of a really cool design pattern when a simpler design would have sufficed. • Excessively long identifiers • Excessively short identifiers • Excessive use of literals – consider replacing with named constants. • Type Embedded in Name – “Avoid placing types in method names; it's not only redundant, but it forces you to change the name if the type changes.” Some Code Smells Overlap with Design Principles Appropriateness of a refactoring depends on context • Refactorings don’t automatically take your code to a better state. Just because there is a refactoring A B and you find state A in your program, there is no guarantee that state B is better for your program than state A. • When to apply a refactoring depends on context. • Here’s proof. There are pairs of refactorings that are opposites. – – – – Extract Method Inline Method Extract Class Inline Class Collapse Hierarchy Extract Subclass Hide Delegate Remove Middle Man Duplicated Code • • • Duplicated code is a specific case of the don’t repeat yourself principle (DRY). Duplicate code is when the same code or nearly the same code appears in more than once place. Duplicate code is undesirable because: – It takes more effort to make a change to the code. The change has to be made in all locations. Easy to make a mistake and allow code to become inconsistent. • Candidate refactorings: – Extract Method – when duplicate code is in the same class, use extract method to get the code in one place and then replace chunks of duplicated code with a call to this new method. – Pull Up Field – when subclasses have duplicate fields use pull up field to move the field declaration to a superclass. There may also be an opportunity to move behavior in subclasses to a superclass as well. – Form Template Method – When the same sequence of steps are repeated in subclasses but with unique behavior for some or all steps, the Template Method design pattern can be used to define the skeleton of the algorithm in a superclass with the behavior of the steps deferred to polymorphic methods in subclasses. (This is a good example of refactoring to patterns. Patterns are often targets of refactoring.) – Extract Class. Used when duplicate code is in two or more unrelated classes. (Before applying extract class, consider whether or not it would be better to make the duplicate code a method of one class that the others call.)] Long Method • Methods that are too long are difficult to understand and reuse. • The general rule-of-thumb is a method should fit on one page. This makes it easier to comprehend. • It’s not so much the number of lines in a method that make it too long, it’s the variety of activity that takes place during the method. (Single Responsibility Principle) • When Extract Method is used to divide a long method, a good method name can add significant semantic value. • Candidate Refactorings: – Extract Method. If many local variables result in long parameter lists on extracted objects, consider using Introduce Parameter Object to minimize the number of parameters passed to extracted methods. Large Class • Similar to long methods, large classes are classes with too much code. • Often it’s not the number of lines of code that is the problem, but rather how the code is used. A class is too large if: – It deals with more than one abstraction and/or these abstractions are unrelated. (A direct violation of the single responsibility principle.) – The volume of code makes it difficult to understand. • Candidate Refactorings: – Extract Class. When class cohesion begins to break down and the features of a class no longer pertain to a single responsibility, consider moving some of the features to a second class. – Extract Subclass. When features of a class only apply to certain instances of a class, consider moving some of these conditional features to a subclass. Divergent Change • The divergent change code smell is what you notice when inspecting a class that doesn’t follow the Single Responsibility Principle (SRP) of design. • Divergent change is when a class changes in different ways for different reasons. • Consider the most likely reasons for making changes to the software you are writing. What modules are affected by each of the reasons for change? Is there any one module that is changed for more than one reason? If so, consider using Extract Class to split this class into two classes based on the reasons for change. • Example: class that calculates and prints values for a report. It will change when calculations change or format of report changes. Shotgun Surgery • Shotgun surgery is the opposite of divergent change. Instead of having one class with multiple reasons to change you have one reason for a change affecting multiple classes. • Candidate Refactorings: – Move Method – Move Field – Inline Class - • Divergent Change: a module has more than one reason to change. • Shotgun Surgery: a single reason to change affects more than one module. Feature Envy • A tall tale sign of feature envy is a method that relies more on data from another class than its own class in order to fulfill its responsibility. • Characteristic of good OOD is keeping data and the methods that operate on this data in close proximity. • Candidate Refactorings: – Use Move Method to move the method to the class that has the preponderance of data it needs to fulfill its responsibility. – When it is a section of a method that seems envious, consider Extract Method to isolate the code followed by Move Method to place it closer to the data it needs. Lazy Class • A lazy class is one that doesn't do much. Perhaps you had grand plans for it when first created, but now it contains just a few fields and mostly getter and setter methods. • Candidate Refactorings: – Collapse Hierarchy – – Inline Class – Data Class • Classes with mostly getting and setting methods. • To promote a data class to a genuine class, find the methods that use the data in the data class and consider moving them to the data class. • Candidate Refactorings: – Move Method – – If the method that uses the data can’t be moved as is, consider applying Extract Method to isolate the logic that is using the data and then Move Method on the newly isolated logic. Speculative Generality • Abstraction in program design can be a good thing. It is the primary means of managing complexity and can enable reuse. However, like all good things in life it can be pursued to excess. • For example, you defined an abstract class with visions of having lots of code written to the interface of the abstract class working with a number of subclasses. However, so far you have just one subclass and prospects for others is increasingly unlikely. • Candidate Refactorings: – Collapse Hierarchy – – Inline Class – Middle Man • Delegation is one way of achieving information hiding. For example, a grocery store is a delegate for farmers and ranchers. It acts as a middle man between consumers and producers. Buying produce from the grocery store simplifies your life and insulates you from the complexity of dealing directly with producers. • However, if you find yourself going to the same store just for milk, eggs and butter, maybe it’s more efficient to just skip the middle man and go straight to the dairy farmer (or farmer’s market) for your produce. • Candidate Refactorings: – Remove Middle Man – – Inline Method – Remove Middle Man Remove Middle Man Hide Delegate Comments • Comments are great you should use them regularly; however, if you have to comment code to make it understandable, consider refactoring to make the code more understandable. • For example, you might use Extract Method in order to create a descriptive name for a chunk of code. • Another example: you might use Replace Magic Number with Symbolic Constant to make code more readable. • Good variable and method names make comments a part of the code. Refused Bequest • A class that violate the principle of substitution is refusing the bequest of its superclass. • Consider: Replace Inheritance with Delegation Extract Interface • Refactoring can also help you conform to principles of good design. • Example: Interface Segregation Principle • If a clients of a class are interested in only a subset of methods on a class, consider using Extract Interface to create a more narrow interface for the client. Refactoring – How to • Each refactoring provides a systematic procedure for making a particular code change. • For each refactoring there are step-by-step instructions for making the (function-preserving) code change. • Small steps reduce the changes of making an error. They also make it easy to catch any errors that are made. • During refactoring you shouldn’t: – Add new functionality – Change existing functionality – Fix defects Many programming devolvement environments (IDE’s) offer automated support for refactoring • Eclipse (right) • Visual Studio (below) Systematic Refactorings • Rename Method – Shakespeare clearly wasn’t a programmer. Ask any good programmer, “What’s in a name?” and the answer you are likely to get is: “A lot!” Good names convey meaning in a way that is inextricably tied to the code. • Move Method and Move Field – Moving methods and fields from one class to another can reduce coupling and/or increase cohesion. • Encapsulate Field – Make a public field private and add accessors. With C# you can convert the field to a property. • Reduce Scope of Variable – • Extract method – Turn a sequence of statements into a method with a descriptive name. • Inline method – (Opposite of extract method.) • Hide delegate – Add methods to hide a delegate. • Replace conditional with polymorphism (complex) More Systematic Refactorings • Introduce Assertion – Add an assertion to make an assumption in your code explicit. • Extract Superclass – When you have two or more classes with similar features (e.g. SceneItem) • Extract Subclass – A class has features that are only used by some instances. • Pull Up Field – Move a field from a subclass to a superclass. • Pull Up Method – Move a method from a subclass to a superclass. • Push Down Field – Move a field from a superclass to a subclass. • Push Down Method – Move a method from a superclass to a subclass. Extract Method • Extract method is not only for removing duplicate code. It can also be used to improve readability and understandability. • Extract method is also useful when a method gets too long to easily read and understand. Adding descriptive method names introduces selfdocumentation. • Added bonus: fine-grain methods are easier to test and reuse. Extract Method Example // Returns true if there is a collision. public bool pixelPerfectCollision(Sprite s) { // Check to see if area of s overlaps with // with self . . . if (!objectsOverlap) { return false; } else { // Check to see if non-opaque pixels in // both objects overlap . . . } } // Returns true if there is a collision. public bool pixelPerfectCollision(Sprite s) { return ( boundsOverlap(s)&& opaquePixelsOverlap(s) ); } Reduce Scope of Variable Encapsulate Field Replace Inheritance with Delegation Inline Class Extract Class Replace Magic Number with Symbolic Constant Functional: // Calculate the length of the outer crust of each // piece of pizza given the diameter of the pizza // and number of desired pieces. public float lengthOfOuterCrust(float diameter, int numberOfPieces) { return (3.1415 * diameter) / numberOfPieces; } Better: public static final float PI = 3.1415; public float lengthOfOuterCrust(float diameter, int numberOfPieces) { float circumference = PI * diameter; return circumference / numberOfPieces; } Remove Middle Man