气味代码与重构
整理自:https://sourcemaking.com/refactoring/smells
类型一:Bloaters
代码、方法和类增长到庞大的量。
Long Method
一般来说,长于10行的方法就应该考虑是否需要重构。如果方法中有些地方需要注释,就应该考虑是否把这些代码放入新方法内,即使该代码只有一行,原因是如果方法名是描述性的,就不需要阅读进行实际操作的源码。
解决方案:
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Replace Temp with Query
例如:
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
改为
double calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98;
}
}
double basePrice() {
return quantity * itemPrice;
}
- Introduce Parameter Object
- Preserve Whole Object
例如:
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
改为
boolean withinPlan = plan.withinRange(daysTempRange);
- Replace Method with Method Object
例如:
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
改为
class Order {
//...
public double price() {
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() {
// long computation.
//...
}
}
- Decompose Conditional
例如:
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
改为
if (isSummer(date)) {
charge = summerCharge(quantity);
}
else {
charge = winterCharge(quantity);
}
Large Class
一个包含了很多字段/方法/代码行的类。
解决方案:
- Extract Class
When one class does the work of two, awkwardness results.
- Extract Subclass
A class has features that are used only in certain cases.
- Extract Interface
Multiple clients are using the same part of a class interface. Another case: part of the interface in two classes is the same.
- Duplicate Observed Data
Is domain data stored in classes responsible for the GUI?
Primitive Obsession
用primitive field而不是small object代表简单事物;用常数来表示信息等。
解决方案:
- Replace Data Value with Object
A class (or group of classes) contains a data field. The field has its own behavior and associated data.
- Introduce Parameter Object
- Preserve Whole Object
例如:
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
改为
boolean withinPlan = plan.withinRange(daysTempRange);
- Replace Type Code with Class
A class has a field that contains type code. The values of this type are not used in operator conditions and do not affect the behavior of the program.
- Replace Type Code with Subclasses
You have a coded type that directly affects program behavior (values of this field trigger various code in conditionals).
- Replace Array with Object
例如:
String[] row = new String[2];
row[0] = "Liverpool";
row[1] = "15";
改为
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
Long Parameter List
一个方法包含了三个或四个以上的参数。
解决方案:
- Replace Parameter with Method Call
例如:
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
改为
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
- Preserve Whole Object
例如:
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
改为
boolean withinPlan = plan.withinRange(daysTempRange);
- Introduce Parameter Object
Data Clumps
不同部分的代码都包含了相同的一堆变量(即数据块)。如果要确定某些数据是否为数据块,只需删除其中一个数据值,然后查看其他值是否仍然有意义。如果不是,表明这组变量应该组合成一个对象。
- Extract Class
When one class does the work of two, awkwardness results.
- Introduce Parameter Object
- Preserve Whole Object
例如:
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
改为
boolean withinPlan = plan.withinRange(daysTempRange);
类型二:Object-Orientation Abusers
对OOP法则的不完整或是不正确的运用。
Switch Statements
存在复杂的switch操作或是一系列if语句。
解决方案:
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Move Method
A method is used more in another class than in its own class.
- Replace Type Code with Subclasses
You have a coded type that directly affects program behavior (values of this field trigger various code in conditionals).
- Replace Conditional with Polymorphism
例如:
class Bird {
//...
double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new RuntimeException("Should be unreachable");
}
}
改为
abstract class Bird {
//...
abstract double getSpeed();
}
class European extends Bird {
double getSpeed() {
return getBaseSpeed();
}
}
class African extends Bird {
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
double getSpeed() {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.getSpeed();
- Replace Parameter with Explicit Methods
例如:
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
改为
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
- Introduce Null Object
例如:
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
改为
class NullCustomer extends Customer {
boolean isNull() {
return true;
}
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ?
order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
Temporary Field
字段只在某些情况下被赋值,而在剩下的情况下,都不会被赋值。
解决方案:
- Extract Class
When one class does the work of two, awkwardness results.
- Replace Method with Method Object
例如:
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
改为
class Order {
//...
public double price() {
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() {
// long computation.
//...
}
}
- Introduce Null Object
例如:
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
改为
class NullCustomer extends Customer {
boolean isNull() {
return true;
}
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ?
order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
Refused Bequest
如果子类仅使用从其父项继承的某些方法和属性,则说明使用这种层次结构是不合适的。
解决方案:
- Replace Inheritance with Delegation
You have a subclass that uses only a portion of the methods of its superclass (or it’s not possible to inherit superclass data).
- Extract Superclass
You have two classes with common fields and methods.
Alternative Classes with Different Interfaces
两个类的功能几乎一样只是方法名不同。
- Rename Method
The name of a method does not explain what the method does.
- Move Method
A method is used more in another class than in its own class.
- Add Parameter
A method does not have enough data to perform certain actions.
- Parameterize Method
Multiple methods perform similar actions that are different only in their internal values, numbers or operations.
- Extract Superclass
You have two classes with common fields and methods.
类型三:Change Preventers
如果你需要在代码中的某个位置更改某些内容,则必须在其他位置进行许多更改。
Divergent Change
当对某个类进行更改时,你发现自己必须更改许多不相关的方法。
解决方案:
- Extract Class
When one class does the work of two, awkwardness results.
- Extract Superclass
You have two classes with common fields and methods.
- Extract Subclass
A class has features that are used only in certain cases.
Shotgun Surgery
进行任何修改都需要对许多不同的类进行许多小的更改。
解决方案:
- Move Method
A method is used more in another class than in its own class.
- Move Field
A field is used more in another class than in its own class.
- Inline Class
A class does almost nothing and is not responsible for anything, and no additional responsibilities are planned for it.
Parallel Inheritance Hierarchies
每当为类创建子类时,你发现自己需要为另一个类创建子类。
解决方案:
- Move Method
A method is used more in another class than in its own class.
- Move Field
A field is used more in another class than in its own class.
类型四:Dispensables
指毫无意义或是不必要的东西,将其移除会使代码更清晰,更有效,更容易理解。
Comments
方法里面充满了解释性注释。
解决方案:
- Extract Variable
例如:
void renderBanner() {
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
}
改为
void renderBanner() {
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIE && wasInitialized() && wasResized) {
// do something
}
}
解决方案:
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Rename Method
The name of a method does not explain what the method does.
- Introduce Assertion
例如:
double getExpenseLimit() {
// should have either expense limit or a primary project
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}
改为
double getExpenseLimit() {
Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}
Duplicate Code
两个或多个代码片段看起来几乎相同。
解决方案:
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Pull Up Field
Two classes have the same field.
- Pull Up Constructor Body
例如:
class Manager extends Employee {
public Manager(String name, String id, int grade) {
this.name = name;
this.id = id;
this.grade = grade;
}
//...
}
改为
class Manager extends Employee {
public Manager(String name, String id, int grade) {
super(name, id);
this.grade = grade;
}
//...
}
- Form Template Method
Your subclasses implement algorithms that contain similar steps in the same order.
- Substitute Algorithm
例如:
String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")){
return "Don";
}
if (people[i].equals("John")){
return "John";
}
if (people[i].equals("Kent")){
return "Kent";
}
}
return "";
}
改为
String foundPerson(String[] people){
List candidates =
Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i < people.length; i++) {
if (candidates.contains(people[i])) {
return people[i];
}
}
return "";
}
- Extract Superclass
You have two classes with common fields and methods.
- Extract Class
When one class does the work of two, awkwardness results.
- Consolidate Conditional Expression
You have multiple conditionals that lead to the same result or action.
例如:
double disabilityAmount() {
if (seniority < 2) {
return 0;
}
if (monthsDisabled > 12) {
return 0;
}
if (isPartTime) {
return 0;
}
// compute the disability amount
//...
}
改为
double disabilityAmount() {
if (isNotEligableForDisability()) {
return 0;
}
// compute the disability amount
//...
}
解决方案:
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Consolidate Duplicate Conditional Fragments
例如:
if (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}
改为
if (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();
Lazy Class
如果一个类的几乎不会被用上,就应该被删除。
解决方案:
- Inline Class
A class does almost nothing and is not responsible for anything, and no additional responsibilities are planned for it.
- Collapse Hierarchy
You have a class hierarchy in which a subclass is practically the same as its superclass.
Data Class
数据类是指仅包含用于访问它们的字段和粗略方法的类(getter和setter)。这些只是其他类使用的数据的容器。这些类不包含任何其他功能,也不能独立操作它们拥有的数据。
解决方案:
- Encapsulate Field
例如:
class Person {
public String name;
}
改为
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String arg) {
name = arg;
}
}
- Encapsulate Collection
A class contains a collection field and a simple getter and setter for working with the collection.
- Move Method
A method is used more in another class than in its own class.
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Remove Setting Method
The value of a field should be set only when it is created, and not change at any time after that.
- Hide Method
A method is not used by other classes or is used only inside its own class hierarchy.
Dead Code
不再被使用的变量,参数,字段,方法或类。
解决方案:
-
使用好的IDE,通过IDE标注找到没被使用的变量,参数,字段,方法或类。
-
Inline Class
A class does almost nothing and is not responsible for anything, and no additional responsibilities are planned for it.
- Collapse Hierarchy
You have a class hierarchy in which a subclass is practically the same as its superclass.
- Remove Parameter
A parameter is not used in the body of a method.
Couplers
类与类之间过度耦合。
Feature Envy
方法访问另一个对象的数据多于访问自己的数据。
解决方案:
- Move Method
A method is used more in another class than in its own class.
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
Inappropriate Intimacy
一个类使用另一个类的内部字段和方法。
解决方案:
- Move Method
A method is used more in another class than in its own class.
- Move Field
A field is used more in another class than in its own class.
- Extract Class
When one class does the work of two, awkwardness results.
- Hide Delegate
The client gets object B from a field or method of object А. Then the client calls a method of object B.
- Change Bidirectional Association to Unidirectional
You have a bidirectional association between classes, but one of the classes does not use the other’s features.
- Replace Delegation with Inheritance
A class contains many simple methods that delegate to all methods of another class.
Message Chains
在代码中你看到类似于这样的一系列的方法调用: $a->b()->c()->d()
。
解决方案:
- Hide Delegate
The client gets object B from a field or method of object А. Then the client calls a method of object B.
- Extract method
例如:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
改为
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
- Move Method
A method is used more in another class than in its own class.
Middle Man
如果一个类只执行将工作委托给另一个类,那么它就没有存在的必要。
解决方案:
- Remove Middle Man
A class has too many methods that simply delegate to other objects.