代码精进之路 从码农到工匠

978-7-115-52102-6
作者: 张建飞
译者:
编辑: 张爽
分类: IT人文

图书目录:

详情

这是一本为专业程序员而写的书,写好代码、追求卓越和工匠精神是每个程序员都应该具备的优秀品质。 本书共有13章内容,主要分为技艺部分、思想部分和实践部分。技艺部分详细介绍了编程技巧和方法论,并配以详尽的代码案例,有助于读者提高编写代码的能力,优化代码质量。思想部分主要包括抽象能力、分治思想,以及程序员应该具备的素养等内容。实践部分主要介绍了常见的应用架构模式,以及COLA架构的设计原理。

图书摘要

版权信息

书名:代码精进之路:从码农到工匠

ISBN:978-7-115-52102-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

编  著 张建飞

责任编辑 张 爽

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


这是一本为专业程序员而写的书,写好代码、追求卓越和工匠精神是每个程序员都应该具备的优秀品质。

本书共有13章内容,主要分为技艺部分、思想部分和实践部分。技艺部分详细介绍了编程技巧和方法论,并配以详尽的代码案例,有助于读者提高编写代码的能力,优化代码质量。思想部分主要包括抽象能力、分治思想,以及程序员应该具备的素养等内容。实践部分主要介绍了常见的应用架构模式,以及COLA架构的设计原理。

本书的目标读者是专业程序员,书中有关命名、函数、抽象和建模等内容是具有普适性的。无论你是否使用Java语言编程,也不管你从事的是业务应用开发、前端开发,还是底层技术开发工作,都可以阅读和使用本书。


软件研发是技术也是艺术,不仅要有一行行的代码,还要有主题思想、想象力、宏观架构……程序员在成为一个架构师梦想的指引下,不断地学习语言特性、编程模型和各种思想方法,并在一次次的系统重构中成长。可以说,程序员的快乐和骄傲很大程度上来自于那些具有美感的代码。

软件的发展如此迅猛,作为程序员,我们经常会面对着没有前人经验的“无人区”。过去的软件大多数是对现实世界的模拟,还有参照物供我们去归纳总结抽象。但随着PC互联网和移动互联网的发展,软件已经演进为一个与现实连接的虚拟世界,成为了社会的基础设施。在这个全新的领域,我们只有不断地突破思想的边界,善于打破旧观念,敢于探索未知,才能有所贡献。

刚认识建飞的时候,他对代码质量的追求和对优雅架构的探索精神打动了我。几年下来,我看到他在这个领域中逐渐形成自己的认知体系、做事方法和评价标准,并沉淀出了在很多业务系统中得到了有效应用的COLA系统框架,也在如何做技术Leader方面形成了自己的思路,带领团队拿到了良好的业务结果。

这本书是建飞的阶段性思考总结,相信读者能从这本书中看到他对软件研发的热情和独到见解。我也希望建飞能够不断地探索总结,给我们带来更多的惊喜!

阿里巴巴技术副总裁

玄 难

2019年6月


写软件的人有一大爱好,就是聚在一起,相互调侃自家的代码。“我司‘祖传’代码,‘五代单传’,无注释,如无字天书……”“我司代码相当的‘浪’,想重构吧,害怕一不小心大水冲了龙王庙……”

软件人员还有一个爱好,就是崇拜“银弹”。

前两年,银弹是敏捷、Scrum,搞得轰轰烈烈,但是实践下来,往往变成了管理者要求软件团队更快和更频繁出产品的工具:两星期一迭代,三个月出产品。架构设计往边靠,先出个MVP,再迭代,将来再重构……有更重要的需求了?没关系,这里加个if/else,那里复制一下代码就可以实现了。当敏捷变成了一种管理工具后,代码架构更加脆弱,用一个“摇摇欲坠”的架构去支撑不断变化的业务需求,要“敏捷”,只能996了。

近几年的银弹是微服务,但是微服务需要更强的业务建模能力和技术管理能力,否则实现和维护微服务系统只能是难上加难。

有时候,我很悲观地思考,变得“臭不可闻”是不是每个系统不可避免的命运?如果不是,靠什么才能避免代码的腐化呢?

