跳转至

抽象:开发者的思维方式

每个开发者都必须理解的最重要的概念是抽象,这是所有软件系统、硬件架构以及实质上所有复杂事物的基本概念。

抽象有助于管理实际世界系统的复杂性,将复杂系统分解为更简单的组件,并使创造者和非创造者能够迅速理解“这个东西是什么”。抽象几乎存在于现代世界的每个方面,从软件应用到硬件,甚至包括“非技术”行业,如动画电影。然而,对于开发者来说,抽象尤其重要,因为他们每天都与这个概念打交道。基本上,抽象应该成为开发者的第二本能。

什么是“抽象”?

一般来说,“抽象”大致意味着“为某人定义某事物时,忽略所有无关信息的过程”,并且该定义(“某事物”是什么)称为一个抽象**。

!!! 注意:“抽象有时被称为“语义”作为同义词。

请注意,有些人可能会对“抽象”有不同的定义,比如“共同于多个实体的抽象本质”。在这里,我将将该定义分为两部分: 其中“抽象本质”部分被定义为抽象; 其中“共同于多个实体的”部分被定义为“标准”,这一概念将在后面的章节中介绍。

根据上述定义,当抽象某事物时,有一些重要的问题需要问:

  1. 要抽象的“事物”是什么?
  2. 那个“某人”是谁?他/她/它将如何与“事物”交互?
  3. 在该“某人”的视角中,这个“事物”是什么?
  4. 哪些信息是相关的?哪些信息是无关的?即,那个“某人”需要了解什么?

请注意,这些问题的答案是依赖于抽象的“目标受众”,即将使用抽象和与“事物”交互的人(或系统)。如果你的受众发生变化,"事物"的样子可能会有所不同,相关信息可能会变得无关,反之也然。

抽象不仅仅意味着为他人而存在;有时候(实际上大部分情况下),受众将是你自己,你正在设计和实现需要抽象化的东西。

尽管我上面给出了一个定义,但抽象本身是一个非常难以清晰定义的“抽象”概念,并且没有被广泛接受的定义。因此,我将使用一个例子来说明抽象的真正含义。

一个抽象的例子

abstracted-car concrete-car

当你早晨赶时间,看到一辆汽车时,你首先会想到什么?是 “我可以开这个东西四处走”,还是底盘、发动机、轮胎和油箱?

我相信大多数人的观点都是前者。当然,后者的观点也很重要,但对你而言,它在此时是无关的:你在赶时间,所以你所关心的只有你可以开一辆车快速到达工作场所。

无论你相信与否,你已经在上述例子中创建了一个抽象。我们来详细拆分一下:

  • 抽象的目标:汽车。
  • 目标受众:你。
  • 这个东西(“抽象”)是什么:你可以用来开车并快速到任何地方的东西。
  • 相关信息:你可以把这个东西开走。它很快。
  • 无关信息:它由发动机、油箱、底盘等构成。它的长度为4.6米,高度为1.6米,最大功率输出为140匹马力。类似的东西。

上述解释应该很容易理解:目标受众是你,你只需要了解你可以开一辆车,所以那就是抽象。

然而,对于汽车工厂的工人来说,观点可能会不同。工人们制造汽车;他们不需要知道如何驾驶汽车,但他们确实需要了解汽车的内部工作原理,以便能够制造它。这次,我们有:

  • 抽象的目标:汽车。
  • 目标受众:工厂工人。
  • 这个东西(“抽象”)是什么:由发动机、油箱、底盘和其他一些东西组成的东西。
  • 相关信息:汽车的内部结构。
  • 无关信息:如何驾驶汽车。

正如您所看到的,抽象取决于目标受众。随着目标受众的变化,抽象的目标定义可以改变,以前相关/无关的信息也可以变得无关/相关。

抽象也取决于目标受众的角色。当你驾驶汽车时,汽车的抽象是“你可以驾驶的东西”;当你修理汽车时,抽象开始包括发动机、轮胎等,因为这些细节现在对你来说是相关的。

因此,识别目标受众并了解它将对抽象目标进行什么操作,然后决定哪些信息是相关/无关的,并将“事物”抽象化给受众,这始终很重要。

与抽象相关的术语和短语

在开发者的世界中,您可能会遇到与抽象相关的一些术语或短语。 下面是其中一些的含义:

抽象(Abstraction)

根据场景的不同,“抽象”可以有三个不同的含义:

  • 第一个含义是对象的“抽象定义”,就像本节中定义的那样。 这个含义强调的是“某物在某人看来是什么”, 但是“某物看起来如何”可以是简单的,也可以是复杂的,这取决于“某人”;
  • 第二个含义是“通过隐藏无关细节创建的简化定义的对象抽象”。 这个含义强调“抽象”是一个隐藏了细节的简化描述。
  • 第三个含义是“共享于许多对象之间的抽象标准”; 这个含义强调的是抽象适用于潜在的多个对象。

在...上的抽象(Abstraction over)

“在\(A\)上的抽象”意味着\(A\)是抽象的目标, 即抽象是从\(A\)创建出来,并且抽象适用于\(A\)

抽象掉(Abstract away)

\(A\)抽象掉意味着在创建抽象时隐藏无关细节; 这里的\(A\)就是那些“无关细节”。 一个例子是“编程语言在计算机硬件中抽象掉了许多细节”。

高/低层抽象(High/Low level abstraction)

高层抽象是更抽象的抽象, 而低层抽象是更具体的抽象。 换句话说,高层抽象通常更简单,但与被抽象化的实际对象(们)之间的距离更远; 低层抽象通常更复杂,但与抽象的目标(们)更接近。

例如,如果抽象的目标是一辆汽车,高层抽象可能是“你可以驾驶的东西”; 低层抽象可能是“有发动机、底盘和几个轮子的东西” (是的,这也是一个抽象。由于每辆汽油动力汽车都有发动机、底盘和一些轮子, 并且该定义隐藏了引擎、底盘等的内部工作原理, 因此可以认为从某种方式上来说它是“抽象和通用”的)。

抽象在开发者的世界中是什么样子?

尽管汽车的例子说明了抽象的概念,但它似乎与开发者的东西无关。现在,让我们看看在现实世界的软件系统和代码中抽象是什么样子。

这是来自KonnyakuGPT的源代码片段,这是一个由人工智能驱动的动画字幕生成工具(无需理解代码):

def simple_split_subtitles(subtitles: Sequence[srt.Subtitle], max_duration: datetime.timedelta) -> List[srt.Subtitle]:
    """Splits subtitles that are too long.

    The splitting scheme is simple; sentences are LIKELY to be broken into pieces.

    Args:
        subtitles (Sequence[srt.Subtitle]): Original subtitles.
        max_duration (float, optional): The maximum duration of each output subtitle, in seconds. Defaults to 10.
    """

    new_subtitles = []
    for subtitle in subtitles:
        remaining_text = subtitle.content
        current_start = subtitle.start
        splitted_subtitles = []

        while len(remaining_text) > 0:
            expected_text_length = math.floor(max_duration / (subtitle.end - current_start) * len(remaining_text))
            actual_text_length = min(expected_text_length, len(remaining_text))
            item_text = remaining_text[:actual_text_length]
            item_duration = len(item_text) / len(remaining_text) * (subtitle.end - current_start)

            splitted_subtitles.append(srt.Subtitle(
                index=len(new_subtitles) + len(splitted_subtitles),
                start=current_start,
                end=current_start + item_duration,
                content=item_text
            ))

            remaining_text = remaining_text[actual_text_length:]
            current_start += item_duration

        new_subtitles += splitted_subtitles

    return new_subtitles

暂时不需要弄清楚代码的作用和工作原理。基本上,代码段形成了一个函数(将其视为一种可以用来做某事的工具),可以将长的字幕拆分成较短的字幕。

注意,在开始时有一些可读性强的文本,即下面的内容:

"""Splits subtitles that are too long.

The splitting scheme is simple; sentences are LIKELY to be broken into pieces.

Args:
    subtitles (Sequence[srt.Subtitle]): Original subtitles.
    max_duration (float, optional): The maximum duration of each output subtitle, in seconds. Defaults to 10.
"""

这样的文本被称为“文档字符串”,顾名思义,它为函数提供了文档。这样的文档字符串作为对函数用户的抽象,它包括并仅包括用户需要知道的内容:函数的功能及其使用方法。函数的其余部分,即文档字符串之后的所有内容,对用户来说都是无关的,即使它们是使函数工作的逻辑代码。用户只需要知道如何使用函数;它不关心函数在内部如何工作。

在这种情况下,抽象非常重要,因为它使函数的用户能够快速理解用户需要理解的内容。如果没有文档字符串,了解函数的工作方式的唯一方法是阅读和理解代码。虽然这个过程是可能的,但比阅读文档字符串要耗费时间更多,而且一个更大的问题是,如果逻辑代码发生变化,用户将需要改变对函数的使用方式,这将导致大量的代码修改。然而,使用抽象,无论逻辑代码如何变化,只要它保持在抽象中定义的接口(即函数的功能和使用方法),用户根本不需要改变对函数的使用方式。

抽象不仅对软件系统的用户重要,对于开发系统的人员也很重要。当一个软件涉及大量代码时(例如,KonnyakuGPT大致有2000行的Python代码),记住每段代码的作用变得困难。没有像文档字符串这样的抽象,即使是你自己也会发现很难理解你之前编写的代码;抽象使理解、组织和维护你所工作的软件变得更加容易。

恭喜!你已经理解了抽象的概念,这是所有编程语言和软件开发流程的基本概念。接下来,我们将看一下层次结构,这是一种管理复杂性的方法,也是开发者世界的一个理念。