Writing in the front

The basic concepts of refactoring and a test system for testing refactoring performance were introduced in the previous two articles, refactoring with Taste 01 — The Road to Refactoring and Refactoring with Taste 02 — Building a Test System, respectively. This article will focus on the common refactoring methods in 11.

In the practice of refactoring, we commonly use methods to extract functions and variables, that is, to extract functions and variables through code, and the most important is naming. Of course, we also used two reverse refactorings of refactorings – inline functions and inline variables. There are many more refactoring methods that are probably already being used quietly in our code.

List of refactoring

A standard format can be used for refactoring, and each refactoring technique contains the following sections:

  • Name: Builds a common refactoring vocabulary that can be used to name variables and functions during refactoring
  • Sketch: Helps you quickly find the refactoring you need
  • Motivation: Introduce “Why we should do this refactoring” and “When we should not do this refactoring”
  • Mechanics: Briefly explain how to do this refactoring
  • Examples: Give simple and clear examples of how this refactoring approach works

The strange art of reconstruction

For experienced developers, there are many techniques for refactoring, and there are many ways to refactor code, which vary from person to person. In post-refactoring thinking, common techniques are summarized as follows:

  • Refining function
  • Inline function
  • Derived variables
  • Inline variable
  • Change the function declaration
  • Encapsulation variable
  • The variable name
  • Introducing parameter objects
  • Function group synthetic class
  • Functions combine into transformations
  • Split phase

1. Refine the function

Original function:

function extractFunc(arg){
  printBanner();
  let outstanding = calculateOutstanding();

  // Print details
  console.log(` name:${arg.customer}`);
  console.log(` amount:${outstanding}`);
}
Copy the code

Refining function:

function extractFunc(arg){
  printBanner();
  let outstanding = calculateOutstanding();
  printDetails(outstanding);
  // Print details
  function printDetails(outstanding){
    console.log(` name:${arg.customer}`);
    console.log(` amount:${outstanding}`); }}Copy the code

In fact, there is not much in the code above, and I didn’t change much when I did the function extraction. I just distilled the code into separate functions based on what it does, and named the functions based on what the code does.

motivation

The best time to refine a function is not when the code can’t be displayed on a screen, nor is it a simple extraction of highly repetitive code, but to separate the intent from the implementation. When you go through code and it takes time to understand what it does, you need to refine it into a purpose function and name it according to the purpose.

practice

We saw if this approach to refining the functions of the code would result in a large number of short function calls that would affect performance. In fact, you don’t have to worry because short functions can be cached more easily, so you don’t have to worry too much about performance when you always make short functions follow performance tuning guidelines. When naming functions, pay attention to concise, verbal, in fact, you can use your annotations translated into English for naming.

In short, you create new functions and name them according to their intent. Check for scope references to variables in the refined code, and then run the processing tests.

Scoping for variables:

  • Without local variables, refine the code directly

  • When there are local variables, the values of these local variables can be passed as parameters to the target function

  • Reassignment of a local variable, such as an assignment of a parameter to a source function, should be split into temporary variables. There are two cases:

    (1) The assigned variable is only used in the refined code. At this time, only the declaration of temporary variable needs to be moved to the refined code segment;

    (2) This variable is also used by code other than the extracted code, in which case only the modified value is returned.

sample

Complete code before refactoring:

function extractFunc(arg){
  let outstanding = 0;

  console.log("* * * * * * * * * * * * * * * * * * * * * * * *");
  console.log("*****Customer Owns******");
  console.log("* * * * * * * * * * * * * * * * * * * * * * * *");

  / / calculate outstanding
  for(const o of arg.orders){
    outstanding += o.amount;
  }

  / / record
  const today = Clock.today;
  arg.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);

  // Print details
  console.log(` name:${arg.customer}`);
  console.log(` amount:${outstanding}`);
  console.log(` due to:${arg.dueDate.toLocaleDateSString()}`);
}
Copy the code

The complete code after refactoring:

function extractFunc(arg){
  // Extract the function that prints the prompt
  printBanner();

  / / calculate outstanding
  const outstanding = calculateOutstanding(arg);

  / / record
  recordDueDate(arg);

  // Print details
  printDetails(arg,outstanding);
}

function printBanner(){
  console.log("* * * * * * * * * * * * * * * * * * * * * * * *");
  console.log("*****Customer Owns******");
  console.log("* * * * * * * * * * * * * * * * * * * * * * * *");
}

function recordDueDate(arg){
  const today = Clock.today;
  arg.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}

function printDetails(arg,outstanding){
  console.log(` name:${arg.customer}`);
  console.log(` amount:${outstanding}`);
  console.log(` due to:${arg.dueDate.toLocaleDateSString()}`);
}

function calculateOutstanding(){
  let result = 0;
  / / calculate outstanding
  for(const o of arg.orders){
    result += o.amount;
  }
  return result;
}
Copy the code

2. Inline functions

Original function:

function whoAreYou(user){
  return whatAreYou(user) ? "Adult" : "Minor";
}

function whatAreYou(user){
  return user.age > 18;
}
Copy the code

The refactored function:

function whoAreYou(user){
  return user.age > 5 ? "Adult" : "Minor";
}
Copy the code

When you look at this code, do you think who would write that stupid code above? Obviously simpler is better. Indeed, short functions make the code clear and readable, making the intent of the function more explicit.

motivation

By inlining, you can find connections between functions, find indirect functions and get rid of useless ones. Unorganized functions can be inlined into a larger function and then refined again.

practice

The actual operation of the inline function is as follows:

  • Check the function to make sure it is not polymorphic. If the function is a class whose subclasses inherit from the function, then it cannot be inlined
  • Find all call points for the function and replace all call points with the function body
  • Test after each replacement. You don’t have to do the entire inline operation at once. Some call points are difficult to inline, so consider processing when the time is right
  • Delete the definition of this function

sample

Functions that are not inlined:

function inlineFunc(student){
  const students = [];
  addStudent(students, student);
  return students;
}

function addStudent(students, student){
  students.push(["name",student.name]);
  students.push(["address",student.address]);
}
Copy the code

Function after inline operation:

function inlineFunc(student){
  const students = [];
  students.push(["name",student.name]);
  students.push(["address",student.address]);
  return students;
}
Copy the code

3. Refine variables

Original function:

function price(order){
  return order.quantity * order.itemPrice - 
  Max.max(0, order.quantity - 500) * order. ItemPrice *0.05 + 
  Max.min(order.quantity * order.itemPrice * 0.1.100)}Copy the code

Refactoring function:

function(order){
  constBasePrice = order. The order quantity *. ItemPrice;const quantityDiscount = Math.max(0, order.quantity - 500) * order. ItemPrice *0.05;
  const shipping = Math.min(order.quantity * order.itemPrice * 0.1.100);
  return basePrice - quantityDiscount + shipping;
}
Copy the code

In fact, expressions can sometimes be very verbose, making them complex and hard to read. During refactoring, local variables can be used to decompose expressions, making the logic clearer, easier to manage, and convenient for variable debugging.

At the same time, the context in which the expression is named after the extracted variable is also taken into account. When a variable is meaningful only in the current function, you can refine the variable; When variables make sense in a wider context, consider exposing them in functional form.

Specifically, when confirming that the expression to be refined has no side effects, declare an unmodifiable variable (constant), copy the expression to be refined, and assign the result of the expression to this variable, so as to achieve the purpose of refining the variable.

Example unrefactored code:

class Order{
  constructor(record){
    this._data = record;
  }
  get quantity() {return this._data.quantity;
  }
  get itemPrice() {return this._data.itemPrice;
  }

  get price() {return this.quantity *this.itemPrice -
      Math.max(0.this.quantity - 500 ) * this.itemPrice * 0.05 +
      Math.min(this.quantity * this.itemPrice * 0.1.100)}}Copy the code

Refactoring code:

class Order{
  constructor(record){
    this._data = record;
  }
  get quantity() {return this._data.quantity;
  }
  get itemPrice() {return this._data.itemPrice;
  }


  get price() {return this.basePrice - this.quantityDiscount + this.shipping;
  }
  get basePrice() {return this.quantity *this.itemPrice;
  }
  get quantityDiscount() {return Math.max(0.this.quantity - 500 ) * this.itemPrice * 0.05;
  }
  get shipping() {return Math.min(this.quantity * this.itemPrice * 0.1.100)}}Copy the code

4. Inline variables

Inside functions, variables can give meaningful names to expressions, but sometimes the name is no more expressive than the expression itself, and may even prevent refactoring nearby code, requiring inlining to eliminate variables.

Specifically, the expression on the right side of the variable assignment statement is checked for side effects. If the variable is not declared unmodifiable, it is changed to unmodifiable before testing. Find where the variable was first used, replace it with the expression directly on the right of the assignment statement, and test it. Then, change and replace all places where this variable is used, removing the declaration point and assignment statement of the variable.

In short, use expressions directly.

function inlineVariable(order){
  let basePrice = order.basePrice;
  return basePrice > 1000;
}
Copy the code

After the refactoring:

function inlineVariable(order){
  return order.basePrice > 1000;
}
Copy the code

5. Change the function declaration

Function is the main way to divide the program into code segments, which can be clearly divided into each function code, so that the articulation of the software system can be clearly understood.

For function refactoring, a good name and an ordered argument list are key. A common technique is to write a clear comment describing the function’s purpose and then modify it to the function’s name. Modifying the argument list increases the scope of the function and changes the conditions required by the module, removing unnecessary coupling.

Such as:

function cirum(radius){}

/ / to
function cirumFerence(radius){}

class Book{
  constructor(){
    this._reservations = [];
  }

  addReservation(customer){
    this._reservations.push(customer)
  }
}
// We added a new function for it whether to make priority reservation, changed to
class Book{
  constructor(){
    this._reservations = [];
  }

  addReservation(customer){
    this.zz_addReservation(customer,false);
  }

  zz_addReservation(customer,isPriority){
    assert(isPriority === true || isPriority === false);
    this._reservations.push(customer); }}Copy the code

6. Encapsulate variables

In refactoring, you are essentially tweaking elements in your program, and functions are relatively easy to tweak by changing calls, renaming, and modifying parameters. For data, it’s a bit trickier. You need to consider the extent to which the data can be accessed, and you usually use functional access to reorganize the data. This method is referred to as “encapsulated data.”

The functions of encapsulated data include: a clear observation point for monitoring changes and usage of data; You can easily add validation or subsequent logic when the data is modified.

All mutable data can be encapsulated as data accessed through a function as long as its scope extends beyond a single function. That’s because the larger the scope of the data, the greater the impact of data modification, and the more prone to coupling. For immutable data, such operations as data update and validation are not required, and can be simply copied.

Typically, create wrapper functions to access and update variable values, and then perform static checks to modify the code that uses the variable one by one and modify it to call the appropriate wrapper function. Tests are required after each replacement. Limit the visibility of variables through data encapsulation.

Code before encapsulation:

let defaultOwner = {
  firstName:"Wen".lastName:"Bo"
}
// If we want to do the code to view and modify the data, it looks like this
spaceship.owner = defaultOwner;
defaultOwner = {
  firstName:"Liu".lastName:"Yichuan"
}
let defaultOwnerData = {
  firstName:"Wen".lastName:"Bo"
}
Copy the code

Code after data encapsulation:

let defaultOwnerData = {
  firstName:"Wen".lastName:"Bo"
}
// If we want to implement multiple operations and use the same operation
export function defaultOwner(){
  return new Person(defaultOwnerData);
}

export function setDefaultOwner(arg){
  defaultOwnerData = arg;
}

class Person{
  constructor(data){
    this._firstName = data.firstName;
    this._lastName = data.lastName;
  }
  get firstName() {return this._firstName;
  }
  get lastName() {return this._lastName; }}Copy the code

7. Rename variables

It’s easy to pick variable names that make sense:

let a = height * width;
/ / to
let area = height * width;
Copy the code

8. Introduce parameter objects

Essentially, it’s replacing a long list of hash parameters with a parameter object with a data structure. As follows:

function carculateVolume(length, width, height){
  returnLength * width * height; }Copy the code

Can be modified to:

const cuboid = {
  width:10.height:20.length:30
}

function carculateVolume(cuboid){
  returnCuboid. Length * cuboid. Width * cuboid in height; }Copy the code

9. Function group synthesis class

As you know, classes are the basic construction of most modern programming languages, the ability to bind data and functions together in an environment that exposes some of them to use. In practice, when you find a set of functions closely tied to the same piece of data, consider encapsulating them as separate classes.

The original code

function base(reading){}
function textbleCharge(reading){}
function calculateBaseCharge(reading){}
Copy the code

Refactoring code:

class Reading{
  constructor(){}
  base(){}
  textableCharge(){}
  calculateBaseCharge(){}}Copy the code

10. Function combination transformation

Personally, I prefer the combination of functions, and the result of the combination of functions is still a function.

Original code:

function base(reading){}
function textableCharge(reading){}
Copy the code

Refactoring code:

function enrichReading(reading){
  const aReading = _.cloneDeep(reaading);
  aReading.baseCharge = base(aReading);
  aReading.textableCharge = textableCharge(aReading);
  return aReading;
}
Copy the code

11. The split phase

When you see a piece of code doing two different things at the same time, you can break it up into separate modules. Simply, you can divide large chunks of code into two phases of sequential execution.

function priceOrder(product, quantity, shipingMethod){
  const basePrice = product.basePrice * quantity;
  const discount = Math.max(quantity - product.discountThreshold,0)
    * product.basePrice * product.discountRate;
  const shippingPerCase = basePrice > shipingMethod.discountThreshold 
    ? shipingMethod.discountFee : shipingMethod.feePerCase;
  const shippingCost = quantity * shippingPerCase; 
  const price = basePrice  - discount + shippingCost;
  return price;
}
Copy the code

Refactoring code:

function priceOrder(product, quantity, shipingMethod){
  const priceData = calculatePricingData(product,quantity);
  return applyShipping(priceData, shippingMethod);
  
  
}

function calculatePricingData(product,quantity){
  const basePrice = product.basePrice * quantity;
  const discount = Math.max(quantity - product.discountThreshold,0)
    * product.basePrice * product.discountRate;
  return {basePrice,quantity,discount}
}

function applyShipping(priceData, shippingMethod){
  const shippingPerCase = priceData.basePrice > shippingMethod.discountThreshold 
    ? shippingMethod.discountFee : shippingMethod.feePerCase;
  const shippingCost = priceData.quantity * shippingPerCase; 
  return priceData.basePrice  - priceData.discount + shippingCost;
}
Copy the code

summary

This article covers 11 common, simple refactoring tricks that can help make your code tasteful, clean, and tasteless.

Refer to the article

Refactoring — Improving the Design of Existing Code (2nd edition)

Write in the last

Thank you for reading, I will continue to share with you more excellent articles, this article reference a large number of books and articles, if there are mistakes and mistakes, hope to correct.

More latest articles please pay attention to the author nuggets a sichuan firefly account and the public number front gravitation.