超级用户 发表于 2004-6-16 11:17:39

the Art of Unix Programming 1

Unix之得道

    目前Linux爆炸性地增长,以及Internet重要性的增加,让我有足够的理由说明怀疑者的怀疑是错误的。但是就算他们的看法是正确的,Unix文化仍然值得学习,因为很明显,如果Unix不是最好的,那就没有最好的了。

开源软件
    尽管术语“开放源代码”以及“开源社区”直到1998年才被使用,但是开放共享源代码确是Unix文化一个关键特性。
    AT&T最早的Unix以及最初的伯克利Unix变种都是随源代码一齐发行的。这就允许一些极好的西西加入其中。

跨平台可移植性以及开放标准
    Unix仍然是目前唯一可以在各种计算机、厂商以及一些非通用硬件上提供统一API的操作系统。也是唯一可以横跨从嵌入式芯片、手持设备到桌面机器、服务器直到超大型的数值计算机或者数据库后台。
    对于完成一个可移植软件,Unix API是最接近硬件无关标准的。所以IEEE将最初的Portable Operating System Standard改变成最终的POSIX并不是偶然。只一个等价的Unix API才配得上成为这样一个标准的可信赖模型。
    那些只提供编译过的二进制代码的应用程序将会和它们的运行环境一起走向死亡,而Unix代码则会永生。至少可以维系Unix文化十年之久。

Internet和世界万维网
    美国国防部将第一TCP/IP协议栈的开发合同交给Unix开发组,重要的原因是Unix被认为是最大的开源软件。除了TCP/IP外,Unix已经成为ISP工业里一个不可缺少的核心技术。此后一直到80年代TOPS系列操作系统的没落,多数Internet服务器(包含PC)依靠Unix。
    微软精明的(译者:对于微软,精明的同义词是恶心)市场运作也无法撼动Unix在Internet上的地位。当TCP/IP标准(Internet的核心协议)被移植到TOPS-10并在理论上证明TCP/IP是可以与Unix分离后,各种将其移植到其它操作系统的尝试都遭受了挫折,不同程度地出现不兼容,不够健壮,bug丛生等。尽管每个人都能得到原理性知识和各种规范,但是工程传统使得TCP/IP仅能在Unix世界里稳定地工作。
    Internet和Unix的技术文化在19世纪80年代早期开始融合,而今此二者已经不可分离并彼此促生。现代Internet的脸面,WWW的出现归功与早期Unix服务的ARPANET。特别的,URL的概念和Unix的唯一名字思想也是一致的。成为一名Internet专家,理解Unix及其文化是必不可少的。

开源社区
    伴随着Unix源代码一起长大的这个社区一直活跃着。90年代早期Internet的流行,这个社区又招募了在家里学习黑客技术的新成员。
    当今,开源社区有力地支持着各种软件开发团队。高品质的开源开发工具“充斥”着Unix世界里。(我们会在本书中测试它们中的很多)开源的Unix应用软件等价于同类的非开源软件,而且经常更好。整个Unix操作系统,包括复杂的工具包和基本的应用程序套件都可以在Internet上获得。为什么当你能适应或者复用这些抓来的代码能节省你90%的工作量?
    这个共享代码的传统强烈遵从着软件团队开发和软件复用方面的经验。这些经验决非空洞的理论,人们取得这些宝贵经验的同时曾付出惨痛的代价。(译者注:想想人/月神话)这个隐含的原则告诉我们,软件彼此间应该相互增效并构成一套工具箱,而不是一个独立的一次性的解决方案。本书的主要目的就是阐述这个经验所包含的原则。
    现在,萌动中的开源运动正焕发新的活力,开辟新的技术途径并促使新一代的程序员步入Unix传统。包含Linux操作系统及其环境(如Apache/Mozilla等等)使得Unix优良传统得以空前地发生效力。看起来开源运动似乎就快取得胜利——权释计算的基础:Unix machines running on Internet

