《SICP》读后感:关于软件本质的一点思考

摘要:软件本身不是目的,人类的需求才是目的,而软件只是达到目的的手段。
软件的本质在于控制复杂性,这个复杂性并非来自于计算机,也并非来自于现实世界,而是来自于人类的思维和知识体系。
软件被使用的广泛性,在于它所满足的人类需求的广泛性。

什么是软件?

从一个简单的例子说起,比如我想计算两个数的和,于是写下这样的python代码

    print a + b

但是,这段代码是我的最终目的吗?显然不是,我需要把它在计算机上实际运行,并赋予a和b实际的数值。也许我是在水果,买了5块钱的苹果和10块钱的香蕉,然后计算一共需要支付多少钱。

可以看出,软件是我们为了达到某种目的,而指挥计算机如何去完成这个任务的一系列指令。我们的目的是满足某种需求,而软件只是一个手段,显然完全可以通过其他的手段完成这一任务。

SICP中指出,计算机科学和计算机其实并没有本质联系,而是人类知识的一种组织形式,重在对过程性知识的形式化。如同数学是对说明性知识的形式化。也就是说,计算机科学是关于“如何完成某某任务”的知识的记录工具。

看到这里你也许会觉得我说了半天废话——因为人类所有的生产活动,都是为了满足人类的某种需求。不过这一观点将是本文的基本出发点。接下来我们简单分析一下软件作为一种手段,为什么存在,以及有什么特点。

为什么要有软件?

如果软件是为了满足人类的某些需求,那么软件存在的原因就很直接——因为计算机不能直接满足人类的需求。其中的距离来自于计算机的通用性、单一性和人类需求的特定性、多样性。从原理上讲,计算机只需要0和1两个符号,以及NAND(与非)或者NOR(或非)一种运算就够了。而人类的需求则千差万别。

但是,前面说到,满足需求有多重方式,比如人工计算,或者直接使用硬件搭建电路完成某些功能(而这实际上也是电子领域早期的方式)。那么,人类为什么选择软件这种手段呢?其实很简单,原因和其它的手段一样,不外乎成本。从经济学角度,成本,也就是人类的工作时间。(金钱,可以看做你从别人那里买时间)。经济的一个基本规律是规模效应,生产规模越大,单位成本越低。硬件做的越通用,就越适合大规模生产,就越能降低成本。当然,这一切都要建立在下面的基础之上:

生产软件要比生产硬件的效率高。

软件易于改动,另外有一个重要特点——它一旦被生产出来,就可以几乎零成本的进行重复利用。这是一个能极大提高全人类生产力的方式。试想一下,假设你能写一个软件炒一盘鱼香肉丝,那么全世界的人无论谁想再生产一盘鱼香肉丝,只需要简单的调用一下这个软件。很多软件最初并不是写出来给大家用的,而是为了解决自己的实际问题,比如为了简化工作流程,开发某个产品,验证某个科研思路等等,软件只是个副产品——这么说也许不太恰当,应当说软件是解决问题过程的完整记录。而完成任务之后,软件就可以被别人复用了——全人类只需要一个人造轮子,完全消除了重复劳动,多么高效!

所以下面要谈谈代码复用的问题。

软件复用

好的软件不会消失,而会被移植到新的平台上。

—— 《Linux/Unix设计思想》

复用软件的成本比生产软件要低的多。所以,软件复用是提高生产率的重要手段。如何更好的复用软件,以及如何编写容易复用的软件?要研究这个问题,需要先考虑另一个问题:软件为什么可以复用?

仍然从基本观点出发:软件是满足人类需求的手段。所以,软件的复用,实际上是人类需求的重复性。考虑两个需求,a)计算一组实数的平均数;b)计算高一3班全体同学的2015年数学期末考试平均成绩。显然,需求a是一个更广泛的需求,并且可以预见这一需求在未来会一再的出现。而需求b则是一个很特定的需求,它只会在2015年期末出现一次。如果有两个软件分别满足两个需求,那么满足需求a的软件将会一再的被重用,而满足需求b的软件将会烂在硬盘上。

所以,为什么“好的软件”会被移植到新平台上?因为它们满足的是人类重复需要的需求,无论技术怎么发展,只要这些需求存在,相应的软件就会存在,只是以不同的形式出现。

