iOSiOS备忘录Swift学习

Clean Code 告诉你什么是好代码

2017-09-30  本文已影响412人  CatchZeng

前言

最近在团队推行Code Review,遇到一个头痛的问题。当向伙伴的代码提一个comment时,他们不解为什么需要这样改。细细想来,是他们不知道何为好代码,也不知道自己的代码有哪些 "坏味道"。因此,分享了几期Clean Code,团队受益良多,故成此文。

Clean Code

由于Clean Code篇幅较长,故先安排如下我认为较为重要的几点:

命名

命名有许多规则,但总结起来就是 “有意义” 才是硬道理。

名副其实

Int d;//逝去的时间

这句代码的问题在于d没有表达好逝去的时间这个概念,故需要注释。请记住“名副其实就不需要注释”

Int elapsedTime;

再来看个例子


谁都很难猜出其意义,看看小优化后的结果

基本看清了意义,这就是命名的重要性。细心的朋友还会发现这段代码的一些瑕疵 :这里的4是什么鬼?习惯性我们管它叫“魔法数字”

还是觉得有点问题,再优化

对比下最早的代码,相信你会有感觉了。

避免误导

生活中的场景也常出现在Code中,看下图,你的Code是否也出现这样的尴尬呢?那就Make it clean



是否傻傻分不清了呢? 再来个

accountList

我知道你想说,这有什么问题。是的,如果你不是做Java开发,不会知道链表叫List,所以如果你不是用链表存储account,请不要用其修饰,或许这个时候你使用acountGroup会更好些。
该点需要在具体开发环境下因地制宜

有意义的区分

Product
ProductInfo
ProductData

可以想象下,当一个项目中同时出现以上三个类的时候,你是如何区分开的,反正我是没有这个能力。类似的还有

game
theGame
name
nameString

分享时,伙伴说nameString有什么问题。我反问说难道你的名字会是Float型的?你懂了吧。

前缀

m_desc

有人提出加m前缀表示该变量为私有变量。
我想说:你的变量很多?需要区分私有的还是公有的?如果你的变量很多,那就要想想是不是没设计好类,没有遵循单一职责原则,另外私有和公有变量编译器会帮忙高亮显示区分的,不需要自己来区分(若某些编译器无此特性,怪编译器去)。

命名惯性

命名需要注重词性
类名:名词 or 名词短语
方法名: 动词 or 动词短语

每个概念对应一个词

在一个模块中不要使用两个相似的概念来表达不同的操作。我在一份代码中看到过一个类中同时出现以下三个词打头的方法

fetch
get
retrieve

请问那个才是真的获取值的方法?我实在分不清。

使用领域名称

使用领域命名能让伙伴更明白你的程序结构(关于领域这个概念,不熟悉的可以看下一本书叫 《领域驱动设计》,俗称DDD)
举个例子,比如你使用访问者模式来构建用户系统,那么

AccountVisitor

就显得明确、易懂

抵制缩写诱惑

缩写需要注意,适当的缩写是可以的,但是要保证缩写后的词语仍然能表达其本意。举个有意思的例子

ABCDEFG

这也是个缩写,但是乍看这个真不知道是什么的缩写,直接公布答案吧

小结

命名是永恒的难题,我提几个建议吧

一些借鉴词

函数(方法)

函数的第一条规则是要短小,第二条规则还是要短小。

短小

那到底多短合适呢?历史上出现过几个标准

代码短小,好处自然很多。

只做一件事

函数应该做一件事。做好这件事。只做一件事。
那么如何判断只做一件事?


请问这个函数做了几件事?伙伴的答案是
1.判断是否为测试页面
2.加入测试数据
3.渲染页面

你的答案是多少呢?其实答案是只做了一件事,主要是没有看清
一件事 OR 一件事的多个步骤,关于这点,大家要好好体会。

另外一个判断是否只做一件事的好方法: 是否能再次分离出新函数

同一个抽象层级

关于层级,比较难讲明,直接看例子吧



再看一个版本



你会发现看第二个版本的代码,明显舒服很多。因为第二的版本的三句代码都在同一个层级。而第一个版本的代码中的第一句是设置roundView的某个属性,但是最后一句却是在设置bubbleView,层级不同(roundView与bubbleView才是同层级)