永远的柔性
    很多操作系统宣称它们较Unix的优势是时髦或者是所谓的用户友好,这些操作系统使用的手段是把用户或开发者圈在一个不可换的界面策略里并提供一套精致的API,这使得系统过于狭隘和钢性。可以想象,在这样的系统上,对付设计者预期的任务会很容易——但是任务并非设计者设计,对于设计者未考虑的任务将不可能完成或者极端困难。
    另一方面,Unix有着深度的柔性。Unix提供了多种手段可以将各种程序”粘“在一起,这就意味着各基本工具箱的组件可以重新结合产生新的且有效的办法对付这些工具的设计者还没有考虑到的问题。
    Unix支持多种风格的程序接口(这通常被认为是一个缺点,因为增加了终端用户对系统认知的复杂度)同样有助于柔性效能;一个简单的数据管件不会被要求承担GUI的复杂性。
    Unix传统要求编程接口保持简、洁和彼此正交,这也是深度柔性产品的特征。贯穿着这个Unix的思想:只有当简单的事情真正简单,复杂的工作才有可能完成。(译者注:此句原文:Throughout a Unix system, easy things are easy and hard things are at least possible.)

黑客的Unix
    Unix技术大拿们不一定会提及究竟是什么使得Unix如此成功。答案是做一名Unix黑客充满乐趣。
    Unix的拥护者对于这一点似乎有些害羞,好像这会破坏他们的正统性。然而这却是事实;在Unix下”玩儿“或者开发确实很有趣,并且从来都是这样。
    没有多少系统可以让人想到”有趣“。事实上在很多其它的环境下编程经常可以被比作把一条搁浅的鲸踢下海。你能听到的最好的形容词也许就是”还行“或者”不算太糟糕“类类。Unix世界可不是这样,操作系统回报一切努力。人们视在Unix里编程为一种真实、有益的工作而不是摧毁成果的敌人。
    这有着重大的经济意义。这个“趣味”因素开始了Unix早期的良性循环。人们喜欢Unix,所以开发了更多的程序使其变得更好。现在人们甚至把建立一个完成的产品级的开源Unix作为一种爱好。为了更好的说明这点,问问那些成功克隆过OS/360、VAX VMS或者M$ Windows的家伙。^_^
    ”趣味“这个因素从设计的观点来看也不是微不足道的。程序员和开发人员用他们的能力搞定一个赋予挑战性的任务时就会得到趣味。获得乐趣意味着他正处于效率的颠峰状态。而令人痛苦的开发环境则浪费劳动力和创造力,他们必须为此空耗时间、金钱以及机会更糟的是他们可能对此一无所知。
    即使Unix在其它任何方面都是失败的,使得开发人员能够获得乐趣的这个技术文化仍然值得学习,因为这会使得开发人员高效多产。

无处不在
    我们得承认Unix程序员随着这个先锋性操作系统的成长已经积累了数十年的经验。非Unix程序员同样可以通过学习这些经验而受益。因为Unix可以提供一个决好的试验床让他们练习这些好的设计原则和开发方法。
    其它的操作系统很难形成好的编程习惯,但是Unix文化可以部分地改善一些。多数Unix代码(包含各种过滤器、主要的脚本语言和多数代码生成器)可以直接移植到支持ANSI C的系统中(一个极好的理由说明C本身就是Unix的一大发明,ANSI C库收录了一个大而稳定的Unix服务!)。


基本Unix哲学

    “Unix哲学”起源于Ken Thompson在早期关于如何设计一个小型但是有用而且服务接口清晰的操作系统的思考。随着人们对如何使得Thompson设计发挥最大效力的学习,Unix文化随之诞生并成长,并从各种途径汲取养份。
    Unix哲学并不是一个形式化的设计方法。它从不在理论上宣称可以生产出更好的软件。也不能妄想指望它在一个很短的期限里使用糟糕的项目管理和廉价程序员做出一个极俱重新而又可以信赖的软件。
    Unix哲学(以及其它领域内被认为是成功的“民间”传统)是自下而上的,而非相反。它注重实效并且基于经验。你不能在正式的方法和标准中找到它们,它们属于隐性、反身性知识。
    Doug McIlroy,Unix管道的发明者也是Unix传统的奠基人之一,说
        (i)让每一个程序只很好得做一件事情。当出现新工作的时候应该重组已有的程序而不是通过添加新特性使得原有程序变得复杂。
        (ii)每个程序的输出应该可以成为另一个程序的输入,甚至一个未知程序。不让让彼此无关的消息混乱你的输出。尽量避免二进制输入格式。不要强求交互式输入方式。
        (iii)设计和建造软件,包括操作系统,应尽早规划好。对于拙劣的部分不要犹豫不决甚至重写它们。
        (iv)使用工具可以减轻编程负担,即使你可能决定这些工具在建造后只使用几次。
