《Unix编程艺术》读书笔记

Posted by xiezg247 on February 26, 2020

哲学

Unix哲学一言以蔽之,“KISS”原则——Keep It Simple,Stupid!

原则

  1. 模块原则:使用简洁的接口拼合简单的部件
  2. 清晰原则:清晰生于机巧
  3. 组合原则:设计时考虑拼接组合
  4. 分离原则:策略同机制分离,接口同引擎分离
  5. 简洁原则:设计要简洁,复杂度能低则低
  6. 吝啬原则:除非却无他法,不要编写庞大的程序
  7. 透明性原则:设计要可见,以便审查和调试
  8. 健壮原则:健壮源于透明与简洁
  9. 表示原则:把知识叠入数据以求逻辑质朴而健壮
  10. 通俗原则:接口设计避免标新立异
  11. 缄默原则:如果一个程序没什么好说的,就沉默
  12. 补救原则:出现异常时,马上退出并给出足够多的错误信息
  13. 经济原则:宁花机器一分,不花程序员一秒
  14. 生成原则:避免手工hack,尽量编写程序去生成程序
  15. 优化原则:雕琢前要先有圆形,跑之前先学会走
  16. 多样性原则:绝不相信所谓”不二法门“的断言
  17. 扩展原则:设计着眼未来,未来总比预想来的快

态度

看到该做的就去做——短期来看似乎做多了,但从长期来看,这才是最佳捷径。如果不能确定什么是对的,那么就只做最少量的工作,确保任务完成就行,至少直到明白什么是对的。

【设计和实践】

