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

简介:



前言


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


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


一、技术债务

何为技术债务?

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

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

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


二、设计的坏味道

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

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

  • 可理解性
    代码理解起来的难易程度

  • 可修改性
    在修改既有功能时,不会导致连锁反应。

  • 可扩展性
    支持新功能,不会导致连锁反应

  • 可重用性
    可以在代码的其他地方引用其一块代码

  • 可测试性
    项目要能够支持单元测试

  • 可靠性
    在正确的实现了功能的同时,也能够考虑各种异常情况如何容错


设计坏味的分类


抽象型坏味道

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(),这种情况下就属于不完整的抽象。


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

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更合适。


三、小结

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



来源:中生代技术

原文链接


相关文章
|
Ubuntu Linux 网络安全
在Linux上安装软件有多种方法
在Linux上安装软件有多种方法
694 64
|
存储 JSON 区块链
【HarmonyOS NEXT开发——ArkTS语言】购物商城的实现【合集】
HarmonyOS应用开发使用@Component装饰器将Home结构体标记为一个组件,意味着它可以在界面构建中被当作一个独立的UI单元来使用,并且按照其内部定义的build方法来渲染具体的界面内容。txt:string定义了一个名为Data的接口,用于规范表示产品数据的结构。src:类型为,推测是用于引用资源(可能是图片资源等)的一种特定类型,用于指定产品对应的图片资源。txt:字符串类型,用于存放产品的文字描述,比如产品名称等相关信息。price:数值类型,用于表示产品的价格信息。
618 5
|
存储 缓存 前端开发
Web端IM聊天消息该不该用浏览器本地存储?一文即懂!
鉴于目前浏览器技术的进步(主要是HTML5的普及),在Web网页端IM聊天应用的技术选型阶段,很多开发者都会纠结到底该不该像原生移动端IM那样将聊天记录缓存在浏览器的本地,还是像传统Web端即时通讯那样继续存储在服务端?本文将为你简洁明了地讲清楚浏览器本地存储技术(Web Storage),然后你就知道到底该怎么选择了。
419 1
|
缓存 前端开发 API
探索后端开发中的API设计原则
【10月更文挑战第37天】本文旨在引导读者理解API设计的核心理念,通过简明的语言和直观的示例,揭示如何构建高效、稳定且易于维护的后端接口。我们将深入浅出地探讨RESTful API的设计规范,并通过一个简易的代码样例,展示如何在实战中应用这些原则。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的参考和启示。
R语言错误处理与调试:如何高效调试R代码
【8月更文挑战第28天】调试R代码是一项需要不断练习和提高的技能。通过理解常见的错误类型、使用`traceback()`查看错误路径、逐步执行代码、利用`tryCatch()`捕获和处理错误、设置更严格的警告级别、利用RStudio的调试工具以及编写可复现的示例,你可以更加高效地调试R代码,并快速解决遇到的问题。
1216 3
|
网络架构
静态路由是什么?
【8月更文挑战第18天】 静态路由是什么?
619 1
|
数据可视化 前端开发 开发者
花样玩转“所见即所得”的可视化开发UI
【7月更文挑战第12天】WYSIWYG)的可视化开发UI带来的便利与创新: 降低开发门槛: 即使无编程基础也能通过直观操作快速构建界面。 提高开发效率: 实时预览减少代码与预览间的频繁切换。 促进团队协作: 设计师与开发者可在同一界面交流修改。 增加创意实现: 自由尝试布局、颜色与交互方式以验证想法。 此类工具(如Adobe XD、Figma、Sketch等)正变革软件开发方式,带来更高效、具创意及易操作的体验。
462 3
|
canal Kubernetes 负载均衡
在K8S中,优先优选哪个CNI插件?为何使用该插件?
在K8S中,优先优选哪个CNI插件?为何使用该插件?
|
监控 安全 数据安全/隐私保护
云端智控:智能监控系统的新时代
云上智能监控系统作为一项重要的技术手段,在保障公共安全、提升生产效率等方面发挥着越来越重要的作用。尽管还面临着一些挑战,但随着技术的不断进步和完善,智能监控系统将更加智能化、人性化。未来,我们可以期待更多的技术创新和应用模式出现,让智能监控系统成为智慧城市中不可或缺的一部分。
|
缓存 网络协议 算法