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 |