记住,好的代码并不是说你写的有多么高深莫测,多么华丽,代码是给开发者来阅读的,能够让人更好的读懂、能够更好的实现业务、能够展示更加清晰优雅的逻辑的代码,就是好代码。 本篇章是小简结合《代码整洁之道》以及自身经验阐述的一些方法,欢迎关注我的公众号:栈堆溢出。
名副其实
使用正确的名称来描述你所定义的内容非常重要! 有人说:”思考名称的时间太浪费了,与其花时间想名称,不如直接开始快速编码。“ 我想说的是,选取一个好的名称可能需要一定的时间,但是这样绝对可以节省你之后维护和其他人阅读的时间成本,作为开发者,要有长远的目光,不能只顾当前,需要综合长远发展考虑。
千万莫要只看眼前发展,这样应了这一句话: 井蛙不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。——《庄子.秋水》
一旦发现了更好的名称,就应该换掉旧的名称,如果某一天,你的同事或者其他人来询问你某个属性或者函数等命名是什么意思,那么你就应该思考应该怎样换一个更加容易看懂并且贴合业务功能的名称了。
一个好的命名往往比注释更重要,我们不能直接依赖注释,注释主要是对复杂实现和无法简短描述的功能的说明补充,规范的doc
注释同时也是为了生成对应语言的doc
而考虑。
一个好的函数、变量或者类名称它应该能够告诉你它为什么存在,它做什么事情,如何使用。 如果名称需要使用注释来解释和补充说明,那它就不是名副其实的,除非过于复杂无法简单描述。
比如,当你想要描述一个简单的从数据库直接拿到数据的函数,你可以使用getXXXByDB
,当然,你也可以有一套自己或者公司的命名规范,但是一切都是为了让命名更加清晰。
如果上述功能涉及到了复杂的查找操作,不妨将名称改为findXXXByDB
,你也可以选择将中间的by
改为其他词汇,如findXXXInDB
。
如果你只是getXXX
,或许这并不详细,如果代码量较少并且清晰的话,自然没问题,如果场景复杂,你需要从很多不同的数据库中查找相同概念的数据,那么这个名称就需要加上你的操作的具体指向,如getNameInCache
、getNameInDB
、findNameInCookieBasisUID
。
当然,如果名称过长也不太好,尽可能思考如何简短并有效的描述,同时结合上下文(比如函数结合类中其他逻辑和属性)来尽可能减少不必要的场景描述词。
比如你已经在UserEntity
类中了,那么属性命名只需要type name
就可以,类名已经可以说明场景了,如:long id
在UserEntity
中我们自然知晓他是属于User
的id
。
但是,如果你说,其他开发者英语不好,那么就有些为难了,因为中文编程至少现在还不够普及与成熟,更别提大范围的企业级应用了,所以作为开发者,我们还是需要学习学习自己有需要的语言,比如英语。
还有!尽可能的避免魔法值或者魔法状态,将魔法值尽可能的内敛,向外抛出更加语义的命名,比如if(xxx == 1)
改为if(xxx.isXXX)
,事实上,你可以尽可能避免使用==
来作为逻辑判断语法中的条件,而是选择将它的判断内敛为一个直接的具有语义的方法,这样可能可读性会增强。
如果你不知道魔法值是什么,我相信你聪明的大脑一定可以获取到答案,比如?浏览器!
避免误导
请避免留下掩藏代码本意的错误线索,比如你感觉某个单词不好看而替换一个语义更加薄弱却美观的词汇,这并不是说不需要注意代码整洁与美观,而是应当可读性优先在前。
又或者你想表示一组账户信息的集合,使用accountOfList
,但是实际上集合是一个不可重复集合,那么实际上你使用的类型是Set
,那么你需要精确的描述它,应当改为accountOfSet
。
还有!**除非某个概念的缩写是独有的,否则不要使用它!**比如你使用cos
来表示余弦函数,但是不确定是否其他的开发者会给你误解为角色扮演的那个cos
,又或者腾讯云的对象存储产品cos
。
有意义的区分
对于有些数据,我们给他命名为xxxData
或者xxxInfo
,这种命名实际上是冗余的,因为他作为一个属性值,我们已经知道xxx是一个数据了,而Data
和Info
无疑是在告诉我们,他是一个数据,这是没有必要的,当然,如果你需要在某些特殊场景下强调他是一个数据的话,可以这样做。
不说废话会让命名更加简洁和内聚,就像table
一词永远不能出现在表名称中,因为你已经知道它是一个表了。
你会觉得
NameString
会比Name
更好吗? 对于上文的accountOfSet
,如果他已经很明确的可以知道他是一个Set
的话,那么也没有必要使用OfSet
,尤其是有强大的IDE类型反应的情况下。
名称标准可读
请不要使用非标准的词汇或者由社会发展而衍生出来的网络新词汇,除非他被编入了标准字典。
请不要使用自造词汇或者非标准的组合词汇,如果你不需要考虑其他国家开发者的阅读和使用,那么你可以适当使用中式英文词汇,但是最好不要这样。
比如,xxxInEveryDay
你最好不要写成xxxInDayDay
。
使用可快速索引的名称
当我们需要在很多地方判断一个属性是否为x(x是一个数字)时,我们如果直接使用数字,比如if(today == 7)
。
那么当你去代码中搜索或者你的项目文档中搜索数字7
,也许会引来一些麻烦,比如说文档中的第7页、第7段、7日后,这些词汇会干扰你快速索引到代码对应位置,而如果使用NumberOfSeven
或者NUMBER_OF_SEVEN
,那么我相信没人会在文档中的正常语言中使用它,他只会出现在代码变量中。
其他情况举一反三即可。
避免使用编码
请不要将类型或者作用域编进名称中,这增加了解码的负担。
比如团队定义一些规则(成员前缀):使用_
前缀表示危险参数,使用v 数字
开头表示业务某版本下的某个属性。
这样会给非本团队或者新加入团队的成员带来更多的阅读负担,除非你有需要。
又或者匈牙利语标记法,许多 Windows 程序员都使用“匈牙利标记法”作为变量命名约定, 这是为了纪念具有传奇色彩的微软程序员 Charles Simonyi。
这种标记法非常简单,其基本原则为: 即变量名(标识符)以一个或者多个小写字母开始,这些字母表示变量的数据型态 ,而我们其实应该避免这样做。
另外,对于使用使用I
开头来标记接口,这个因个人或者团队喜好而定义,有人喜欢使用I
来标记接口,有人不喜欢这样做,他们或许更喜欢使用Impl
来表示实现,所以这个无需争议。
避免思维映射
实际上和上文”不要自造词汇“一个概念,不要让代码的阅读者去理解你的命名是说明含义,而是应该倾向于让阅读者能够直接理解,命名需要语义通达,需要标准普遍。
类名
类名称和对象名称应当是名词或者名词短语,至少不应该是动词。
另外,一个纯的entity
类不应该以复数命名,这是不应该的,比如你不应该使用Users
来命名一个用户实体类,而是应该在使用它对应的对象时再去表示复数,如查询数据库后返回了一个User
列表,此时我们使用类似List
或者List
类型来存储多个User
,此时这个容器才应该命名为users
。
避免无意义的词汇,上文已经说过了,Data
或者Info
这种词汇是不必要的。
对于Manager
、Processor
这种词汇,只有在贴近业务的用于处理逻辑的类命名中才去使用,当然,也不是所有人都喜欢这样干,主要在于自己或者团队的编码风格与约束规则。
方法名称
方法名称应该是动词或者动词短语,如queryUserByName
、saveUser
或postPayment
,尽可能标准的描述行为与动作。
对于构造函数,可以的话,也可以尝试将默认构造器私有,使用一个纯粹的方法名称来构造对象,如:new Object(user)
不如禁止直接使用new Object
,同时向外部抛出 Class.buildFromUser(user)
,这样构造更加清晰明了。
描述操作的词汇需要单一对应
上文中使用find
、get
、query
这些动词来命名,但是如果一个相同概念的操作用到多种动词,那么就不太好了,应该让这些词汇功能单一,比如数据库原子(不做处理)查询只用get
,而逻辑查询和复杂查询则只用find
或者query
,因为get
操作看起来太过于纯粹,而实际上我们对于复杂数据的索引实则不是直接获取,而是查找筛选,当然,如果你愿意使用其他方式表达这种关系或者不在意这么细节,那就没有多大关系,综合当前环境和条件来考虑编码规范。
包括上文的类名中的manager
和processor
这些词汇也是一样,在于综合可虑,不能一棍子打死,说这样不行或者这样可以,他对于一些刚开始熟悉你代码的开发者可能会带来理解上的成本,比如他会想Manager
结尾的类是做什么的,此时你如果提供一份说明文档,那么他会更方便阅读你的代码,而不是像以前所有的逻辑全是Service
,这样Servcie
的职责概念太过于广泛,容易在命名上模糊职责而不能准确体现,那么这样看来,定义一些新的规约是一把双刃剑,合理使用才是王道。
不要使用双关语
比如add
这个词汇,Number.add()
是将数字相加的含义,但是如果有些开发者想保持风格统一,就会在一些类中将元素添加到容器的方法命名为add
,那么此时实际上不是相加的意思,应该用append
更加合适。
迎合当前技术领域
对于大部分的后端程序员,我相信对于JobQueue(工作任务的队列)的意义一眼就能秒懂,这个名称放在前端貌似也没问题,但是如果我改为EventQueue(这里是笔者自己觉得在前端使用event词汇概念更多,job我在前端几乎没有使用过,因为前端本身所有操作都是基于浏览器事件循环,我更乐意理解为每一个操作都是一个事件而不是一个任务)或许对于前端程序员会更加易懂。
以及在C/C
中使用包含pointer
单词命名我不会觉得有问题,但是在Java
中如果出现了一个包含pointer
词汇的命名,我或许真的会有些疑惑。
迎合业务领域命名
不多说,财务系统会有自身一套成体系的术语,OA
系统也会有一套自身体系的术语,对于专业领域开发,迎合术语命名能让业务和开发交流更加通畅。
结合语境
比如你在其他逻辑中表示地址逻辑相关的属性时,你可以带上源业务的场景或者领域描述,比如在一个业务逻辑中包含用户填写的地址的用户名称和用户名称,那么你就可以使用adderssUserName和userName来命名,这样更好区分。
总而言之,冲突的时候需要更详细准确的描述,不要含糊不清。
但是也注意,不要使用无畏的语境命名,只有该需要的时候才使用,没有冲突或者在自身领域逻辑中的情况下,何必使用它,比如在AdderssService
中使用AdderssUserName
命名。
尾述
不要只参看书中或者文章中所说的,结合业务与环境以及场景来做对应的定义才是最好的,很多时候,这些概念和内容并非绝对,而是抛砖引玉。
This was written on the evening of 2024.05.19 By JanYork.