UNIX编程艺术

I-场景

  1. 哲学
    机制而不是策略,自由放纵注意风格,产生了多样性。比如Unix应用程序提供很多的行为选项,让非技术的用户晕头转向,而失去了很多用户;但是策略相对短寿、机制才会长存,具有很大的灵活性。
    趣味性是一个峰值效率的标志。对于程序员和开发人员来说,如果完成某项任务所需要付出的努力对他们是个挑战又恰好还在力所能及的范围,他们就会觉得很有乐趣。 而UNIX有Hack趣味。
    UNIX哲学是自下而上的,注重实效,鼓励分清轻重缓急的感觉以及怀疑一切的态度,并鼓励你以幽默达观的态度对待这些。
    简洁是UNIX程序的核心风格。 UNIX哲学的KISS原则:Keep it simple, stupid!
    先制作原型,再精雕细琢。优化之前,先确定能用。
    2.历史–双流记
    本章主要回顾UNIX的历史,来阐明如今的UNIX文化为什么呈现当前的状态。 两个历史分别是UNIX的起源和历史以及黑客的起源和历史

UNIX的起源和历史
UNIX的祖辈是CTSS–兼容分时系统,父辈是颇具开拓性的Multics项目(试图建立一个具备众多功能的系统);
创世纪:1969-1971年,UNIX诞生于贝尔实验室; *出埃及记:1971-1980,C语言的加入,其表述方式带来的可读性、可改性和可移植性,给UNIX带来了一定的成功。
TCP/IP和UNIX内战:1980-1990, TCP/IP的引入,让UNIX更加有活力。 然而微软的兴起和AT&T的拆分,让UNIX进入了炼狱。UNIX的产业化,破坏了UNIX源码的自由交流,而自由交流却正是滋养UNIX系统早期的活力。
反击帝国:1991-1995,Linus Torvalds宣布了Linux项目,依赖分布式开发和patch(补丁)工具,加入了Internet和图形界面,实现了Unix的平价之梦,并拼装了UNIX的传统元素。 1995年之后,UNIX的故事就变成了开源运动的故事。

黑客的起源和历史:1961-1995
游戏在校园的林间:1961-1980年,MIT的人工智能实验室的程序员应该是第一批自称”hacker“的人;
互联网大融合与自由软件运动:1981-1991. 1983年,BSD植入了TCP/IP,UNIX文化和ARPANET文化开始融合。RMS创建了GNU项目,致力于编一个完全自由的操作系统。自由软件术语的提出,让黑客文化更加有自我意识。
Linux和实用主义者的应对:1991-1998. Linus Torvalds巧妙地跨越了GPL和反GPL的派别之争。他利用GNU工具包搭建了自创的Linux内核,用GPL的传染性质保护它。Torvalds明确的表示他认为自由软件通常很好,但他偶尔也用专有软件,他拒绝成为狂热分子。

开源运动:1998年及之后
一个部落的零距离可能来自他们维护的代码库,或是一个或多个有着超凡影响力的领导者,或是一门语言,一个开发工具,或是一个特定的软件许可,或是一种技术标准,或是基础结构某个部分的管理组织。 1995年之后,Linux扮演了一个特殊的角色:既是社区内多数软件的统一平台,又是黑客中最被认可的品牌。整个黑客文化开始凝聚在一个共同目标:推动Linux和集市开发模式向前发展。
”开源“背后另一个意图就是希望将黑客社区的方法以一种更亲和市场、更少对抗性的方式介绍给外部世界。