我隐隐约约地觉得一个很好的架构可能是解决问题的办法。因为我在写代码前,如果没有业务压力,我可以优哉游哉地想个几天,等动手写代码时,头脑中的分治联合早已清楚,洋洋洒洒,如有神助;可是如果业务复杂繁忙,需要多个团队合作开发,就很难保证架构在演化过程中保持清晰健壮,也很难保证团队不走捷径、不做“变通”,更难以自动自觉地写出干净的代码为己任,而不会为了完成任务去堆砌代码。

可惜,我并没有深入且系统地思考解决方案。但是,本书的作者想了,更可贵的是,他做出来了。这本书从最底层的技术细节开始讲起,从命名、代码规范、设计模式,到技术人员的素养、技术Leader的修养,再到COLA架构和如何使用COLA架构快速开发系统。整本书的风格如我喜欢的代码一般,清晰、简洁、有力。

我尤其喜欢的是第11章“技术Leader的修养”。技术高手在任何公司都很重要,但是以我所观察到的情况,很多高手都是“救火队长”,哪里有火就扑向哪里,成为大家顶礼膜拜的“救火英雄”;或者有些所谓的高手只在纸上做架构,指点江山。我认为,真正的技术Leader是能够创建并且演进架构,在架构层面上帮助大家比较容易地写出好代码的人;是能够创建良好的技术氛围,以写好代码为荣,以写坏代码为耻,促使大家不停学习的人。张建飞分享了他的团队中一些很有意义的做法,使我深受启发。

好代码才是真正的银弹!COLA架构能够在架构层面上帮助程序员写出好代码、研读源代码,它是作者及其团队多年来孜孜不倦地践行工匠精神打磨出的系统产物。对于读者,我的建议是一边研读源代码,一边反复阅读本书,并进一步阅读书中推荐的其他书籍。我相信,不管你是刚入行的新人,还是工作多年、经验丰富的人,抑或是技术管理人员,都能从本书中收获良多。

2013年的“搞笑诺贝尔奖”中提到屎壳郎在迷路时能够利用银河导航。即使我们的工作是在维护“屎山”(Shit Mountain),也请不要忘记时时仰望星空……

Micro Focus架构师

陈萍

2019年7月


我有一个梦想,我写的代码,可以像诗歌一样优美。

我有一个梦想,我做的设计,能恰到好处,既不过度,也无不足。

这种带有一点洁癖的完美主义就像一把达摩克利斯之剑,时刻提醒我不能将就、不能妥协。

完美主义的代价使我在很长时间持续地迷茫和焦虑,甚至一度感到失望和怀疑。在软件的世界里,到底有没有优雅的代码和整洁的架构呢?

每每看到“剪不断、理还乱”的代码,我都会感到懊恼和羞愧。懊恼的是,不知道如何能有效地治理混乱、控制复杂度;羞愧的是,我真的无能为力吗?

一边是无止境的业务需求,一边是补丁加补丁的业务代码,开发人员被夹在中间,像一只困兽,向左走,还是向右走?方向在哪里?我倍感困惑。就像Robert C. Martin说的:“不管你们有多敬业,加多少班,在面对烂系统时,你仍然会寸步难行,因为你大部分的精力不是在应对开发需求,而是在应对混乱。”

的确,软件是具有天然的复杂性的,而且不可能彻底地消除这种复杂性。不甘于向复杂度屈服的我们,花了很多时间研究复杂性的根源,随着对复杂性理解的不断深入,我们发现造成软件复杂性的主要因素如下。

念念不忘,必有回响;不忘初心,方得始终。经过不懈的努力,我们的坚持和努力终于在2018年有了一些阶段性的成果,我们找到了一些切实可行的控制复杂度的办法,并沉淀了整洁面向对象分层架构Clean Object-oriented and Layered Architecture,COLA)。COLA[1][1]的诞生给了我们很大的鼓舞和希望,就像是在茫茫大海上漂流,终于看到了彼岸的灯塔。

