基于自然语言的软件工程和程序设计
软件发展至今,无论是编程语言,还是软件工程,乃至是互联网的趋势发展,都是飞速发展。于是,我们便迷茫于这样形形色色的语言和概念之间,无所适从。其实,我们不妨返璞归真,回到最初,让我们从语义出发,来讨论这形形色色的种种,你是否恍然大悟呢?
1. 何为语义
我们无论是在编程,还是在架构一个完整而庞大的软件,总结起来,无外乎在构建一个场景,或者说,在搭建一个虚拟的世界。
那么我们在回想一下我们在小学写作文的过程,无外乎也是在描述一段场景,或者是重绘当初的场景,也就是老师常说的要写真人,写真事,抒发真实情感;或者是描述自己虚幻的场景,或表达梦境,或表达理想。
让我们对比这两件事情,我们会发现,他们都是在用语言构建一个世界,唯一不同的只是一个是用我们的自然语言,一个是用我们的计算机语言,只此而已。
那么,当我们埋怨我们被无数的概念所砸到,被无数的编程思想所淹没的时候,埋怨着设计无从下手的时候,我们是否可以去尝试着返璞归真,用我们小时候最擅长的作文来反过来思考这些问题呢?说白了,也就是从自然语言向计算机语言,或者是自然语言思维向计算机语言思维的转化过程。
这就是我想在本文中提出的,针对自然语言的编程。
2. 看编程语言的发展
让我们先来探讨下编程语言的发展。
从第一代语言开始,当时的世界只有0和1,伟大的程序员们靠着向纸带上打孔来编程。
而后到了第二代产生了汇编语言,程序员们可以用一些指令来操作内存,从而达到自己想要的效果。
在我看来,机器语言到汇编语言的转变绝对是一个质的飞跃,不仅代表着软件和硬件地分离,更是程序员们可以用自己的语言(类英语)来表达自己的想法。
在之后就产生了过程式语言,这里以C语言为代表,这时也产生了一种编程思想,叫做面向过程的软件设计。程序员们可以用自己的思维去思考问题:这个事情应该先做什么,后做什么,而每个步骤就是一个方法。我们也称之为自顶向下的软件过程。
随着软件的发展,软件越来越庞大,功能越来越多,从软件过程的角度来讲,我们可以说,树顶越来越多,我们再也无法像曾经一样,抓住一个或少数的树顶,然后向下延伸,这时,便产生了近十年的热潮:面向对象的软件设计。
面向对象的软件设计是考虑,这个有哪几个对象,每个对象都能干什么,从而根据对象的协作去完成一个大的任务。而每个对象对其他的对象都是不知情的,也就是说每个对象都可以不去关注整个软件的整体业务逻辑,我也把这个特性称为软件对对象的透明性。
在旁支,又产生了函数式语言,面向服务的软件设计,面向契约的软件设计,并发式编程等形形色色的概念,而这些,我会在下文中来简要探讨。
3. 思维成熟的历程
短短地几十年,看软件世界的变革,你是否有些无所适从呢?那么,在未来的软件世界里,也就是在我们未来的工作里,编程语言又会变成什么样呢?当面向过程与面向对象这些我们熟悉地概念被时代所抛弃后,我们又该何去何从呢?
好,我们先不去想这些烦心的事,让我们向前追溯,回归童年时代,来想想我们写作文的历程。
从幼儿园时,我们不认识几个字,我们想写一篇文章,想的不是我们怎么样构建好一个场景,而是我们要如何把我认识的这些有限的字用上,而最后故事的过程,我们不得而知。这时,我们不是在为了写作文而写作文,而是为了可以向大人们展示,我认字的多少。这不正像最初的机器语言时代么?我们关注的是0和1的组合,把自己的着重点都放在了词汇之间的组合上,而一些程序员却常常会为此忽略了整个宏观的程序。
接下来,我们上了小学,我们不再沉迷于生字,生词的堆积,我们学会了很多漂亮的句子,诗词,名言警句,那么这里,我们可能关注的是,我们如何更好地组织这些句子,让整个文章读起来更通顺,也是在小学时,诞生了中国经典的语文题目:改病句。好,我们来想想汇编语言,汇编语言把0,1代码封装,暴露给我们一个个操作内存和运算的指令,我们需要关注的是我们怎么样把这些指令组合到一起,是先mov然后add然后中断,抑或是……..否则,整个程序很可能都因此崩溃。
然后,我们上了初中,我们开始写记叙文,这时的情节非常简单,出场人物很少,这时,老师向我们灌输了经典的“三段论”思想:事情的起因是什么,事情是什么样的,事情的结果是什么。当我们构思一篇文章的时候,需要做的就是把这件事情考虑清楚,然后按照顺序去一件事情一件事情按顺序去写,这也就是我们的面向过程编程。
当我们长大了,成熟了,当时韩寒,郭敬明,安妮宝贝,以及越来越多的网络小说让我沉迷上了构造一个个悲情的故事,故事的主角开始多了起来,主角之间都有着错综复杂的关系,三角恋,甚至四角恋的横行,以及多故事线索的穿插,让我们曾经的思维越来越不够用。于是,就产生了一种当时我很痴迷的格式,在文章开始,我会首先介绍所有的故事主角,然后根据他们的角色去划他们的关系,行为。这就是我们的面向对象软件设计。
由此可见,软件设计思想的发展其实和我们的思维是一样的,是随着世界的复杂性的增加而调整设计的思维,调整我们思考问题的方式和出发点。
4. 未来的发展历程
在前文我们说到,世界的复杂性增加驱动着软件工程思维的成熟,那么我们关注未来软件工程的发展方向,实际上也就是在关注未来软件世界,也就是软件业务,需求的发展方向,也就是关注未来人们需要什么样的软件。然后从自然语言出发,究竟怎么样的组织思维可以更好地我们所要构建的虚拟世界。
同样,当我们理解一个全新的软件思维时,如果我们无法理解,我们不妨去思考着与之对应的自然语言思维。
诸如面向服务的概念产生时,其实是不是,我们在借用着其他文章的结果或者结局呢?比如我想写一篇作文,其中有一个场景是我想抨击政府,我可能就直接说“刘和珍君的死,在今天是否依然会重演呢”,这里我只是引用了,或者说是借用了鲁迅先生所为我搭建好的场景,而只关注其结果,然后对外展示,或者作为我整个文章过程的一个小插曲。如果我们没有面向服务,那么我们是不要要再把整个《纪念刘和珍君》的文章再重写一次呢?
5. 面向过程是先总后分的行文顺序
相信大多数程序员都和我一样,C语言都是自己的第一门语言,那么面向过程的这个概念也自然深入人心。其实,我相信,每个人心中的思维都是面向过程的。
“我们应该先干嘛,再干嘛”。这是每个人在做一件事情前的常规思维,编程时也是一样,我们在实现一个方法,或者在完成一个功能时,都会去想,应该先做什么,再做什么,然后我们会相应地把一个方法拆分成若干个子方法,这就是我们传统的面向过程的编程。
我们把这种方式称之为“自顶向下”的编程思维。在做一件事情之前,我们要了解这件事情是什么,然后再去想这件事情应该怎么做。
这种与我们常规的对应叫做记叙文,说得难听一些我们可以称之为流水账。
我们不妨说得偏激一点,面向过程的核心不在于方法,而在于过程。面向过程本是没有方法的,后来为了可读性才增加了方法这个概念。本就是辅助,当然可有可无。
概括起来,面向过程的编程方式是先总后分的行文方式,先知道总体意思,再逐条去写。
6. 面向对象是先分后总的行文顺序
逐渐地,我们开始接触了Java,C#等面向对象的语言,但是我们是否却始终没有办法扭转思维,来找到如何来面向对象呢?抑或是我们经常为了面向对象而面向对象呢?
我们究竟错在了哪?我就是按照常规的语义来分析整个的项目,那为什么无法面向对象呢?错只错在我们解析语法的角度出现了问题。
高中时非常常用的一种记叙方式,就是我们会在文章的题记前,先把开场的主人公全部都陈列出来,其中包括他们的主要性格,以及简要概括他们的陈年往事。如此做的原因是因为文章的故事结构比较复杂。
那么我会做的是,首先在提纲上列去所有的主人公,然后想清楚他的主要性格,之后再去根据他的性格来为他设定一些事件,最后根据这些事件安排这些主人公的出场顺序,以及人物之间的关系。
这也得出了我对面向对象的核心思想的概述:以人为本。这里的人不仅仅是人,是指所有存在的生物,乃至非生物。换句话说,所有需要有行为的物体。可以是人,可以是计算机,甚至可以是一个衣柜。
面向对象的核心不在于业务逻辑的过程,而在于找到所有的“人”,然后知道每个人都能做什么,最后我们再把这些行为加以组装而已。
概括起来,面向对象的编程方式是先分后总的行文方式,在文章伊始,每件事情都不知道自己是在干嘛,只是在表达着自己。知道最后,才会发现,原来这些事情的作用是这个样子的。
有这样一本书,叫像堆积木一样做软件。我不清楚这本书的内容是什么,但是这个题目很形象,面向对象的基础实现过程实际上就是在做积木,每个积木都不知道自己未来会干嘛,而当积木都做好了之后,堆积木就变得简单而易行了。
7. 并发式编程是交错式的行文顺序
随着软件对性能要求的逐渐提升,并发已经成为了我们不可回避的话题,从Erlang到C#4.0的并行库,再到显卡编程的盛行,并发编程已经逐渐走到了我们的面前。
在高中时,有一种文体非常盛行,我也曾多次尝试,最后的分数也还不错。这种问题是在行文时,分别描述两件事情或多件事情,这几件看上去毫无关系,只是由每一段的ABCD,abcd等不同的符号来标识,然而这些事情到最后却揭示的同一道理,或者是最后他们在同一场景汇聚,来构成了故事的结局。
这种文体在新概念作文大赛上屡有出现。其实这就对应着我们常规意义上的并发编程。每件事情和每件事情之间从局部上没有着直接联系,但是最后宏观上,他们都是在为了做同一件事情,达到同一个目的。就像装配脑袋某天在群里讨论的冒泡排序的并发版——奇偶排序一样,从微观上,对奇数排序和对偶数排序没有必然联系,实则他们在完成着同一项工作。
8. 重谈总体行文
我一直在编程中都很强调一点,语义的重要性!
完美的编程语言应该是与自然语言完全对应的,可以把一篇文章翻译成与之对应的计算机语言,发展到那时,程序员就真的变成了翻译工作,只需要把对应的需求文档无需设计,翻译成计算机认识的一串串代码就可以了。
我们究竟如何分析一个软件需求。在我看来,软件的创造过程实则就是语义的分析过程。
当拿到一份需求时,按照面向对象的软件工程思想,我们应该首先做到提取出这份需求文档中所有的“主人公”,然后找到这些主人公每个人都可以干嘛,能干嘛,要干嘛。然后为之建立模型。这是面向对象的过程。
然后是每个行为的分析过程,要做这个行为,我们要先怎么做,再怎么做,为这个行为建立相应的流程图,这是面向过程的分析过程。
接下来,我们可以分析某个行为怎么做,是否可以一边干嘛,一边干嘛。这是面向并发的建模过程。
当我们要描述某个算法时,我们可以说这个算法要怎么做,只是基于语义的描述,例如斐波那契数列要用第一个数加上第二个数,这其实就很有可能是一个函数式编程的产生过程。
最后是组装,也就是堆积木的过程。我们的积木都已经做好了,我们只需要按照不同的业务逻辑,去把这些积木搭起来就可以了。
就是说,当我们建立一个项目时,首先建立人物传记(面向对象),然后对人物的经历进行描述(面向过程)。最后成文时,我们可以用交错式行文方式对其重组(并发式)。
那么好,我们概括下整个的过程:面向对象分析-->面向过程分析--->在过程的细节上并发或者函数式--->组装时再次考虑并发。
9. 小议过时说
经常会听到这样的声音,未来是面向服务的,未来是面向对象的,面向函数的,面向过程已经过时了之类的话。
其实,看过我上面的分析过程,一个完整的工程只靠着某一种编程范式是行不通的,一个工程的搭建需要的是各种范式的配合使用,每种范式都有着它所擅长的部分。
就像面向对象再好用,也无法脱离了面向过程而单独存在,哪怕你的原子拆分得再细,也无法逃开对每件事情进行分析的过程,况且还有着业务逻辑组装过程的存在。
而面向函数,面向并发再强大,他也无法取代面向对象在分析一个大型项目中自底向上这样巨大的优势。因此任何编程范式皆无过时说。就像语言之争一样,每种语言都有着其合适的应用范围,谈语言优劣,过时与否,我觉得毫无必要。
10. 面向对象与语义分析
我们都知道,面向对象是自顶向下的分析过程和自底向上的设计过程。在这里,首先,我们先不谈分析过程,只谈设计过程。
系统有多少个类,不是一拍脑袋想出来的,不是经验累积而成的,而是根据需求分析中提炼出来的。
类的产生是名词提炼的过程,我们知道,每个对象都是对应着现实中的一个实体,而每个类都是对具有相同特征的对象的抽象。越是恰当的抽象,我们就越能提炼出精确的类。
这时,让我们不得不感叹古人诗词的精妙:
枯藤老树昏鸦,小桥流水人家,古道西风瘦马。夕阳西下,断肠人在天涯。
诗词,是对文章高度的抽象过程;面向对象的设计过程,也是对现实世界的抽象过程;何时,我们能将需求分析文档精确提炼成古诗词,此时乃大悟面向对象之道也。
11. 设计是违反语义的过程
与其说面向对象可重用,易维护,不如说面向对象更贴近我们的现实设计,让我们的每一个类的产生都有章可循。此乃为面向对象之妙也。
在之前的一部,我们将现实社会映射成了我们的程序中对应的类,可是这时肯定会有人跳出来说,这是面向对象么?继承,多态,封装,你这什么都没有啊!
这就是我在本节中要提到的,在我看来:设计是一个违反语义的过程。
例如:”老师讲课,学生听课。“这样的语义环境,自然会产生老师和学生两个类,可是大家这时都会想到,此时应该提取出来”人“作为老师和学生的基类(父类)。可是,我们知道,人在此语义中是不存在的,他只是我们根据经验来假设出来的。我们在之前说过,类是对对象(现实事物)的抽象,而父类又是对类的再抽象过程。因此,我认为:继承是对抽象的再抽象。
提到设计,我们就要提到设计模式,我们来想想常见的设计模式,工厂,适配器,策略等等,这些在我们的语义中都是无法分析出来的,因此,在我看来,设计模式实际上是牺牲了语义的自然性,来换取软件的可重用性和可维护性。
12. 数据库设计与自然语义的冲突
在此,我指的数据库特指关系型数据库。因此我说,数据库设计与自然语义的冲突,其实就是说关系型数据库与面向对象语言之间无可调和的矛盾。
换句话说,在面向对象中,属性不一定是原子的,数据也不一定没有冗余,因此数据库的三大范式对于面向对象设计来说,是不适用的,这也就间接导致了,关系型数据库和面向对象设计时冲突的。
(注:由于本人对 ORM了解甚为,以下言论请大家选择性相信,也希望大家不吝赐教)其中ORM就是为了解决面向对象与关系型数据库不匹配而产生的技术。很多人在用ORM时有一个误区,就是首先建立数据库,然后由数据库生成实体对象,但是在我看来,数据库只应该是存储数据的工具,而绝不应该成为整个项目设计的核心,正确使用ORM的办法应该是把程序自动持久化到关系数据库中,而我们在程序中对数据库是一无所知的,我们操作的只是一个对象的集合罢了。我不清楚ORM当今的发展,但是在我看来,一个完美的ORM系统应该具备解析对象,然后将对象转换为符合范式的数据库结构的能力。
另外,视图和缓存表也是解决方案之一。
最后,我们只能期待对象型数据库的进一步成熟了。
13. 我眼中的未来语言
在我眼中,语言的发展方向应该是逐步贴近语义,试想从第一代语言发展至今,语言的趋势无非是越来越适合于程序员使用,提高程序员的工作效率,说句再难听些的,就是逐步降低系统开发程序员的门槛,其体现一者在于方法封装的逐步完善,二就在于越来越接近自然语言,越来越接近“大型作文”的写作过程。
那么当软件发展到一定程度,我认为未来的语言是等同于自然语言的程序设计语言。从而人人可编程,方法高度封装,编译器可识别人们的自然语言语义从而转换成机器可识别的语言。我们需要做的只是把需求整理成“无语病”的需求分析文档,然后把文档移交给“编译器”,返回给我们的是一个个.exe,.aspx。
也许到了那一天,程序员这个职业不复存在了,取而代之的是作家,这一天,我们说,软件真的发展到了最高阶段。