写代码的时候应该靠感觉还是靠理性
对于程序员来说,往往工作几年以后,如果使用的技术没有太大变化,写起代码来往往会有一种得心应手的感觉,好像不需要过多思考就能写出来。在业务简单的情况下,这样子不会有任何的问题,因为面对的问题足够的简单,根本没有太多发挥的空间。可是当业务变得复杂起来的时候,这种感觉就会变得非常不靠谱,明明脑子想得通,但是代码写不通,又或者不知道如何下手。
不管是看别人的代码,还是看自己的代码,总会有出现这种感觉的时候。我们可能会想,明明以前写得那么顺畅,怎么到今天就很难接着原来的代码继续写下去呢?可能在实现那个需求的时候,那样实现确实是没问题的,但是那样写出来的代码,可能并不适应新的需求,也就是说,我们的代码并不够灵活。又或者是,我们忽略了脑海中曾经浮现过的一些比较理性的思考,选择了比较顺畅的、耗时更短的方法,为了赶在 deadline
前完成任务,当然这也是一种选择。
但如果我们想要写出更好维护的代码,那么我们就需要更多地依赖一些理性的思考,而不是感觉。也就是需要花时间思考一下当前软件的设计是否合理,是否能够适应可遇见的变化,是否能够方便地扩展等。
你真的需要 setter 吗?
当我们想为一个类添加一个属性的时候,我们可能会想到使用 setter
方法来设置这个属性,这是一种非常符合感觉的做法。这样写代码我们也不会有太多的阻力,因为所见即所得。
在实际工作中,我们可能会经常看到这样的代码:
这里讨论的不是实体类的
setter
也就是通过 setter
方法来修改对象的属性,这样的代码看起来很简单,但是却隐藏了一些问题:
缺乏封装:在这里,我们本意要实现的操作是 “审核”,但是通过
setter
方法来修改对象的属性,使得我们的操作不再是 “审核”,而是 “修改属性”。变化不可控:任何人在任何地方都能使用
setter
方法来修改对象的属性,这样会导致对象的状态变得不可控。很难追溯变化是从哪里引起的。
关于第二点,我们可以用数据库的变更来类比一下:
在 Java 中,对数据库的操作,往往有一层 dao
层,这一层负责和数据库交互,而不是直接操作数据库。这样一来,对数据库的操作就变得可控了,我们可以通过查看 dao
层的方法被哪里调用,来追溯数据库的变更。相比之下,在某些脚本语言中,比如 PHP,因为书写很自由,没有明显的分层,数据库操作的代码散落在各个地方,导致数据库的变更变得不可控;代码仓库变得越来越庞大的时候,我们就很难得知某个字段是在哪里被修改的,这样一来,维护成本就会变得非常高,因为操作数据库变更的代码有各种形态,无法简单地通过搜索得到;这种情况下,我们只能在出问题的时候出修复脚本去修复一些出错的数据,而某段本来应该被修复的代码,埋藏在一个不为人知的角落里。
过多的 setter
透露着一些问题:
对象暴露了太多的细节,缺乏封装。
class
的职责不明确/职责过多,如果职责比较单一,那它依赖的参数应该也是比较固定的。对其他开发者而言,可能不知道什么时候该调用
setter
方法,因为暴露了setter
之后,意味着setter
对应的属性是可set
也可不set
,等于给其他人出了一个难题,因为这个时候他们必须考虑传和不传会有什么影响。
这样的
setter
多了之后,作为维护的人心智负担会越来越重,可能你处理一个业务的时候,就得想很多次该不该set
某个属性的问题。
不用 setter,那用什么?
好了,理性地思考一番过后,我们知道了 setter
方法的问题,那么我们应该怎么做呢?
这需要分情况讨论,下面是两种非常常见的情况:
更新对象的属性/状态:如上面这个例子。
参数传递:有时候我们在修改一个类的时候发现需要加参数的话,可能有的人会选择加一个
setter
方法来传递新的参数。
针对第一种情况,我们可以简单地封装一个方法,方法名表示我们想要做的操作,比如,上面这个例子,我们的本意是 “审核”:
在 book
对象中,我们可以定义一个 approve
方法,这个方法负责修改对象的状态:
也许你会觉得这样做有些多余,但是这样写了之后,我们就知道,任何审核操作都是通过 approve
方法来实现的,如果我们想知道审核状态是哪里修改的,可以直接查看哪里使用了 approve
方法即可。
针对第二种情况,我们有两种办法:
通过构造函数传递参数:如果是全局的参数,也就是整个类都需要用到的参数,我们可以通过构造函数传递参数,然后设置为对象的属性。
通过方法传递参数:如果只是某个方法的参数,我们就不需要在
class
上增加属性,然后通过setter
方法来传递参数,直接在方法参数中传递即可。
你应该重构那些遗留代码吗?
很多时候,我们在看到某些屎山代码的时候,会有一种冲动,想要将这些代码全部重构一遍,但是这样做真的好吗?或者说,重构后会更好吗?还是会引入更多的问题?可能很多人回答不了这几个问题,“屎山” 特征其实很明显,但是真让我们去改的时候,未必就能改得更好。因为我们只是感觉上觉得 “屎”,但是缺乏具体的理性分析。在这种情况下,就算贸然重构,也还是会存在很多新的问题,可能其他人看了也还是觉得看不懂、不好维护。
这就好比,虽然我们不会做菜,但是我们知道这道菜不好吃,但是我们不知道哪里不好吃,也不知道怎么做才好吃。如果你让一名美食家来品尝的话,他真的可以给你说出个所以然来,比如他们会针对火候、口感、味道各方面进行分析。同样的,可能我们不知道怎样写才更好,但是看到那些写得不好的代码的时候,其实我们是有感觉的。但真要让我们说的话,可能又说不出来。
在这种情况下,我们看到好不好的时候,我们还是可以评判的,但是我们很难往好的方向去改进,因为我们都是凭感觉。
在还没有明确要重构的代码存在哪些问题之前,选择维持现状可能是一个更好的选择。
那我们与 “美食家” 的区别又在哪里呢?
我们知道,我们度量长度的时候,需要有一把尺子,这把尺子就是我们的标准。同样的,对于美食家而言,他们也有一把尺子,但不同于直尺,他们的尺子是多维度的,比如有火候、口感、味道几个维度等等:
而对于普通食客而言,他们的尺子可能只有一个维度,就是好吃不好吃,也就是本文所说的感觉。
衡量代码的尺子
同样的,对于代码而言,我们也有一把尺子,比如可能我们关注的是以下几个维度(可能每个团队关注的维度不一样,这里这是抛砖引玉):
可读性:代码是否容易阅读,变量和函数命名是否清晰且具有描述性,注释是否能够帮助理解代码逻辑。
可维护性:代码是否容易维护,是否容易修改;结构是否清晰。
整洁性:代码是否整洁,是否有冗余代码。
可复用性:代码是否可以在其他项目或模块中复用?是否遵循了
DRY
(Don't Repeat Yourself)原则?灵活性:代码是否容易适应变化?是否使用了设计模式等提高代码灵活性的方法?
一致性:代码风格是否一致?是否遵循了团队的代码规范?
评判的维度并不固定,取决于你们更关注哪方面的问题。比如我们这个图就没有把性能这一维度加上去,因为一个项目中,并没有那么多的性能问题,在优化性能之前,把代码写得更好维护可能更重要。
重构的时机
现在,我们知道了,对于代码的好坏,也有一把尺子,那么我们何时应该重构呢?我觉得应该是拿 “尺子” 好好量过的时候,也就是,我们从各个维度上去看这段代码,看看它是否符合我们的标准。
在我们没有比较清晰的标准的时候,我们就不要轻易去重构,因为我们不知道重构后的代码是否会更好,还是会引入更多的问题。有了方向,我们就能知道该怎么做了,同时做完之后,我们也能依据这些标准来判断我们的重构是否成功。
如何摆脱对感觉的依赖?
当然,这里并不是说靠感觉不好,只是说,如果你的感觉并不准的话,我们最好还是有一些可以依赖的标准(代码规范)。
拿一些竞技游戏来说,很多人可能玩很久,依然是个比较菜的水平,他们可能甚至花了 1 万个小时在上面,从 1 万小时理论来说,这些人应该是个专家了,可惜现实并不是这样的,又菜又爱玩的人其实不少,比如我。经过长时间的玩耍,他们有一些比较模糊的感觉,但是缺乏反馈,所以他们的水平并没有提高;对于这部分玩家来说,游戏只是个消遣,比如我,他们并不会去深究游戏的规则,也不会去看一些攻略,只是单纯地玩而已,这些人就是一年经验用了十年。而有的人,玩起来就很认真,他们会去看一些攻略,去了解游戏的规则,并且在跟其他玩家对抗的时候,不断改进自己的策略,这样一来,他们的水平才会提高。
好了,我想说的是,如果没有刻意地去思考,我们的感觉都只是一种非常不靠谱的感觉,如果一直依赖于这种感觉,我们也很难做得比昨天好。专业跟业余的差别就在于,专业的人对一件事总有个 1234,能道出个所以然来,而业余的人只是感觉上觉得这样做好,那样做不好。结果就是,业余的人偶尔能取得好的成绩,而专业的人可以持续地取得好的成绩。
如果我们想摆脱对感觉的依赖,我们就需要有一些可以依赖的标准,也就是上面所说的 “尺子”。对于程序员来说,写代码的时候,我们可能有如下标准:
命名规范:变量、函数、类的命名是否符合规范?
是否符合设计原则:比如
SOLID
原则、DRY
原则等。是否足够整洁:是否有冗余代码?
有很多可以判断代码好坏的标准,我们可以根据这些标准来判断我们的代码是否符合规范,是否可以更好维护。当我们的这把 “尺子” 越来越好用的时候,我们也就逐渐摆脱了对感觉的依赖,我们分析的时候会更加理性。
当我们觉得某样东西好、不好,但是又说不出所以然来的时候,可以尝试找一些标准来判断。比如针对产品经理给你的原型、需求,你可能觉得不好,但是你又说不出哪里不好;你可以依据下面的标准来判断一下:
是否有一个好的用户故事(
User Story
),能让大家都清楚要解决的问题?清晰性:需求是否明确且易于理解?
完整性:是否考虑了不同的用户场景?
针对背后的问题,当前的产品设计是否就是最好的解决方案?
同样的,如果从来没做过菜的人去买菜,眼花缭乱的也不知道应该怎么选。在经历多次购买之后,他们会形成一些自己的判断标准,比如选菜的时候看新鲜度、颜色、看手感等等。
想摆脱对感觉的依赖,我们就得找到一把合适的 “尺子”,对程序员来说,《代码整洁之道》、《重构》里面包含了一些可以依赖的标准。
什么时候可以依赖感觉?当我们的感觉越来越靠谱的时候,这个时候的感觉就不再是简单的感觉了,而是一种洞见,也就是 “insight”,也就是我们很熟悉各种维度的判断标准的时候。当然,我们并不可能等到了解了所有的标准之后,才能开始写代码;只是说,不管怎么做,我们总得有一些明确的理由。准确来说,我们并不是要完全摆脱对感觉的依赖,而是要摆脱对不靠谱感觉的依赖。
总结
如果用一句话来总结的话,那就是:不管是写代码还是其他事情,在自己的感觉并不靠谱、不能依赖于感觉的时候,尝试先找一把 “尺子”,也就是找一些可以依赖的判断标准,熟悉了这些标准之后,我们就可以摆脱对不靠谱感觉的依赖,结果就是,我们的判断会更加准确,我们的决策也会更加理性。
版权声明: 本文为 InfoQ 作者【rubys_】的原创文章。
原文链接:【http://xie.infoq.cn/article/8e85bac3093048347b39d029d】。文章转载请联系作者。
评论