在COLA日趋成熟之际,我迫不及待地想要将这些发现和应用整理分享出来。在探索复杂度治理的相关工作和研究中,我不止一次地感叹如果能更早地了解这些知识、掌握这些方法该有多好,这样就能避免很多不必要的焦虑,少做有缺陷的设计,少写丑陋的代码了。相信你在看完本书后也会有同样的感受,因为我相信对代码的极致追求是每个技术人员的基本动力和诉求。我们都知道“写出好代码”是比“写出代码”要难得多的要求,一个程序员的“美德”就在于他是否能为后人留下一段看得懂、可维护性好的代码。

写好代码的技艺不是一蹴而就的,它是一个系统化的工程,不是看几本书、写几年代码就能轻松习得的,而需要我们对自己的思维习惯、学习方法和工程实践进行彻底的反省和重构。本书记录了一个普通码农如何通过认知升级、知识重构、持续学习,继而转向工匠的过程。作为一个技术人,我有义务将这个过程分享出来,以期给同样在路上的你带来一些启发,缩短你“从码农到工匠”的探索路径。

由于认知水平有限,本书的很多观点可能只是一家之言,因此我更希望读者带着批判的眼光来看这本书,取其精华,并对有疑问的地方提出质疑和见解。

灵活性和没有银弹(Silver Bullet),也是软件行业的有趣之处。在这个行业里,一个问题会有很多种解法,即使是最简单的函数也至少可以写出10种不同的代码来实现。因此,知识储备、判断力和思辨力是软件行业给我们提出的更高要求,任何不区分上下文和情景的教条都有可能在实施过程中遭遇惨败。我真诚地期待读者对书中的内容进行批评和指正,如果你对本书或者COLA架构有任何想法和意见,都可以通过下面的微信公众号来联系我。

软件设计不仅是“技术”(Technique),更是一门“技艺”(Craftsmanship)。要想控制复杂度,防止系统腐化,我们不能只满足做一个搬砖的“码农”,而是要坚持自己的技术梦想和技术信仰,怀有一颗“匠人”之心,保持专注、持续学习,每天进步一点点。唯有如此,我们才有可能“从码农走向工匠”!

本书共分为三大部分:技艺部分、思想部分和实践部分。

这部分详细介绍了一些实用的编程技巧和方法论,并配以详尽的代码案例。掌握这些方法论可以有效地提高我们的编程素养,培养更好的编程习惯,写出更好的程序。

第1章 命名。好的命名可以极大地提升代码可读性和可理解性,本章主要介绍命名的重要性、命名要注意什么,以及我们如何对不同的软件构建(Artifact)进行命名。

第2章 规范。在Google的代码审查(Code Review)实践中,代码是否符合规范(Norms)是最重要的检查项。在本章中,我们将了解必需的规范、如何制定规范,以及如何贯彻实施规范。

第3章 函数。有时即使你不采用任何面向对象(Object Oriented,OO)技术,只把函数写好,代码也会呈现完全不一样的风貌。本章介绍许多写函数的技巧和方法,非常实用。

第4章 设计原则。本章介绍了很多前人总结的优秀设计原则,包括最著名的SOLID,它为我们提供了非常好的OO设计指导原则,比如扩展性的终极目标是满足OCP。我个人特别推崇DIP,因为它是架构设计的重要指导原则。

第5章 设计模式。好的设计模式能够使代码具有恰到好处的灵活性和优雅性,工程师之间的沟通也会变得简单。本章没有详细介绍GoF中的全部24种模式,只重点介绍几个日常使用频率高、实用性强的设计模式。

第6章 模型。软件工程就是一个对现实世界的问题进行分析、抽象、建模,然后转换成计算机可以理解的语言,解释执行,实现特定业务逻辑的过程。本章主要介绍了什么是模型、软件工程中常见的建模方法论,以及如何运用这些模型为软件服务。

第7章 DDD的精髓。领域建模是面向对象技术的精髓,本章的主要思想都来自于领域驱动设计(Domain Driven Design,DDD),但是并没有教条地照搬,而是结合实践对DDD进行了改良、萃取和优化。

思想是比技艺更高层次的能力要求,如果说技艺是“术”,那么思想就是“道”,领悟这些道理,对我们的职业发展会大有裨益。

第8章 抽象。抽象能力是工程师需要的核心能力之一。本章介绍了什么是抽象、抽象的层次性、如何进行抽象,以及如何培养结构化思维和抽象思维。

