庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

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

软件设计有两种方式:一种是设计得极为简洁没有看得到的缺陷;另一种是设计得极为复杂有缺陷也看不出来。第一种方式的难度要大得多。

模块化代码的首要特质就是封装API在模块间扮演双重角色,实现层面作为模块之间的滞塞点阻止各自的内部细节被相邻模块知晓;在设计层面,正是API真正定义了整个体系。

养成在编码前为API编写一段非正式书面描述的习惯,是一个非常好的方法。

模块的最佳大小,逻辑行200-400行,物理行在400-800之间

紧凑性就是一个设计能否装入人脑中的特性。测试软件紧凑性的一个简单方法是:一个有经验的用户通常需要用户手册吗?如果不需要,那么这个设计是紧凑的。

理解紧凑性可以从它的“反面”来理解,紧凑性不等于“薄弱”,如果一个设计构建在易于理解且利于组合的抽象概念上,则 这个系统能在具有非常强大、灵活的功能的同时保持紧凑。紧凑也不等同于“容易学习”:对于某些紧凑 设计而言,在掌握其精妙的内在基础概念模型之前,要理解这个设计相当困难;但一旦理解了这个概念模型,整个视角就会改变,紧凑的奥妙也就十分简单了。紧凑也不意味着“小巧”。即使一个设计良好的系统,对有经验的用户来说没什么特异之处、“一眼”就能看懂,但仍然可能包含很多部分。

评测一个API紧凑性的经验法则是:API入口点通常在7左右,或者按《代码大全2》的说法,7+27-2的范围内。

重构技术中的很多坏味道,特别是重复代码,是违反正交性的明显例子,“重构的原则性目标就是提高正交性”。

DRY原则,或者称为SPOT原则(single point of truth)——真理的单点性。重复的不仅仅是代码,还包括数据结构,数据结构模型应该最小化,提倡寻找一种数据结构,使得模型中的状态跟真实世界系统的状态能够一一对应

要提高设计的紧凑性,有一个精妙但强大的方法,就是围绕“解决一个定义明确的问题”的强核心算法组织设计,避免臆断和捏造,将任务的核心形式化,建立明确的模型。


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

文本流是非常有用的通用格式,无需专门工具就可以很容易地读写和编辑文本流,这些格式是透明的。如果担心性能问题,就在协议层面之上或之下压缩文本协议流,最终产生的设计会比二进制协议更干净,性能可能更好。使用二进制协议的唯一正当理由是:如果要处理大批量的数据,因而确实关注能否在介质上获得最大位密度,或者是非常关心将数据转化为芯片核心结构所必须的时间或指令开销

数据文件元格式:

1DSV风格DElimiter-Seperated Values

使用分隔符来分隔值,例如/etc/passwd

适合场景:数据为列表,名称(首个字段)为关键字,而且记录通常很短(小于80个字符)

2RFC 822格式

互联网电子邮件信息采用的文本格式,使用属性名+冒号+值的形式,记录属性每行存放一个,如HTTP 1.1协议。

适合场景:任何带属性的或者与电子邮件类似的信息,非常适合具有不同字段集合而字段中数据层次又扁平的记录。

3Cookie-jar格式。简单使用跟随%%的新行符(或者有时只有一个%)作为记录分隔符,很适用于记录非结构化文本的情况。

适合场景:词以上结构没有自然顺序,而且结构不易区别的文本段,或适用于搜索关键字而不是文本上下文的文本段。

4Record-jar格式cookie-jarRFC-822的结合,形如

name:dennis
age:
21
%%
name:catty
age:
22
%%
name:green
age:
10

这样的格式。

适合场景:那些类似DSV文件,但又有可变字段数据而且可能伴随无结构文本的字段属性关系集合。

5XML格式,适合复杂递归和嵌套数据结构的格式,并且经常可以在无需知道数据语义的情况下仅通过语法检查就能发现形式不良损坏或错误生成的数据。缺点在于无法跟传统unix工具协作。

6Windows INI格式,形如

[DEFAULT]
account
=esr

[python]
directory
=/home/ers/cvs/python
developer
=1

[sng]
directory
=/home/esr/WWW/sng
numeric_id
=1001
developer
=1

[fetchmail]
numeric_id
=4928492

这样的格式

适合场景:数据围绕指定的记录或部分能够自然分成“名称-属性对”两层组织结构。

Unix文本文件格式的约定:

1、如果可能,以新行符结束的每一行只存一个记录

2、如果可能,每行不超过80字符

3、使用”“引入注释

4、支持反斜杠约定

5、在每行一条记录的格式中,使用冒号或连续的空白作为字段分隔符。

6、不要过分区分tabwhitespace

7、优先使用十六进制而不是八进制。

8、对于复杂的记录,使用“节(stanza)”格式,要么让记录格式和RFC 822电子邮件头类似,用冒号终止的字段名关键字作为引导字段。

9、在节格式中,支持连续行,多个逻辑行折叠成一个物理行

10、要么包含一个版本号,要么将格式设计成相互独立的的自描述字节块。

11、注意浮点数取整。

12不要仅对文件的一部分进行压缩或者二进制编码。

应用协议元格式

1、经典的互联网应用元协议 RFC 3117《论应用协议的设计》,如SMTPPOP3IMAP等协议

