stdjkdblom 2019-07-01
排序算法很少会是独立程序,更可能是大项目的一小部分,例如:微软的Office大约有4000万行代码。为了写大型程序,程序员用各种工具和方法,所有这些形成“软件工程”学科
“软件工程”这个词由工程师Margaret Hamilton
创造,她帮助NASA在阿波罗计划中避免了严重问题。
Margaret Hamilton
曾说过:“有点像牙根管治疗:你总是拖到最后才做,但有些事可以预先做好,有点像预防性体验,只不过是预防性软件出错”。
对象,面向对象编程
把大项目分解成小函数,可以让多人同时工作,不用关心整个项目,关心自己的函数就好了。如果接到的任务是写排序算法,只需要保证高效和正确就可以了。
然而把代码打包成函数,依然不够,如果只是这样,微软Office会有几十万个函数。解决办法是:把函数打包成层级,把相关代码都放在一起,打包成对象(Objects)
例如:汽车软件中可能有几个和定速巡航有关的函数,比如,设定速度,逐渐加速减速,停止定速巡航,因为这些函数都相关,可以包装成一个“定速巡航对象”。但不止如此,还可以做更多,“定速巡航”只是引擎软件的一部分,可能还有“火花塞点火”,“燃油泵”和“散热器”。
所以可以做一个“引擎对象”来包括所有“子”对象。除了子对象,“引擎对象”可能有自己的函数,比如,开关引擎。它也会有自己变量,比如汽车行驶了多少千米,总的来说,对象可以包其它对象,函数和变量。
当然,“引擎对象”只是“汽车对象”的一部分,还有传动装置,车轮,门,窗等。作为程序员,如果想设“定速巡航”,要一层层向下,从最外面的对象往里找,最后找到想执行的函数:Car.Engine.CruiseControl.setCruiseSpeed(55)
。
把函数打包成对象的思想叫“面向对象编程”
通过封装组件,隐藏复杂度,之前把晶体管,打包成了逻辑门,现在软件也这样操作。
API和IDE
把大型软件,拆分一个个更小单元,适合团队合作,在不同岗位同时工作,各尽其能。
各个团队负责不同项目,但也有所依赖,所以团队需要文档,帮助理解代码都做什么,以及定义好“程序编程接口”简称“API”
API
帮助不同程序员合作,不用知道具体细节,只要知道怎么使用就行了。
API
控制哪些函数和数据让外部访问,哪些仅供内部。“面向对象”的编程语言,可以指定函数是public
或private
来设置权限。
“面向对象编程”的核心是隐藏复杂度,选择性的公布功能。因为做大型项目很有效,所以广受欢迎。
代码在编译前就只是文字而已,只要编写出来,就可以。但是一般来说,现代软件开发者,会用专门的工具来写代码。工具里集成了很多有用的功能,帮助写代码,整理,编译和测试代码。因为集成了所有东西,因此叫集成开发环境简称IDE。
IDE会定位到出错代码,还会提供信息,帮助解决问题,这叫“调试(debug)”,调试很重要,大多数程序员会花70%~80%时间调试,而不是在写代码。
给代码写文档,文档一般放在README
的文件里,告知其它程序员,看代码前先看这个文件。文档也可以直接写成“注释”,放在源码里。
注释存在的唯一作用,就是帮助开发者理解代码。
源代码管理,也叫“版本控制”。会把代码放到一个中心服务器上,叫“代码仓库”。
不希望提交的代码里有问题,因为其他人可能用到了这些代码,导致他们代码崩溃,造成困惑而且浪费时间,代码的主版本(master
)应该是编译正常,尽可能少bug。
写代码和测试代码密不可分,测试一般由个人或小团队完成,测试可以统称“质量保证测试”,简称“QA”。
让软件在各种情况下按照预期运行。
bate 版
软件:意思是软件接近完成,但不是100%完全测试过。alpha 版
软件:很粗糙,错误很多经常只在公司内部测试。
重点:晶圆的制作流程:光刻
在大概50年代里,软件从纸带打孔变成面向对象,在集成开发环境中写程序。但如果没有硬件的大幅度进步,软件是不可能做到这些的。
硬件性能的爆炸性增长。
分立元件, Discrete components
电子计算机的诞生年代,大约1940年代~1960年代中期这段时间里,计算机都由独立部件组成,叫“分立元件”。然后不同组件再用线连在一起。
例如,ENIAC有1万7千多个真空管,7万个电阻。1万个电容器,7千个二极管,5百万个手工焊点。
如果想提升性能,就要加更多部件,这导致更多电线,更复杂。这个问题叫“数字暴政”。
分立元件:只有一个电路元件的组件,可以是被动的(电容,电阻,电感)或主动的(晶体管或真空管)
1950年代中期,晶体管开始商业化(市场上买得到)开始用在计算机里,晶体管比电子管,更小更快更可靠,但晶体管依然是分立元件。
1959年,IBM把709计算机从原本的电子管全部换成晶体管,诞生了新机器 IBM 7090速度快6倍,价格只有一半。
晶体管标志着“计算机2.0时代”的到来。
虽然更快更小,但晶体管的出现,还是没有解决“数字暴政”的问题,有十几万个独立元件的计算机不但难设计而且难生产。
1960年代,这个问题的严重性达到顶点,电脑内部常常一大堆电线缠绕在一起。
解决办法就是引入一层新抽象,封装复杂性。
突破性进展在1958年,当时Jack Killby在德州仪器工作,演示了一个电子元件:“电路的所有组件都集成在一起”。
简单说就是:与其把多个独立部件用电线连接起来,拼装出计算机。把多个组件包在一起,变成一个新的独立组件,这就是:集成电路(IC)
几个月后,在1959年Robert Noyce的仙童半导体让集成电路变为现实,Killby用锗来做集成电路,锗很稀少而且不稳定,仙童半导体公司用硅,硅的蕴含量丰富,占地壳四分之一,也稳定可靠。所以Noyce被公认为现代的集成电路之父。开创了电子时代,创造了硅谷。
起初,一个IC只有几个晶体管。
例如:早期样品,由西屋公司制造。
即使只有几个晶体管,也可以把简单电路,逻辑门,能封装成单独组件。IC
就像电脑工程师的乐高积木,可以组合出无数种设计,但最终还是需要连接起来,创造更大更复杂的电路,比如整个计算机。
所以工程师再度创新,印刷电路板,简称PCB
。PCB
可以大规模生产,无需焊接或用一大堆线。它通过蚀刻金属线,把零件连接到一起。
把PCB
和IC
结合使用,可以大幅减少独立组件和电线,但做到相同的功能。而且更小,更便宜,更可靠。
许多早期IC
都是把很小的分立元件,封装成一个独立单元,例如这块1964年的IBM样品。不过,即使组件很小,塞5个以上的晶体管还是很困难。
光刻
为了实现更复杂的设计,需要全新的制作工艺:“光刻”。
简单说就是:用光把复杂图案印到材料上,比如半导体,它只有几个基础操作,但可以制作出复杂电路。
例子:
用类似步骤,光刻可以制作其它电子元件,比如电阻和电容,都在一片硅上,而且互相连接的电路也做好了。
例子中,只做了一个晶体管,但现实中,光刻法一次会做上百万个细节。
芯片放大之后,导线上下交错,连接各个元件。
尽管可以把光掩膜投影到一整篇晶圆上,但光可以投射成任意大小。就像投影仪可以投满荧幕一样。
可以把光掩膜聚焦到极小的区域,制作出非常精细的细节。
一片晶圆可以做很多IC
,整块都做完后,可以切割然后包进微型芯片,微型芯片就是在电子设备中,那些小长方体。记住,芯片的核心都是一小篇IC
。
随着技术的发展,晶体管变小,密度变高。
1960年代初,IC
很少超过5个晶体管,因为塞不下。
但1960年代中期,市场上开始出现超过100个晶体管的IC
。
摩尔定律,Moore's Law
1965年,戈登●摩尔看到了趋势:每两年左右,得益于材料和制造技术的发展,同样大小的空间,能塞进两倍数量的晶体管。这叫摩尔定律。(它不是定律,只是一种趋势,但它是对的。)
芯片的价格也急剧下降,1962年平均50美元,下降到1968年2美元左右。如今,几美分就能买到IC
。
晶体管更小密度更高,还有其它好处,晶体管越小,要移动的电荷量就越少,能更快切换状态,耗电量更小。
电路更紧凑,还意味着信号延迟耕地,导致时钟速度更快。
Inter 4004 CPU,是个重要里程碑,发布于1971年,是第一个用IC做的处理器,也叫微型处理器。因为真的非常小。它有2300个晶体管。
惊叹于它的整合水平,整个CPU在一个芯片里,而仅仅20年前,用分立元件会占满整个屋子。
集成电路的出现,尤其是用来做微处理器,开启了计算3.0
晶体管数量大幅度增长,1980年三万个,1990年一百万个,2000年三千万个,2010年十亿个,在一个芯片里。
为了达到这种密度,光刻的分辨率,从大约一万纳米,大概是人类头发直径的1/10。发展到如今的14纳米,比血红细胞小400倍。
当然,CPU不是唯一受益的元件。大多数电子器件都在指数式发展:内存,显卡,固态硬盘,摄像头感光元件,等等。
如今的处理器,比如iphone7的A10 CPU,有33亿个晶体管。面积仅有1cm * 1cm,比一张邮票小。
现代工程师设计电路时,当然不是手工一个个设计晶体管,这不是人力能做到的。
1970年代开始,超大规模集成(VLSI)软件用来自动生成芯片设计。用比如“逻辑综合”这种技术可以放一整个高级组件,比如内存缓存。软件会自动生成电路,做到尽可能高效。(EDA技术,HDL自动综合技术)
进一步做小,会面临2个大问题:
1940,1950年代的电脑,每次只能运行一个程序,程序员在打孔纸卡上写程序,然后拿到一个计算机房间,交给操作员。等计算机空下来,操作员会把程序放入。然后运行,输出结果,停机。
以前计算机慢,这种手动做法可以接受。运行一个程序通常要几个小时,几天,甚至几周。
计算机越来越快,指数级增长,很快,放程序的时间,比程序运行时间还长,需要一种方式,让计算机自动运作。于是“操作系统”诞生了。
操作系统,简称OS
,其实也是程序。但它有操作硬件的特殊权限,可以运行和管理其它程序。操作系统一般是开机第一个启动的程序,其它所有程序,都由操作系统启动。
操作系统开始与1950年代,那时计算机开始变得更强大更流行。第一个操作系统,加强了程序加载方式。
之前只能一次给一个程序,现在可以一次多个。当计算机运行完一个程序,会自动运行下一个程序,这样就不会浪费时间,找下一个程序的纸卡,这叫: “批处理”。
电脑变得更快更便宜,开始在出现在世界各地,特别是大学和政府办公室,很快,人们开始分享软件,但有一个问题,在哈佛1号和ENIAC那个时代,计算都是一次性的,程序员只需给那“一台”机器写代码,处理器,读卡器,打印机都是已知的,但随着电脑越来越普遍,计算机配置并不总是相同的。比如计算机可能有相同CPU,但不同的打印机。不仅要担心写程序,还要担心程序怎么和不同型号的打印机交互,以及计算机连着的其它设备,这些统称“外部设备”。
设备驱动程序,Device drivers
和早期的外部设备交互,是非常底层的,程序员要了解设备的硬件细节,加重问题的是,程序员很少能拿到所有型号的设备来测代码。所以一般是阅读手册来写代码,祈祷能正常运行。
现在是“即插即用”,以前是“祈祷能用”。这很糟糕,所以为了程序员写软件更容易,操作系统充当软件和硬件之间的媒介。更具体的说,操作系统提供API来抽象硬件,叫“设备驱动程序”。
程序员可以用标准化机制,和输入输出硬件(I/O)交互。比如,程序员只需要调用print(highscore)
,操作系统会处理 输到纸上的具体细节。
到1950年代尾声,电脑已经非常快了,处理器经常闲着,等待慢的机械设备(比如打印机和读卡器)。程序阻塞在I/O上,而昂贵的处理器则在度假。
50年代后期,英国曼切斯特大学开始研发世界上第一台超级计算机,Atlas
,他们知道机器会超级快,所以需要一种方式来最大限度的利用它。他们的解决方案是一个程序叫Altas Supervisor
于1962年完成。这个操作系统,不仅像更早期的批处理系统那样,能自动加载程序,还能在单个CPU上同时运行几个程序。它通过调度来做到这一点。
假设Atlas
上有一个游戏在运行,并且调用一个函数print(highscore)
。它让Atlas
打印一个叫highscore
的变量值。
print
函数运行需要一点时间,大概上千个时钟周期,但因为打印机比CPU慢,与其等着它完成操作,Altas
会把程序休眠,运行另一个程序,最终,打印机会告诉Altas
打印已完成,Altas
会把程序标记可继续运行。print
语句之后的下一行代码。这样,Altas
可以在CPU上运行一个程序,同时另一个程序在打印数据,同时另一个程序读数据。
Altas
的工程师做的还要多,配了4台纸带读取器,4台纸带打孔机,多达8个磁带驱动器。
使多个程序可以同时运行,在单个CPU上共享时间,操作系统的这种能力叫:“多任务处理”。
同时运行多个程序有个问题,每个程序都会占一些内存,当切换到另一个程序时,不能丢失数据。解决办法是:给每个程序分配专属内存块。
例如:假设计算机一共有10000个内存位置,程序A分配到内存地址0到999。而程序B分配到内存地址1000到1999,以此类推。(开着软件占缓存)
如果一个程序请求更多内存,操作系统会决定是否同意,如果同意,分配哪些内存块,这种灵活性很好,但带来一个奇怪的后果。程序A可能会分配到非连续的内存块,比如内存地址0到999,以及2000到2999。(解决方法:虚拟内存)
真正的程序可能会分配到内存中数十个地方。这对程序员来说很难跟踪。
虚拟内存,Virtual Memory
也许内存里有一长串销售额,每天下班后要算销售总额,但列表存在一堆不连续的内存块里,为了隐藏这种复杂性,操作系统会把内存地址进行“虚拟化”。这叫“虚拟内存”,程序可以假定内存总是从地址0开始,简单又一致。而实际物理位置,被操作系统隐藏和抽象了。
用程序B来举例,它被分配了内存地址1000到1999。对程序B而言,它看到的地址是0到999。操作系统会自动处理,虚拟内存和物理内存之间的映射。如果程序B要地址42,实际上是物理地址1042。这种内存地址的虚拟化,对程序A更有用。
在例子中,A被分配了两块隔开的内存,程序A不知道这点。以A的视角,它有2000个连续的地址,当程序A读内存地址999时,会刚好映射到物理内存地址999,但如果程序A读下一个地址1000,会映射到物理地址2000。这种机制使程序的内存大小可以灵活增减叫“动态内存分配”。
对程序来说,内存看起来是连续的。它简化了一切,为了操作系统同时运行多个程序提供了极大的灵活性。给程序分配专用的内存范围。
另一个好处是这样隔离起来会更好,如果一个程序出错,开始写乱七八糟的数据,它只能捣乱自己的内存,不会影响到其它程序。这叫“内存保护”。防止恶意软件(如病毒)也很有用。
例如:不希望其它程序有能力读或改邮件程序的内存。如果有这种权限恶意软件可以以你的名义发邮件,甚至窃取个人信息。
Multics
Atlas
既有“虚拟内存”也有“内存保护”,是第一台支持这些功能的计算机和操作系统。到1970年代,计算机足够快且便宜。大学会买电脑让学生用,计算机不仅能同时运行多个程序,还能让多用户能同时访问。
多个用户用“终端”来访问计算机。“终端”只是键盘+屏幕,连到主计算机终端本身没有处理能力,冰箱大小的计算机可能有50个终端,能让50个用户使用。
这时操作系统不但要处理多个程序,还要处理多个用户。为了确保其中一个人,不会占满计算机资源,开发了分时操作系统,意思是:每个用户只能用一小部分处理器和内存等。因为电脑很快,即使拿到1/50的资源也足以完成许多任务。
早期分时操作系统中,最有影响力的是Multics
(多任务信息与计算系统),于1969年发布,Multics
是第一个,从设计时就考虑到安全的操作系统,开发人员不希望恶意用户访问不改访问的数据。这导致Multics
的复杂度超过当时的平均水准,操作系统会占大约1Mb内存,这在当时很多。可能是内存的一半,只拿来运行操作系统。
Multics
的研究人员Dennis Ritchie曾说过:阻碍Multics
获得商业成功的一个明显问题是从某种方面来说,它被过度设计了,功能太多了。
所以Dennis和另外一个Multics
研究员Ken Thompson联手开发新的操作系统叫Unix
。
他们想把操作系统分成两部分:
紧凑的内核意味着功能没有那么全面。Multics
的另外以为开发者Tom Van Vleck回忆说:我对Dennis说,我在Multics
写的一半代码都是错误恢复代码。他说:“Unix 不会有这些东西, 如果有错误发生,我们就让内核‘恐慌’(panic)当调用它时,机器会崩溃,你得在走廊里大喊,‘嘿,重启电脑’”。
内核如果崩溃,没有办法恢复,所以调用一个叫“恐慌”(panic)的函数,起初只是打印“恐慌”一词,然后无限循环。
这种简单性意味着,Unix可以在更便宜更多的硬件上运行,使Unix在Dennis和Ken工作的贝尔实验室大受欢迎,越来越多开发人员用Unix写程序和运行程序。工具数量日益增长,1971年发布后不久,就有人写了不同编译语言的编译,甚至文字处理器,使得Unix迅速成为1970~80年代最流行的操作系统之一,到1980年代早期,计算机的价格降到普通人买得起,这些叫“个人电脑”或“家庭电脑”。这些电脑比大型主机简单得多,主机一般在大学,公司和政府。因此操作系统也得简单。
例如:微软的磁盘操作系统(MS-DOS)只有160KB,一张磁盘就可以容纳操作系统。
于1981年发布的,成为早期家用电脑最受欢迎的操作系统。虽然缺少“多任务”和“保护内存”这样的功能。意味着程序经常使系统崩溃。虽然很讨厌但还可以接受,因为用户可以重启。哪怕是微软1985年发布的早期Windows虽然在90年代很流行,但却缺乏“内存保护”。当程序行为不当时,就会“蓝屏”。代表程序崩溃的非常严重,把系统也带崩溃了。
如今的计算机,有现代操作系统,比如Mac OS X, Windows 10, Linux, iOS和Android。虽然大部分设备只有一个人使用。操作系统依然有“多任务”,“虚拟内存”, “内存保护”。因此可以同时运行多个程序:一边使用浏览器,一边使用PS修图,播放音乐,同步笔记等。如果没有操作系统这几十年的发展,这些都不可能。
操作系统: