第26条:每个方法都应该基于单一的抽象级别而编写
Not Kotlin-specific、Basics
Last updated
Not Kotlin-specific、Basics
Last updated
计算机是一种极其复杂的设备,但我们能够轻松使用它,是因为它的复杂性被划分为不同层次上的不同元素。
从程序员的角度来看,计算机最底层的抽象是在硬件上。再往上,因为我们通常为处理器(CPU)编写代码,所以紧跟着的层次就是处理器的控制命令,为了可读性,它们用非常简单的语言表示,并被一对一翻译成机器码,这种语言叫做汇编语言。用汇编语言编程是困难的,以这种方式构建当今的应用程序是绝对不可想象的。为了简化编程,工程师们引入了编译器:将一种语言解释成另一种语言(通常是低级语言)的程序,首先,编译器是用汇编语言编写的,它们把文本形式编写的代码翻译成汇编指令。这就是第一个高级语言是如何被创建的。它们又被用来为了更好的语言编写编译器。 因此,引入了 C、C++等高级语言。这些语言是用来编写程序和应用程序的,后来,抽象机器和解释性语言的概念被发明了出来,很难再把 Java 或 JavaScript 这样的语言放在这个金字塔上,但抽象层的通用概念仍然是一个想法。
拥有分离良好的层次最大的优势是:当你在特定层次上操作时,它们可以依赖于较低的层次按预期工作,而无需完全理解细节,我们可以在不了解汇编程序或 JVM 字节码的情况下进行编程,这很方便。类似地,当汇编程序或 JVM 字节码需要更改时,只要创建者调整上层 —— 原生语言或被编译到 JVM 的地方 —— 他们就不需要担心更改引用程序。程序员在单一层次上操作时,它们通常为上层工作,这是所有开发人员需要知道的,它是非常方便的。
如你所见,在计算机科学中,层次是逐步往上的。 这就是为什么计算机科学家开始区分高级别的东西。越高级,就离物理层面越远,在编程中,我们说层次越高,就是离处理器越远。级别越高,我们所关心的细节就越少。但本质是用缺乏对程序的控制去换取简化的编程。在 C 语言中,内存管理是工作的重要组成部分。 而在 Java 中,垃圾收集器会自动帮你处理它,但是优化内存使用要困难的多。
就像计算机科学问题被提取到各个层次一样,我们也可以在代码中去创造这样的抽象。 我们使用的最主要的基础工具就是函数。同样,就算在计算机中一样,我们喜欢一次只在一个抽象层次上操作,这就是为什么编程社区提出了 “单一抽象层” 原则,该原则规定:每一个函数都应该按照单一抽象层来编写。
假设你需要创建一个类表示一台咖啡机,其中只有一个按钮用来煮咖啡, 煮咖啡是一项复杂的操作,需要咖啡机的许多不同部件,我们将用一个类来表示它,这个类只有一个名为 makeCoffee
函数,我们可以在这个独特的函数中实现所有必要的逻辑:
这个函数可以有数百行,相信我,我见过这样的事情。 特别是在那些比较老的程序中,这样的函数是完全不可读的。 要理解函数的一般行为是非常困难的,因为阅读它时,我们会不断地把注意力放在细节上。也很难找到任何东西,想象一下,你被要求做一个小修改,比如修改水的温度,要做到这一点,你可能需要理解整个函数,这是荒谬的。我们的记忆是有限的,我们不希望程序员在不必要的细节上浪费时间,这就是为什么最好将高级步骤提取为单独的函数。
现在你可以清楚地看到这一函数的流程,这些私有函数就像书中的章节,正因如此,如果你需要改变某些内容,你便能够直接跳到具体执行内容的地方。我们刚刚提取了更高层次的处理,这大大简化了我们对第一个过程的理解。我们让它更具有可读性,如果有人想在较低层次上理解它们,他们可以直接跳到那个部分去阅读它。 通过提取非常简单的抽象,我们提高了可读性。
遵循这个规则,所有这些新函数都应该一样简单,这是一条通用规则 —— 函数应该是最小的, 并且有最小化的职责。如果它们中间的一个比较复杂,则应该提取这部分。因此,我们应该事先写出许多小而易读的函数,所有这些函数都处于同一个抽象级别。在每个抽象层次上,我们都是用抽象协议(方法和类)进行操作的,如果你想了解它们,可以随时跳到它们定义的地方(在 IntelliJ 或 Android Stduio 按住 Ctrl(Mac 则是 Command) + 点击函数名,会跳到函数实现的地方),通过这种方式,我们提取这些函数时不会有任何损失,并使我们代码更具可读性。
额外的好处是,以这种方式提取的函数更容易复用和测试,假设我们现在需要做一个单独的功能来生产浓缩咖啡,区别是它不含牛奶。 当提取流程的某些部分后,我们就可以轻松的重用它们:
这也使得我们现在可以单独对较小的函数进行单元测试,比如对 boilWater
或 brewCoffee
,而不是像 makeCoffee
或 makeEspressCoffee
这样更复杂的函数测试。
抽象层次的概念也适用于比函数更高的级别,我们分离抽象以隐藏子系统的细节,允许分离关注点以促进代码间的可操作性和平台独立性。这意味着用问题领域的术语来定义更高的层次:
当我们设计模块化系统时,这个概念也很重要。 分离模块是一个强大的操作,可以隐藏层次特定的元素。当我们编写应用程序时,一般情况下,那些表示输入或输出(前端的 view、后端的 Http 请求)的模块,通常处在较低的层次。另一方面,那些代表用例和业务逻辑的层次,通常都很高。
我们认为良好的项目都是层次分离的,在一个分层良好的项目中,任何人都可以在任意一个层次上查看系统,并得到一致的观点,程序通常需要分层。
在编程中,创建单独的抽象层一个流行的概念。它帮助我们组织知识,并隐藏子系统的细节,允许分离关注点以促进平台之间操作性和独立性。 我们用很多方法分离抽象,比如函数、类、模块。我们应该尽量不要让一层太大,在单一层次上操作的较小的抽象更容易理解。抽象级别的一般概念是:越接近具体的操作、处理器或 I/O ,它的级别就越低,在较低的抽象层中,我们为了更高的层次定义了术语语言(API)。