软件开发中的“重复”问题,你真的了解吗?

软件开发中的“重复”问题分为上下篇,这里是上篇:

下篇:怎么解决Maven和Spring配置重复的问题?

你好,我是雷威。

你有没有发现,现在大家对于软件开发中的“重复”已经见怪不怪了?而且大部分开发人员并没有意识到“重复”的危害性,它会让软件难以维护。那“重复”问题到底有多严重呢?《代码整洁之道》这本书中说到,“重复可能是软件中一切邪恶的根源”。

“重复”是项目中最常见的代码坏味道,大大降低了代码的可维护性,还存在着许多的隐患:比如有两段重复代码,实现了完全相同的功能,很容易修改了其中一处,却漏改了另一处,导致 Bug。再比如有些代码高度相似,却并不完全相同,实现的功能也有细微的差异,开发人员修改这类代码时,很容易改错,复制粘贴更会把原本有区别的代码改成一样的。

另外,因为重复代码太常见,项目中充斥着大量的坏味道,受“破窗效应”影响,开发者会很自然地产生更多的坏代码,还觉得理所当然。俗话说,常在河边走,哪有不湿鞋的,这些隐患终有一天会造成线上 Bug。

实际上,我通常将重复代码作为衡量项目质量的一个重要指标。在带领团队进行项目重构时,也会花大量时间来消除重复代码。我也经常在思考,为什么会有重复?我总结出了三个原因:
一是开发者没有编写优秀软件的态度;
二是开发者能力不足,不能发现重复问题或者不知道怎么解决;
三是项目工期紧,开发者优先完成任务,确保按时上线。

那么,在具体工作场景中,我们该怎么解决重复问题?我会分两节课来跟你重点讲讲代码重复、代码注释的重复、Maven 配置中的重复和 Spring 配置重复这四种类型的重复问题。希望通过这两节课的学习,你对软件中的重复问题能有更深的理解。

在这节课呢,我们先了解代码重复、代码注释重复这两个问题的解决方法。

代码重复

首先,我们来聊聊日常开发中遇到最多的,也是被讨论最多的重复:代码重复。常见的有重复的常量、重复的方法、重复的代码段、重复的类等,这些重复的代码都实现了相同的逻辑。对于一些简单的由于复制粘贴而产生的重复,可以提取重复代码到一个单独的地方。比如提出公共变量,提取方法,提取到类中。

我发现大部分开发人员都是手动来提取的。其实更好的方式是使用 IDE 来完成这个操作。开发者手动重构一方面是操作复杂,尤其是当待重构代码比较复杂的,比较多的情况下。另一方面,手动提取在某些场景下很容易出错,需要特别小心,比如提取的方法参数比较多,重构后,给方法传递参数时,可能会传错。而使用 IDE 就可以做到又快又准确。开发者选择手动,而不是 IDE 还是因为个人习惯,或 IDE 的重构功能使用较少,希望你以后可以多使用 IDE 提供的重构功能来优化代码。

几乎所有的主流 IDE 都支持 Extract Variable 和 Extract Method 操作,可以自动地提取公共变量和方法。接下来,我通过一个最简单的例子,来说明如何使用 IDEA 的 Extract Method 操作。

假设有这样的两行代码:每行代码都是简单地把两个整数做加法,然后将结果赋值给一个变量。那么就可以使用 Extract Method,提取一个将两数相加的方法。首先,选择其中一行,依次打开菜单 Refactor 》Extract 》Method。在弹出的窗口中输入抽取的方法名。假设取名为 add,这样 IDEA 就会自动为我们生成一个 add 方法,它接收两个整形参数,返回一个整数,并且同时修改代码为调用该 add 方法。你可以自己实际操作一下。

img
图 1:选择待提取的代码段

img
图 2:填写提取方法的信息

img
图 3:消除重复后的代码

