Very little time is spent in writing code. Most programmers spend their time debugging and figuring out what's going on in the codebase they are working with. Fixing the bug is usually pretty quick, but finding it is usually hard. Refactoring is a skill that every software developer must have.
refactoring
Refactoring needs tests. Read the code, gain some insight, and use refactoring to move that insight from your head back into the code. The cleaner code then makes it easier to understand it, leading to deeper insights and a beneficial positive feedback loop. This process will improve your conceptual mental models of the codebase and simplify your understanding of the domain. 

Code Smells
  1. Mysterious Name, that doesn't indicate the purpose of a variable, function, class or a module.
  2. Duplicated Code
  3. Long Function, Large Class
    • Difficult to understand and change
    • Does too many things
  4. Long parameter list
  5. Data clumps, 3/4 items together in lots of places
  6. Primitive obsession

Fundamental Refactoring Techniques

  1. Extract Method
    1. If you have to spend effort looking at a fragment of code and understand what it’s doing, then extract it into a function and give it an intention-revealing name. 
    2. Then, when you read it again, the purpose of the function leaps right out at you, and most of the time you won’t need to care about how the function fulfills its purpose (which is the body of the function).
    3. Extract small pieces first. Before you extract this small piece of a monster method, it looks like it won’t make any difference at all. After you extract more pieces, you’ll probably see the original method in a different way. You might see a sequence that was obscured before or see a better way for the method to be organized. When you see those directions, you can move toward them. 
    4. This is a far better strategy than trying to break up a method into large chunks from the beginning. Too often that isn’t as easy as it looks; it isn’t as safe. It’s easier to miss the details, and the details are what make the code work.
  2. Extract Variable
    1. Expressions can become very complex and hard to read. In such situations, local variables may help break the expression down into something more manageable. 
    2. In particular, they give me an ability to name a part of a more complex piece of logic. This allows me to better understand the purpose of what’s happening. They also improve debugging. 
  3. Rename Methods and Variables
    1. So that the code is more expressive and reads better like a prose. 
  4. Introduce Parameter Object
    1. For groups of data items that regularly travel together, appearing in function after function. 
    2. If you create a class for these items, you can capture the common behaviour over this data into class methods and add validation. 
  5. Combine Functions Into Class
    1. When I see a group of functions that operate closely together on a common body of data (usually passed as arguments to the function call), I see an opportunity to form a class. 
    2. Using a class makes the common environment that these functions share more explicit, allows me to simplify function calls inside the object by removing many of the arguments, and provides a reference to pass such an object to other parts of the system.
    3. This refactoring also provides a good opportunity to identify other bits of computation and refactor them into methods on the new class.

Encapsulation
    Identify secrets that modules should hide from the rest of the system. 

  1. Encapsulate Record
    1. Favour objects over records/tuples. You can hide what is stored and provide methods to access the data. 
  2. Encapsulate Collection/Mutable data
    1. This makes it easier to see when and how data is modified. You can simply put a breakpoint on the setter. 
    2. Common mistake: Access to a collection variable may be encapsulated, but if the getter returns the collection itself, then that collection's membership can be altered without talking to the encapsulation.
    3. Force changes to the collection go through the owning class, giving you the opportunity to modify such changes as the program evolves. 
    4. Ensure that the getter for the collection does not return the raw collection, so that the users cannot accidentally change it. Instead, return a copy of the underlying collection. (watch out for huge collections)
  3. Replace Primitive with Object
    1. Often in the beginning, you represent simple facts as simple data items, such as numbers or strings. As development proceeds, those simple items aren't so simple anymore. 
    2. Create a new class for this data. Initially, it does little more than wrap the primitive, but you have a place to put behaviour specific to its needs. 
  4. Replace Temp with Query
    1. If you are breaking up a large function, turning temporary variables into their own functions makes it easier to extract parts of the function, as you no longer need to pass in variables to the extracted functions. 
    2. Allows you to avoid duplicating calculation logic everywhere, if the variables are calculated in the same way in different places. 
  5. Extract Class
    1. Classes grow as development goes on and become bloated. A class with many methods and a lot of data is too complicated to understand easily. Split it. 
    2. A good sign to know when to split a large class is when a subset of the data and a subset of the methods seem to go together. 
  6. Move Function 
    1. If a function references elements in other contexts more than the one it currently resides in, move that function together with those elements. 
    2. Move all the data and elements used by the chosen function if they fit in the new context. 
    3. Rename the function to fit the new context. 

Simplifying Conditional Logic

  1. Extract Conditional to a Function
    1. Complex conditional logic is one of the most common sources of complexity in a program. 
    2. Move the complicated conditional logic to a function with an intention-revealing name. 
    3. If you are doing a series of conditional checks where each check is different yet the resulting action is same, consolidate them into a single conditional check with the resulting action. 
  2. Replace Conditional with Polymorphism
    1. If you have several functions that have a switch statement on a type code, 
    2. Remove the duplication of the common switch by creating classes for each case and using polymorphism to bring out the type-specific behavior. 
  3. Introduce Special Case / Introduce Null Object
    1. If you find many parts of a code base having the same reaction to a particular value, bring that reaction into a single place. 
  4. Introduce Assertions
    1. Often, sections of code work only if certain conditions are true. 
    2. Such assumptions are often not stated, but can only deduced by looking through the algorithm, sometimes the assumptions are stated with a comment. 
    3. A better technique is to make the assumption explicity by writing an assertion. 
    4. An assertion is a conditional statement that is assumed to be always true. Failure of an assertion indicates a programmer error. 

Refactoring APIs

  1. Separate Query from Modifier
    1. A function should do only one thing. Differentiate between functions with side effects and those without. 
    2. Any function that returns a value should not have any side effects. 
    3. If you come across a method that returns a value but also has side effects, always separate the query from the modifier. 
  2. Remove Setters if not needed
    1. If you don't want a property to change once the object is created, don't provide the setter and make it immutable (read-only)
    2. Initialize the property in the constructor. 
  3. Replace Constructor with Factory Function/Class
    1. Factory function will call the constructor, but you can replace it with something else while testing. 
  4. Replace Function with Method Object / Command Object
    1. There are times when it's useful to encapsulate a function into its own object, called a command object. 
    2. The sole purpose of the command object is the execution of the method it's replacing. 

Inheritance

  1. Pull Up Method/Data
    1. If methods/data are duplicated in the subclasses, pull them up into the super class. 
  2. Push Down Method/Data
    1. If a method/data is only relevant to one subclass, removing it from the super class and putting it only on the subclass makes that clearer. 
  3. Replace Type Code with Subclasses
    1. This allows you to use polymorphism to handle conditional logic. This is most useful when you have several functions that invoke different behavior depending on the value of the type code. 
    2. With subclasses, you can apply "replace conditional with polymorphism". 
  4. Extract Superclass
    1. If you see two or more classes doing similar things, then pull their similarities together into a superclass. 
    2. Use 'Pull up data' and 'Pull up method' refactorings to move the common behavior. 

Guidelines
  1. Save the code you start with. Make sure you are using version control. 
  2. Make sure you can get back to the code you started with. 
  3. Make sure you have a test for the piece of code you are trying to refactor
  4. Keep refactorings small, and do them one at a time. 
  5. Don't mix different refactorings into a single commit. If you find one, add a comment to come back to it later.