所以,为了编写能够复用的软件,我们需要关注的是需求。然而,不幸的是,实际中的需求往往是特定的、易变的。比如,公司要在今年双十一推出一项特定的活动,那么单纯为这个活动所编写的软件,在活动结束后就失去了作用。

要解决这个问题,需要对软件进行层次划分。不同层次的软件,通用性和特定性不同。底层的软件单一、通用,重用度高;而上层的软件则特定性高,生命周期短。为了研究这个问题,需要考察另外的问题——复杂性与抽象。

复杂性

软件的首要技术使命是管理复杂度。

——《代码大全》

为什么会出现复杂性这种问题?仍然从基本观点出发——复杂性来自于“计算机仅能提供0、1运算”和人类需求之间的距离。人类的一个需求,可能需要成千上万的0、1运算才能完成,而人脑——很遗憾——只能同时处理7个左右的事物。可以说,软件的复杂性,实际上来自于人类需求的复杂性以及人脑处理能力的局限。

实际上,这种复杂性不仅出现与软件领域,而是人类所有知识和概念体系的共同特点。在SICP中,引用了洛克的一段话,对此有提纲挈领的论述:

心智的活动,除了尽力产生各种简单的认识之外,主要表现在如下三个方面:
1)将若干简单认识组合为一个复合认识,由此产生出各种复杂的认识。
2)将两个认识放在一起对照,不管它们如何简单或者复杂,在这样做时并不将它们合而为一。由此得到有关
它们的相互关系的认识。
3)将有关认识与那些在实际中和它们同在的所有其它认识隔离开,这就是抽象,所有具有普遍性的认识都是这样得到的。
                                                   —— John Locke 1690

软件开发的过程,就是将简单元素组合为一个复杂元素,再将复杂元素抽象为简单元素,这样一个不断“组合->抽象->组合->抽象 ……”的迭代过程。

抽象

基于上面的讨论可以看出,软件开发过程中的抽象,本质上是人脑中概念的抽象。这种概念的结构映射到代码上,就成为软件。那么,怎么样是一个好的抽象?好的抽象应该在错综复杂的事物中分解出不变的部分,将它和易变动的部分隔离开。而软件的变动,则取决于人类需求的变动。也就是说,一个好的抽象,也就是一个好的概念,应当反映出人类需求中稳定不变的部分。这样的软件,就会在空间和时间上达到更广泛的复用。

无论过程式、面向对象还是函数式编程,都是提供了一种看待现实世界、进行抽象的方法。如果我们明白了软件结构设计的本质在于为问题设计一个良好的抽象概念体系,那么这些方法之间其实并无本质区别,只是各自具有优缺点的具体的抽象办法而已。

下面我们来看一下计算机科学领域所采用的一些最基础的抽象。

硬件层面

为什么硬件也算抽象呢?因为我们讨论的起点是布尔代数,这是现代电子计算机的数学基础。硬件层面对0、1运算进行了第一层抽象,即一系列的CPU指令,将原本只能进行简单“与、或、非”运算的概念,抽象为提供一系列字节、整数、浮点数、地址等概念。

计算机语言

计算机语言是在硬件层之上的第一层抽象,而本身又分为不同的层次。计算机语言是一种概念抽象方式,所以它是为人设计的,而不是为计算机设计的——因为,如果要指挥计算机做事情,我们只需要“0”、“1”两个符号就够了。计算机语言将硬件提供的功能抽象为若干数据类型的运算、流控制结构、函数等概念,以组成更复杂的软件。可以说,编译器是最大的一个软件复用的例子。然而,这儿不妨思考一个问题:在这个抽象的过程中,是否丢失了什么?不同语言的抽象方式是不一样的,由此所能提供的功能也是有所差别。

操作系统

操作系统是又一种抽象方式,它把一些常用的操作封装为系统调用,这些操作是软件开发中大量的重复性的需求。

各种软件包、框架等等

软件包、框架等不是计算机语言的一部分,而是使用计算机语言编写的软件模块。然而从抽象的角度来说,使用语言构建的模块,和语言本身提供的特性,并无本质区别。同一种数据结构,在某种语言里属于语言本身的一部分,而在另一种语言里则需要使用软件包。然而无论那种情况,并没有本质区别。

业务代码

业务代码是最终完成现实任务的代码,它们最具体、最不具有一般性,所以过时最快。大部分公司的硬盘里沉睡着这类代码。

再论软件复用