第9章 分治。分治思想的伟大之处在于,我们可以将一个很复杂的问题域分解成多个相对独立的子问题,再各个击破。分治思想在软件领域可谓是无处不在。

第10章 技术人的素养。做一个优秀的工程师不容易,然而还是有一些特质是值得我们学习的。本章主要介绍了技术人应该具备的一些素养,以及如何培养这些素养。

第11章 技术Leader的修养。一个优秀的工程师不一定是一个好的技术Leader,一个技术Leader在很大程度上决定了团队的技术味道和技术追求。在本章中,我会介绍自己在技术管理上的一些心得。

“Talk is cheap, show me the code”,一本没有实战的技术书是难以服众的。如果说思想是务虚的最高境界,那么实践就是务实的最低要求。

第12章 COLA架构。本章主要介绍了什么是架构,重点介绍COLA架构及其背后的设计理念和设计原理。

第13章 工匠平台。本章通过COLA架构在工匠平台实际业务场景中的落地,介绍如何使用COLA来搭建一个完整的应用架构,以及如何通过领域建模来实现业务逻辑。

本书的特色之处在于“虚实结合”——既重视思想,又兼顾实践。

所谓思想,是我们分析和解决问题所需要的底层能力。我利用大量篇幅介绍了抽象、批判思维、辩证思维,以及程序员的素养等。思想是我们构建技术大厦的底层基石,是我们必须要掌握的底层能力,它超越了软件行业的范畴,是一种哲学和世界观。

实践即COLA架构。这不仅是一本技术书,也是开源框架COLA的技术指导手册。到目前为止,我还没有看到比COLA更轻量、更简洁、可直接应用到生产系统中的应用框架。看完本书,相信你会对COLA,以及如何应用COLA进行应用架构和复杂性治理有一个全面的了解。

本书的目标读者是专业程序员。无论你使用哪种编程语言、从事哪个岗位的工作,写好代码、追求卓越和工匠精神是每个程序员都应该具备的优秀品质。

本书最适合的读者是具有一定经验、从事以Java语言为主的业务应用开发人员,主要有以下两个原因。

最后,我想特别对以下不同类型的读者说几句话。

感谢我的团队成员沈学良、冯贝、詹向阳、聂晓龙、廖康丽、李克华和吴才强,是你们这一两年来陪我共同探索,才促成了这本书的诞生。

特别感谢玄难,虽然您不是我的直接汇报领导,但是您的为人处世之道、务实之风,以及对技术的专注和热情都深深影响了我。是您让我明白,不论身处什么位置,只要还在技术线,就没有理由“逍遥于代码之外”。记得在代码大赛夺冠的晚餐会上,我和您提到要写这本书,您表示支持,并答应为书作序。正是有了您的鼓励和期许,我才有勇气动笔写作,再次感谢您!

最后,还要感谢我亲爱的家人:我的父母(张东方、徐凤英),岳父岳母(柯超、张小妹),妹妹(张晨琛),妻子(柯霁)和两个超级无敌可爱的女儿(张艾可、张慕溪)。写书的过程比我预想的要难得多,记得最初的两个月,我常常坐在电脑前很长时间,却不知从何写起,大纲也被反复调整了很多次。正是家人一直在我背后默默地付出和陪伴,特别是我的岳父岳母和妻子,照顾小孩,并且承担了所有家务,我才能有精力全力投入在工作和写作上。你们是我最强大的后盾,谢谢你们,永远爱你们!

[1] COLA的开源地址是https://github.com/alibaba/COLA。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书源代码请到前言中提到的GitHub开源地址中获取。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您所在的学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


图7-15 四色模型

图7-16 在线电子书店的关键业务流程

图7-17 在线电子书店的业务关键时刻对象

图7-18 在线电子书店的人-事-物对象

图7-19 在线电子书店的角色对象

图7-20 电子书店的描述对象



名为万物之始,万物始于无名,道生一,一生二,二生三,三生万物。

——《易经》

命名常常被认为是编程中的细节问题,其重要性往往被低估。而所谓的工匠精神,往往就是体现在细节之处,就像日本的“煮饭仙人”50年专注于做好1碗米饭。一个名字虽然并不影响程序的执行,但是却对代码的表达力和可读性有着重要的影响。

