同步操作将从 turnon/blog 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
title: 代码坏味道之非必要的
date: 2018-10-13 22:48:00
categories:
- 设计
- 重构
tags:
- 设计
- 重构
- 代码的坏味道
permalink: /pages/47acb5/
翻译自:https://sourcemaking.com/refactoring/smells/dispensables
非必要的(Dispensables)这组坏味道意味着:这样的代码可有可无,它的存在反而影响整体代码的整洁和可读性。
冗余类(Lazy Class)
理解和维护总是费时费力的。如果一个类不值得你花费精力,它就应该被删除。
也许一个类的初始设计是一个功能完全的类,然而随着代码的变迁,变得没什么用了。 又或者类起初的设计是为了支持未来的功能扩展,然而却一直未派上用场。
将类内联化(Inline Class)
来干掉。折叠继承体系(Collapse Hierarchy)
。问题
某个类没有做太多事情。
解决
将这个类的所有特性搬移到另一个类中,然后移除原类。
问题
超类和子类之间无太大区别。
解决
将它们合为一体。
夸夸其谈未来性(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();
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。