优美的代码首先要遵循一些规范,比如等号前后要有空格(x = 1 ),比如要有一致的缩进。
做到这些后,还要让代码好维护。所谓的好维护,就是要改起来方便(新
需求永远在召唤)。
如果一个代码,拿过来,宁可重写,也不去动,那这样的代码肯定有问题。
提高维护性,有很多方式,比如写注释、测试。
SCIP 里面也讲了一种,就是 Build Abstraction Barriers。
最经典的是网络协议,每一层和每一层都是分离的。每一层都是为上层提供服务,消费下层服务。这样,就可以轻轻松松换掉一层实现,而不影响功能。
Clean Code 也有类似的建议
Don't Mix Different Levels of Abstractions.
这些都建立在抽象层级(build abstraction barriers)之上。
我们来实现有理数的运算吧,功能如下:
我们先不考虑如何表示有理数,而是考虑如何做操作,代码如下(这里为了示例,不使用 Ruby 的特性):
def add_rational(x, y)
numer_x, denom_x = numer(x), denom(x)
numer_y, denom_y = numer(y), denom(y)
rational(numer_x * denom_y + numer_y * denom_x, denom_x * denom_y)
end
def multiply_rational(x, y)
rational(numer(x) * numer(y), denom(x) * denom(y))
def rational_eq?(x, y)
numer(x) * denom(y) == numer(y) * denom(x)
def rational(numer, denom)
[numer, denom]
end
def numerator(x)
x.first
end
def denominator(x)
x.second
end
之所以先实现 add_rational
、multiply_rational
,然后再实现 Constructor 和 selector 是因为,功能和数据表示可以是两件不相关的事(当然也会有影响,不过最好还是分离开),我们可以先有功能,再有表示。
写代码这事儿,各自可能有各自的看法。不过肯定要面对一个问题 -- 需求变更。
假设产品经理跑过来说,这个 rational
不行啊,我要是传个 3 / 6 你得给我返回 1 / 2。
So easy,
# Improved constructor
def rational(n, d)
gcd = n.gcd(d)
[n / gcd, d / gcd]
end
我们看到,这里只改了一个函数!假设我们没写 rational
、numer
、numerator
、denominator
呢?我们要改多少代码?如果实际需求比这个再复杂一倍呢?
以上代码之所以好维护,是因为构建了如下的抽象界限:
那就更容易了,Ruby 本身就支持和鼓励这样的抽象(之前的实现是为了表达,语言特性和抽象界限没有关系)。
class Rational
# 构建 Rational
def initialize(numer, denom)
xxx
end
# getter 方法
def number
@numer
end
def multiply_rational
xxx
end
end
再比如 carrierwave
,mount_uploader
后,对应的字段,就会返回一个 uploader 实例。
抽象界限可以说是件小事,尤其是上面代码,就是不那么写,也不会怎样。但写代码,不论是业务代码也好,还是操作系统也好,都离不开两件事,抽象和组合。
最后,亚马逊的 CEO 曾写过一封有趣的邮件,大意是,今后所有小组间的调用,都要走接口(interface),不这么干的,就要走人(当然这个只是亚马逊的规则啦)。.