2、作为通用应用协议的HTTP,在HTTP上构建专用协议,如互联网打印协议(IPP

3BEEP:块可扩展交换协议,既支持C/S模式,又支持P2P模式

4XMP-RPCSOAPJabber,基于XML的协议。


透明性:来点光

美在计算科学中的地位,要比在其他任何技术中的地位都重要,因为软件是太复杂了。美是抵御复杂的终极武器

如果没有阴暗的角落和隐藏的深度,软件系统就是透明的。透明性是一种被动品质。如果实际上能预测到程序行为的全部或大部分情况,并能建立简单的心理模型,这个程序就是透明的,因为可以看透机器究竟在干什么。

如果软件系统所包含的功能是为了帮助人们对软件建立正确的“做什么、怎样做”的心理模型而设计,这个软件系统就是可显的。

不要让调试工具仅仅成为一种事后追加或者用过就束之高阁的东西。它们是通往代码的窗口:不要只在墙上凿出粗糙的洞,要修整这些洞并装上窗。如果打算让代码一直可被维护,就始终必须让光照进去。例如fetchmail-v选项将处理SMTPPOP的处理过程打印到标准输出,使得fetchmail行为具有可显性。

在“这个设计能行吗?”之后要提出的头几个问题就是“别人能读懂这个设计吗?这个设计优雅吗?”我们希望,此时大家已经很清楚,这些问题不是废话,优雅不是一种奢侈。在人类对软件的反映中,这些品质对于减少软件bug和提高软件长期维护性是最基本的。

要追求代码的透明性,最有效的方法是很简单,就是不要在具体操作的代码上叠放太多的抽象层。

OO语言使得抽象变得容易——也许是太容易了。OO语言鼓励“具有厚重的胶合和复杂层次”的体系。当问题领域真的很复杂,确实需要大量抽象时,这可能是好事,但如果coder到头来用复杂的办法做简单的事情——仅仅是为他们能够这样做,结果便适得其反。

所有的OO语言都显示出某种使程序员陷入过度分层陷阱的倾向。对象框架和对象浏览器并不能代替良好的设计和文档,但却常常被混为一谈。过多的层次破坏了透明性:我们很难看清这些层次,无法在头脑中理清代码到底是怎样运行的。简洁、清晰和透明原则通通被破坏了,结果代码中充满了晦涩的bug,始终存在维护问题。

胶合层中的“智能数据”却经常不代表任何程序处理的自然实体——仅仅只是胶合物而已(典型现象就是抽象类和混入(mixin)的不断扩散)

OO抽象的另一个副作用就是程序往往丧失了优化的机会。

OO在其取得成功的领域(GUI、仿真和图形)之所以能成功,主要原因之一可能是因为在这些领域里很难弄错类型的本体问题。例如,在GUI和图形系统中,类和可操作的可见对象之间有相当自然的映射关系

Unix风格程序设计所面临的主要挑战就是如何将分离法的优点(将问题从原始的场景中简化、归纳)同代码和设计的薄胶合、浅平透层次结构的优点相组合。

太多的OO设计就像是意大利空心粉一样,把“is-a”have a的关系弄得一团糟,或者以厚胶合层为特征,在这个胶合层中,许多对象的存在似乎只不过是为了在陡峭的抽象金字塔上占个位置罢了。这些设计都不透明,它们晦涩难懂并且难以调试。

为透明性和可显性而编码:

1、程序调用层次中(不考虑递归)最大的静态深度是多少?如果大于,就要当心。

2、代码是否具有强大、明显的不变性质(约束)?不变性质帮助人们推演代码和发现有问题的情况。

3、每个API中各个函数调用是否正交?或者是否存在太多的magic flags或者模式位?

4、是否存在一些顺手可用的关键数据结构或全局唯一的记录器,捕获系统的高层次状态?这个状态是否容易被形象化和检验,还是分布在数目众多的各个全局变量或对象中而难以找到?

5、程序的数据结构或分类和它们所代表的外部实体之间,是否存在清晰的一对一映射。

6、是否容易找到给定函数的代码部分?不仅单个函数、模块,还有整个代码,需要花多少精力才能读懂

7、代码增加了特殊情况还是避免了特殊情况?每一个特殊情况可能对任何其他特殊情况产生影响:所有隐含的冲突都是bug滋生的温床。然而更重要的是,特殊情况使得代码更难理解。

8、代码中有多少个magic number?通过审查是否很容易查出代码中的限制(比如关键缓冲区的大小)?

隐藏细节无法访问细节有着重要区别。不要过度保护

无论何时碰到涉及编辑某类复杂二进制对象的设计问题时,unix传统都提倡首先考虑,是否能够编写一个能够在可编辑的文本格式和二进制格式之间来回进行无损转换的工具?这类工具可称为文本化器(textualizer).

宁愿抛弃、重建代码也不愿修补那些蹩脚的代码。

“代码是活代码、睡代码还是死代码?”活代码周围存在一个非常活跃的开发社团。睡代码之所以“睡着”,经常是因为对作者而言,维护代码的痛苦超过了代码本身的效用。死代码则是睡得太久,重新实现一段等价代码更容易


评论

# re: 《Unix编程艺术》重读笔记(二)  回复  更多评论   

2011-03-30 16:06 by xufeng
受益匪浅!

只有注册用户登录后才能发表评论。


网站导航: