开发者编程时应该围着“程序”转吗?
编程的世界围绕着程序转,这是毋庸置疑的。本文的作者则提出了一个更广泛的、以系统为中心的观点,他认为“系统”才是编程的最终目标。
以下为译文:
维基百科“计算机”页面上的第二句话就提到了“程序”。我们对程序很着迷,了解如何创建程序,如何构建、运行和调试程序,如何可视化程序,选择哪个语言编写我们的程序,每个语言的类型系统和语法是什么......等等。
从表面上看,这似乎并没有错,但是这里面蕴含着一种假设,即编程的世界围绕着程序转。
如果我们的立足点是我们想“编写程序”,那么我们已经将自己局限在了某个特定的思维框架中,我们必须完整地编写一个程序,然后提交给系统,经过构建和集成,最后再运行。
但这不是有点过头了?“程序”真的是最终目标吗?我的立场是,我们需要使用以系统为中心的观点重新建立我们的目标,具体如下:
系统由与我们交互的硬件和软件组成:这包括硬件设备和一些没有名字的软件实体或产物。
系统展示行为:我们与系统交互,系统响应输入,产生输出(屏幕上的图像,声音等)。
我们修改行为。
换句话说:
系统没有展示我们想要的行为;
我们“采取措施”(包括操控没有名字的软件实体);
然后系统展示出我们期望的行为。
这里的“采取措施”一般情况下指的都是对系统进行编程,而这样的系统自然是指可编程的系统!程序本身是系统交互的一种类型,也许我们不应该明确地划分“对系统进行编程”和“使用系统”。
好了,废话不多说,现在我可以提出这样一个目标:
我们真正的目标是设计软件“实体”的形式和性质,而该软件“实体”即为我们在编写和使用系统时操控和创作的“可编写的基板”。
我有意将这项工作全部归结为设计的问题,在设计中处理概念和抽象的发明,而这些我们可以称之为基板。这个设计的范围非常广阔,你可以非常自由地发明各种记法、概念、工具、媒体等等,以任何方式勾勒“软件编写的虚拟世界”——但是我们必须小心地甄别所有引入的概念。
需要引入文件和文件系统吗?需要编译器、数据库、操作系统、可执行文件和操作系统进程吗?在这个空间中,所有这些计算的附属概念还都不存在,我们只有一张白纸。在这个后编程时代(或许应该叫做“前编程时代”)的思维模式中,我们该怎么处理这些自由度?
程序的思想
首先,让我们回顾一下程序的概念,但要注意程序只是对系统编程的方式之一(请注意动词“编程”和名词“程序”的区别)。
我们说“有一个叫做程序的东西”,然后继续描述这个概念是什么、如何使用等等,这通常是我们在计算机入门教材或维基百科上“程序”的页面中所能看到的内容。我们对“编程思想”已了然于心,但是我想在这里谈论有关编程思想的事情。
程序就好像程序员撰写的文章,它有自己的结构,通过一种主要的视觉表现手法呈现,程序员通过这种表现手法(即源代码)来查看并操控程序。程序包含了一次“执行”的详细步骤,这意味着它可以在某个时候“运行”,从而产生一个“运行中的程序”。请不要误解成存在一种“不运行的程序”,这里的“运行中的程序”只不过是原来的程序加上某种运行时状态而已,这些状态附着在程序的各个部分,并会随着程序的运行而改变。这就引入了两个阶段的概念——运行和未运行。程序的定义也是一种概念一般化,即程序可以拥有多个运行中的实例,每个实例的细节都不完全相同。
操作系统进程也是程序
在Unix(以及Windows和Mac)中,程序的概念通常被当做操作系统的进程。如果我们接受这种关联,将操作系统的进程看作一种程序,我们就可以继续提供更多描述性的细节。例如,当一个运行程序停止时,所有运行时的状态会被立即清除。对于长期的状态,程序可以在运行的时候修改外部状态,也就是说我们需要定义外部状态是什么,或者至少要定义内部和外部之间的耦合。
我们还需要一些编写和调用程序的工具软件,我们通过其他程序(比如编辑器、编译器、shell和GUI等)从外部操作程序。
最后,我们需要一些显示和交互的概念,运行程序通过这些概念与我们交互。通常,这就是为用户设计的“运行时的用户界面”(程序的第二种视觉表现手法)。在有些情况下,程序员同时也是用户,但这并不妨碍我们使用两种不同的表示方法。程序还可以与其他程序交互,这就涉及两个程序之间如何互相定位,以及交互本身的性质等问题。
我想说明的是,程序有一个框架,即有关程序方面的工作中常见的、不言而喻的假设。我想在这里注明以下三个方面:
1、我们需要做出很多的选择,我们可以随时决定改变方向。毕竟,这些概念都是人为提出来的。例如,我们可以选择非纯文本的方式表示所有。或者我们可以只选择一种表现手法(而不是两种——主要表现手法和第二种表现手法,即源代码和运行时的用户界面)。我们仍然能得到程序,虽然它们的工作方式不同。
2、似乎我们不能只引入一个单独的概念。概念之间相辅相成,所以我们需要引入一整套相互关联的概念。这是否意味着如果我们将“程序概念”引入另一个世界,我们就需要引入许多相关概念?可能吧,如果设计整个软件世界的话,就需要考虑这些情况。
3、程序或操作系统进程的概念可以解决的问题与不能解决的问题之间有一个界限。例如,如果我想在一个大程序内定义小程序(这个想法很合理,因为所有进程都可以分解成小进程),那么就只能自己想办法了。
想想主流操作系统的工作原理,我们还可以确定一系列的设计选择,这就可以决定包含哪些内容,不包含哪些,例如:
所有外部进程的持久系统状态都以命名字节blob的方式保存在分层文件系统中,并由运行中的进程明确进行管理;
IPC和进程内核通信通过C语义实现成API调用;
系统不提供将单个进程分成不同的部分的隔离机制;
不提供保存运行进程的状态或回滚到前一个状态的机制;
不提供替换正在运行的进程的某个部分的机制。
系统不提供的内容都可以由“用户空间”特定的程序解决,但核心抽象仍然可以决定常见且简单的内容。
有关“程序”的讨论够多了,让我们继续看看更有趣的几个问题:
我们是否需要将“程序”的概念引入到我们的各种软件中?在没有“编程思想”的情况下,我们能否拥有一个可编程的系统?
没有程序的编程
这个问题最简单最明显的例子就是各种Smalltalk系统,它们把所有程序视为一个完整的系统。程序并不是在另一个系统内运行的程序(即不是像Unix中的进程,而是启动后的系统中的一部分)。
Smalltalk系统提供了“对象”和“消息”的概念,而没有“程序”(或文件)的概念:系统是一组互相发送消息的对象;每个对象都像一个微型系统(负责接收和发送消息),每个微型系统都有自己的行为;一些特殊对象称为类,它们负责大量类似对象的行为定义;输入设备、输出设备和其他用户界面设备都表示成对象;事件和交互表示成消息。
对这个系统进行编程需要通过发送消息来重新配置对象的集合,或者在需要的时候创建一些新对象和类。但它不需要编写程序。
该系统中没有编译,因此它只有一个“阶段”,也就是说系统总是活跃的。系统支持自动持久,因此当系统关闭后再打开时,对象会回到上次的状态。
这个系统中还有许多有趣的地方:对象可能有多种表示方法,所有使用该系统的人都可以用,无论是用户还是程序员。
对象间的消息传递没有内置的验证系统,但是我们可以想象根据某些概念进行分层,比如引入某种一致性检查,来检查对象簇,或在对象间的点对点“连接”建立时的进行检查。
这里并不是说Smalltalk是一个更好的系统,而是它从根本上揭示了一个完全不同的系统观点,而且还可能有其他不同且更有趣的观点。
系统观点
程序只是大系统中的一小部分。通过编写程序的方式来制作系统,意味着首先我们需要将系统分割成程序,这些程序有各自的特定特征。我们也可以用许多其他方法来分割系统——这正是我们应该探索的空间。
我们可以对比一下以程序为中心的观点和与系统为中心的观点,如下所示:
系统分割的大部分在我们开始编写之前就已经完成了,它通过主流软件提供的概念巩固自己的地位:操作系统、文件、应用程序、数据库、编译器等。在我看来,我们需要重新考虑整个系统分解模型。仅仅发明某一既有部分的新版本(例如新的文本编程语言)不会危害到它,因为我们仍然在相同的模型中运行。
我们需要一种不同的方法来分割系统。为了进一步说明我的观点,下面是一些模糊的想法。如果我们设计一个新的操作系统,那么实际上我们就是在重新定义操作系统的意义。
不同轴上的自由度
虽然Smalltalk模型在单个“空间”内保存了大量对象,但我们可以想象有一个系统,里面的每个对象都有内部空间,空间里保存了一组内部对象,依此类推。这样就在设计中引入了递归。
从另一个方向,我们还可以考虑用增量模型替换先写后运行的编程思想(即“提交论文”的模型),在增量模型中程序员可以连续提供更多有关期望行为的细节,以及细化该行为的约束求解系统(即“对话”模型)。
另一种方法是引入时间管理,将其作为基板的第一个类概念,这样可以给所有工作成果加上版本进行管理,并追踪每个版本的变迁。这意味着你可以在任何上下文中回复或前进,例如暂停或回滚多个跨度。
如果在操作系统里创建一个高级的消息模型,怎么样呢?那我们就不需要重实现各个类型的字节显示方式了。
或许我们可以运行一个虚拟机,并在这个虚拟机上编程,那么分配、隔离和分割在多台机器上的执行任务就会更加容易。
我们的系统是否可以成为一个巨大的互连单元网格,拥有Excel的功能、一些合理的命名空间以及自动处理更新?输入/输出也可以表示为特殊的单元格。
我们认为在设计新的编程语言时我们有很多自由,但实际上整个主流编程语言只是主流系统的新兴模式。不同的基板会产生不同的模式。
如果系统给我们一些响应单元,而不是'文件'和'进程',那么'编程语言'会是什么样子?有关语法的争论将会大大不同,因为我们需要处理的是单元而不是自由形式的纯文本。反应度是系统核心功能的一部分,因此我们根本无需想象和设计构建系统。
语言和工具之间的界限开始变得模糊。
这个领域中有太多的选择可供探索,甚至是我们引入概念的顺序也会产生影响。也许我们可以在这个系统中寻找我们了解的好点子,看看是否可以提炼出本质?接下来该怎么办我也不是很清楚,但这让我感到兴奋。
相关的工作
在这篇优秀的论文Semprola paper(https://www.shift-society.org/salon/papers/2018/revised/semprola.pdf)中,Oli Sharpe写道:
另一种传统的选择是,将程序视为一个孤立的数学或其他形式的构造,其语义主要在编译时参考自身及其导入的库来确定。考虑到编程的历史,我们继承这一观点是很合理的,但是如今许多“程序”实际上只是一个更大的、“活跃的”程序和服务网络中的一小部分,每个程序和服务都按照自己的频率更新。因此,没有统一的编译时间,而某一部分与整体相关的语义即使没有更新,也可以跟着整体一起更新。
这段描写很好地捕捉了我们在以程序为中心的思维模式中采用的观点。另一篇很棒的文章是Tomas Petricek写的交互式编程(http://tomasp.net/blog/2018/programming-interaction/),其中写道:
换句话说,编程语言研究不应该学习程序,而应该学习编程!
Tomas强调了要查看整个工作流程,而不是只关注“输入程序”的最终产品。
总结
我们不应该考虑“编写程序”,而是应该将“制作系统”作为首要目标。这个系统是一个不断发展且永远存在的实体,我们希望使用抽象的概念(我们称之为“软件基板”)来编写这个系统。那么我们的核心基板都有哪些想法、概念和抽象呢?这正是我们需要提出的问题。
在初学者的心目中有很多种可能性,但在专家的脑海里却几乎没有。——Shunryu Suzuki
我们需要站在初学者的立场重新思考编程。
原文:https://shalabh.com/programmable-systems/systems-not-programs.html
作者:shalabh,美国软件工程师,主要从事Python开发。
译者:弯月,责编:郭芮
推荐阅读: