首页 > 代码库 > 由学习《软件设计重构》所想到的代码review(一)

由学习《软件设计重构》所想到的代码review(一)

技术分享

前言

对于一个程序猿来讲怎样来最直接的来衡量他的技术能力和产出呢?我想最直观的作法是看他的代码编写能力,就拿我常常接触的一些程序猿来看,他们买了非常多技术重构类书籍。可是看完后代码编写能力并没有显著提高。有人说可以用代码review工具啊,可是像市面上的这些代码review工具。仅仅能帮助我们解决表面的bug和规范点。还无法帮助我们发现更深层次的设计问题。

以下我将结合《软件设计重构》这本书谈谈在进行代码review的时候。须要关注的哪些点。

一、技术债务

何为技术债务?

技术债务是有意或无意的做出错误的或非最优的设计决策所引发的俩务

我们在代码review的时候。常常碰到一些实现有瑕疵的方案。然后对方说由于时间太紧急暂时採用的方案,等第二期项目将其完好,于是往往第二期以后这个暂时方案就非常难再去触动了,时间越长代码冗余越大,越难去做改动。于是这就是典型的技术债务。债务越积越多,最后仅仅能又一次彻底重构项目才干解决这个问题,这也叫做技术破产。

于是我们的做法有一个债务管理系统,在代码review的时候。会将这些债务或者暂时方案录入到系统中并制订偿还日期,那么兴许债务顺利偿还后。更改系统状态。否遭遇一直没有偿还的。系统将以邮件的方式提醒,债务累积到一种数目后将与绩效挂钩考核。

二、设计的坏味道

前面仅仅是从债务的角度说明了所带来的危害。事实上引起技术债务的一个非常重要的原因是对设计坏味和重构认识不足。

我们从设计的角度来看代码时,要遵循六要素:

  • 可理解性
    代码理解起来的难易程度
  • 可改动性
    在改动既有功能时,不会导致连锁反应。
  • 可扩展性
    支持新功能,不会导致连锁反应
  • 可重用性
    可以在代码的其它地方引用其一块代码
  • 可測试性
    项目要可以支持单元測试
  • 可靠性
    在正确的实现了功能的同一时候,也可以考虑各种异常情况怎样容错

2.1、设计坏味的分类

技术分享

2.1.1 抽象型坏味道

技术分享

1、缺失抽象
举例说明:
* 问题点:
在JDK1.0中方法printStackTrace()以字符串的方式将栈跟踪打印到标准错误流:

public class Throwabe {
    public void printStackTrace();
}

在须要以编程方式訪问栈跟踪元素的客户程序中,必须要编程代码来获取数据,如行号等,由于客户程度依赖这样的字符串格式。JDK设计人员仅仅能在兴许版本号中兼容这样的格式了。

  • 解决方法
public class Throwabe {
    public void printStackTrace();
    public StackTraceElement[] getStackTrace();
}

从Jdk1.4起对JAVA的API进行了改进,StackTraceElement类就是原来设计中缺失的对象,定义例如以下:

public final class StackTraceElement {
    public String getFilename();
    public int getLineNumber();
    public String getClassname();
    ......
}

2、命令式抽象

举比例如以下:

  • 问题点:
    技术分享
    注:当中每一个类都仅仅包含一个方法。这些方法各自是:create、display和copy等,因此存在命令式投象坏味。这样的问题不仅会添加类的数量,还会添加开发和维护工作复杂性。并且将本应内聚的方法进行了不必要的分享。

  • 解决方式
    技术分享
    注:依据高内聚原则。统一归集到一个Report类中。

3、不完整的抽象
抽象未支持全部互补或相关的方法时,将导致不完整的抽象,比方一个抽象的公有接口提供了用于分配资源的initalize()方法。可是却没有提供删除或者回收资源的方法dispose(),这样的情况下就属于不完整的抽象。

一些常见的互补方法对例如以下:

列一 列二 列三 列四
min/max open/close create/destroy get/set
start/stop print/scan first/last begin/end
source/target lock/unlock show/hide up/down
enable/disable acquire/release left/right on/off

供大家參考。

4、多方面的抽象
对象被赋予不止一项职责时,将导致这样的问题。

举比例如以下:
* 问题点
java.util.Calendar类承担了多项职责,不仅提供了日期相关的功能,还提供了与时间有关的功能,存大多方面抽象。由于同一时候支持日期和时间的方法,Calendar类接口非常大且难为理解。在JDK7中,java.util.Calendar类包含了2825行代码,有67个方法和71个字段。

  • 解决方式
    对于Calendar类,一种可能的重构是,将Calendar类与时间相关的功能提取到新类Time中,并将相关方法和字段移到新提取的类中。在Java8中引入了一些支持日期和时间的新类,这些类位于java.time中。

5、不必要的抽象
举比例如以下:
* 问题点:

public interface WindowConstants {
    public static final int DO_NOTHING_ON_CLOSE=0;
    public static final int HIDE_ON_CLOSE=1;
}

注:这个接口是典型的常量接口javax.swing.WindowConstants,为啥用接口来存储常量。由于首先枚举是jdk1.5才引入的。其次通过接口中定义常量。可方便类通过继承而不是托付来使用它们,由于通过实现接口。类可方便的訪问接口中的常量,为什么不使用类来存储常量呢。由于接口支持多继承。
那么接口这样定义常量有哪些问题呢?
A、派生类被无关的常量影响。
B、这些常量属于实现细节。通过接口暴露它们违反封装原则。
C、接口中存储常量。改动它们会影响其它使用者。

  • 解决方式
    将WindowsConstants定义为枚举,直接使用。

6、反复的抽象
依据DRY原则规定:对于每一个技术点。系统中都仅仅能有一个明白的表示。
导致反复抽象的原因有:
A、复制-粘贴编程手法
B、即兴维护
C、交流不畅

举例说明:
* 问题点:
java.util.Date和其派生类java.sql.Date同名,这两个类位于不同的包中,编译器不会由于它们同名而报错。但这让使用者一头雾水。这样将导致二义性。

  • 解决方式
    将Date名称前面加上用途限定语。比方java.sql.SQLDate更合适。

三、小结

由于内容太多,我们在第一部分仅仅介绍抽象型设计原则,接下来我将继续写模化型设计原则,封装型设计原则和层次化设计原则,与大家深入讨论从设计角度来看,什么样的代码才是真正的好代码。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

由学习《软件设计重构》所想到的代码review(一)