: Notebook: This article is filed under “blog”
Translate: sourcemaking.com/refactoring…
This group of bad bouts means that groups of classes, functions, or fields in code are not properly organized, they are simply piled up. This type of problem is usually not obvious in the early days of the code, but accumulates as the code grows in size (especially if no one is trying to root them out).
Basic type paranoia
Primitive Obsession
- Use primitive types instead of small objects for simple tasks (such as currency, ranges, telephone number strings, and so on).
- Encode information using constants (such as a constant to reference administrator permissions)
USER_ADMIN_ROLE = 1
).- Use string constants as field names in arrays.
Question why
Like most other bad smells, base type paranoia was born when classes were first built. You might start with a few fields, but as you represent more and more features, you get more and more basic data type fields.
Primitive types are often used to represent the type of a model. You have a set of numbers or strings that represent an entity.
Another scenario: in the simulation scenario, a large number of string constants are used to index arrays.
The solution
Most programming languages support both basic data types and structural types (classes, constructs, and so on). Structural types allow programmers to organize basic data types to represent a model of something.
Basic data types can be thought of as building blocks of organization types. Once the number of basic data types is scaled up, organizing them together makes it easier to manage the data.
- If you have a large number of basic datatype fields, it is possible to organize some of the fields that are logically related into a class. Further, the methods associated with this data are moved into the class as well. To achieve this goal, try
Replace Type Code with Class
。 - Can be used if the value of the base datatype field is the parameter for the method
Introduce Parameter Object
或Preserve the Whole Object
。 - This can be used if the data value you want to replace is a type code that does not affect behavior
Replace Type Code with Class
I’m going to replace it. If you have a conditional expression associated with a type code, use itReplace Type Code with Subclass
或Replace Type Code with State/Strategy
To be processed. - Use this if you find yourself picking data from an array
Replace Array with Object
。
earnings
- The code is much more flexible thanks to the use of objects instead of primitive data types.
- Code becomes more readable and more organized. Special data can be manipulated centrally instead of dispersing as before. No more guessing about what these strange constants mean and why they’re in the array.
- It’s easier to find duplicate code.
Reconstruction method Description
Replace Type Code with Class
The problem
There is a numeric type code in the class, but it does not affect the behavior of the class.
To solve
Replace the numeric type code with a new class.
Introduce Parameter Object
The problem
Certain parameters naturally come together.
To solve
Replace these parameters with an object.
Preserve the Whole Object
The problem
You take values from an object and use them as arguments to a function call.
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
Copy the code
To solve
Pass the entire object instead.
boolean withinPlan = plan.withinRange(daysTempRange);
Copy the code
Replace Type Code with Subclass
The problem
You have an immutable type code that affects the behavior of the class.
To solve
Replace the type code with a subclass.
Replace Type Code with State/Strategy
The problem
You have a type code that affects the behavior of the class, but you can’t eliminate it through inheritance.
To solve
Replace type codes with state objects.
Replace Array with Object
The problem
You have an array where each element represents something different.
String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";
Copy the code
To solve
Replace an array with an object. Each element in the array is represented by a field.
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
Copy the code
Data mud pie
4. Data Clumps
Sometimes, different parts of the code contain the same set of variables (for example, parameters to connect to a database). These bundled data should have their own objects.
Question why
Often, data blobs arise because of poor programming structures or “copy-and-paste programming.”
A good way to tell if it’s a data blob: Delete one of the many items. Does that make other data meaningless? If they no longer make sense, this is a clear signal that you should create a new object for them.
The solution
- First find out where these data appear in the form of fields and apply them
Extract Class
Refine them into a separate object. - If the data blob appears in the parameter column of the function, apply
Introduce Parameter Object
Organize them into a class. - If part of the data blob appears in another function, consider applying it
Preserve the Whole Object
Pass the entire data object into the function. - Take a look at the code that uses these fields and maybe move them into a data class is a good idea.
earnings
- Improve code readability and organization. Operations on special data can be processed centrally rather than dispersively as before.
- Reduce code.
When to ignore
- Sometimes passing the entire object as an argument to a function for some of the data in the object can create undesirable dependencies between the two classes, in which case the entire object may not be passed.
Reconstruction method Description
Extract Class
The problem
A class does more than one thing.
To solve
Create a new class and move related fields and functions from the old class to the new class.
Introduce Parameter Object
The problem
Certain parameters naturally come together.
To solve
Replace these parameters with an object.
Preserve the Whole Object
The problem
You take values from an object and use them as arguments to a function call.
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
Copy the code
To solve
Pass the entire object instead.
boolean withinPlan = plan.withinRange(daysTempRange);
Copy the code
Too much class
Large classes
A class with too many fields, functions, lines of code.
Question why
Classes usually start small but swell as the program grows.
Similar to overly long functions, programmers often find it easier to add new features to an existing class than to create a new one.
The solution
There is an important principle in design patterns: the single responsibility principle. A class should be given only one responsibility. If it takes on too much responsibility, consider reducing it.
- This can be used if part of the behavior in a large category can be distilled into a separate component
Extract Class
. - This can be used if part of the behavior in a large category can be implemented in different ways or used in special scenarios
Extract Subclass
. - This can be used if it is necessary to provide a set of actions and behaviors for the client
Extract Interface
. - If your large class is a GUI class, you may need to move the data and behavior into a separate domain object. You may want to keep duplicate data on both sides and keep them in sync.
Duplicate Observed Data
I can tell you what to do.
earnings
- Refactoring a class that is too large saves the programmer from having to remember a large number of attributes in a class.
- In most cases, splitting classes that are too large avoids duplication of code and functionality.
Reconstruction method Description
Extract Class
The problem
A class does more than one thing.
To solve
Create a new class and move related fields and functions from the old class to the new class.
Extract Subclass
The problem
There are features in a class that are only used in specific scenarios.
To solve
Create a subclass and put features in it for a particular scenario.
Extract Interface
The problem
Multiple clients use partially the same function in a class. Another scenario is that some functions in two classes are the same.
To solve
Move the same partial function to the interface.
Duplicate Observed Data
The problem
If the data stored in the class is responsible for the GUI.
To solve
A better approach is to put the data responsible for the GUI into a separate class to ensure the connection and synchronization between the GUI data and the domain classes.
Too long to function
Long Method
A function contains too many lines of code. As a general rule, when any function is longer than 10 lines, you can think about whether it’s too long. In principle, the number of lines in a function should not exceed 100.
Cause of the problem
Often, creating a new function is more difficult than adding functionality to an existing one. Most people think, “I’m adding two lines of code, so creating a new function is a storm in a teacup.” So, Zhang SAN plus two lines, Li Si plus two lines, Wang Wu plus two lines… The function gets bigger and bigger, and eventually it’s like a pot of paste, and no one can really understand it anymore. So people are even more afraid to touch the function, only to add code in a vicious cycle. So, if you see a function that’s more than 200 lines long, it’s usually cobbled together by multiple programmers.
To solve the function
A good tip: Look for comments. There are several reasons to add comments: the code logic is obscure or complex; This code is functionally independent; Special treatment. A comment at the front of the code reminds you that you can replace the code with a function, and that you can name the function based on the comment. If the function has a name that describes it properly, you don’t need to look at how the internal code is actually implemented. Even if it’s a single line of code, if it needs to be commented out, it’s worth distilling it into a separate function.
- To slim down a function, use
Extract Method
. - Can be used if local variables and parameters interfere with the refining function
Replace Temp with Query
.Introduce Parameter Object
或Preserve the Whole Object
。 - If the first two do not help, you can pass
Replace Method with Method Object
Try to move the entire function into a separate object. - Conditional expressions and loops are also often signals for refinement. For conditional expressions, you can use
Decompose Conditional expressions
. As for loops, they should be usedExtract Method
Extract loops and the code inside them into separate functions.
earnings
- In all types of object-oriented code, classes with shorter and sharper functions tend to have longer lifespans. The longer a function is, the harder it is to understand and maintain.
- In addition, excessively long functions often contain repetitive code that is hard to find.
performance
Does increasing the number of functions affect performance, as many claim? In almost the vast majority of cases, the effect is negligible, so don’t worry. In addition, now that you have clear and readable code, it will be easier to find really effective functions to reorganize your code and improve performance when you need them.
Reconstruction method Description
Extract Method
The problem
You have a piece of code that you can put together.
void printOwing(a) {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
Copy the code
To solve
Move this code into a new function, replacing the old code with a call to the function.
void printOwing(a) {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
Copy the code
Replace Temp with Query
The problem
Place the result of an expression in a local variable and use it in your code.
double calculateTotal(a) {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98; }}Copy the code
To solve
Move the entire expression into a separate function and return the result. Use query functions instead of variables. You can merge the new functions in other functions if necessary.
double calculateTotal(a) {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98; }}double basePrice(a) {
return quantity * itemPrice;
}
Copy the code
Introduce Parameter Object
The problem
Certain parameters naturally come together.
To solve
Replace these parameters with an object.
Preserve the Whole Object
The problem
You take values from an object and use them as arguments to a function call.
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
Copy the code
To solve
Pass the entire object instead.
boolean withinPlan = plan.withinRange(daysTempRange);
Copy the code
Replace Method with Method Object
The problem
You have an excessively long function whose local variables are so intertwined that you cannot apply the Extract Method.
class Order {
/ /...
public double price(a) {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
/ /...}}Copy the code
To solve
Move the function to a separate class so that local variables are fields of that class. You can then split the function into multiple functions in this class.
class Order {
/ /...
public double price(a) {
return new PriceCalculator(this).compute(); }}class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// copy relevant information from order object.
/ /...
}
public double compute(a) {
// long computation.
/ /...}}Copy the code
Decompose Conditional expressions
The problem
You have complex conditional expressions.
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
Copy the code
To solve
Decompose the entire conditional expression into several functions based on conditional branching.
if (notSummer(date)) {
charge = winterCharge(quantity);
}
else {
charge = summerCharge(quantity);
}
Copy the code
Too long parameter column
Long Parameter List
A function has more than 3 or 4 input parameters.
Question why
Excessively long parameter columns can occur when multiple algorithms are combined into a single function. An input parameter in a function can be used to control which algorithm is ultimately chosen for execution.
An excessively long parameter column can also be a byproduct of decoupling dependencies between classes. For example, the code used to create a specific object needed in a function has been moved from the function to the code calling the function, but the created object is passed to the function as an argument. As a result, the original class no longer knows the relationships between objects, and dependencies have been reduced. But if you create these objects, each of them will need its own arguments, which means that the argument column is too long.
Too long a parameter column is hard to understand, and too many parameters can be inconsistent, hard to use, and have to be modified when more data is needed.
The solution
- If making a request to an existing object can replace a parameter, then you should use it
Replace Parameters with Methods
. In this case, “existing object” may be a field in the class of the function, or it may be another parameter. - You can also use
Preserve the Whole Object
Collect a bunch of data from the same object and replace it with that object. - If some data does not have proper object ownership, it can be used
Introduce Parameter Object
Make a “parameter object” for them.
earnings
- Easier to read, shorter code.
- Refactoring may expose duplicates of code that were previously unnoticed.
When to ignore
- There is one important exception: sometimes you obviously don’t want to create some kind of dependency between the “called object” and the “larger object”. It also makes sense to separate the data from the object and use it as a parameter. But notice the cost. If the parameter column is too long or changes too frequently, you may need to rethink your dependency structure.
Reconstruction method Description
Replace Parameters with Methods
The problem
The object calls a function and passes the result as an argument to another function. The function that accepts this parameter can itself call the previous function.
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
Copy the code
To solve
Let the argument receiver remove the argument and call the previous function directly.
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
Copy the code
Preserve the Whole Object
The problem
You take values from an object and use them as arguments to a function call.
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
Copy the code
To solve
Pass the entire object instead.
boolean withinPlan = plan.withinRange(daysTempRange);
Copy the code
Introduce Parameter Object
The problem
Certain parameters naturally come together.
To solve
Replace these parameters with an object.
Further reading
- Bad code smell and refactoring
- Code bloat with bad code smell
- Bad taste of code abuse of object orientation
- Barriers to code bad taste change
- Code that doesn’t necessarily smell bad
- Coupling of bad code smells
The resources
- Refactoring – Improving the design of existing code – by Martin Fowler
- sourcemaking.com/refactoring