神在细节之中

碎碎念

这其实是《代码整洁之道》的学习笔记,起了一个看起来很厉害的名字。

之前读过《如何阅读一本书》,但是读完之后还是没什么收获,只懂读,不懂消化。前几天看了这篇文章《一年读100本书的技能,你1分钟就能学得会》,有恍然大悟状。怪不得我一直读书很慢,很少,读了一点就不想读,原来我是一直用的学生考试时代的基础阅读,这种是最低级的阅读方式,一个字一个字读,漫无目的地读。现在我改用了检视阅读分析阅读,不求甚解,跳过了一些无用章节,比如 Java 相关的,测试相关的,所以一个下午就把大半本书给啃下来了,效率十分高。另外,书的配图很棒

我只挑自己仍需注意的点记录下来,并结合自己的经验谈谈。而一些最基本的,SRP、DRY 等是老生常谈的问题,就不赘述。特别是 DRY,懂的人很多,敲代码的时候能想到的人就少了,想到并且做好的人就更少。工程里面经常看到这段代码复制过来,那段代码复制过去,是最恶心的。这个原则值得用一生去践行、体会并提高

前言

阅读本书有两种原因:第一,你是个程序员;第二,你想成为更好的程序员。

我是第二种。希望有一天,我写的代码,能成为一种艺术

衡量代码质量的唯一有效标准: WTF/min

《代码整洁之道》最有道理的一句话,即代码阅读者每一分钟爆出的”What The Fuck”数量,哈哈哈

使用可搜索的名称

名称长短应与其作用域大小相对应

比如,在一个简单的 for 循环里,使用简单的 i 来命名下标就很足够了;而如果一个变量的作用域很大,那么其命名应该易于搜索(名称长是为了易于搜索)

不要写多余的注释,别让你写的注释比看代码还累

与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那段糟糕的代码

确实,这点我做得不够,有时候为函数的声明写的注释太多,需要改进

如果可以,尽量别注释代码

其他人不敢删除注释掉的代码。他们会想,代码依然放在那儿,一定有其原因,而且这段代码很重要,不能删除

一方面,我看到注释掉的代码真的很讨厌,因为我是极简主义,能少写一行代码甚至一个空格就少写,一坨被注释的代码放在那里会影响阅读体验

但是另一方面我又不完全同意这个观点,作者说现在已经有版本管理,所以没必要保留注释的代码,但是谁修改代码的时候没事会去查看之前的版本呢?比如,上次我想为频道列表页添加缓存逻辑,但是发现之前有人加过了,但是被注释了,询问之后才发现直接为这个页面添加缓存是有坑的,于是这次添加缓存就顺利绕过了这个坑。那么如果没有这段注释的代码,我可能就会直接跳进这个坑了,因为我修改之前并不可能漫无目的去查看 SVN

不要刻意去水平对齐

刻意的水平像是在强调不重要的东西,分散读者注意力

1
2
3
4
@property (nonatomic, assign) int                   leftNum;
@property (nonatomic, copy) NSString *dataKey;
@property (nonatomic, retain) QLJCEShareItem *shareItem;
@property (nonatomic, retain) QLJCEONAPosterTitle *tips;

下面这段代码更好,因为注意力能更集中在变量的类型上

1
2
3
4
@property (nonatomic, assign) int leftNum;
@property (nonatomic, copy) NSString *dataKey;
@property (nonatomic, retain) QLJCEShareItem *shareItem;
@property (nonatomic, retain) QLJCEONAPosterTitle *tips;

要编写简洁的代码,必须先写肮脏的代码,然后再清理它

问题是写完了肮脏的可运行的代码之后,别着急开始下一个任务而忘记改进它

使用多个函数,通常优于向单个函数传递某些代码来选择函数行为

先看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)pay:(BOOL)isVip
{
// 通用代码

if (isVip)
{
// VIP处理代码
}
else
{
// 普通成员处理代码
}
}

首先作者认为这种代码不好,因为调用的时候应该去记住传递的 BOOL 代表什么意思,应该改为以下这种

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)pay
{
// 通用代码

if ([self isVip])
{
// Vip处理代码
}
else
{
// 普通成员处理代码
}
}

或这种

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)vipPay
{
// 通用代码

// VIP处理代码
}

- (void)commonPay
{
// 通用代码

// 普通成员处理代码
}

首先方法1并不是处处可行,因为对于 Vip 的判断是否合适放在本类要根据具体情况讨论,合适则方法1可行

而方法2我不敢苟同,因为这首先违背了一种重要的原则——DRY,通用代码在2处地方重复,维护起来十分恶心

同时,在 IDE 日益强大的今天,多写一个参数并不对程序员的记忆有所挑战,即使有,也比重复代码好

代码要尽可能具有表达力

1
2
3
4
if (curTime >= startTime && curTime <= endTime && !hasShown)    // 是否应该展示
{

}

应该处理为下面这种,这比写注释好

1
2
3
4
5
6
7
8
9
- (BOOL)shouldShow
{
return (curTime >= startTime && curTime <= endTime && !hasShown);
}

if ([self shouldShow])
{

}

得墨忒耳定律(最少了解原理)

A 有一个属性叫 B,B 有一个属性叫 C,C 有一个 doSomething 的函数。现在需要在 A 里面调用 C 的函数。有以下2种写法

方法1,B 把属性 C 暴露在头文件,让 A 可以直接读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// A.m
[self.b.c doSomething];

// B.h
@property (nonatomic, strong) C *c;

// C.h
- (void)doSomething;

// C.m
- (void)doSomething
{
NSLog(@"Hello World!");
}

方法2,B 把 C 中的函数封装成接口提供给外部使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// A.m
[self.b doSomething];

// B.h
- (void)doSomething;

// B.m
- (void)doSomething
{
[self.c doSomething];
}

// C.h
- (void)doSomething;

// C.m
- (void)doSomething
{
NSLog(@"Hello World!");
}

曾经,我不知道方法1和方法2孰优孰劣,甚至更倾向于方法1,因为代码量更少,但读了这本书后,我明白了,方法2更好,为什么?

  • 方法1违背了得墨忒耳定律,C 不是 A 的属性,而 A 居然要去了解 C 里面的函数
  • 假如不止A,还有A1,A2,A3…直接调用了 C 的函数,那么有一天,当 C 修改 doSomething 这个函数名,或者想在 B 和 C 之间再添加一个类 D 的时候,此时方法1就需要修改很多地方,而方法2只需要修改 B 文件
一杯冰可乐