Ruby SICP - Abstraction Barriers

yfractal · 2017年06月11日 · 2322 次阅读

优美的代码(Beautiful Code)

优美的代码首先要遵循一些规范,比如等号前后要有空格(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)

实现 constructor 和 selectors

def rational(numer, denom)
  [numer, denom]
end

def numerator(x)
  x.first
end

def denominator(x)
  x.second
end

之所以先实现 add_rationalmultiply_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

我们看到,这里只改了一个函数!假设我们没写 rationalnumernumeratordenominator 呢?我们要改多少代码?如果实际需求比这个再复杂一倍呢?

以上代码之所以好维护,是因为构建了如下的抽象界限:

使用 Ruby 的类来实现

那就更容易了,Ruby 本身就支持和鼓励这样的抽象(之前的实现是为了表达,语言特性和抽象界限没有关系)。

class Rational
  # 构建 Rational
  def initialize(numer, denom)
    xxx
  end

  # getter 方法
  def number
    @numer
  end

  def multiply_rational
    xxx
  end
end

再比如 carrierwavemount_uploader 后,对应的字段,就会返回一个 uploader 实例。

抽象界限可以说是件小事,尤其是上面代码,就是不那么写,也不会怎样。但写代码,不论是业务代码也好,还是操作系统也好,都离不开两件事,抽象和组合。

最后,亚马逊的 CEO 曾写过一封有趣的邮件,大意是,今后所有小组间的调用,都要走接口(interface),不这么干的,就要走人(当然这个只是亚马逊的规则啦)。.

参考

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号