使用描述性名称

如果长一点的名称可以更加清晰,不要犹豫,用清晰的吧(注意是要有意义的)

calculate
calculatePrice

相比起来calculatePrice就好很多。
再来看个例子

addComment
addCommentAndReturnCount

你不是说长一点更清晰吗,那addCommentAndReturnCount很好吧。
关于这点大家要注意,如果你需要用and、or之类的介词来修辞函数时,要考虑下你是否违背了单一职责原则

参数个数

0个最好,
1个次之,
2个还行,
3个以上不是太好了。
参数与函数名位于不同的抽象层级,它要求你必须了解目前并不特别重要的细节。
解决办法有许多,比如某些场景可使用DTO

嵌套层次、分支过多


嵌套、分支过多会让代码变得很难理解,解决的办法有如下:

分割指令与查询


set这个函数很不明确的是到底是设置成功了返回true,还是名字存在返回true,但真正的问题在于,它是个指令但是掺杂了查询的功能。



将查询和命令分离后,代码便清晰很多了。

小结

如何写出好的函数

注释

“别给糟糕的代码加注释 — 重新写吧。” –Brian & P.J.
“注释总是一种失败” –Bob

用代码来阐述



感受两段代码会发现代码即注释的美

坏注释

先来看看什么是坏的注释

喃喃自语

这注释绝对是给自己看的

多余的注释

解释跟没解释一样,不如代码来的简单明了

误导性的注释

你在误导吧

循规式注释

这个一定要注意,循环式的注释完全多余(除了做sdk、开源)

括号后的注释

如果括号后需要注释,只表明你这段代码太长了,需要做的不是加注释,而是将它变短。

归属于署名

Git、SVN知道是你提交的,不用这样刷存在感

注释掉代码

注释掉的代码,只会让修改你代码的人蒙圈,如果你觉得这段代码有可能以后会用,也不用担心,Git、SVN会帮你找回来

信息过多

面向对象讲究,暴露操作,隐藏实现,如果你还要注释这些信息,表示你没有封装好。这些信息,可考虑放个链接或者其他的简短提示,太长的注释,别人懒得读、也难读懂


好注释

看了那么多坏注释,来看看什么是好的注释

法律信息
提供信息
对意图的注释
阐释
警示
TODO注释
放大

对象、数据结构

数据抽象


将变量设置为私有(Private),主要是不想让其他人依赖这些变量。所以,不要随便给变量添加赋值方法和取值方法(set/get方法),这样其实是把私有变量公之于众。
隐藏变量和实现,并不是在变量与外界之间放一个函数层那么简单。隐藏关乎抽象。
类并不简单地用赋值方法和取值方法将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现而能操作数据本体。
要以什么方式呈现对象所包含的数据,需要做严肃的思考。随便加赋值方法和取值方法,是最坏的选择。

数据、对象的反对称性

前者是一种过程式代码,后者是面向对象式代码。我们会发现假如要添加一个新形状的话,后者绝对是不错的选择,因为以上代码都不需要修改,只需写一个新形状类,这符合“开放--封闭”原则。然而假如添加一个计算周长的功能的话那就杯具了,因为这样子每个形状类都得改动。但是假如是用过程式代码的话只需要添加一个新函数。

过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。
面向对象代码便于在不改动既有函数的前提下添加新类。
一切都是对象只是一个传说

组织

短小

函数的短小标准是行数,那类是什么呢?答案是职责
类需要遵循单一职责原则

内聚


如以上代码,内聚性高,除了size方法外,其他方法都使用了两个实例变量。
内聚:模块内部各个元素彼此结合的紧密程度(类中方法和变量间的结合程度)
保持内聚会得到许多短小的类
当一个类丧失内聚性时我们应当拆分它

总结

Clean Code能帮助团队构建代码质量体系,有助于开发的各个环节(静态分析、持续集成、Code Review...)。当然,对个人的能力提高也很有好处,建议大家都应该熟悉。等团队Code Review一段时间后,有其他收获的话,再给大家分享。
预祝大家国庆节快乐!

上一篇下一篇

猜你喜欢

热点阅读