Unix的历史教训
距开源越近就越繁荣;
过度依赖任何一种技术或者商业模式都是错误的–相反,保持软件及其设计传统的灵活性才是生存之道;
别和低价而灵活的方案较劲;
真正的专业和奉献精神,正是我们在屈服于世俗观念的”合理商业做法“之前的所作所为。

  1. 对比:Unix哲学同其他哲学的比较
    操作系统的风格元素
    操作系统的统一性理念。比如UNIX系统的”一切皆文件“模型和由此基础上建立的管道概念。
    多任务能力。Unix系统拥有抢先式多任务的能力。 多任务和多用户不是一回事。
    协作进程:UNIX的IPC(进程间通信方式)很灵活。
    内部边界: Unix是相信程序员的,但是程序员不能破坏其他人的数据,所以Unix设立了内部边界来防范恶意用户或者有缺陷的程序。
    开发的门坎: Unix将编译器和脚本工具放在默认安装中,支持了一种跨越众多机器的玩家开发文化。

操作系统的比较
列举了若干操作系统的优缺点。

种什么籽,得什么果
竞争对手的一些缺点: 比如不可移植性、不具备良好的网络支持能力。
而Windows在服务器上的缺陷(安全等原因),使得Linux取得了重大突破。
就像造房子一样,在坚实的地基上修理上层建筑当然要比更换地基而不破坏上层建筑来得容易。

II–设计
4. 模块化:保持清晰,保持简洁。
早期的Unix程序员擅长模块化是因为他们被迫如此,如果没有良好的架构,操作系统就会崩溃。
封装良好的模块,不会过多向外部披露自身的细节,不会直接调用其他模块的实现码,也不会胡乱共享全局数据。
具有最佳尺寸的模块并不意味着代码有高质量,还得考虑紧凑性和正交性。
紧凑性就是一个设计能否装入人脑。比如一个设计有经验的用户不需要操作手册,这个设计就是紧凑的。
正交性是指任何操作均无副作用,每一个动作只改变一件事,不会影响其他。
不要重复自身(don’t repeat yourself)。
软件的分层:自顶向下和自底向上。 当自顶向下和自底向上发生冲突时,顶层的应用逻辑和底层的域原语集必须用胶合逻辑层来进行阻抗匹配。 胶合层是个挺讨厌的东西,必须尽可能薄。
OO语言使抽象变得容易,但是过多的层次破坏了透明性,我们很难看清层次,无法理清代码的运行过程。
单个函数与其说是行数计算问题,不如说是内部复杂度性的问题(比如说局部变量太多、代码存在太多缩进)。

  1. 文本化:好协议产生好实践
    设计文件格式和应用协议需要考虑的重要方面:互用性、透明性、可扩展性以及经济型。
    使用二进制的唯一正当理由:处理大批量的数据集或者关心时间或指令开销。
    数据文件元格式有多重不同的元格式,比如DSV、RFC 822、Cookie-Jar、Record-Jar、XML、Windows INI等;
    应用协议如果是文本格式的,凭肉眼可以很容易地分析,很多事情变得容易。可以看看SMTP、POP3和IMAP三种经典的应用协议。
    应用协议元格式:尽管网络带宽比存储昂贵许多,需要重视事物处理的经济性,但是文本格式的透明性和互用性优势还是十分显著,大多数设计者还是选择了采用可读性更高的文本格式。比如HTTP协议、BEEP协议、XML-RPC/SOAP/Jabber协议。

