年轻的程序员请尽量减少抽象

Abstraction is the purposeful suppression, or hiding, of some details of a process or artifact, in order to bring out more clearly other aspects, details, or structure. 抽象是有目的的隐藏过程中的某些细节,以便更清楚地展现其他方面的细节。

抽象的价值

为什么需要抽象,一方面我们需要将不重要的细节隐藏起来,另外一方面因为我们每个人都不是全知全能之神,我们需要站在别人的肩膀之上完成更高层级的事情。

比如我们需要在城市中投递快递,无论是 中通快递 申通快递 顺丰快递 对于快递的抽象就是将某个物品从 A 传输到 B,因此对于我们来说,快递就是一个传递性的协议。

但是值得注意的是

The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.
抽象不是模糊不清的复用,而是创建一个绝对精准的新语义层

如果现在没有出现的抽象,绝大多数的时候我们不需要再继续抽象。因为我的抽象往往作为别者的依赖,如果出现 坏抽象 的时候,坏抽象 会变成病毒般的传播起来,而坏抽象往往会有这么几个味道

坏抽象

You thought that the abstraction world was made of rivers of honey, roads of chocolate and clouds of marshmallow? Everything has a cost: using abstractions should be a thoughtful decision.

为了解决重复的抽象

Duplication is far cheaper than the wrong abstraction. [重复比糟糕的抽象好得多]

  • 程序员 A 看到一段重复的代码。
  • 程序员 A 将其提出出来并给他起了一个名字。

此时创建了一个新的抽象,也许是函数/类/模块

  • 程序员 A 基于新的抽象将代码进行了重构。

此时的代码看其里非常的完美

一段时间之后

新的需求产生,现在的抽象几乎是满足需求的

  • 程序员 B 被安排来实现这个任务
  • 程序员 B 由于并非每种情况都完全相同,因此他们更改代码以采用一个参数,然后添加逻辑以根据该参数的值有条件地做正确的事情。

原本一样的抽象开始有点不一样

  • 另一个新要求到来了。
  • 程序员 X
    另一个附加参数 另一个新的条件 循环直到代码变得令人费解

既有的系统代码代表了正确,不幸的是随着复杂度的上升,保留的压力会越来越大,那些正确的代码让你小心翼翼,你只能加上 more & more 的条件 flag 来完成你的新任务,在 梭罗 的 《瓦尔登湖》 中指出,剥夺你人生自由的恰好就是那些你拥有的。

尤其是在一些抽象能力比较弱的语言 (说的就是你Go) 上,基于反射的抽象只会让你更加难理解

糟糕的命名

There are only two hard things in Computer Science: cache invalidation and naming things.

想想当你定义了一个 deleteUser 函数的时候,在执行这个函数的时候,我们将这个 User 置为 Disable 并且将其账号余额给清空了,拜托,下一个接手的程序员如果能从 deleteUser 里面读到这些就有鬼了。

简化问题

让你设计一个 Json 转换成 Xml 的神奇函数,你想要这个函数简单通俗易懂。你定义了一个 Parser Class,然后给他一个万能的 Parser::parse(String json) 函数,这个糟糕的设计没有任何参数,我试图在转换的过程中将某些字段忽略,我们就不得不去破坏抽象了。

过渡简化问题就好像只有一个旋钮的洗衣机,我们可以用 旋转 + 按下 来完成我的任务,但是多放几个按钮也许更高效的多。

脱离现实的OOP

OOP 是一个很不错的工具,但并不是灵丹妙药,在使用 OOP 之前考虑下是不是用简单的函数就可以满足你的需求,这也是为什么现在大量的语言都在将 FP 的精华融入系统之中。

创建一个 class 的时候,考虑一下三点:

  1. 简化和消除复杂性。
  2. 其他的类可以使用它,但他不需要是一个全局的 magic class
  3. 将数据和作用于数据的函数封装在一起。

面向未来

premature optimization is the root of all evil

这简直就是 Java 程序员的重灾区,一大堆单一实现的 interface,当在 codereview 的时候,每一个人都抱着我这是为了未来的可拓展性,面向未来 只会带来复杂性,在需要抽象时使用抽象。

programming to interfaces(面向接口编程) 显然不是你的接口,你的 Customer 只有唯一的一个实现类 CustomerImpl 实际上并没有多态性和可替换性,这里就是一个虚假的多态。

抽象泄露

all non-trivial abstractions, to some degree, are leaky. [在某种程度上,所有非平凡的抽象都是泄漏的]

首先想一想抽象会是完美的吗?
如果我们将汽车抽象成一个带方向盘和四个轮子的大铁盒子,会导致什么,当我们的车正常行驶的时候一切刚刚好,但是车出现故障的,我们并不会知道车发生了什么,因为这一切都在你的抽象范围呢,我们为这个车加上一个中控,然后显示故障码?相信我在真实的世界里面,故障是无可穷举的。而恰到好处的抽象并不意味着滴水不漏,Leaky Abstraction 也是一个可选项。
但是 Leaky Abstraction 有些时候就是对操作者有益,而有些时候是非常糟糕的。比如 NFS 这样的远程文件系统,可以让用户如同操作本地文件一般操纵远程的文件,对于用户来说会出现 Connection Error 这是本地文件从来不会发生的错误,这就是一种抽象泄露,所幸这种泄露并没有什么糟糕之处。
但是另外一种当你在教导某人在编写一个 GWT 系统,GWT 目的就是用 Java 来编写 JS+HTML 的系统,隐藏了 JS 的实现,此时如果用户将 JS 禁用了,Java 程序员就面临着一无所知的境地。

The law of leaky abstractions means that whenever somebody comes up with a wizzy new code-generation tool that is supposed to make us all ever-so-efficient, you hear a lot of people saying “learn how to do it manually first, then use the wizzy tool to save time.” Code generation tools which pretend to abstract out something, like all abstractions, leak, and the only way to deal with the leaks competently is to learn about how the abstractions work and what they are abstracting. So the abstractions save us time working, but they don’t save us time learning.

我们拥有越来越高的编程工具并具有越来越好的抽象性,反而成为熟练的程序员也变得越来越困难。每一层都不是完美的抽象,总是存在着泄露。

抽象泄露也不一定都坏的,我们在汽车的抽象中,我们将离合,控制盘,刹车,油门泄露给用户,一个老道的用户可以根据油门的感觉,刹车的时间都可以判断车出现了什么故障,这样的泄露也不全是缺点,就好像电视机的遥控器总不是按键越少越好。

编程中的 ORM 总是处于如此糟糕的境地,无论你花了多少的努力想要把 SQL 隐藏在 Proggraming 之下,hibernate 用了 500 多页的教程来教导你如何用代码规避一个复杂的 SQL,而用户只会选择将 SQL 写在代码之中。


谨记:
所有的抽象

  • 当脱离上下文的时候就变的无效
  • 并不完美
  • 与现实世界关联

参考