《Unix 编程艺术》 第一章 哲学
#1.4 Unix 之失
最终的用户永远比操作系统设计人员更清楚他们究竟需要什么。
用错误的方法解决正确的问题,总比用正确的方法解决错误的问题好
#1.5 Unit 之得
#1.5.2 跨平台可移植性和开放标准
IEEE 早期定义的”可移植操作系统标准(Portable Operating System Standard, POS)“ 被社区为了读起来更像 Unix 而加了后缀变为 ”POSIX“。因为只有称之为 Unix API 等价物才能算是可移植操作系统标准比较可信的模型。
#1.5.5 从头到家的灵活性
许多操作系统自诩比 Unix 来的更”现代“,”友好“,它们漂亮外表的背后,却是以貌似精巧实则脆弱狭隘难用的编程接口,把用户和开发者禁锢在单一的界面方针下。在这样的操作系统中,完成系统任务很容易,而完成设计者没有预料到的需求,用户要么是无计可施,要么是痛苦不堪。
Unix 传统是让各个程序接口小巧,简洁和正交。整个 Unix 系统,容易的事还是那么容易,困难的事,至少是可能做到的。
#1.5.6 Unix Hack 之趣
充满痛苦的开发环境只会浪费劳动力和创造力;这样的环境会在无形之中耗费大量时间,资金,机会。
乐趣是一个符号,意味着效能,效率和高产。
#1.6 Unix 哲学基础
Doug Mcllroy (Unix 管道发明人,Unix 传统奠基人之一) 说:
- 让每个程序做好一件事。如果有新任务,就重新开始,不要往原程序中加入新功能而搞得复杂
- 假定每个程序的输出都会成为另一个程序的输入,哪怕那个程序还是未知的。输出中不要有无关的信息干扰。
- 对拙劣的代码别犹豫,扔掉重写
- 优先使用工具而不是拙劣的帮助来减轻编程任务的负担。工欲善其事,必先利其器。
同样可以将 Doug Mcllroy 描述中的“程序”替换为“模块” 去理解。
Rob Pike(最伟大的 C 语言大师之一),在 《Notes on C Programming》从另一个不同的角度表述 Unix 的哲学:
- 你无法判定程序会在什么地方耗费运行时间。瓶颈经常出现在想象不到的地方,所以别胡乱找个地方改代码,除非你已经证实那就是瓶颈所在。
- 在你没对代码进行估量,特别是没找到最耗时的那部分之前,别去优化速度。
- 花哨的算法在 n 很小时通常很慢,而 n 通常很小。除非你确定 n 总是很大,否则不要用花哨的算法(即使 n 很大,也优先考虑原则 2)。
- 花哨的算法比简单算法更容易出 Bug,更难实现。尽量使用简单的算法配合简单的数据结构。
- 编程的核心是数据结构,而不是算法。
Ken Thompson(Unix 最初版本的设计者和实现者,对 Pike 原则 4 做了强调):拿不准就穷举
Unix 的原则,可以概括为如下几点:
- 模块原则:使用简洁的接口拼合简单的部件
- 清晰原则:清晰胜于机巧
- 组合原则:设计时考虑拼接组合
- 分离原则:策略同机制分离,接口同引擎分离
- 吝啬原则:除非确无它法,不要编写庞大的程序
- 透明性原则:设计要可见,以便审查和调试
- 健壮原则:健壮源于透明和简洁
- 表示原则:把知识叠入数据以求逻辑质朴而健壮。
- 通俗原则:接口设计避免标新立异。
- 缄默原则:如果一个程序没什么好说的,就沉默。
- 补救原则:出现异常时,马上退出并给出足够错误信息。
- 经济原则:宁花机器一分,不花程序员一秒。
- 生成原则:避免手工 hack,尽量编写程序去生成程序。
- 优化原则:雕琢前先要有原型,跑之前先学会走。
- 多样原则:绝不相信所谓的“不二法门”的断言。
- 扩展原则:设计着眼未来,未来总比预想来的块。
#模块原则:使用简洁的接口拼合简单的部件
Brian Kernighan(Unix 小组的 3 号人物)曾经说过:“计算机编程的本质就是控制复杂度”。
排错占用了大部分的开发时间,弄出一个拿得出手的可用系统,通常与其说出自才华横溢的设计成果,还不如说是跌跌撞撞的结果。
要编制复杂软件而又不至于一败涂地的唯一方式就是降低其整体复杂度——用清晰的接口把若干简单的模块组合出一个复杂软件。如此一来,多数问题只会局限在某个局部,那么就还有希望对局部改进而不至于牵动全身。
#清晰原则:清晰胜于机巧
在写程序时,要想到你不是写给执行代码的计算机看的,而是给人——将来阅读维护源码的人,包括你自己——看的。
为了取得程序一丁点的性能提升就大幅度增加技术的复杂性和晦涩性,这个买卖做不得——这不仅仅是因为复杂的代码冗余滋生 bug,也因为它会使日后的阅读和维护工作更加艰难。
#组合原则:设计时考虑拼接组合
如果程序彼此之间不能有通信,那么软件就难免会陷入复杂度的泥淖。
Unix 传统极力提倡采用简单、文本化、面向流、设备无关的格式。…Unix 程序员偏爱这种做法并不是因为他们仇恨图形用户界面,而是因为如果程序不采用简单的文本输入输出流,他们就极难衔接。
#分离原则:策略同机制分离
策略和机制是按照不同你的时间尺度变化的,策略的变化要远远快于机制。
可以将机制视作规则,策略则是在规则下实现目的的方式。
比如系统定义了消息机制,定义了消息的发送、接受、处理、返回
应用可依赖机制通过不同的策略实现不同的功能。
#简洁原则:设计要简洁,复杂度能低则低
程序员们都很聪明,常常以能玩转复杂东西和耍弄抽象概念的能力为傲,这一点无可厚非……但他们的设计能力大大超出他们的实现和排错能力,结果便是代价高昂的废品。
过度的复杂性往往来自于项目的要求,而这些要求常常基于当月的推销热点,而不是基于顾客的需求和软件实际能够提供的功能。许多优秀的设计被市场推销所需要的大堆大堆“特性清单”扼杀——实际上这些特性几乎从没用过。然后恶性循环开始了:比别人花哨的方法就是把自己变得更花哨。很快,庞大臃肿变成了业界标准,每个人都在使用臃肿不堪、bug 极多的软件,连软件开发人员也不敢敝帚自珍。
要避免这些陷阱,唯一的方法就是鼓励另一种软件文化,以简洁为美。
#吝啬原则:除非确无它法,不要编写庞大的程序
程序大了,维护起来就困难。由于人们对花费了大量精力才做出来的东西难以割舍,结果导致在庞大的程序中把投资浪费注定要失败或者并非最佳的方案上。
#健壮原则:健壮源于透明与简洁
让程序健壮的方法,就是让程序的内部逻辑更易于理解。要做到这一点主要靠 透明化
和 简洁性
。
软件的透明性就是指一眼能看出是怎么回事。人们不需要绞尽脑汁就能够推断出所有可能的情况,那么这个程序透明的。
模块性是组织程序达到更简洁目的的一个方法。
#表示原则:把知识叠入数据以求逻辑质朴而健壮
数据要比编程逻辑更容易驾驭。如果要在复杂数据和复杂代码中选择一个,宁愿选择前者。在设计中,应该主动将代码的复杂度转移到数据之中去。
#通俗原则:接口设计避免标新立异
接口设计应该避免毫无来由的标新立异和自作聪明。
最小立异原则的另一面是避免表象相似而实际却略有不同。这会极端危险,因为表象相似往往导致人们产生错误的假定。所以最好让不同的事物有明显区别,而不要看起来几乎一模一样。 —— Henry Spencer
#缄默原则:如果一个程序没什么好说的,就保持沉默
行为良好的程序应该默默工作,决不唠唠叨叨,碍手碍脚。沉默是金。
设计良好的程序将用户的注意力视为有限的宝贵资源,只有在必要时才要求使用。
#补救原则:出现异常时,马上退出并给出足量错误信息
软件要尽可能从容地应付各种错误输入和自身的运行错误。但是,如果做不到这一点,就让程序尽可能以一种容易诊断错误的方式终止。
如果补救措施明明没有成功,却悄无声息地埋下崩溃的隐患,直到很久以后才显现出来,这就是最坏的一种情况。
“就算输入的数据很不规范,一个设计良好的程序也会尽量领会其中的意义,以尽量与别的程序协作;然后,要么响亮地倒塌,要么为工作链下一环的程序输出一个严谨干净正确的数据”—— Jonathan Postel(互联网主要架构者之一,第一个互联网 RFC 系列标准的编纂者)
#经济原则:宁花机器一分,不花程序员一秒
#生成原则:避免手工 hack,尽量编写程序去生成程序
#优化原则:雕琢前先得有原型,跑之前先学会走
”90% 的功能现在能实现,比 100% 的功能永远实现不了强“。
先给你的设计做个未优化,运行缓慢,很耗内存但正确的实现,然后进行系统的调整,寻找那些可能通过牺牲最小的局部简洁性而获得较大性能提升的地方。
借助原型化找出哪些功能不必实现,有助于对性能进行优化;那些不用写的代码显然无需优化。目前最强大的优化工具就是 delete 键。
我最有成效的一天,就是扔掉了 1000 行代码 —— Ken Thompson(图灵奖获得者,GO 发明者)。
#多样原则:决不相信所谓 ”不二法门“ 的断言
设计一个僵化、封闭、不愿与外界沟通的软件,简直是一种病态的傲慢。
Unix 奉行的是广泛采用多种语言、开放的可拓展系统和用户定制机制。
#扩展原则:设计着眼未来,未来总比预想快
#1.7 Unix 哲学之一言以蔽之
KISS 原则: Keep It Simple, Stupid!
#1.9 态度也要紧
你必须相信,软件设计是一门艺术,值得你付出所有的智慧、创造力和激情。…………否则你就会在应该思考的时候急急忙忙跑去编程,你就会在该无情删繁就简的时候反而把问题复杂化——然后你还会反过来奇怪你的代码怎么会那么臃肿,那么难以调试。
要良好地运用 Unix 哲学,你应该珍惜你的时间决不浪费。一旦某人己经解决了某个问题,就直接拿来利用,不要让骄傲或偏见拽住你又去重做一遍。
永远不要蛮干:要多用巧劲,省下力气到需要的时候再用,好钢用在刀刃上。善用工具,尽可能将一切都自动化。