本系列文章皆在从记录日常重构项目代码中发现的一些"丑陋的代码",同时分享记录开发中容易忽视的问题和错误,带你规避Java开发中的各种"坑"。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
点进文章进来的那一刻起,相信你十分认同代码要有可维护性 的观点。事实上,已经有很多书来讲解如何写出可维护性的代码。比如,《代码整洁之道》、《重构》等,毫无疑问,这些都是编程
中的经典。
但回到真实的工作中,我发现了一个无情的事实:程序员们大多会认同这些书上的观点,但每个人对于这些观点的理解却是千差万别的。
比如,我们知道变量命名要做到见名知意,即变量命名是要有意义的。 但什么样的命名才算是有意义的呢?有的人只理解到不用 abc
命名就是有意义。或许有时,虽然起出了自认为"有意义"
的名字,但这些名字依然是难以理解的。事实上,大部分程序员在真实世界中面对的代码,就是这样难懂的代码。
前言
由于最近业务变动,需要对公司之前旧项目
进行维护。拉取项目源码后,我注意到其中有一个服务层的Service
类代码量写了近6000
行逻辑。其中存在重复代码、魔法值、已注释代码
、System.out
生成环境使用、输出/输出流
用完不关闭等"错误"
的编码风格。此外,体量如此庞大的代码,注释内容屈指可数。
看到这些代码
的时候,我的内心是十分"震撼"
的。但秉持着代码能运行,坚决不重构的原则" ,我放弃了最开始对代码重构的想法,转而想换一种方式来重构
。那就是将 中我觉得设计不好分享出来。事实上,很多东西听别人说百遍不如自己经历一遍,有些知识也只有脑海中有了一个印象
,这样才能在后续开发中有所注意。
为变量做一个有意义的区分
如果只是为满足编译器
或解释器
的需要来写代码,那编写代码将是一件很容易
的事情。例如,编译器规定在同一作用范围内下,不允许有两个名称相同的变量名
。因此对于代码段
中重名的变量,可能会随手加一个数字
来避免重名的发生,从而通过编译器
的检查。具体如下:
java
public void copyArrays(int[] a1 , int[] a2) {
for (int i = 0 ; i < arr1.length; i ++) {
a2[i] = a1[i]
}
}
为了保证方法
可以通过编译
,上述代码中
定义了两个不同的参数名称arr1,arr2
。在工作中,有时出于工期的push
,我们经常通过给变量添加数字
来保证代码可以通过编译
。
但当我们回看代码时,这些变量信息真的能让我秒懂
代码逻辑吗?我想答案肯定是否定的。不妨换一种方式,我们将上述的的参数名改为source
和destination
是不是会比单纯的使用a1、a2
好一点?
修改后,我们便更加copyArrays
方法的使用细节。具体来看,copyArrays
方法作用就是为了拷贝数组,其会需要两个参数,首先需要传入一个原数组
,接着需要传递一个目标数组
。
事实上,当你为变量做了一个有意义区分后,你会得到如下好处:
- 提高可读性: 使用描述性的变量名可以让代码更容易理解。其他开发人员(包括您自己将来)可以更快地理解变量的含义和用途。
- 降低维护成本: 有意义的变量名使代码更易于维护。当需要对代码进行修改或修复错误时,不必花费过多时间弄清楚每个变量的含义。
- 减少错误: 使用有意义的命名可以减少由于误解变量含义而引发的错误。这有助于提高代码的稳定性和可靠性。
因此为变量做有意义的区分,可以使代码更易于理解和维护,减少错误,提高可靠性,促进协作,以及提高代码质量。这是一种良好的编程实践,有助于提高开发效率和代码的可维护性。
为变量提供一个可搜索的名称
开始之前,我们先来看一段如下的代码:
java
public String checkOutUserType(Integer type) {
if (type === 1) {
// ....省略其他逻辑
} else if (type == 2) {
//
}
// 省略其他else...if判断
}
当接触到上述代码时,你慌
了吗?反正我是挺慌
的,这1、2
都代表什么意思啊?如果这段代码中出现一个bug
我如何来快速判断究竟是那个else..if
中出现问题呢?像这种在代码
中乱用魔法值所带来的直接后果就是:
- 代码可维护性差: 当代码中大量使用
魔法值
时,如果需要对这些值进行更改或更新,就需要在整个代码中寻找并替换它们,这会非常繁琐且容易出错。这会降低代码的可维护性。 - 可读性差:
魔法值
通常没有描述性名称,因此其他开发人员可能难以理解它们的含义和用途,尤其是在没有注释的情况下。
事实上,在代码中大量使用魔法值
是一种不良的编程实践,其通常表现为直接在代码中使用硬编码的常量或字面值,而不是将它们抽象成有意义的命名常量或变量。
所以减少代码中魔法值
的使用,转而为变量提供一个可搜索的名称 有利于我们在项目中轻松搜索和定位。此外,这样更能有助于我们快速排查和定位bug
位置。
避免过多的使用技术词进行命名
java
List<Produt> productList = service.getProducts();
如果有java
相关的开发经验,看到这段代码一定不会陌生。这可以说idea
编辑器自动生成变量
一种常见手段了。
事实上,这个变量
之所以叫bookList
原因就是它声明的类型是List
类型。但这种命名却会带来很多问题。假如后期我们后期将service.getProducts()
的返回类型改为Set
或者此时大多数程序员只是将前面的List<Produt>
改为Set<Product>
,其中的变量bookList
是肯定不会变动的。
熟悉设计模式
的应该知道,设计模式中有一个重要的原则是面向接口编程 。换言之,就是不 要面向实现编程,因为相较于具体实现接口是更稳定的 。这样的思想同样可以应用于变量
的命名上。正如我们前面所说,如果getProducts()
方法的返回值类型从 List
改成 Set
变量类型你一定会改,但变量名
你会改吗?
对于这个问题我是存疑的,而一旦出现遗 忘,就会出现一个奇怪的现象,一个叫bookList
的变量,它的类型是一个Set
。此时后期维护者如果不细心看的话,只看到其变量名为bookList
然后以List
的方式去处理变量,我想最终的结果肯定是bug
频出。
那有什么更好的名字吗?回看上述代码,其实,我们在这段代码里真正要表达的是拿到了一堆产品,所以我觉得这个变量
的名称不妨改为 products
更合适。事实上,我们更需要一个更面向意图的名字。
尽量使用业务领域词汇来定义
在为变量
命名时,使用业务领域词汇来定义变量是笔者比较推崇的一种编程实践 。这样的命名的变量
可以使代码更易于理解和与业务需求相关联。但这样做的前提是你需要做以下的功课:
- 了解业务领域: 在编写代码之前,深入了解与您的项目或任务相关的业务领域。理解业务流程、术语和需求将有助于选择合适的词汇来定义变量。
- 与领域专家合作: 如果可能的话,与业务领域的专家或相关团队成员合作。他们可以为您提供有关业务领域的词汇和术语,以便更好地在代码中使用它们。
- 参考业务文档: 如果有业务规范、文档或需求说明书可供参考,务必查看它们,以确定哪些词汇是标准的业务术语,并在代码中使用它们。
如果你使用过Spring
的框架的话,你一定会知道如下注解:
@Autowired
:用于自动装配依赖项的注解。@Controller
:用于标识控制器类的注解。@Service
:用于标识服务类的注解。
不难发现,上述注解在命名
上就是出于其应用范围进行命名的。所以说Spring
框架中在变量
的命名上是十分规范的。
(注:事实上,这样根据业务领域范围来对变量进行命名方式在Spring、SpringMVC、Mybatis
框架中随处可见。)
事实上,使用业务领域词汇来定义变量
,它有助于提高代码的可读性、可维护性和与业务需求的相关性,从而增加了代码的质量和可理解性。
总结
选择适当的变量和方法命名对于编写高质量的代码至关重要。好的命名可以更直观地反映代码的意图,无需花费大量精力去研读代码的详细逻辑。正如《代码整洁之道》的作者所说:"任何一个人经过培训后都能写出计算机可以理解的代码,但只有写出人类容易理解的代码,才是优秀的程序员"。
命名规范和标准虽然可能被视为小事,但实际上它们是编写高质量代码的重要组成部分。在日常开发中,也许会有人认为抓住命名规范不重要,但这是一个有难度但正确的事情。即使处理看似简单的任务,也应该坚持做正确的事情。通过每天的小进步,才能逐渐积累技能,取得长远的进步。正如古罗马谚语所说:"罗马并不是一日建成的",同样,成为优秀的程序员也需要时间和努力。
或许,你会觉得,在日常开发中去抓着别人命名规范会显得自己爱计较,而且缺少对方的认同。但这种本来就是仁者见仁,智者见智的事情。
我们永远无法左右别人的观点和看法。命名本身就是对问题抽象的定义,不能描述业务含义的命名往往是因为抽象角度不正确或不明确导致的。 试想,一个对业务都无法做到深入理解的人,怎么可能开发出一个令人满意的系统?
诸君共勉~