如何写出容易阅读的代码

一. 开篇废话

很多年前,我写过一篇文章叫《关于阅读代码 》。是在2014年写的,那时候刚开始写博客。根据后台统计数据显示,这篇文章这些年来也没什么人看。

其实文章分两种,一种是给自己看的,一种是给别人看的。就像写代码一样,有的代码你自己写的爽就行了,也没打算给别人看;还有一种,写出来的目的就是为了给别人看的。

所以回到《关于阅读代码》这篇文章,当时写的时候更多的是因为随着职业经历的积累自己有一些了感悟,觉得自己有一些绝妙的想法,想记录下来。这篇文章也一样。

作为程序员,工作职责中,可能写代码只占很小的一部分,不过因人而异。如果作为Salesfoce从业者,工作职责中80%以上都是写代码的话,我倒是觉得这其实是一件挺可悲的事情。应该考虑换一个平台或者试试别的角色。

但是,单就开发这部分来说,由于标准功能的存在,代码在Salesforce开发中的比重也不再如传统开发的100%。可以说我们的目标就是要干掉所有代码。

虽然说我们要干掉代码,但是代码却极其重要。毕竟所有标准功能无法实现的功能还是要代码去实现。这时候“对人友善”的代码在Salesforce开发活动中就格外的重要。毕竟在梳理业务流程的时候,谁也不想面对方便面一样的代码皱眉头。或者,被问到头上的时候,只能╮(╯▽╰)╭说:“这个功能好用但我不知道为什么。”

二. 不容易阅读的代码怎么来的

这些年我发现一个现象。习惯疯狂抽象分层的Java程序员,在转Salesforce开发之后往往更容易写出散文诗式流水账代码。然后再进行几次需求变动,代码就变成了方便面。

那么,为什么一个优秀的coder做salesforce之后会写出方便面代码?我觉得原因是接受到的信息的不同。

在Java时代,代码写手在动手之前接受到的信息为具体的功能模块,已经有架构师或同等角色的人将功能完全拆解或者绘制好蓝图。代码写手要做的是实现规划好的功能模块,将自己的模块成功嵌入到大框架内。而Salesforce代码写手接受的信息往往是业务描述——只是说了要做哪些事情,而不是严谨的功能模块划分。

举个例子,就像做意大利面,非Salesforce的厨师被提供了一个精确的食谱和严格的验收标准,要做的是用规定的食材严格按照食谱在指定地点把意大利面做好就行。而Salesforce的厨师只是被告知要做一盘什么味道的意大利面,可能连食材都还没买,到做好上桌之前都要自己搞定。

如果有自由发挥的空间,就像在饭店点蛋炒饭,每个厨师都会做出不同的味道。

这种输入的转变带来的则是开发思路的转变。视角变了,出发点变了,验收标准也变了。做Salesforce更在意的是业务合不合理,能不能跑通,系统能不能整体运转起来。当然不是说完全不考虑拓展性,维护性,鲁棒性,而是对于代码写手的要求中,上述各种性不再是第一要求。(简而言之就是重视方向大于精致)

再举一个例子。把大象关冰箱里。
非Salesforce: 把大象关冰箱里要有几个动作,比如开,放,关,我们放到until类里,然后把大象抽象成物件,把冰箱抽象成容器。然后建几个工厂类。最后在主业务里实例化大象,实例化冰箱,调用common的动作,最好动作能做成反射以实现解耦,完成。
Salesforce:检索到我要的大象,检索到我要的冰箱,还得注意检索的时候不能超50000条,注意where条件用到带索引的字段。然后要注意批量化处理,保证不能循环大象的时候查询冰箱。然后批量开门,批量化的把大象放到冰箱里,批量关门。如果有关门失败的要注意提示用户,把大象从冰箱里拿出来,剩余的不受影响。写完。

最终发现,无可抽象,无可提取,线性的一口气的将功能就实现了。

可能很多人会点反对(本博客不提供反对按钮),说我们项目会提炼出很多common method要求所有业务代码使用。代码非常精简,大家只要搭积木就好了。但是这种项目对私有工具包不熟悉的人来说,阅读门槛和上手门槛会往往会变得极高。

三. 好阅读的代码应该是什么样子

说到底代码还是给人看的。如果只是为了给机器看,直接写0和1就好了,对于机器来说效率还会更高一些。对于Salesforce来讲,既然输入与非Saleforce项目有所不同,那么对于“好阅读”的鉴定,也应该调整判断的标准。

好阅读的代码并不是说搭积木一般的把所有common method堆一起就行了。这样只是提高了开发效率,提高了代码复用率,其实和提高阅读性没有什么太必然的联系。或者说不断的提取通用代码的根本目的并不是方便阅读。你是觉得在两本甚至三本代码之间跳来跳去的阅读代码容易,还是在一本代码之内看到所有内容阅读起来容易?

我以前说写代码如写文章。倒不如说写代码如写书。

写文章重要的是分段和标点,写书重要的则是标题和分章节。
《西游记》很好看,但是如果不分章回,一口气从头印刷到尾。是什么感觉。如果每一章的标题都是只编号,从001开始,是什么感觉。如果每本书的书名都是ISBN号,是什么感觉。

其实对于读者本身来讲,似乎不分段不写标点更难受。但是如果是与其他读者交流呢?

现代开发是团队活动,单兵作战的情况会越来越少。交流的需求越来越大。

所以,在每个人都独立负责一整条业务线的情况下,好阅读的代码首先应该有个好的标题。好的代码标题就像好的书名,一眼就能让队友知道这本代码存在的目的是什么。比如,表明自己是controller还是Trigger,是until还是const。然后表明自己做的业务线是哪个,是把大象装冰箱,还是做意大利面。再有说明自己的属性,是被人调用的辅助类(helper),还是主业务入口类。

其次,好阅读的代码,应该有好的函数名,好的函数名就如同书中的章回的名字,一眼就能让队友知道这个函数存在的目的是什么,不管是入口函数还是辅助函数,应该从返回值和业务目的出发,做判断的叫is/has/check,响应单个动作的叫do/action/handle,辅助功能的表明自己的功能和辅助属性。

然后就是函数内容本身。

之前说的方便面代码,发生地就是在函数内容里。洋洋洒洒一千多行一气呵成。将来想改就费劲了。
那好了,我发现1000多行有600行能提取成common method,一口气写四百行是不是就成了好阅读的代码。
不对,也不能说完全不对。

写这种业务代码的本质是在讲一个故事,一个也许简单,也许复杂的故事。复杂的故事不外乎是出场的人物多,涉及的事物多,人物之间关系比较复杂,故事比较曲折,故事线比较多。放到代码里,就是变量多,涉及的表多,表关系复杂,处理的逻辑比较曲折,要处理的业务比较多。

首先说变量名,我见过太多图省事的变量叫flag1,flag2,list1,list2的代码。这就像你写个小说人物名全是小A,小B,小C,或者都是绕口难记极为相似的外国人名。我曾经看过一本外国小说,到最后都没弄清楚谁是谁。。。

如果故事比较曲折,我建议不要用代码是否复用的角度去拆解你的代码,也不要事无巨细的每一个处理都罗列到一起。而是用写小说的思路来写。

写复杂的小说很难提笔一气呵成。一气呵成的只能是散文。一个故事的形成,首先要有故事大纲,故事大纲决定了故事的走向和剧情先后安排。有了故事大纲,然后分配角色,编排每个大纲事件的具体推进细节。

写代码也是如此。当输入是一条业务流的时候。业务流天然就是一个故事。所以必然也要先构思业务流的故事大纲,然后再安排每一步具体的故事情节。人物最终要为剧情服务。填充了大纲的人物和情节,就是完整的故事。这种思路下,优先考虑的是故事如何组织,如何拆解故事并逐个填充,而不是单纯的为了降低代码重复率而提取common method。

举个例子。你要实现的业务是把大象关冰箱里。那么这个故事主要情节是把大象放冰箱里。然后角色涉及到大象和冰箱,那么就要提到大象和冰箱的来历。结局是大象在冰箱里。这个是故事大纲。所以理想状态下,代码应该组织成。

public void putElephantToFridge() {
    getElephant(); // 介绍大象怎么来的
    getFridge(); // 介绍冰箱怎么来的
    put(); // 大象“放”到冰箱里,故事结束
}

可是开门和关门去哪了?开门和关门是大纲中“放”这个动作的具体情节。自然要放到具体的章节中。

public void putElephantToFridge() {
    Elephant elephant = getElephant(); // 介绍大象怎么来的
    Fridge fridge = getFridge(); // 介绍冰箱怎么来的
    put(elephant, fridge); // 大象“放”到冰箱里,故事结束
}
public Elephant getElephant() {
    // Query Elephant and Return
}
public Fridge getFridge() {
    // Query Fridge and Return
}
public void put(elephant, fridge) {
    open(fridge); // open和close中有一些代码是可以复用,可提取。
    put(elephant, fridge);
    close(fridge);
}

如果open和close动作可以提取common method,自然可以提取出来简化open和close的实现。
但是要保证整个代码的组织是由简入详,由上自下,用变量作为剧中人物去将所有情节穿插起来。
这样才能最大化的保证队友可以快速的了解你负责的业务流的内容,而不是拘泥于代码细节,通读所有的代码之后才能理解到底要做了一件什么事情。
如果想了解大概做了什么事情,找到入口函数即可。
如果想了解细节是怎么做的,根据大纲叙事顺序定位具体的情节实现即可。
如果想了解数据流,紧跟变量走向即可。

这就是我所理解的“好阅读”的代码。

《如何写出容易阅读的代码》有一个想法

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据