在程序员的工作中,大部分的时间都在阅读和理解代码,好的命名能够让代码的概念清晰,增加代码的表达力;词不达意的命名会破坏我们思考的连贯性,分散有限的注意力。

无论是对于人名,还是企业名、产品名,命名都有着巨大的力量。

在阿里巴巴初创时期,马云想做一个国际化的电子商务网站,要起一个全球化的名字。有一天,他在旧金山的街上发现阿里巴巴这个名字蛮有意思的,正在思考时,一名服务员送咖啡过来。马云问他:“你知道阿里巴巴吗?”他说:“当然知道了,就是open seasame(芝麻开门)”。然后马云在街上找了来自不同国家的数十个人,问他们知道阿里巴巴吗?他们大多能讲到芝麻开门。在英文单词里,“a”排名又在第一位,而且大多数人一听(看)到阿里巴巴这个名字都会感到奇怪,这样足以给人留下深刻的印象,“阿里巴巴”的名字由此而来。

在Java企业级应用开发的历史上,也有一段和命名有关的有趣历史。在2000年左右,EJB(Enterprise Java Bean)大行其道,这让Martin Fowler、Rebecca Parsons和Josh MacKenzie等人感到很困惑。后来他们发现人们之所以不愿意在他们的系统中使用普通的Java对象,是因为其缺少一个酷炫的名字,因此他们在一次会议上给普通的Java对象起了个名字——POJO(Plain Old Java Object)。当时的EJB在开发和部署上给开发者带来了沉重的负担,POJO概念的提出很快得到了开发者的拥护。Spring等一系列轻量级框架的诞生,很快终结了EJB的统治地位,因此在一定程度上,POJO这个名字加速了EJB的消亡。

起名字这件事看似不难,但是要经过深思熟虑,取出名副其实、表达性好的名字并不是一件很容易的事。

命名为什么难呢?因为命名的过程本身就是一个抽象和思考的过程,在工作中,当我们不能给一个模块、一个对象、一个函数,甚至一个变量找到合适的名称的时候,往往说明我们对问题的理解还不够透彻,需要重新去挖掘问题的本质,对问题域进行重新分析和抽象,有时还要调整设计和重构代码。因此,好的命名是我们写出好代码的基础。

就像Stack Overflow的创始人Joel Spolsky所说的:“起一个好名字应该很难,因为一个好名字需要把要义浓缩在一到两个词中。(Creating good names is hard, but it should be hard, because a great name captures essential meaning in just one or two words.)”

此外,Martin Fowler也表示过,他最喜欢的一句谚语是:“在计算机科学中有两件难事:缓存失效和命名。(There are only two hard things in Computer Science: cache invalidation and naming things.)”

代码即文档,可读性好的代码应该有一定的自明性,也就是不借助注释和文档,代码本身就能显性化地表达开发者的意图。这种自明性在很大程度上依赖于我们对问题域的理解,以及命名是否合理。

通常,如果你无法想出一个合适的名字,很可能意味着代码“坏味道”、设计有问题。这时可以思考一下:是不是一个方法里实现了太多的功能?或者类的封装内聚性不够?又或者是你对问题的理解还不够透彻,需要获取更多的信息?

变量名应该是名词,能够正确地描述业务,有表达力。如果一个变量名需要注释来补充说明,那么很可能说明命名就有问题。

int d; // 表示过去的天数

观察上面的命名,我们只能从注释中知道变量d指的是什么。如果没有注释,阅读代码的人为了知道d的含义,就不得不去寻找它的实例以获取线索。如果我们能够按照下面这样的方式命名这个变量,阅读代码的人就能够很容易地知道这个变量的含义。

int elapsedTimeInDays;

类似的还有魔术数,数字86400应该用常量SECONDS_PER_DAY来表达;每页显示10行记录的,10应该用PAGE_SIZE来表达。

这样做还有一个好处,即代码的可搜索性,在代码中查找PAGE_SIZE很容易,但是想找到10就很麻烦了,它可能是某些注释或者常量定义的一部分,出现在不同作用的各种表达式中。

