UNIX编程艺术
本文主要是 《UNIX编程艺术》的摘录,摘录的主要是我觉得对从事软件开发有用的一些原则。
对于程序员和开发人员来说,如果完成某项任务所需要付出的努力对他们是个挑战却又恰好还在力所能及的范围内,他们就会觉得很有乐趣。
UNIX的设计哲学是:一个程序只做一件事,并做好。程序要能协作,程序要能处理文本流,因为这是最通用的接口。
UNIX设计的原则:
-
模块原则,使用简洁的接口拼合简单的部件;
-
清晰原则,清晰胜于技巧;
-
组合原则,设计时考虑拼接组合;
-
分离原则,策略同机制分离,接口同引擎分离;
-
简洁原则,设计要简洁,复杂度能低就低;
-
吝啬原则,除非别无他法,不要编写庞大的程序;
-
透明性原则,设计要可见,以便审查和调度;
-
健壮原则,健壮源于透明和简洁;
-
表示原则,把知识叠入数据以求逻辑的质朴而健壮;
-
通俗原则,接口设计避免标新立异;
-
缄默原则,如果一个程序没什么好说的,就缄默;
-
经济原则,宁花机器一分,不花程序员一秒;
-
补救原则,出现异常时,马上退出并给出足够的错误信息;
-
生成原则,避免手工hack,尽量编写程序去生成程序;
-
优化原则,雕琢前先要有原型,跑之前先学会走;
-
多样原则,决不相信不二法门;
-
扩展原则,设计着眼未来,未来总比预想来得快;
罗马在燃烧,而我们还在拉小提琴;
GCC
GCC由一系列处理阶段组成,并由一个驱动程序将其紧密结合在一起。它们是预处理器、解析器、代码生成器、汇编器和链接器;
为透明性和可显性而编码
要追求代码的透明,最有效的方法很简单,就是不要在具体操作的代码上叠放太多的抽象层;
在设计良好的代码时,需要考虑以下几个问题:
-
程序调用层次中最大的静态深度是多少?也就是说,不考虑递归,为了建立心理模型来理解代码的操作,人们将要调用多少层;
-
代码是否具有强大、明显的不变性质;
-
每个API中各个函数调用是否正交;
-
是否存在一些顺手可用的关键数据结构或全局唯一的记录器;
-
程序的数据结构或分类和它们所代表的外部实体之间,是否存在清晰的一对一映射;
-
是否容易找到给定函数的代码部分;
-
代码增加了特殊情况还是避免了特殊情况?每一个特殊情况可能对任何其它特殊情况产生影响;
-
代码中有多少个 magic number(意义含糊的常量)?
代码能简单最好。但如果代码很好地解决了上述问题,则代码也可以复杂;
如果希望让代码成为活代码,则最有效的时间花费方法之一就是投入精力使代码具备可维护性
7. 多道程序设计--分离进程为独立的功能
7.1 IPC方法的分类
-
临时文件;
-
信号;
-
套接字;;
-
共享内存;在现代Unix中,共享内存的实现通常依靠
mmap
shell
脚本的惯例是在临时文件名中包含“$$”符号,这样这个 shell
变量将被展开为载入 shell
的进程ID,从而保证文件名的唯一性,避免程序中多个实例都使用同一个名字带来的冲突;
8. 微型语言,寻找歌唱的乐符
Unix的 makefile 是为了自动化编译过程而设计的,表达了源文件和派生文件之间的依赖关系以及从各个源文件生成派生文件所需要的命令。
m4
m4 宏处理程序解释描述文件转换的声明性微型语言。一个 m4 程序就是一套宏命令集,规定了将文本串扩展成其它字符串的形式。
10. 迈出正确的第一步
传统上,一个 Unix 程序可以在启动环境的五个地方寻找控制信息:
-
/etc 下的运行控制文件;
-
由系统设置的环境变量;
-
用户主目录中的运行控制文件;
-
由用户设置的环境变量;
-
启动程序的命令行所传递的开关和参数;
11 接口
在 Unix 接口设计的传统中,我们会反复涉及到两个主题:
-
与其它程序通讯的前瞻性设计;
-
最小立异原则;
11.1 接口设计评估
使用五种度量标准对接口风格进行分类:
-
简洁: 一个事务处理需要的动作时间及复杂度有较低的上限;
-
表现力: 接口可以触发相当广泛的行为;
-
易用:与要求用户记忆的东西成反比;
-
透明:用户在使用接口时,几乎没有什么问题、数据或程序的相关状态需要记忆,一个高度透明的接口,对于用户动作的效果,能够自然在给出中间结果、有用反馈和错误通知。
-
脚本化能力 : 接口能够容易地为其它程序所使用;
11.2 过滤器设计
定义过滤器的一些原则:
-
宽进严出,尽可能自由宽松地接受输入格式,并输出结构良好的严谨输出格式;
-
在过滤时,不需要的信息也决不丢弃;
-
在过滤时,决不增加无用数据
12 优化
过早优化乃万恶之源;
-
先估量再优化;
-
吞吐量和延迟;
12.1 吞吐量和延迟
快速处理器的另一个效应是性能经常受限于 I/O 以及网络程序--网络事务的开销。因此为得到良好性能而进行网络协议的设计是非常有价值的。
最重要的问题是尽量避免协议的往返。每个要求握手的协议事务都有可能从任何连接延迟发展到潜在的严重降速。经验法则是:尽可能低的时延设计和忽略带宽成本,
有三种常规的策略来减小时延:
-
对可以启动共享开销的事务进行批处理;
-
允许事务重叠;
-
缓存;
14 C 还是非 C
14.1 语言评估
我们不仅应该具备相当数量的多种语言应用知识,并且还必须能够判断这些语言在什么地方最合适、以及怎样把它们组合在一起的经验;
14.1.1 C
要求速度快并且具有实时需求的程序,或者与 OS 内核紧密联系的程序非常适合用 C
编写;
C 语言最佳之处是资源效率和接近机器语言。而最糟糕的地方是其编程简直就是资源管理的炼狱;
14.1.2 C++
对身后兼容 C 的要求迫使 C++ 在设计 中做出了许多妥协。这个要求也阻碍了 C++完全自动化动态内存管理,从而无法解决 C 最严重的问题。 近年来,C++已经包含了一些重要的非 OO 的概念:具有和 Lisp 类似的异常; STL 提供了泛型编程;
高效的编译语言;对 C 的向上兼容,面向对象的平台,STL 和泛型等最前沿的技术工具——C++ 试图满足所有人的要求,但代价是C++ 比任何一个程序员所能处理的复杂度都高。
总结: C++ 的最佳之处是编译效率以及面对对象和泛型编程的结合。最糟之处是它非常怪异复杂,往往鼓励过分复杂的设计;
14.1.3 Python
Python 是一种脚本语言,设计本意是与 C 语言紧密集成。它既可以从动态载入的 C库程序中接收数据也可以向其传输数据,它也能在 C 中作为嵌入脚本语言调用;
Python 语言的设计非常优雅,具有非常出色的模块化特性;
**总结 : ** Python 的最佳之处在于它鼓励清晰、易读的代码,易学易用,又能够扩展到大型项目。最糟之处在于它效率低下、速度缓慢。
14.1.4 Java
Java 语言的设计目标是:"write once,run anywhere(一次编写,到处运行)"。Java 设计聪明地抓住了自动管理内存的优势,也抓住了支持 OO 设计这一优点。Java 保留了大量的类 C 语法,大多数程序员对此感觉非常舒服。
**总结 : ** 对于除系统编程以及大多数速度关键的应用程序外的一切编程而言,Java 比 C++高级
成为标准的最好方法就是发布一个高质量的开源实现;