编程开发中抽象的代价

抽象一定要恰到好处, 过度的抽象则会带来一定的代价

程序开发中常用抽象来简化开发,好的抽象设计,会大大简化程序实现。但是,如果过渡进行抽象设计或者滥用抽象,那么负面效应也特别明显。

常见的抽象方法,如C++/Java等面向对象开发中的“设计模式”(或者说软件设计套路),是很多复杂共性代码的抽象和概括,就是典型的容易付出代价的抽象。如果我们能很好的理解这些套路的含义,那么使用这些抽象,自然会让代码非常条理和简洁。但是如果不能够深度理解这些套路的话,就会付出抽象的代价,比如滥用单例模式,盲目给每个类都搭建一个工厂类,看起来专业,其实非常没有必要。总的来说,抽象的代价具体表现有:1. 对代码只有局部理解,没有全局把握,无法对整体框架进行修改和重构;2. 不知代码轻重,特别是那些一层层封装抽象的API,无法知晓它们的运行时复杂度,不清楚API的局部和整体影响;3. 严重依赖表达式,就是通过简单的表达式来代替复杂的API调用,最后的代码虽然少,但是代价是不容易看懂且难以维护。

Python语言由于可以重载一个类的各种数学操作,比如+ - * /等,在Pytorch/NumPy中就大量利用了这一点,用户可以通过近似数学公式的表达式来实现复杂的矩阵/张量操作,这就给开发者带了抽象的代价(或者说负担),某名奇妙的需要学习所谓的向量化代码实现,那些复杂的向量化的表达式(写起来占用字符少),不少带有明显Matlab代码的影子。这样的抽象经常被滥用,造成很多的AI软件总有几行神秘代码,及其不好维护且容易遗忘,好的软件工程实践是用普通函数和高阶函数,用函数名和命名参数来自文档化,而不是依靠烧脑的broadcast机制和向量化代码来实现特殊的矩阵/张量操作。

C++代码中也有这样的例子,最容易见到的例子就是拷贝构造函数,在用别人类开发的时候,我总是很紧张,一个简的=操作,想弄清楚到底是深拷贝还是浅拷贝,需要花代价去看文档或者代码实现。我认为好的实践是,给出两个接口 copy/clone 对应深浅拷贝即可,这一点Rust就做的非常好。

优秀的抽象设计,如C++中的标准模板库STL,真正做到了无负担使用,统一的抽象模式(比如迭代器),对用户来说安全透明,学习成本低。开发系统程序出身的程序员(比如我),特别不喜欢深度抽象,害怕复杂的抽象,喜欢浅显直白哪怕啰嗦的表达方式,总认为透明开放的表达方式那样才安全可靠。