他后来又这样总结:This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a
universal interface.
    Rob Pike,一位伟大的C语言大师,从一个略微不同的角度给出了一些C编程的注解:
        规则1:你无法预料一个程序在何处花费时间。瓶颈常会发生在出人意料的地方,所以在你能证明瓶颈所在之前,不要做第二次无谓的猜想。
        规则2:测量。在你测量之前不要盲目追求速度,除非某个部分的代码淹没了其它的。
        规则3:在n很小的时候,一个新异的算法会很慢,而且通常n不会很大。这些算法会有很大的常数。除非你确信n会很大,否则不要使用。(即使n很大,也先适用规则2)
        规则4:新异的算法要比简单算法大得多,并且很难实现。尽量使用简单得算法和简单得数据结构。
        规则5:数据支配。如果你准确选择了数据结构并正确组织它们,那么算法很可能已经自动构筑完成。数据结构,而不是算法适编程的重点。
        规则6:没有其它规则。

    Ken Thompson,Unix得设计和缔造者之一,用了禅宗里得一句话进一步描述了上面得规则4:
        When in doubt,use brute force(告诉我谁把佛经翻译成了英语,我去杀了他)
    更多得Unix哲学隐含在先哲们的行为以及Unix本身,而不是在先哲们留给我们的话语。纵观全局,我们可以总结出一部分思想:
        1 模块化原则:编写简单的组件,用整齐的接口让它们互联。
        2 清晰性原则:清晰要比灵巧重要。
        3 整合化原则: 新设计的程序要考虑和其它程序共同工作。
        4 分离性原则: 让机制与策略分离;让接口同引擎分离。
        5 简单化原则: 为了简单而设计;只在你确信必要的地方增加复杂度。
        6 吝啬性原则: 只有被证明了其它西西无法完成工作时才编写“大”程序。
        7 透明化原则: 设计一个高度可见的程序可以使检查及debugging更容易。
        8 健壮性原则: 健壮性是透明化以及简单化的产物。
        9 Rule of Representation:Fold knowledge into data so program logic can be stupid and robust.
        10 最小立异化原则: 在接口设计方面,一定不要总想标新立异。
        11 安静性原则: 当一个程序没有什么值得炫耀时,就不要炫耀。
        12 修复原则: 是的,一旦当你不得不面对失败时,失败就会变得愈发喧闹。
        13 经济性原则: 程序员的时间是宝贵的,把它让度于机器时间。
        14 生产化原则: 避免手工hacking操作;尽可能让程序完成编程工作。
        15 优化性原则: 先做模具再打磨润色;先让程序工作,然后优化它。
        16 多样化原则: 千万不要信赖包治百病的药。
        17 控制性原则: 多为将来考虑,因为它会比你预料更快地来到现实。

    如果Unix对你还是陌生地,这些原则值得你去沉思。软件工程的教科书会也向你夸耀这里的多数原则;但是大多数操作系统缺少好的工具和传统,所以这些平台上的程序员根本无法应用它们。这些程序员不得不接受生硬的工具,坏的设计,疲劳工作以及过度膨胀的代码,甚至习以为常(他们甚至不知道Unix爱好者为什么如厌恶他们的工作和习惯)。

模块化原则:编写简单的组件,用整齐的接口让它们互联。
    Brian Kernighan曾经总结说,“复杂度控制是计算机编程最重要的事情。”测试(debugging)决定了整个开发时间。好的方法可以事半功倍。
    汇编器,编译器,流程图,结构化编程,“人工智能”,第四代语言(译者注:指各类*nix脚本语言),面向对象,以及不计其数的软件工程方法都试图并宣称解决这个问题,而然只要提升程序的复杂度,这些方法依然会因为人脑的限制而失败,在Fred Brooks看来,不存在“银弹”。
    构筑一个复杂软件的唯一途径就是降整体的复杂性分担到一个个通过良好定义的接口互联的简单模块,多数问题是局域性的,这样完全可以修改出问题的模块而不破坏整体。