6.透明性:来点光
在第五章中,讨论了数据格式和应用协议进行文本化的重要性,文本化让透明性和可显性的品质得到了提升。 * 如果实际上能够预测到程序行为的全部或大部分情况,这个程序就是透明的; * 如果程序可以帮助人们建立“做什么、怎样做”,这个软件系统就是可显的。比如对用户来说,文档有助于可显性; 对程序员而言,好的命名规范有助于提高可显性。 * 要追求代码的透明,最有效的方法很简单,就是不要在具体操作的代码上叠放太多的抽象层。 * 透明的系统在bug发作时,更容易实施恢复措施;同时,透明的系统更容易让人理解,从而更加方便维护。

  1. 多道程序设计
    UNIX最具特点的程序模块化方法就是将大型程序分解成多个协作进程。
    多个并发进程除了带来模块化的好处之外,另一个原因是为了更强的安全性。
    UNIX IPC:把任务转给专门程序(shell out)、管道/重定向(管道主要缺点是单向性,命名管道可以作为两者间的配接器)、从进程、对等进程(临时文件、信号、套接字、共享内存)。

  2. 微型语言:寻找歌唱的乐符
    UNIX班有个长期传统,存在小型的、为专门应用领域特制、大量减少程序行数的语言。 比如无数Unix排版语言(troff/pic)、shell使用程序(awk/sed/dc/bc)和软件开发工具(make/yacc/lex等)。 微型语言与脚本语言之间的界限都很模糊。 * 一切可计算的问题都可以计算,叫做图灵完备。 * 明白微型语言在什么时候什么场景下使用。 * 某些情况下,需要我们我们去设计一个微型语言。 首先要尽可能保持微型语言的简单(复杂度),思考能否通过扩展或者嵌入现有脚本语言来实现自己的微型语言(这是实现命令性语言的正确方法),慎用宏。

  3. 生成:提升规格说明的层次
    数据比程序逻辑更易驾驭,更加直观,透明性和清晰性方面更胜一筹。
    数据驱动编程,将代码和数据结构划分清楚,在改变程序逻辑时,只要改变数据结构而不用修改代码。
    专用代码的生成,比如用工具生成HTML代码。尽可能少干活,建设性的懒惰是大师级程序员的基本美德之一。

10.配置:迈出正确的第一步
配置在哪里?在下面这些地方,查询通常按照下面的顺序进行,后面的设置会覆盖前面的设置。
运行控制文件,在/etc/目录下。
环境变量,系统环境变量、用户环境变量。
命令行选项
上述配置是从最不易改变到最易改变的顺序排列的。

  1. 接口:Unix环境下的用户接口设计模式
    程序的接口就是程序同用户或者其它程序通信的方法总和。
    最小立异原则:少来标新立异,是所有接口设计中的通用原则,且并非仅局限于软件设计。一心不能二用,应该把中心放在接口所属的任务上。而这个原则,也不应被理解为机械的保守主义。 如有可能,尽量允许用户将接口功能委派给熟悉的程序来完成,不能委派时就效仿。
    Unix程序中存在丰富的接口风格:面向行的、面向屏幕字符阵列的和基于X的,不同的接口风格,适用于不同的任务。
    接口的几种度量标准:简洁、表现力、易用、透明性和脚本化能力。
    CLI和可视化接口之间的权衡。命令行更具表达力,尤其是针对复杂的任务,同时具有高度的脚本化能力,但是CLI需要费劲地记忆(易用性低),并且透明性也非常低。比如说数据库的SQL语句和图形界面操作来说,可以很明显看出二者之间的差别,同样命令行的计算器和图形界面的计算器对比也比较明显。
    Unix接口设计模式。根据输入和输出可以分为这些模式:过滤器模式(比如grep)、Cantrip模式(比如clear)、源模式(不需要输入,比如ls)、接收器模式(只接收不输出,例子较少,比如lpr)、编译器模式(既无标准输入也无标准输出,将错误消息发送到标准错误端)、ed模式(比如ftp、sh)、Roguelike模式(字符阵列比如vi)、“引擎和接口分离”模式(MVC模式作为GUI原型的建议)、CLI服务器模式
    网页浏览器作为通用前端,无需编写一个定制的GUI前端。

  2. 优化
    过早优化是万恶之源
    不到万不得已,尽量别去优化一个工作中的系统,而是等上几个月,期待硬件性能更好。
    先估量,再优化。通过profiler,去明确瓶颈所在,而profiler本身也是存在工具误差的。
    最有效的代码优化方法是保持代码短小简单。目标机器是分层的,将核心的数据结构和指令代码放在快速缓存。
    快速处理器的另一个效应是性能经常受限于I/O以及网络事务的开销,要尽量避免协议的往返。
    有三种常规策略来减少延迟:批操作、重叠操作、缓存操作结果。

  3. 复杂度:尽可能简单,但别简单过了头
    “Keep it simple, stupid”, 对于简单的理解其实是很复杂的。
    复杂度的三个来源:程序员实现的复杂度、顾客和用户使用的复杂度以及代码量,关于怎么去折中,是没有标准答案的。
    通过五个编辑器的故事,了解编辑器在处理更复杂任务时产生的不同程度的选择复杂度。
    吝啬原则:只有实证了其它方法不通时才写庞大程序。