这个例子比较简单,我主要是为了说明怎么使用 IDEA 的 Extract Method 功能。而针对某些重复代码,我们可能需要使用一些 OOP 的设计模式,比如模板方法、策略模式等,甚至还需要用到 Java 注解、反射等高级功能,或者使用 Lombok 等类库来消除样板代码。通过模板方法可以将公共代码放在模板类中,通过策略来封装不同的逻辑。利用 Java 注解和反射可以为类添加元数据,来消除类结构性的重复。可以参考 JSON 序列化类库(Jackson 和 Fastjson)中注解的使用。使用 Lombok 来消除 Java 类的 get 和 set 方法。

代码注释的重复

接下来,我们聊聊注释的重复。我们经常调侃两件事情:第一是讨厌写注释,第二是讨厌别人不写注释,可见大家对注释这个问题的重视。虽然现状是大部分项目都缺少注释,但是大家依然会达成一个共识:好的注释可以帮助理解代码。但是你知道吗?为代码写注释也可能会有重复的问题。

关于代码注释有两个原则:第一,不要使用注释来解释代码做了什么,尤其是代码实现很容易理解的时候。第二,尽量使用自解释的代码,而不是为它添加注释。

我们可以回想一下,自己刚刚做程序员的时候,是不是会这样使用代码注释?因为这段代码的逻辑或者算法很复杂,就在注释中详细说明第一步做了什么,第二步做了什么,最后做了什么。

后来我们发现,这样做代码注释会有一个问题,那就是代码的实现和注释是重复的,而且一旦修改了代码就要同步修改注释。比如说,随着业务的发展,这段逻辑有变化,你需要同时修改代码实现和注释,如果忘记了修改注释,就会导致注释和代码实现不一致。

这里我们要注意了,一个错误的注释比没有注释的危害更大。针对这个问题,有一个好的实践是使用自描述的代码代替实现注释(Implementation Comments)。比如使用《重构》一书中提到的 Replace Temp with Query 和 Extract Method 方法,将注释的代码段提取到一个独立的方法中,并取一个良好的命名。这样就可以消除注释,因为方法名本身就是一个良好的注释。

举个例子,一个方法中有一段判断一个整数是否是质数的逻辑,并且有一个注释,说明这段代码用于检查变量是否是质数。

function showPrimes(n) {
  nextPrime:
  for (let i = 2; i < n; i++) {

    // 检测 i 是否是一个质数(素数)
    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }
    alert(i);
  }
}

可以将这段代码提取到一个独立的方法中,命名为 isPrime。现在我们可以很容易地理解代码了,根据方法名就知道方法的用途,这种代码被称为自描述代码。

function showPrimes(n) {

  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;

    alert(i);  
  }
}

function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if (n % i == 0) return false;
  }

  return true;
}

需要特别说明一下,虽然我聊的是如何消除代码和注释的重复,但并不意味着所有的注释都是重复的,都应该被删掉。这就涉及到另一个话题,注释应该写什么?

注释不是代码的简单重复,注释应该是说明代码之外的信息,即“代码为什么这么写”,而不是“代码写了什么”。比如,注释可以写某段代码在某年某月某日已被废弃了,仅仅为了兼容而保留,新的接口是什么。

注释也可以说明某段代码的特殊逻辑是为了解决某个问题的临时方案,待重构。对于这类注释,它不是代码的重复,也就不存在重复问题了。

总结 + 延伸思考

今天的分享到这里就结束了,对于今天这节课所讲的内容呢,我画了一张思维导图,供你参考。

img

今天我们主要讲了代码重复和注释重复的问题,我们可以将重复的代码或注释提取到一个命名良好的方法中,来消除重复。而且,我推荐你一定要使用 IDE 来进行重构,会更高效和准确。在下一讲我将和你探讨 Maven 重复和 Spring 配置重复问题的解决方法。你可以先思考一下,你平时在使用 Maven 和 Spring 时,有没有遇到重复的问题?又是怎么解决的呢?

参考文章:整理于极客时间每日一课
https://time.geekbang.org/dailylesson/detail/100056854?tid=148

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Apple_Web

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值