清晰性原则:清晰要比灵巧重要
    软件的维护是重要的并且昂贵的,写出的程序并是仅仅给执行它的计算机处理,更重要的是要给你的人类朋友看,给那些将来维护软件的人看(这其中也包括你自己)。
    Unix传统中,注释你的代码只是一部分。好的Unix方法同样会注重于你的算法和实现是否有利于将来的维护。为了一点点儿性能上的优势,而增加大量的复杂性是一桩糟糕的买卖。复杂的代码容易窝赃bug,更会让维护人员头疼。
    优雅而简洁的代码,不易破裂且让修改者很容易维护。这点非常重要,特别是当这个维护者是几年后的你。
        不要试图阅读那些复杂的代码超过三次。一次就读懂或许是侥幸,如                果你发现很长一段时间后必须再次看它,而你已经忘记了细节。是时        候为它编写注释了,这会让你第三次阅读时轻松很多。
                                -- Henry Spencer


整合化原则: 新设计的程序要考虑和其它程序共同工作
    如果你编写的程序彼此间不能通讯,那么将无法避免过度的复杂性。Unix传统强烈期望程序可以读、写简单的、基于文本和流式以及设备无关的格式。经典的Unix中,有大量的程序作为过滤器(filter)出现,过滤器从一个程序获得文本输入处理后再将文本输出。
    出现这一情况并不是Unix程序员都不喜欢图形用户界面。而是因为如果不用字符流,程序间很难挂钩。
    字符流作为消息传递的Unix工具是面向对象的。



一句话Unix哲学

    所有这些这些哲学其实可以浓缩成一个被专家工程师奉为经典的法则:
        KISS 法则
        KISS 的全写是KISS Is Simple,Stupid(或者Keep It Simple, Stupid)

    Unix能够给你提供一个极好的平台应用KISS法则。本书的余下部分会帮助你学习如何应用。




应用Unix哲学

    这些哲学原则绝不是含糊的概念。在Unix世界,它们直接源于经验,并指导我们正在从事的具体开发实践。这里有一份可能尚不完全的表:

        * 任何与源代码、目标无关的过滤器都要有一个。
        * 数据流只要可能就应该是文本的(这样才可以用标准的工具阅读和过滤)
        * 数据库规划和应用协议只要可能应该是文本的(文本可读、文本可编辑)
        * 复杂的前端(用户界面)和负责的后端之间应该被清晰分离
        * 如果可能,在决定用C语言编码前尝试解释性语言(译者注:包括脚本语言)
        * 使用多种语言要比单一的语言好,除非这这会增加复杂性
        * 更多得接受各种输入,对于输出则要吝啬的控制
        * 过滤时不要丢掉你可能并不需要的信息
        * 小就是好的,写程序时做到刚好能够完成你的任务

    我们将会观察Unix设计规则以及从这些规则中导出的原则,并应用它们。就像预料中的,它们正成为其它领域内软件工程里最好的原则。




态度决定一切

    当你面对正确的选择,去做它——在一个短期计划中,经常会是这样,而然在一个长期的计划中这也许并不能取得什么效果。如果你还不能确定到底改作什么,那么就做能让工作完成的最少的事情,至少在你确定真正正确的事情前。

    正确地应用Unix哲学,需要你的忠诚。你必须绝对相信软件开发是一个要动用你所有智慧、创造力和激情的活计。此外你不能指望简单的套用过去方法实现现在的设计;你必须在你应该思考的地方进行新的编码。你会在不经意间使得本该简单的实现复杂化-接着你会疑问并抱怨为什么你的代码会迅速膨胀并难以调试。

    正确地应用Unix哲学,充分利用你的所有时间不要浪费。如果某个人已经针对某个问题做出了好的解决方案,不要因为好面子而进行无意义的重复工作。不要做出比预计更复杂的设计,让设计轻巧更重要。用你的工具,让可能的事情自动化。

    软件开发应该是一个令人愉乐的艺术,一个高级的游戏。如果你不同意这个看法,停下手边的工作,问问自己遗忘了什么?为什么你做软件开发而不是去挣更多的钱或者空耗时间?你必须将软件作为一件燃烧你激情的事情。

    正确地应用Unix哲学,你需要接受这个态度。你需要维护它,需要去游戏,需要有探索的意愿。

    我们希望你带着这个态度去阅读本书。或者至少希望本书能帮助你建立其这种态度。
页: [1]
查看完整版本: the Art of Unix Programming 1