越通用的软件,可复用性越强,但是不能直接用来解决实际问题;而满足特定需求的软件,又不具有通用性,如果没有将其中一般性的模块抽象出来,那么将很难复用。既然每个人有自己特定的需求,而软件开发者不可能为每个人开发一套软件,这就要求任何软件都要提供“组合-抽象”的能力,供软件使用者对满足自己的特定需求的功能进行抽象——这种功能可以叫个性化,或者二次开发,或者插件等等。下面举几个具体例子:

  • linux系统提供了一系列ls, grep等工具,它们是通用的。我现在有个需求,检查某服务的log中是否有error,如果有则向某个特定邮箱发送邮件。当然我可以依次手动运行这些通用工具,但是更好的方法显然是写一个shell脚本,并命名为check_log_err.sh。这样每次只要运行这一个命令就够了。这就是一种抽象。
  • 编辑word文档时,有个特殊需求:将每段的第一个字的字体增大一号、加粗并设置为红色背景。显然我也可以每段每段的进行操作。更简单的方法是录制一个宏,然后在每段上回放这个宏。宏提供了一种抽象手段,将“第一个字字体增大一号、加粗并设置为红色背景”这几个操作抽象为一个操作。从这里大家大概已经能看出,从抽象的角度上讲,宏和脚本没有本质区别,都是实现抽象的手段。实际上,word的宏也是以VBA代码形式保存的。只是对于很多非计算机专业用户,写代码是不方便的,而宏用起来比较直观。
  • 用浏览器下载文件的时候,它每次都会询问存放位置。我想把它放在某个download目录下面,于是设置了一下下载路径,这样就不需要每次设置存放位置了。这也是一种抽象:通过配置文件,将通用的“下载”、“存盘”两个功能组合起来,并提供具体参数,来满足我的特定需求。
  • 还有各种插件,比如chrome、sublime text、foobar等,提供了二次开发的能力,这样用户可以针对自己的特定需求进行开发。从这个意义上说,软件的二次开发功能做的越好,生命力就越强。

从抽象的角度讲,任何软件都应当具有二次开发的能力:使用者将软件提供的若干个相对通用的功能,组合、抽象为一个功能,来满足自己的特定需求。这种组合、抽象的功能越强大、越方便,软件的生命力就越强。

小结

本文的主要观点总结如下:软件不是目的,而是手段。人的需求才是目的。在布尔代数和人的需求之间,存在着巨大的距离;而人脑处理能力是有限的,这就产生了复杂性。控制复杂性是一个“组合->抽象”的不断迭代过程。良好的抽象应当满足人类普遍的、长期的需求,这样的抽象以及相应的软件将实现更大程度上的复用。

扩展:关于人工智能

在这个基础上,笔者思考一个问题:未来是否会出现人类理解不了的人工智能?沿着本文的思路,不妨这样考虑:人工智能建立在计算机的基础上,那么它就不能超越布尔代数;而人工智能也是一种软件,它的目的是满足人类的某种需求。但是,中间的过程可能是不一样的,也就是说,人工智能可能会采取和人类不同的抽象方式,或者它根本就不需要抽象。大量的中间层次,可能是人脑不能理解的。但是至少从基础的层面上,人工智能只要还构建在图灵机之上,那么它的基础——0、1运算至少还是能被人脑理解的。

时间: 2024-12-19 16:28:42

《SICP》读后感:关于软件本质的一点思考的相关文章

对于函数名本质的一点思考

自己在学习函数指针的时候对函数名的意义产生了一点疑惑,经过一些尝试和思考,感觉应该可以像下面这样理解,如果有啥不对的希望大家指正. 首先 我们对变量名的定义做一下回顾: 在C语言里面,我们声明一个变量的时候就会给这个变量名分配一个内存空间,也就是说变量名和内存空间相对应:同时每个内存空间都会有个地址. int a; a=5; 上面两句话说明了:变量a对应的内存空间存储了一个整型数5,并且我们还可以知道这个内存空间的地址是&a. 对于函数的调用我们一般是直接用函数名的,但是在汇编语言里面,调用函数

关于单片机软件框架的一点思考

软件产品的文档很重要,其实我想说,任何东西都要有说明书,不然别人是很难使用的.最近一段时间有在看OSAL这个为操作系统,看了很就也不会用,其原因嘛,我实例有限,另外就是TI自己的文档不够全面,仅仅是zigbee好蓝牙的芯片中使用,其他mcu的平台基本上没有现成比较好的,有的网友移植了,也没有好好说明,导致osal的这个使用率没有rtos的高. 其实我个人认为,小项目使用裸机(定时器+状态机),稍稍大一点项目就使用RTOS. 还有一种是时间片的框架,我反而认为不太好,适合玩玩,因为这个框架,说实在