函数命名要具体,空泛的命名没有意义。例如,processData()就不是一个好的命名,因为所有的方法都是对数据的处理,这样的命名并没有表明要做的事情,相比之下,validateUserCredentials()或者eliminateDuplicateRequests()就要好许多。

函数的命名要体现做什么,而不是怎么做。假如我们将雇员信息存储在一个栈中,现在要从栈中获取最近存储的一个雇员信息,那么getLatestEmployee()就比popRecord()要好,因为栈数据结构是底层实现细节,命名应该提升抽象层次、体现业务语义。合理的命名可以使你省掉记住“出栈”的脑力步骤,你只需要简单地说“取最近雇员的信息”。

类是面向对象中最重要的概念之一,是一组数据和操作的封装。对于一个应用系统,我们可以将类分为两大类:实体类和辅助类。

实体类承载了核心业务数据和核心业务逻辑,其命名要充分体现业务语义,并在团队内达成共识,如Customer、Bank和Employee等。

辅助类是辅佐实体类一起完成业务逻辑的,其命名要能够通过后缀来体现功能。例如,用来为Customer做控制路由的控制类CustomerController、提供Customer服务的服务类CustomerService、获取数据存储的仓储类CustomerRepository。

对于辅助类,尽量不要用Helper、Util之类的后缀,因为其含义太过笼统,容易破坏SRP(单一职责原则)。比如对于处理CSV,可以这样写:

 CSVHelper.parse(String)
 CSVHelper.create(int[])

但是我更建议将CSVHelper拆开:

 CSVParser.parse(String)
 CSVBuilder.create(int[])

包(Package)代表了一组有关系的类的集合,起到分类组合和命名空间的作用。在JavaScript的早期阶段,因为缺乏明确的分包机制,导致程序(特别是大型程序)很容易陷入混乱。

包名应该能够反映一组类在更高抽象层次上的联系。例如,有一组类Apple、Pear、Orange,我们可以将它们放在一个包中,命名为fruit。

包的命名要适中,不能太抽象,也不能太具体。此处以上面提到的水果作为例子,如果包名过于具体,比如Apple,那么Pear和Orange放进该包中就不恰当了;如果报名太抽象,称为Object,而Object无所不包,这就失去了包用来限定范围的作用。

这里说的模块(Module)主要是指Maven中的Module,相对于包来说,模块的粒度更大,通常一个模块中包含了多个包。

在Maven中,模块名就是一个坐标: <groupId, artifactId>。一方面,其名称保证了模块在Maven仓库中的唯一性;另一方面,名称要反映模块在系统中的职责。例如,在COLA架构中,模块代表着架构层次,因此,对任何应该遵循COLA规范的应用都有着xxx-controller、xxx-app、xxx-domain和xxx-Infrastructure这4个标准模块。更多内容请参考12.3节。

保持命名的一致性,可以提高代码的可读性,从而简化复杂度。因此,我们要小心选择命名,一旦选中,就要持续遵循,保证名称始终一致。

每个概念对应一个词,并且一以贯之。例如,fetch、retrieve、get、find和query都可以表示查询的意思,如果不加约定地给多个类中的同种查询方法命名,你怎么记得是哪个类中的哪个方法呢?同样,在一段代码中,同时存在manager、controller和handler,会令人感到困惑。

因此在项目中,作者通常按照表1-1所示的约定,保持命名的一致性。

表1-1 方法名约定

CRUD操作

方法名约定

新增

create

添加

add

删除

remove

修改

update

查询(单个结果)

get

查询(多个结果)

list

分页查询

page

统计

count

遵守对仗词的命名规则有助于保持一致性,从而提高代码的可读性。像first/last这样的对仗词就很容易理解;而像fileOpen()和fClose()这样的组合则不对称,容易使人迷惑。下面列出一些常见的对仗词组:

很多程序中会有表示计算结果的变量,例如总额、平均值、最大值等。如果你要用类似Total、Sum、Average、Max、Min这样的限定词来修改某个命名,那么记住把限定词加到名字的最后,并在项目中贯彻执行,保持命名风格的一致性。

