两年前,我在高中母校举办的夏令营上做了关于信息学竞赛的报告。两年前,我告诉同学们,编程是一种技能,每个人都可以且应该掌握,就如同驾驶一样。两年前,我浏览同学们的反馈,有一个女生写道,驾驶不仅仅是一种技能,人们还能从中获得享受,比如驾驶一辆好车。我陷入沉思,编程在作为一种技能的同时,还有没有其他属性,难道也如驾驶一般,用一台更好的电脑编程更能获得享受?两年后,回过头来发现自己的思考实在荒谬:人们从编程中当然能获得享受,但这种享受与硬件条件关系甚少,而更多源自编程本身。驾驶可以说是操控载具的艺术,而编程,则是操控数据的艺术。
数据
当我谈编程时,我想说的是数据。这是一个滥用“大数据”的时代,那么“数据”究竟是什么呢?整数?实数?还是字符串?不对,这些都只是数值,数据本身应该是一种更抽象的东西。可以拿表格来举例子,一张表格就可以认为是数据。能不能把表格视作与整数、实数、字符串一样,只不过是另一种数值呢?恐怕不太适合,表格有一个很明显的差异,就是它是有结构的,即表格有一些内在的结构(如行列),而数值填充在这结构中。所以,数据是有结构的数值集合,而数值,则是相对无结构的信息实体。
人们从实践中获得各种数值,为了分析其意义,往往按照一定的方式对数值进行结构化,得到适于分析的数据。初中的时候每逢大型考试,老师们在批改结束后会将每个学生每个科目的得分一一输入电脑,对每个学生生成一个元组,包括了学号、姓名、班级、分数等属性的数值,最后把所有的元组聚集在一起构成数据库,此时就可以做各种分析了(如平均分)。那么,人们为什么要这么做呢?所有人的试卷堆在一起,想算平均分一张一张加就好了,为什么要花时间整理成结构化数据呢?原来,人们不仅仅想算平均分,可能还要算最高分、及格率什么的,这时直接操作原始数值的堆砌就显得很笨拙了。整理得到结构化数据,能够帮助更方便地重用数据。
还是考试的问题,现在我们得到数据了,可以得心应手地做各种分析了,但很快我们又发现了新问题:最高分、平均分等任务操控数据的方式其实是非常雷同的,但我们却重复了一遍又一遍。人力成本是很高的,如果有一个机器能帮助我们完成各种相似的分析过程,该多好!但问题在于,我们显然不可能为每种数据操控任务去造一台机器。这个问题要怎么解决呢?
前辈们已经给出了答案,就是通用数据操控机器。什么叫“通用”呢?前辈们观察到,很多任务都可以分解为很多小的动作,比如算平均分,就可以分解为把两个数加起来放到一个地方,以及把两个数作除法放到一个地方,这两个小的动作。如果我们能找到一个“小动作”集合,由这些“小动作”的各种组合可以进行多种多样的数据操控,然后我们再为每个小动作找到一种高效的机械实现,那么下一次我们要处理数据时,就可以把其“小动作”序列输入给这个机器,机器不就可以自动地去处理数据了吗!
如果你认可上面的解答,那你已经触及到编程的核心了。所谓编程呢,就是在给定“小动作”集合的前提下,对于一个数据处理任务,合理地组织“小动作”构成操作序列,使得其正确而高效地完成任务。
算法
当我谈编程时,我想说的是算法。上面提到,编程的核心就是对数据处理任务写出正确而高效的操作序列。这乍一看是个很容易的问题——不就是用机器模拟人的动作嘛!但别忘了,这个通用机器相比起人,其能力是很受限的,优势也许仅仅在于完成“小动作”时能比人更快。举个例子,假设我们要把所有学生的数据按学号排序,而我们能使用的“小动作”又只有简单的数据比较和数据移动呢?一个简单的想法是,就像打扑克时大家摸牌的方法一样,每次取出一个元组,将它与前面排好序的元组一一比较,并插入到合适的位置。这个方法固然正确,但它效率如何呢?真的高效吗?
还有一个或许不太容易理解的方法,就是我们先把所有学生元组均分为两组,然后两组分别排序,然后将两组排序的结果合併在一起。注意在描述中出现了“两组分别排序”,这其实是一个自引用,即分别排序时使用的方法也是:将元组分成两组,然后再分别排序。举个例子,对下面8个数进行排序:
[8,7,2,1,4,5,6,3]
先分为两组:
[8,7,2,1] [4,5,6,3]
为了省篇幅就拿第一组继续演示:
[8,7] [2,1]
[8] [7] [2] [1]
当分到每组只有一个时,自然可以认为这组已经排好序了,所以我们可以合并结果:
[7,8] [1,2]
再合并:
[1,2,7,8]
假设第一次分的时候的另外一组也这样排好了,得到:
[1,2,7,8] [3,4,5,6]
最后合并:
[1,2,3,4,5,6,7,8]
例子很简单。那么问题来了,这个方法对任意的序列都正确吗?高效吗?与上一段的逐一插入的方法孰优孰劣?
这并不是一个那么容易回答的问题,而这还仅仅只是一个简单的数据处理任务。人们为了研究这个问题,提出了算法,即对“小动作”操作序列的正式称谓,并发展出各种各样的研究:从分析算法的正确性、效率,到各种算法设计的方法。实际上,算法设计与分析是一个很大的领域,人们针对各种各样的问题设计出了很多优美的算法,难怪大家会感叹算法设计的艺术。有前辈说过,“程序=数据结构+算法”,由上面的讨论,这个断言便很好理解了吧。
抽象
当我谈编程时,我想说的是抽象。这也是我认为编程是一种艺术的原因。这里的抽象,与印象派那种抽象是不一样的。编程里的抽象,与所谓的建模有点类似。人们在解决不同的问题时,往往会建立不同的模型。如果我们把一些建模的过程给串起来,即在一个模型之上再构建一个模型(后者某种程度上依赖前者),那么不同的模型相当于可以解决不同层次的问题,这就叫做抽象。
举个例子,还是刚才的考试数据处理,从原始数值到有结构的学生信息元组数据,可以认为后者是对前者的抽象——我们建立了学生信息的模型;然后,我们为了建立通用数据操控机器,设计了一套操控数据的“小动作”,这套“小动作”就是在学生信息模型上建立起新的抽象,我们可以通过这个层级上的“小动作”去操控相对底层的数据。还能进一步抽象吗?比如我们的任务常常需要对一列数求和,那么我们完全可以在“小动作”层上再做一层抽象,提供数列的求和接口,那么我们在编程时可以直接用这个接口对一列数求和,而不用触碰相对底层的“小动作”。
从这个例子中可以看出,抽象是编程解决问题中至关重要的方法,因为我们使用的是通用数据操控机器,而我们解决的问题却是领域特定的,这个领域可能有它自己的描述系统。那么,我们可以通过抽象,在通用数据操控层上设计领域特定模型,为解决领域特定问题提供接口。其实,抽象正是一种设计,从而它使得编程成为了一种艺术。
电脑上安装的操作系统,也是个很好的例子。电脑可以认为是一种通用数据操控机器。这个机器本来是几乎啥也没有的,没有计算器、没有表格……而操作系统的设计者则先建立一层抽象,设计出各种接口,包装起对硬件的操控,并另外设计了一套“小动作”,可以更方便、更有逻辑地调用各种接口。然后,操作系统的设计者再在这一层上建立各种模型——处理文档的模型、处理幻灯片的模型……其实,这就是所谓的软件。
作为结尾,需要提醒大家的是,编程者设计抽象的品味是大相径庭的。一些商业上很成功的程序其实有着很坏的设计。如果想要学习编程,那一定要严格地审视自己的和他人的程序,思考并评价其设计,并慢慢形成自己的“编程品味”。
网友评论