简单之美-软件开发实践者的思考 01

几天就读完了倪建大牛写的这本别具风味的作品,主要是对软件开发过程的一些思考,读后感.作者的写作方式很特别,通过叙述故事的方式讲解了软件开发的一整套流程和流程中需要注意的地方.作者的主要态度是批判的,带有理想主义的色彩,然而却是发人深省的. 这本书给我最大的收获就是在软件开发中要学会思考.思考所有步骤和方法存在的目的与意义.是否符合软件开发行业发展的趋势.作者主要涉及的是方法论上的层次,俯瞰着大地上的开发组织和人员.看到的问题和解决方案往往是直指本质的. 这里摘几条印象深刻的见解和需要识记的名词.

"简单设计"的一点思考

简单设计是Xp技术实践中开发实践的核心实践,“简单也是价值观中智力色彩最强烈的”,然而,提到简单设计,大家更觉得像原则或者价值观,感觉上还是比较泛,我们不妨从下面的几个角度看一下  1. 为什么要简单设计 <1>. 简单的代码更容易读懂. <2>. 好的设计更能应对变化.  这两点是基于成本和收益考虑的,这里的价值是时间及金钱.更快的满足需求,减少复杂带来的故障排查.修复成本,代码大量修改或者重写成本.  2. 什么是简单设计 对一个团队来讲,简单设计就是团队中每个人都能轻松的读懂

关于互联网商业模式的一点思考

传统互联网的商业模式,说白了就是通过流量获得收益.这里面有两个问题,一个是怎么获得流量,另外一个是怎么获得收益? 先来说说怎么获得流量.互联网企业一般通过提供平台,服务,或内容来吸引用户.这里面有一个基本原则就是,一般提供的基础平台基础服务或基本内容都是免费的.比如门户网站提供信息的整理和分类,搜索引擎提供信息的快速获取,电子商务提供交易平台来连接买家和卖家,社交网络提供人们互相通信和交流的渠道,游戏平台提供人们打发碎片时间的娱乐活动,安全服务提供人们上网的安全需要,以及对本地系统和数据的归档整

关于后台系统自动生成的一点思考

大量实践发现后台管理程序,其实90%的代码都是相同的,当然是在抛弃复杂逻辑业务的情况下,那么如何能高效的节约这些时间呢,那就是接下来我要说的,对于后台系统自动生成的一些思考. 适用情景: 1.表编号id为自增(基于现在大部分表编号都是自增的情况): 2.没有太复杂业务关联关系,比如表的某一个字段,存储了一个json对象,为了平衡后台用户使用,需要友好的分段展示给用户的定制ui界面:还比如表中存储了外键的多个id,但为了方便用户使用,只能已标签name的方式,给用户展示,等等这些超强业务黏合逻辑的

关于前端的一点思考

关于前端的一点思考 Author:tkorays 最近写前端代码,写着写着就突然开始惆怅.忧伤.愤怒.发狂,我TMD到底在干什么啊! 很多东西写了n遍了,但是还是在不停地写着.自己写过的代码也不想再修改完善.重新利用,只是觉得,可能重新写一遍可能要好点.面对这很多库以及框架,虽然喜爱,但是也是有所顾忌,我只要使用其中的一个功能,根本不需要引入这么大的整个库. 事实上,我们可能在动手写任何代码之前,先要思考下,我们到底要的是什么! 0x00 界面真的需要这么炫酷么 在使用某个界面库之前,我们可能先

关于Emit中动态类型TypeBuilder创建类标记的一点思考

  利用TypeBuilder是可以动态创建一个类型,现在有个需求,动态生成一个dll,创建类型EmployeeEx,需要继承原dll里面的Employee类,并包含Employee类上的所有类标记.   网上有很多例子, //创建TypeBuilder. TypeBuilder myTypeBuilder = myModBuilder.DefineType(typeName, TypeAttributes.Public); myTypeBuilder.SetParent(type);   大概

关于失败的一点思考

睡觉之前突然想到马云说过的一句话:我们要习惯于拒绝,习惯失败,如果我们还没成功,那是因为我们的失败还不够 --------2016.4,11  以此自勉 关于失败的一点思考