III– 工具
14. 语言:C还是非C
Unix下面存在非常多的语言种类。一是因为Unix广泛应用于研究和教学平台,二是因为应用设计和实现语言的合理搭配对生产力有极大促进。
C语言很厉害、很经济。但是要求程序员自己完成内存管理,很复杂,并且随着硬件设备的性能提升,主要瓶颈集中在I/O事件等待、网络延迟以及缓存列填充等限制上,所以Python、Java等语言慢慢兴起。

  1. 工具:开发的艺术
    本章将介绍Unix下的开发策略–编译代码、管理代码配置、性能分析、调试以及自动完成各种脏活累活。 这一套工具比起IDE更加灵活。 * 编辑器选择:vi和Emacs * 专用代码生成器:yacc、lex * 自动化编译:make。 一个笑话:输入”make love”,输出是“Don’t know how to make love”。 * 版本控制系统 * 性能分析:gprof

  2. 重用:论不要重新发明轮子
    很多人喜欢自己造轮子,因为库可能不是透明的、充满bug,还不如自己来的痛快。
    随着开源的提出,我们应当选择好的轮子,这样可以节省时间,提高效率。
    很多开源的网站,如github等。
    注意一些许可证的问题。

IV–社区
17. 可移植性:软件可移植性与遵循标准
移植性一直是Unix的主要优势,可移植的戒律往往在架构、接口和实现上施加了一种简单化的影响,提高了项目成功的几率也降低了生命周期的维护成本。
C语言、Unix标准,IETF、RFC标准,一系列的标准和草案让接口更加规范,从而更方便移植。
编程语言的可移植:Java和Python具备良好的可移植性。
可移植工具:autoconf来处理移植问题,configure/make/make install来干净利落的编译。
可移植性需要标准,而开放源代码同样给标准化的过程带来了重要的影响。

  1. 文档:向网络世界阐述代码
    文档一般分为两类,所见即所得(word)和标记为中心(XML、markdown),各有优缺点;
    编写Unix文档最佳实践:信息密度适中、不要过于庞大、也不要省略功能细节和存在的问题、将文档放在网上。

  2. 开放源码:在Unix新社区中编程
    开源开发的规则很简单:源码公开、尽早发布-经常发布、给贡献以表扬(物质奖励或者精神奖励)
    与开源工作者协同工作的最佳实践:版本控制系统(Git、svn等)、良好的代码注释、良好的代码规范和文件命名规范、测试好再发布大妈、良好的交流实践(邮件列表、网站等)。
    许可证的逻辑,挑选合适的许可证。

  3. 未来:危机与机遇
    回顾过去,网络互联、位图图形显示以及个人计算机这三个特殊的技术变化驱动了Unix设计风格的重大变革。
    尽管伴随着许多创新,但所有对这三个技术的响应都保持着Unix的设计准则–模块化、透明性、机制同策略分离以及之前提到的品质。
    Unix对GUI的支持较弱。Unix的API没有使用异常(C语言缺乏抛出异常的机制)。
    从历史来看,我们只要能够从错误中汲取教训,文化薪火相传,Unix是不会输的。

发布了47 篇原创文章 · 获赞 2 · 访问量 2923

猜你喜欢

转载自blog.csdn.net/m0_46560389/article/details/105396639