:notebook: 本文已归档到:「blog」java
翻译自:https://sourcemaking.com/refactoring/smells/dispensablesgit
非必要的(Dispensables)这组坏味道意味着:这样的代码无关紧要,它的存在反而影响总体代码的整洁和可读性。程序员
冗余类(Lazy Class)github
理解和维护老是费时费力的。若是一个类不值得你花费精力,它就应该被删除。算法
也许一个类的初始设计是一个功能彻底的类,然而随着代码的变迁,变得没什么用了。 又或者类起初的设计是为了支持将来的功能扩展,然而却一直未派上用场。设计模式
将类内联化(Inline Class)
来干掉。折叠继承体系(Collapse Hierarchy)
。问题bash
某个类没有作太多事情。框架
解决ide
将这个类的全部特性搬移到另外一个类中,而后移除原类。函数
问题
超类和子类之间无太大区别。
解决
将它们合为一体。
夸夸其谈将来性(Speculative Generality)
存在未被使用的类、函数、字段或参数。
有时,代码仅仅为了支持将来的特性而产生,然而却一直未实现。结果,代码变得难以理解和维护。
折叠继承体系(Collapse Hierarch)
。将类内联化(Inline Class)
消除。内联函数(Inline Method)
消除。移除参数(Remove Parameter)
消除。问题
超类和子类之间无太大区别。
解决
将它们合为一体。
问题
某个类没有作太多事情。
解决
将这个类的全部特性搬移到另外一个类中,而后移除原类。
问题
一个函数的本体比函数名更清楚易懂。
class PizzaDelivery {
//...
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
}
复制代码
解决
在函数调用点插入函数本体,而后移除该函数。
class PizzaDelivery {
//...
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
}
复制代码
问题
函数本体再也不须要某个参数。
解决
将该参数去除。
纯稚的数据类(Data Class)
指的是只包含字段和访问它们的 getter 和 setter 函数的类。这些仅仅是供其余类使用的数据容器。这些类不包含任何附加功能,而且不能对本身拥有的数据进行独立操做。
当一个新建立的类只包含几个公共字段(甚至可能几个 getters / setters)是很正常的。可是对象的真正力量在于它们能够包含做用于数据的行为类型或操做。
封装字段(Encapsulated Field)
来隐藏字段的直接访问方式。封装集合(Encapsulated Collection)
把它们封装起来。搬移函数(Move Method)
把那些调用行为搬移到 纯稚的数据类(Data Class)
来。若是没法搬移这个函数,就运用 提炼函数(Extract Method)
产生一个可搬移的函数。移除设置函数(Remove Setting Method)
和 隐藏函数(Hide Method)
。问题
你的类中存在 public 字段。
class Person {
public String name;
}
复制代码
解决
将它声明为 private,并提供相应的访问函数。
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String arg) {
name = arg;
}
}
复制代码
问题
有个函数返回一个集合。
解决
让该函数返回该集合的一个只读副本,并在这个类中提供添加、移除集合元素的函数。
问题
你的程序中,有个函数与其所驻类以外的另外一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中创建一个有着相似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数彻底移除。
问题
你有一段代码能够组织在一块儿。
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);
}
复制代码
问题
类中的某个字段应该在对象建立时被设值,而后就再也不改变。
解决
去掉该字段的全部设值函数。
问题
有一个函数,历来没有被其余任何类用到。
解决
将这个函数修改成 private。
过多的注释(Comments)
注释自己并非坏事。可是经常有这样的状况:一段代码中出现长长的注释,而它之因此存在,是由于代码很糟糕。
注释的做者意识到本身的代码不直观或不明显,因此想使用注释来讲明本身的意图。这种状况下,注释就像是烂代码的除臭剂。
最好的注释是为函数或类起一个恰当的名字。
若是你以为一个代码片断没有注释就没法理解,请先尝试重构,试着让全部注释都变得多余。
提炼变量(Extract Variable)
将表达式切分为易理解的子表达式。提炼函数(Extract Method)
。函数更名(Rename Method)
来为函数起一个能够自解释的名字。引入断言(Introduce Assertion)
。注释有时候颇有用:
问题
你有个难以理解的表达式。
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
}
}
复制代码
问题
你有一段代码能够组织在一块儿。
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);
}
复制代码
问题
函数的名称未能恰当的揭示函数的用途。
class Person {
public String getsnm();
}
复制代码
解决
修改函数名。
class Person {
public String getSecondName();
}
复制代码
问题
某一段代码须要对程序状态作出某种假设。
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)
重复代码堪称为代码坏味道之首。消除重复代码老是有利无害的。
重复代码一般发生在多个程序员同时在同一程序的不一样部分上工做时。因为他们正在处理不一样的任务,他们可能不知道他们的同事已经写了相似的代码。
还有一种更隐晦的重复,特定部分的代码看上去不一样但实际在作同一件事。这种重复代码每每难以找到和消除。
有时重复是有目的性的。当急于知足 deadline,而且现有代码对于要交付的任务是“几乎正确的”时,新手程序员可能没法抵抗复制和粘贴相关代码的诱惑。在某些状况下,程序员只是太懒惰。
提炼函数(Extract Method)
提炼出重复的代码,而后让这两个地点都调用被提炼出来的那段代码。提炼函数(Extract Method)
,而后对被提炼出来的函数运用 函数上移(Pull Up Method)
,将它推入超类。构造函数本体上移(Pull Up Constructor Body)
。塑造模板函数(Form Template Method)
得到一个 模板方法模式(Template Method) 。替换算法(Substitute Algorithm)
将其余函数的算法替换掉。提炼超类(Extract Superclass)
,以便为维护全部先前功能的这些类建立一个超类。提炼类(Extract Class)
,并在另外一个类中使用这个新的组件。合并条件表达式(Consolidate Conditional Expression)
将这些操做合并为单个条件,并运用 提炼函数(Extract Method)
将该条件放入一个名字容易理解的独立函数中。合并重复的条件片断(Consolidate Duplicate Conditional Fragments)
将它们都存在的代码片断置于条件表达式外部。问题
你有一段代码能够组织在一块儿。
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);
}
复制代码
问题
有些函数,在各个子类中产生彻底相同的结果。
解决
将该函数移至超类。
问题
你在各个子类中拥有一些构造函数,它们的本体几乎彻底一致。
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;
}
//...
}
复制代码
问题
你有一些子类,其中相应的某些函数以相同的顺序执行相似的操做,但各个操做的细节上有所不一样。
解决
将这些操做分别放进独立函数中,并保持它们都有相同的签名,因而原函数也就变得相同了。而后将原函数上移至超类。
注:这里只提到具体作法,建议了解一下模板方法设计模式。
问题
你想要把某个算法替换为另外一个更清晰的算法。
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 "";
}
复制代码
问题
两个类有类似特性。
解决
为这两个类创建一个超类,将相同特性移至超类。
问题
某个类作了不止一件事。
解决
创建一个新类,将相关的字段和函数从旧类搬移到新类。
问题
你有一系列条件分支,都获得相同结果。
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
//...
}
复制代码
问题
在条件表达式的每一个分支上有着相同的一段代码。
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();
复制代码