模块化:保持清晰,保持简洁

  • 封装和最佳模块大小(这个大小与所使用的语言无关,即尽可能用最强大的语言和工具编程)

    • Hatton的经验数据表明,假设其他所有因素都相同,200400之间的逻辑行的代码是“最佳点”,可能的缺陷密度达到最小。
    • Hatton的经验数据表明,建议逻辑行与物理行之间为两倍的折算率,即最佳物理行数建议应在400800行之间。
  • 紧凑性和正交性

    • 紧凑性——设计是否能装进人脑中的特性。(如果一个有经验的用户不需要操作手册,那么这个设计是紧凑的)
    • 正交性——在纯粹的正交设计中,任何操作均无副作用:每个动作只改变一件事,不会影响其他。
    • 重构概念是作为“极限编程”学派的一个明确思想首次出现的,与正交性紧密相关。重构代码就是改变代码的结构和组织,而不改变其外在行为。
    • SPOT(Single Point of Truth)原则(真理的单点性)
      • 任何一个知识点在系统内都应当有一个唯一,明确,权威的表述。
      • 推论:
        • 如果有大量重复的样板代码,是不是可以用单一的更高层表现形式生成这些代码,然后通过提供不同的细调选项生成不同个例呢?
    • 紧凑性和强单一中心
      • 要提供设计的紧凑性,有一个精妙但强大的方法,即围绕“解决一个定义明确的问题”的强核心算法组织设计,避免臆断和捏造。(比如说Unix的工具grep
    • 分离的价值
      • 尽量不要去想一种语言或操作系统最多能做多少事情,而是尽量去想这种语言或操作系统最少能做的事情——不要带着假想行动,而是从零开始。
  • 软件是多层的

    • 自顶而下和自底而上
      • 自顶而下,从抽象到具体——从最高层面描述整个项目的规格说明或应用逻辑开始,向下进行,直到各个具体操作。
      • 自底而上,从具体到抽象——从问题域中确定要进行的具体操作开始,向上进行。
      • 实际代码往往是自顶而下和自底而上的综合产物
    • 胶合层
      • 当自顶而下和自底而上发生冲突时,其结果往往是一团糟,顶层的应用逻辑和底层的域原语集必须用胶合层来进行阻抗匹配。
    • 被视为薄胶合层的C语言
    • 程序层
      • Unix编程风格强调模块性和定义良好的API,强烈倾向于把程序分解成由胶合层连接的库集合,特别是共享库(DLL)。
    • Unix和面向对象语言
      • Unix——薄胶合层原则
      • 面向对象语言——具有厚重的胶合和复杂层次,OO在其取得成功的领域(GUI,仿真和图形)之所以成功,主要原因之一可能是这些领域很难弄错类型的本体问题。
    • 模块式编码
      • 模块性体现在良好的代码中,但首先来自良好的设计。
      • 在编写代码时,问问自己一下问题,可能有助于提高代码的模块性:
        • 有多少全局变量?
        • 单个模块的大小是否在Hatton的最佳范围内?
        • 模块内的单个函数是不是太大了?
        • 代码是不是由内部API,即可作为单元向其他人描述的函数调用集和数据结构集,并且每一个单元都封装了某一层次的函数,不受其他代码的影响?
        • API的入口点是不是超过七个?有没有哪个类有七个以上的方法?数据结构的成员是不是超过七个?
        • 整个项目中每个模块的入口点数量如何分布?是不是不均匀?有很多入口点的模块真的需要那么多入口点吗?模块复杂性往往和入口点的数量的平方成正比——这也正是简单API优于复杂API的另一个原因。

文本化:好协议产生好实践

  • 文本化的重要性
    • 互用性,透明性,可扩展性和存储/事务处理的经济性——这些都是设计文件格式和应用协议时需要考虑的重要方面。
    • 设计一个文本协议往往可以为系统的未来省不少力气。格式本身不能表示数字域的范围,二进制格式通常指定了给定值的分配位数,要扩展位数非常困难。
  • Unix文本文件格式的约定
    • 如果可能,以新行符结束的每一行只存一个记录
    • 如果可能,每行不超过80个字符
    • 使用“#”引入注释
    • 支持反斜杠约定
    • 在每一行记录的个始终,使用冒号或任何连续的空白作为字符分割符
    • 不要过分区分tab和whitespace
    • 优先使用十六进制而不是八进制
    • 对于复杂的记录,使用“节(stanza)”格式:一个记录若有多行,就使用%%\n或%\n作为记录分割符
    • 在节格式中,要么每行一个记录字段,要么让记录格式和RFC 822电子邮件头类似,用冒号终止的字段名关键字作为引导字段。
    • 在节格式中,支持连续行
    • 要么包含一个版本号,要么将格式设计成相互独立的子描述字节块
    • 注意浮点数取整问题

透明性:来点儿光

  • 透明性:如果没有阴暗的角落和隐藏的深度,软件系统就是透明的。透明性是一种被动品质,如果实际上能预测到程序行为的全部或大部分情况,并能建立简单的心理模型,这个程序是透明的,因为可以看透机器究竟在干什么。透明性指的是“美”,“优雅”。
  • 可显性:如果软件系统所包含的功能是为了帮助人们对软件建立正确的“做什么、怎么做”的心理模型而设计,这个软件是可显的。举例说明,对用户来说,良好的文档有助于提高可显性;对程序员来说,良好的变量和函数名有助于提高可显性。

多道程序设计:分离进程为独立的功能

Unix最具特点的程序模块化技法就是将大型程序分解成多个协作进程。

Unix设计风格都运用“做单一件事并做好”的方法,强调用定义良好的进程间通信或共享文件来连通小型进程。因此,Unix操作系统提倡把程序分解成更简单的子进程,并专注考虑这些子进程的接口。至少可通过以下三种方法来实现:

  • 降低进程的生成开销
  • 提供方法(shellout[shell 执行模块]、I/O重定向、管道、消息传递和套接字)简化进程间通信
  • 提倡使用能由管道和套接字传递的简单、透明的文本数据格式

从性能调整中分离复杂度控制

通过模块化规则把程序划分成多个协作进程。

Unix IPC(进程间通信)方法的分类

把任务转给专门的程序
  • 一个程序调用另一个程序来完成专门任务。被调用的程序经常通过system的调用被指定为一个Unix Shell命令,因此这通常称作对被调用程序“shell out”(外壳执行)。
  • 专门程序通常借由文件系统与父进程进行通信,方法是指在指定位置读取或修改文件。
管道、重定向和过滤器
  • 管道依赖这样的约定,即每个程序一开始(至少)有两个I/O数据流可用:标准输入和标准输出(文件描述符数字分别为0和1)。许多程序都可写作过滤器,从标准输入顺序读数据,并且只向标准输出写数据。
  • 管道操作把一个程序的标准输出连接到另一个程序的标准输入。用这种方式连接起来的一系列程序被称为管线。
包装器
  • 包装器或者将调用程序专用化,或者为它创建新的接口。
  • 包装器经常用于隐藏shell管线的复杂细节。
安全性包装器和Bernstein链
  • 包装器脚本的常见用法是安全性包装器,安全性包装器可调用守门程序检查某类凭证,然后根据返回的状态值有条件地来执行另一个程序。
  • Bernstein链是一个专用化的安全性包装器技法。
从进程
  • 子程序通过连接到标准输入和标准输出的管道,交互地和调用程序收发数据。
  • 主进程和从进程都需要内部状态机处理它们之间的协议以及避免发生死锁和竞争。
对等进程间同信
  • 临时文件,把临时文件作为协作程序之间的通信中转站
  • 信号,一个进程向另一个进程发送信号
  • 系统守护程序和常规信号
  • 套接字,通过套接子通信的两个程序都存在双向字节流
  • 共享内存,使用套接字通信的两个进程可能在不同机器上,而共享内存要求生产者和消费者必须在同一硬件上

要避免的问题和方法

远程过程调用
  • RPC接口不是那么容易做到那么可显的,难以按功能查询接口。
  • 不编写和被监控程序同样复杂的专用工具,难以监控程序的行为。
  • RPC接口和库一样具有版本不兼容问题,但更难追查,因为是分布的,且不会体现在链接过程中。
  • 类型标记越丰富的接口往往越复杂,因而越脆弱,随着时间的推移,在接口之间传递的类型总量逐渐变大,单个类型越来越复杂,往往产生类型本体蠕变问题。
  • RPC增加了程序的全局复杂度。
线程——恐吓或威胁
  • 线程成为滋生bug温床源于它们太容易知道过多彼此的内部状态。
  • 与有着独立地质空间、必须通过明确IPC进行通信的进程不同,线程没有自动封装,这样,基于线程的程序不仅产生普通的竞争问题,而且产生了新一类bug:时序依赖。

从设计层次的进程划分

  • 降低复杂度的有效方法是把程序划分成客户端/服务器对,在应用程序的多个实例必须管理共享资源访问的情况下,这种划分特别有效。
  • 临时文件、主从进程关系、套接字、RPC和其他一些双向IPC方法在某种程度上是等价的——它们不过是程序在生命期内交换数据的方法。在使用套接字或者共享内存这种复杂的方法所完成的任务,大多数都可以通过临时文件作为信箱和通知信号这种简单的方法来完成。主要体现在如何建立通信、何时何地完成信息的列及和散集、可能产生何种缓冲问题,以及如何保证获取信息的原子性。

微型语言:寻找歌唱的乐符

Unix包容小型的、为专门应用领域定制、大量减少程序行数的语言。

专门领域语言的实例包括Unix排版语言(troff,eqn,tbl,pic,grap)、shell实用程序(awk,sed,dc,bc)和软件开发工具(make,yacc,lex)。

这种类型的专门领域语言在Unix世界中被称为“小语言”或“微型语言”,原因是相对通用语言而言的。

理解语言分类法

  • 当微型语言明确成为完备图灵机时,它就是解释器,即是可以进行条件和循环(或递归)操作,并且具有用于控制结构的特性。

应用微型语言

设计微型语言面临两个挑战,一个是在工具包中有已经存在方便好用的微型语言时,认识到何时可以直接应用它们,另一个是知道应该在什么时候为应用程序设计自定的微型语言。

设计微型语言

对微型语言的设计风格和设计方法的有趣总结可以参考《领域专用语言的设计模式》。

选择正确的复杂度
  • 设计微型语言的第一要素是尽可能保持微型语言的简单
  • 更好的安全特性
  • 图灵完备
  • 自顶而下的设计
  • 明确的报错信息
扩展和嵌入语言
  • 简单地在解释性语言中编写服务函数就可能实现命令性语言,为了这个目的,把这种解释性语言称作“主”语言,微型语言不过是加载服务库的脚本并把主语言的控制结构和其他功能当作框架来使用。主语言提供的一切机能都不必亲自动手,例如Tcl、python和perl。
  • 主语言可能无法作为所需要代码库的接口,或者,从内部而言,它的数据结构类型可能不足以完成所需要的计算机类型。又或者,测量过原型性能后,发现速度太慢。出现以上情况时,解决方案通常是通过C(或C++)编码,然后把结构整合到自己的微型语言中。
编写自定义语法
  • 无需自定义语法,遵循最小立异原则,支持描述数据文件时所谈到的Unix约定
  • 需要自定义语法,yacc和lex(或所用语言中的本地等价实现)或许是最好的朋友
宏——慎用
  • 宏扩展的优势在于它无需知道基础语言的任何基础语法就可以用来扩展语言
  • 不幸的事,这种能力容易被滥用,生成奇怪的、不透明的代码,成为滋生难于辨识bug的温床
语言还是应用协议
  • 微型语言引擎是否可被其他程序作为从进程交互调用,如果可以,那么可能看上去不太像可供人类交互的会话语言,更多像应用协议
  • 主要区别在于事务边界的标定程度
  • 人类更加擅长发现CLI的会话式输出在哪里结束,下一个输入的提示在哪里,可根据上下文来判断什么重要,什么应该忽略。但计算机程序要完成这个就困难多了,如果输出没有明确的结束标记或没有提前告诉输出长度,那么机器就无法判断何时停止读取

生成:提升规格说明的层次

数据驱动编程

进行数据编程时,需要把代码和代码作用的数据结构划分清楚,这样,在改变程序逻辑时,就只需要编辑数据结构,而不是代码。

数据驱动编程有时会跟面向对象混淆,后者是另一种以数据结构为中心的风格,至少有两点不同,第一,数据驱动编程中,数据不仅仅是某个对象的状态,实际上还定义了程序的控制流;第二,OO首先考虑的是封装,而数据驱动编程看重的是尽可能少的固定代码。

专用代码的生成

Unix有一些强大的专用代码生成器,用于建造词法分析器和语法分析器等目的。

配置:迈出正确的第一步

什么应是可配置的

一个很好的经验法则:提高适应能力,除非这样做会产生超过0.7秒的延迟。人们几乎察觉不到少于0.7秒的启动延时;人们还没有来得及转移注意力,它消失了。

  • 首先,对于能够可靠地进行自动检测的东西,就不要提供配置开关
  • 其次,用户不应该看到优化开关
  • 最后,能用脚本包装器或简单管道实现的任务,就不要用配置开关实现

无论何时想增加配置选项,请考虑以下这些较普遍的问题:

  • 能省掉这个功能吗?为什么在加厚手册之外还要加重用户负担?
  • 能否某种无伤大雅的方式改变程序的常规行为从而无需这个选项?
  • 这个选项是否花哨没用?是否应该少考虑用户界面的可配置性而多考虑正确性?
  • 这个选项附加的行为是否应该用一个独立的程序来代替?

配置在哪里

传统上,一个Unix程序可以在启动环境的五个地方寻找控制信息:

  • /etc下的运行控制文件(或者系统中其他固有位置)
  • 由系统设置的环境设置
  • 用户主目录中的运行控制文件
  • 由用户设置的环境变量
  • 启动程序的命令行所传递的开关和参数

运行控制文件

  • 共享程序的系统级配置,则通常在/etc目录下有一个运行控制文件。

  • 用户专有的配置信息通常存放在用户主目录下一个隐藏的运行控制文件中。
  • 程序也可以拥有运行控制目录或者点目录。

环境变量

  • 系统环境变量
  • 用户环境变量

命令行选项

  • 从-a到-z的命令行选项

如何挑选方法

上面依次分析了系统和用户运行控制文件、环境变量和命令行参数,注意从最不易改变到最易改变的顺序过程。表现良好的Unix程序如果不只使用一种设置方式,就应该按照指定的顺序来考虑,允许后面的设置覆盖前面的设置。

论打破规则

如果必须打破规则,就放手去做——但在做之前要确信自己完全知道为什么要这样做。

### 接口:Unix环境下的用户接口设计模式

最小立异原则的应用

最小立异原则:“少来标新立异”,是所有接口设计中的通用原则,且并非仅局限于软件设计。

接口设计评估

对接口风格使用五种度量标准进行分类:简洁、表现力、易用、透明和校本化能力。

  • 简洁是指一个事务处理需要的动作时间及复杂度有较低的上限
  • 表现力是指接口可以触发相当广泛的行为
  • 接口易用性同接口要求用户记忆的东西成反比
  • 接口透明性是指用户在使用接口时,几乎没有什么问题、数据或程序的相关状态需要记忆
  • 脚本能力是指接口能够容易地为其他程序所用,例如IPC机制,可脚本化的程序通常被其他程序作为组件来使用,减少定制代码的昂贵需求并使得重复任务的自动化相对容易

Unix接口设计模式

  • 过滤器模式
  • Cantrip模式
  • 源模式
  • 接收器模式
  • 编译器模式
  • ed模式
  • Roguelike模式
  • “引擎和接口分离”模式
    • 配置者/执行者组合
    • 假脱机/守护进程组合
    • 驱动/引擎组合
    • 客户端/服务器组合
  • CLI服务器模式
  • 基于语言的接口模式

优化

什么都别做,就站在那儿

程序员工具箱中最强大的优化技术就是不做优化。如果仅仅是为了减少资源使用的一个常数部分而优化,往往得不偿失。更加明智的做法是集中精力将时间复杂度或空间复杂度从O(n^2)降至O(n)或O(nlogn)。

先估量,后优化

如果有真凭实据证明应用程序运行缓慢,这时才可以考虑优化代码。但付诸实施前,要先估量。

Unix带有性能剖析程序(profiler):要善加利用。阅读profiler诊断的结果是一门学问,存在几个经常出现的问题:一是工具误差,二是外部强加的延迟,三是过度调用图中顶部节点。

非定域性之害

最有效的代码优化方法就是保持代码短小简单。

吞吐量和延迟

有三种常规策略来减少时延,一是对可以共享启动开销的事务进行批处理,二是允许事务重叠,三是缓存。

复杂度:尽可能简单,但别简单过了头

谈谈复杂度

复杂度的三个来源
  • 程序员为了能够试图理解一个程序,从而建议其思维模型并调试该程序的困难程度
  • 顾客和用户往往从程序界面的复杂度来看待这个问题
  • 系统中的代码行总数
接口复杂度和实现复杂度的折中

良好的品味和工程判断力要求,情况不同,则答案不同。重要的是要培养每一个设计习惯。

本质的、选择的和偶然的复杂度
  • 本质复杂度,太多复杂问题需要复杂的解决方案
  • 偶然复杂度,因为没有找到实现规定功能集合的最简方法,可以由良好的设计或重新设计来去除
  • 选择复杂度,同某个期望的功能相关联,只能由改变工程的目标来去除
映射复杂度

当简洁不能胜任

对于Unix坚持简单的传统,往往伴随着一种错误的理解模式,Unix程序员常常认为似乎所有的可能复杂性都是偶然复杂性。更为甚者,在Unix传统中存在一个强烈的偏好,宁可去掉功能,也不接受可能复杂性。

软件的适度规模

最简原则:选择需要管理的上下文环境,并且按照边界所允许的最小化方式构建程序。这就是“尽可能简单,而不过于简单”,集中关注选择共享上下文环境。实际上这不仅仅使用于框架,也适用于应用和程序系统。

工具

语言:C还是非C

混合语言是一种知识密集型的编程,要让它能够工作,我们不仅应该具备相当数量的多种语言应用知识,并且还必须具备能够判断这些语言在什么时候最适合、以及怎样把它们组合在一起的潜经验。

工具:开发的战术

  • 开发者友好的操作系统
  • 编辑器
  • 专用代码生成器
  • make:自动化编译
  • 版本控制系统
  • 运行期调试
  • 性能分析
  • 整合工具

重用:论不要重新发明轮子

避免重新发明轮子的最有效方法就是借用别人的设计和实现。换句话说,重用代码。

社区

可移植性:软件可移植性与遵循标准

IETF和RFC标准化过程

所有d饿IETF标准都要经过RFC(请求标注)的阶段。RFC的提议过程故意地设置为非正式的。RFC可以提出标准、调查结果、建议后续RFC的哲学基础,或者甚至是玩笑。

IETF的指导委员会(IESG)负责将成功的RFC推向标准之路,他们讲合格的RFC标明为“Proposed Standard(提倡标准)”。

对于提倡标准,如果至少存在两个可工作的、完备的、独立发展的、可互用的实现,就能够由IESG提升到“草案标准”状态。

一旦RFC达到了草案标准状态,就只能更改规格说明中的逻辑错误。草案标准可随时被部署使用在崩溃敏感环境中。

当草案标准经过了实现的广泛测试并且达到了普遍接受的程度,就可以成为一个互联网标准。互联网标准保留自身的RFC编号,也可以申请一个STD序列号。

尚未成为标准的RFC被标记为Experimental(实验性)、Informational(资料性),或者Historic(历史性)。

IETF标准化过程有意提倡由实践而非理论驱动的标准化过程,并确保标准协议都经过受过严格的同行评审和测试。

可移植性、开发标准以及开放源码

可移植性需要标准。无论是发布传播一项标准还是督促专有软件遵循标准,开放源码基准实现都是已知最有效的方法。

文档:向网路世界阐释代码

今天,当全世界都受到了万维网和XML的影响,把表现形式和结构标记在文档中区别开是再正常不过的了——前者是关于文档外观的指令,后者是关于文档如何组织、有何用意的指令。

编写Unix文档的最佳实践

  • 数量多不会被认为是质量高。尤其是,决不要因为害怕别人看不懂儿省略功能细节,决不要为了面子而不对存在的问题提出警示。
  • 信息密度要适中。少用屏幕截图。
  • 如果项目规模比较大,应该发布三种不同的文档;手册页当作参考资料,教程手册和常见问题答疑列表。应该有个网站,作为发布中心。
  • 没人喜欢庞大的手册。如果确实需要,考虑编写参考手册,手册页提供快速的摘要,并指向参考手册,以及程序如何调用的细节。
  • 在源码中的README
  • 手册页应该为传统的Unix用户以传统风格的命令引用形式呈现。考虑非技术用户,入门手册应该多用全称,少用缩写。
  • FAQ应该随着软件支持群体对软件常用问题及如何回答的深入了解而不断改进。

开放源码:在Unix新社区中编程

Unix和开放源码

开源开发利用了这样的事实,甄别和修改bug的任务适合分解成多个并行的子任务——这和实现某个特殊算法不一样。

开源开发的规则很简单:

  • 源码公开
  • 尽早发布,经常发布
  • 给贡献以表扬

与开源开发者协同工作的最佳实践

良好的修补实践
  • 发送补丁而不是完整档案包或文件
  • 发送针对当前版本代码的补丁
  • 不要包含可生成文件的补丁
  • 不要发送与只是优化RCS或者SCCS¥-symbols的补丁段
  • 使用-c或-u格式而不是缺省的-e格式
  • 在补丁中包含文档
  • 在补丁中包含解释
  • 在代码中包含有用的注释
  • 如果补丁被拒绝,别往心里去
良好的项目、档案文件命名实践
  • 使用GNU风格的命名法:主干加major.minor.patch的编号法
  • 尊重适当的本地约定
  • 努力选择唯一且容易键入的名称前缀
良好的开发实践
  • 不要依赖专有代码
  • 使用GNU自动工具
  • 先测试再发布代码
  • 发布前对代码进行健全检查
  • 发布前对文档和README进行拼接检查
  • 推荐的C/C++移植性实践
良好的发行制作实践
  • 确保打包文件总是解包到单一的新目录下
  • 包含README文件
    • 项目的简短描述
    • 指向项目站点的链接
    • 开发者编译环境的注意事项以及潜在的移植性问题
    • 描述重要文件和子目录的路标
    • 编译及安装的指令或者指向同样内容的文件(通常是INSTALL文件)
    • 维护者光荣榜列表或者指向同样内容的文件(通常是CREDITS文件)
    • 项目的最近新闻或者指向同样内容的文件(通常是NEWS文件)
    • 项目邮件列表地址
  • 尊重和遵从标准文件命名实践
    • README:最先被阅读的路标文件
    • INSTALL:配置、编译和安装指导
    • AUTHORS:项目贡献者列表(GNU惯例)
    • NEWS:最近的项目新闻
    • HISTORY:项目历史
    • CHANGE:修订版本之间重大更改的日志
    • COPYING:项目许可证条框
    • FAQ:项目常见问题解答的纯文本文档
  • 为可升级性设计
  • 在Linux下提供RPM
  • 提供校验和
良好的交流实践
  • 在Freshmeat上发布通告
  • 在相关的主题新闻组上发布通告
  • 建议一个网站
    • 项目说明(项目存在的理由、项目受众等)
    • 项目源码的下载链接
    • 如何加入项目邮件列表的指导
    • 常见问题回答列表(FAQ)
    • 项目文档的HTML版本
    • 相关或者竞争项目的链接
  • 提供项目邮件列表
  • 发布到主要的档案站点

许可证的逻辑:如何挑选

许可证条框的选择涉及这样的决定,软件作者是否希望对人们怎样处理软件施加某些限制。

为什么应使用某个标准许可证

同开发源码定义保持一致并广芳为人所知的许可证,已经具备长期建议的传统默认解释。开发者指导这些许可证究竟意味着什么,并且理性地承认所涉及的风险和折衷。

各种开源许可证

MIT或者X Consortium许可证

授予无限权力的拷贝、使用、修改和对修改拷贝的再发行,只要在所有修改的版本中保留版权和许可证条款,放弃控告维护者的权利。

经典BSD许可证

授予无限权利的拷贝、使用、修改,以及对修改拷贝的再发行,只要在所有修改的版本中保留版权和许可证条款,并且在广告和软件包相关文档中包含致谢,受让者也必须放弃控告维护者的权利。

Artistic许可证

授予无限的拷贝、使用和本地修改的权利,允许再发行修改后的二进制版本,但是限制修改源码的再发行以保护作者和自由软件社区的利益。

通用公共许可证

GNU通用公共许可证(及其派生,Library或Less GPL)是最广泛使用的单一自由软件许可证,如同Artistic许可证一样,若修改后的文件带有“显著声明”则允许修改源码再发布。

Mozilla公共许可证

Mozilla公共许可证支持开源软件,但是可以链接闭源的模块和扩展,它要求发行的软件(被涵盖代码,Covered Code)仍然保持开源,但附加软件,如果通过良好定义的API来调用,允许保持闭源。

未来:危机与机遇

Unix传统中的平质和偶然

分辨出哪些特征是从暂时的技术中发展而来的,哪些又是紧密联系于核心的Unix设计挑战——如何正确的模块化和抽象化的同时而保持系统的透明和简洁的。

Plan9:未来之路

Unix的未来看起来会怎样,它被称作“来自贝尔实验室的Plan 9”,由在贝尔实验室编制了Unix的研究组设计,Plan 9尝试重做Unix,做更好的Unix。

Unix设计中的问题

  • Unix文件就是一大袋字节
  • Unix对GUI的支持孱弱
  • 文件删除不可撤销
  • Unix假定文件系统是静态的
  • 作业控制设计拙劣
  • Unix API没有使用异常
  • ioctl(2)和fcntl(2)是个尴尬
  • Unix安全模型可能太过原始
  • Unix名字种类太多
  • 文件系统可能有害论
  • 朝向全局互联网地址空间

Unix的环境问题

  • 如何让开源开发获得持续的经济支持
  • 一个有关真正大型软件商维护越来越困难的子问题是如何组织终端用户测试
  • 微软等巨头对不受控制的软件开发发起了多重攻击

Unix文化中的问题

  • 小挑战是内部转型
  • 大挑战是要客服历史上的优越感