这种方法有很多优点。首先,变量名中最重要的部分,即为这一变量赋予主要含义的部分应位于最前面,这样可以突出显示,并会被首先阅读到。其次,可以避免同时在程序中使用totalRevenue和revenueTotal而产生的歧义。如果贯彻限定词后置的原则,我们就能收获一组非常优雅、具有对称性的变量命名,例如revenueTotal(总收入)、expenseTotal(总支出)、revenueAverage(平均收入)和expenseAverage(平均支出)。

需要注意的一点是Num这个限定词,Num放在变量名的结束位置表示一个下标,customerNum表示的是当前客户的序号。为了避免Num带来的麻烦,我建议用Count或者Total来表示总数,用Id表示序号。这样,customerCount表示客户的总数,customerId表示客户的编号。

为什么要统一业务语言呢?试想一下,如果你每天与业务方讨论的是一种编程语言,而在团队内部交流、设计画图时使用另一种语言,编写的代码中体现出来的又是毫无章法、随意翻译的内容,这无疑会降低代码的表达能力,在业务语义和文档、代码之间出现了一条无形的鸿沟。

统一语言就是要确保团队在内部的所有交流、模型、代码和文档中都要使用同一种编程语言。实际上,统一语言(Ubiquitous Language)也是领域驱动设计(Domain Driven Design,DDD)中的重要概念,在7.4.1节中会有更加详细的介绍。

有些技术语言是通用的,业内人士都能理解,我们应该尽量使用这些术语来进行命名。这些通用技术语言包括DO、DAO、DTO、ServiceI、ServiceImpl、Component和Repository等。例如,在代码中看到OrderDO和OrderDAO,马上就能知道OrderDO中的字段就是数据库中Order表字段,对Order表的操作都在OrderDAO里面。

有人说“代码是最好的文档”,我并不完全赞同,我认为更准确的表达应该加上一个定语:“好的代码是最好的文档”。也就是说,代码若要具备文档的功能,前提必须是其本身要具备很好的可读性和自明性。所谓自明性,就是在不借助其他辅助手段的情况下,代码本身就能向读者清晰地传达自身的含义。

我们可以通过添加中间变量让代码变得更加自明,即将计算过程打散成多个步骤,并用有意义的变量名来命名中间变量,从而把隐藏的计算过程以显性化的方式表达出来。

例如,我们要通过Regex来获得字符串中的值,并放到map中。

  Matcher matcher = headerPattern.matcher(line);
  if(matcher.find()){
      headers.put(matcher.group(1), matcher.group(2));
  }

用中间变量,可以写成如下形式:

    Matcher matcher = headerPattern.matcher(line);
    if(matcher.find()){
       String key = matcher.group(1);
       String value = matcher.group(2);
       headers.put(key, value);
    }

中间变量的这种简单用法,显性地表达了第一个匹配组是key,第二个匹配组是value。只要把计算过程打散成一系列良好命名的中间值,不透明的语义自然会变得透明。

使用设计模式语言也是代码自明的重要手段之一,在技术人员之间共享和使用设计模式语言,可以极大地提升沟通的效率。当然,前提是大家都要理解和熟悉这些模式,否则就会变成“鸡同鸭讲”。因此,我们有必要在命名上就将设计模式显性化出来,这样阅读代码的人能很快领会到设计者的意图。

例如,Spring里面的ApplicationListener就充分体现了它的设计和用处。通过这个命名,我们知道它使用了观察者模式,每一个被注册的ApplicationListener在Application状态发生变化时,都会接收到一个notify。这样我们就可以在容器初始化完成之后进行一些业务操作,比如数据加载、初始化缓存等。

又如,在进行EDM(邮件营销)时要根据一些规则过滤掉一些客户,比如没有邮箱地址的客户、没有订阅关系不能发送邮件的客户、3天内不能重复发送邮件的客户等。

下面是一个典型的pipeline处理方式,责任链在处理该问题上是一个很好的选项,FilterChain这个名字非常恰当地表达出了作者的意图,Chain表示用的是责任链模式,Filter表示用来进行过滤。

FilterChain filterChain = FilterChainFactory.buildFilterChain(
            NoEmailAddressFilter.class, 
            EmailUnsubscribeFilter.class, 
            EmailThreeDayNotRepeatFilter.class);

