Refactoring Notes
The following notes are from Martin Fowler’s Refactoring
Smells
Smells are indicators of when to refactor. The following are just notes I took when reading through Refactoring.
| Smell | Description |
|---|---|
| Mysterious Name | When it isn’t obvious what something does from its name. Somtimes a sign that the design itself has issues. |
| Duplicated Code | Sometimes hides subtle differences between copies. Hard to maintain if duplicates are meant to stay in sync. |
| Long Function | Often a result of a function doing too much. Can be hard to name descriptively and hard to understand. |
| Long Parameter List | Long parameter lists obscure the meaning of a function. |
| Global Data | Hard to trace the source of unexpected changes. |
| Mutable Data | Unexpected changes can cause hard to detect and infrequent failures. |
| Divergent Change | The opposite of Single Responsibility Principle. Modules should only have one reason to change. |
| Shotgun Surgery | When responsibilities are spread across classes. E.g. multiple classes perfroming database access. |
| Feature Envy | When a function interfaces with fields/functions of another class more than its own. A sign that the function might be in the wrong class. |
| Data Clumps | Groups of data which travel together. Opportunity for grouping into a class. |
| Primitive Obsession | Using primitives to represent something which could be better represented as a class. E.g. date strings, currency, etc. |
| Repeated Switches | When there are several of the same switch logic. Possibly a sign that it’s time to use polymorphism. |
| Loops | Loop logic doesn’t always communicate intent well. Pipelines are more descriptive. |
| Lazy Element | A class that won’t lose meaning if replaced by a function. A function that won’t lose meaning if replaced by a statement. |
| Speculative Generality | Adding behaviour which doesn’t have a client yet. Bloats the code base. |
| Temporary Field | An object should be expected to use all of its fields. A temporary field is only used by some objects. |
| Message Chains | When a client recursively requests object access. E.g. student.School().Address().PostCode() |
| Middle Man | When a class is delegating most of its behaviour to another class. |
| Insider Trading | Classes which communicate too much. Most class behaviour is better encapsulated. |
| Large Class | A sign of either duplicated code, or a class having multiple reasons to change. |
| Alternative Classes with Different Interfaces | Two classes which could share an interface but don’t. Sharing an interface simplifies client code. |
| Data Class | A data class only has setters/getters. Useful manipulations are clearly being performed elsewhere. |
| Refused Bequest | When a subclass only uses a fraction of inherited data/functionality. A sign that the inheritance model is incorrect. |
Refactorings
Common Refactors
| Refactor | Description |
|---|---|
| Extract Function | |
| Inline Function | |
| Extract Variable | Used for naming statements, aka “introduce explaining variable” |
| Inline Variable | Remove non-descriptive temporaries |
| Change Function Declaration | aka. “rename function”, “change signature” |
| Encapsulate Variable | Replace shared variable access with accessor functions |
| Rename Variable | |
| Introduce Parameter Object | Create a parameter object to group a “data clump” (a group of data that tends to travel together) |
| Combine Functions into Class | |
| Combine Functions into Transform | Combine data computations into a transform instead |
| Split Phase | Break a block of code down into “phases” based on responsibilities. Ideally different responsibilities are grouped making other refactors easier |
Encapsulation
| Refactor | Description |
|---|---|
| Encapsulate Record | Replace structures (aka “records”) with classes and accessors. Allows consistency for accessing derived data |
| Encapsulate Collection | Collections include arrays, maps, dictionaries, etc |
| Replace Primitive with Object | Most primities have additional meaning which isn’t represented precisely by the primitive type. Allows applying additional behaviour to the object |
| Replace Temporary with Query | Replace temporary variables holding computations with a query |
| Extract Class | Break up a class with multiple responsibilities. Look for methods and data that are strongly linked |
| Inline Class | Combines two classes when they share a single responsibility anyway. Also a good inbetween refactor if the responsibilities should be split differently |
| Hide Delegate | Creates a “delegating method” to encapsulate the delegates used by a class. e.g. a class Person has a delegate structure Education, instead of allowing external users to access person.education.grade, provide Person::Grade() instead |
| Hide Middleman | Inverse of “Hide Delegate”, useful when a class has unneccessary encapsulation of classes the user already works with |
| Substitute Algorithm | Replace the body of an algorithm with another algorithm |
Moving Features
| Refactor | Description |
|---|---|
| Move Function | Move a function from one class to another |
| Move Field | Move a field from one class to another |
| Move Statements into Function | Move common external statements into a function |
| Move Statemenets into Caller | The inverse of “Move Statements into Function” |
| Replace Inline Code with Function Call | Replace a some statements with a pre-existing function call that does the same thing |
| Slide Statements | Move related statements together |
| Split Loop | Breaks up loops with multiple responsibilities. Makes loops easier to understand and use |
| Replace Loop with Pipeline | Improves the expressiveness of the data transform |
| Remove Dead Code |
Organisizing Data
| Refactor | Description |
|---|---|
| Split Variable | When a variable is used for more than one responsibility, split it so that it handles its individual responsibilities |
| Rename Field | Rename fields of a struct/class for comprehension |
| Replace Derived Varable with Query | Query computations are safer and more repeatable than derived variables |
| Change Reference to Value | Make fields values instead of references to take control of when they change |
| Change Value to Reference | Inverse of “Change Reference to Value” when we want value updates to affect all consumers |
Conditional Logic
| Refactor | Description |
|---|---|
| Decompose Conditional | Apply extract function to conditional statements/conditional bodies to make them read better |
| Consolidate Conditional Expression | Consolidate a series of related checks into a function which describes the related meaning of the checks |
| Replace Nested Conditional with Guard Clauses | Use guard clauses to keep unexpected behaviour documented outside of the expected behaviour |
| Replace Conditional with Polymorphism | Add structure and extendability to complex contitional logic by putting the conditional behaviour in subclasses instead |
| Introduce Special Case | Uses a special case subclass handle special casses of a class. Null/"unknown" are common targets for this |
| Introduce Assertion | Communicate assumptions through assertions |
Refactoring APIs
| Refactor | Description |
|---|---|
| Separate Query from Modifier | Functions shouldn’t modify observable state and return data. Separates the modifying responsibilities from the querying responsibilities |
| Parameterize Function | Combines several functions with similar logic using a parameter |
| Remote Flag Argument | Flag arguments determine how a function executes. Instead of flag arguments, break functions into a set of better named functions |
| Preserve Whole Object | When a function uses derived data, consider just passing the original object instead |
| Replace Parameter with Query | Useful when the function could determine the parameter on its own without having the parameter passed in |
| Replace Query with Parameter | Inverse of “Replace Parameter with Query” when we want to manipulate the dependencies |
| Remove Setting Method | Some fields we don’t want to change once the object is instantiated |
| Replace Constructor with Factory Functions | Factory functions are more flexible and can return subclasses when appropriate |
| Replace Function with Command | A “Command” is an object with an execute() function. Enables slightly more flexibility and inheritance. Only useful in special cases |
| Replace Command with Function | Inverse of “Replace Function with Command” |
Dealing with Inheritance
| Refactor | Description |
|---|---|
| Pull Up Method | Pull up a method from the subclasses into the superclass. Useful when subclasses share a method which makes more sense in the superclass |
| Pull Up Field | Pull up a field from the subclasses into the superclass |
| Pull Up Constructor Body | Move useful statements shared in subclasses into the superclass constructor |
| Push Down Method | Push down a method from the superclass into the subclass(es). Useful when superclasses have methods that are more specific to only some/one subclass |
| Push Down Field | Push down a field from the superclass into the subclass(es) |
| Replace Type Code with Subclasses | Use subclasses instead of a type field. Allows breaking up specific fields and applying “Replace Conditional with Polymorphism”. If the class already has subclasses, can make a Type class with its subclasses and behaviour instead |
| Remove Subclass | Absorb a subclass doing too little into its superclass |
| Extract Superclass | Absorb shared behaviour between subclasses into the superclass |
| Collapse Heirachy | Merge a subclass/superclass pair together. Useful when differences are trivial |
| Replace Subclass with Delegate | Use a delegate (another object) which provides the functionality of the removed subclass. Enables object composition over inhertance |
| Replace Superclass with Delegate | Inheritance can couple functions which aren’t important to the subclass; when this happens, use a delegate instead |