//具体的Filter
public class NoEmailAddressFilter implements Filter {
    @Override
    public void doFilter(Object context, FilterInvoker nextFilter) {
        Map<String,  Object> contextMap = (Map<String,  Object>)context;
        String email = ConvertUtils.convertParamType (contextMap. get ("email"), String.class);
        if(StringUtils.isBlank(email)){
            return;
        }
        nextFilter.invoke(context);
    }
}

如果注释是为了阐述代码背后的意图,那么这个注释是有用的;如果注释是为了复述代码功能,那么就要小心了,这样的注释往往意味着“坏味道”(在Martin Fowler的《重构:改善既有代码的设计》一书中,注释就是“坏味道”之一),是为了弥补我们代码表达能力的不足。就像Brian W.Kernighan说的那样:“别给糟糕的代码加注释——重新写吧。”

1.不要复述功能

为了复述代码功能而存在的注释,主要作用是弥补我们表达意图时遭遇的失败,这时要考虑这样的注释是否是必需的。如果编程语言足够有表达力,或者我们擅长用代码显性化地表达意图,那么也许根本就不需要注释。因此,在写注释时,你应该自省自己是否在表达能力上存在不足,真正的高手是尽量不写注释。

在JDK的源码java.util.logging.Handler中,我们可以看到如下代码:

   public synchronized void setFormatter(Formatter newFormatter) {
        checkPermission();
        // Check for a null pointer:
        newFormatter.getClass();
        formatter = newFormatter;
    }

如果没有注释,那么可能没人知道“newFormatter.getClass();”是为了判空,注释“Check for a null pointer”就是为了弥补代码表达能力的失败而存在的。如果我们换一种写法,使用java.util.Objects.requireNonNull进行判空,那么注释就完全是多余的,代码本身足以表达其意图。

2.要解释背后意图

注释要能够解释代码背后的意图,而不是对功能的简单重复。例如,我们在一个系统中看到如下代码:

   try {
        //在这里等待2秒
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        LOGGER.error(e);
    }

这里的注释和没写是一样的,因为它只是对sleep的简单复述。正确的做法应该是阐述sleep背后的原因,比如改写成如下形式就会好很多。

   try {
        //休息2秒,为了等待关联系统处理结果
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        LOGGER.error(e);
    }

或者直接用一个private方法将其封装起来,用显性化的方法名来表达意图,这样就不需要注释了。

private void waitProcessResultFromA( ){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            LOGGER.error(e);
        }
    }

“他山之石,可以攻玉”,当你不知道如何优雅地给变量命名时,可以使用命名工具,快速搜索大型项目中的变量命名,看其他大型项目源码是如何命名的,哪些变量名的使用频率高。特别是对于英语非母语的我们,命名工具会非常有用。

我们可以在IDE中安装一个搜索插件,便于搜索海量的互联网上的开源代码。举例说明,如图1-1所示,作者一般会安装一个叫作OnlineSearch的插件,插件里自带了像SearchCode这样的代码搜索工具,也可以自己配置像Codelf这样的代码搜索工具。

图1-1 OnlineSearch插件

命名在软件设计中有着举足轻重的作用,命名的力量就是语言的力量,好的命名可以保证代码不仅是被机器执行的指令,更是人和人之间沟通的桥梁。

命名的重要性不仅体现在提升代码的可读性上,有意义的命名更能够引导我们更加深入地理解问题域,理清关键业务概念,进行合理的业务抽象,从而设计出更加符合业务语义、易于理解的系统。

因此,每一个程序员都应该掌握一套命名的方法论:了解如何给软件制品(Artifact,包括Module、Package、Class、Function和Variable)命名,如何写注释,如何让代码自明地表达自己,以及如何保持命名风格的一致性。


相关图书

元宇宙中的硬科技
元宇宙中的硬科技
AIGC提示词美学定义
AIGC提示词美学定义
专利写作:从创意到变现
专利写作:从创意到变现
架构思维:从程序员到CTO
架构思维:从程序员到CTO
产品经理方法论——构建完整的产品知识体系(第2版)
产品经理方法论——构建完整的产品知识体系(第2版)
开发者关系实践指南
开发者关系实践指南

相关文章

相关课程