标签 高质量程序设计艺术 下的文章

台风“巨爵”离去后,今晚又恢复如常的看书计划。《高质量程序设计艺术》来到第6章了,本章主要探讨程序的可移植性问题。

可移植性主要面对的几个难题是:操作系统、处理器体系结构、编译器与语言特性、图形界面环境、区域(如:显示多语言)、硬件设备与平台等方面的差异。提高程序的可移植性,主要就是解决以上几个难题。

解决可移植性的常用方法是使用一个代码层来对功能进行抽象,这实际上将差异性用一部分代码隔离了。例如:JavaScript的话,可以编写通用方法来获取屏幕大小,而该方法是通过判断不同浏览来调用不同的方法来实现。又例如:C/C++可以使用预处理。

 本章用了较大的篇幅探讨了图形界面的问题。值得注意的有以下几点:

1)字符编码应该采用更通用、兼容性更高的UTF-8,而不是特定于某个区域的编码。虽然GB1080已经提高了其兼容性,但面向国际的话,还是很勉强。

2)程序/软件中的消息,应该与代码分离,作为独立的、容易替换的资源来处理,以简化其本地化的工作。

3)可移植GUI(图形用户界面)除了可以考虑建立可移植层(如建立统一的API,分别在Windows和X上封装实现该API,程序便可该API)、采用仿真层(好象就是WINE的做法)、基于可移植层的平台(如Java的Swing、SWT等)以外,还可以考虑使用HTTP/HTML或AJAX层来提供用户界面。

首先,今天是个很特别的日子,因为很多情侣特意选择今天结婚(09年09月09日,有长长久久之意)。

废话说完。《高质量程序设计艺术》终于完成第5章了。第4章时间性能和第5章空间性能,其实可以合起来,就是程序优化的问题。首先,程序的时间性能跟空间性能,可以说是矛盾的,因为很多时候都是空间换时间,或者时间换空间。其次,程序优化,就有可能导致可移植性降低。所以,很多专家都认为,程序优化的首要准则是:不要优化(出自Michael A. Jackson)。当然,前提是该程序已经一个良好的程序(不懂怎么表达,反正就是好程序最好不要优化)。

优化的另一个指导思想是帕雷托法则(Pareto Principle)。本来是用在经济学问上的,而用在程序执行上表明,20%的代码通常占用了80%的执行时间。那么,只要找出这20%的代码,就可以对程序进行优化了。

优化的方法嘛,不外乎那几种:空间换时间、减少I/O操作、降低算法复杂度等等。

对于空间性能方面,由于涉及到硬件方面,所以主要是C/C++方面的问题。对于Java程序员来说,底层硬件都是不可见的,所以研究的意义不大。有个问题值得注意的,代码的长度,准确来说是编译后的文件(目标文件)大小,有时候跟执行效率有关。目标文件越小,占用内存空间就越小。

还有一个收获,认识了“内存对齐”。例如:在32位计算机中,一个char变量(占1个字节,即1 byte,也就是8位,8 bit)和一个long变量(占4个字节,即4 byte,也就是32位,32 bit)储存在一起。如果不进行内存对齐,要读取该long变量时,就要读取两个32位字的内容,再进行复杂的操作。但如果对齐后,读取该long变量就只要读取一个32位字的内容,没有复杂的操作,但这样就意味char变量后面紧跟着3个字节来实现对齐,就是有3个字节的内存空间浪费了。这是明显的空间换时间。同时这就知道了为什么内存容量总是32的倍数(因为32位的计算机每次处理32位数据或指令)。我估计下一代的计算机是64位(32的两倍),而不是40位、48位之类,是为了向下兼容。

PS. 可能近来睡眠不足,导致看书时精力不足(总是打瞌睡)。

睡觉前总结一下今天看书的收获。

《高质量程序设计艺术(Code Quality: The Open Source Perspective)》已经看到第3章了。本章的内容仍然很精彩!

正如书中所说,软件安全性是一个复杂又独特的难题。

1、缓冲区溢出,一个恒久的入侵课题。以前看很多黑客文章(应该是黑客故事),一开场就说溢出,但就是不知道接着做什么。书中介绍的例子是首先使得程序溢出,使得程序的指针指向要执行的恶意代码所在的内存地址,然后恶意代码便可以执行了。虽然不太懂具体的做法,但是只要想到指针能被替换,就已经很唬人了。

PS. 就算是Java/.NET,声称对缓冲区溢出漏洞是免疫的(因为它们提供了数组边界检查机制),但使用对象数组实现的环形缓冲区也有可能出现缓冲区溢出。

2、文件替换。有些软件是在root权限下运行的,并且带有更改文件权限或删除文件操作。如果把这些软件运行时所操作的文件指向一些重要的系统文件,那后果是非常严重的。

3、不可信输入。这个最简单最有名的例子是SQL注入。很多时候,特别是登陆系统时,输入的用户名都没有过滤特殊字符,或者只是在页面上用JavaScript来过滤,到了服务器就直接组装成SQL语句来查询。这时候,如果用户名后加个“;”(分号),再加个恶意的SQL语句,本来一个语句就会当作两个语句来执行(一般的数据库系统都可以识别分号为一个SQL语句的结束符),后果就不说了。

4、木马。又是恒久的主题。书中提及的不是怎么上传木马程序并运行,而是说某些开源软件的代码如果在服务器上被修改过,那用户下载来编译运行后会带来灾难。即使绝大部分的开源代码都会提供验证码(如MD5、PGP签名等)来检验是否被修改过,但绝大部分的用户都不会去检验一下。

看完本章后,突然想起以前想买的那本《入侵的艺术》,于是去“卓越”看看(http://www.amazon.cn/mn/detailApp/ref=sr_1_1?_encoding=UTF8&s=books&qid=1252254843&asin=B0011CU4RU&sr=8-1)。看过试读后,感觉这本说在写小说似的,剧情很跟《越狱(第四季)》有点像。其实最难防的是,那人直接到你机房去。

对了,还有两个细微的地方要记一下:

1)Windows 的某文件夹下,有两个可执行文件aa.com和aa.exe。如果在命令状态执行aa,那默认会先找到aa.com并执行,而找不到aa.com才会找aa.exe来执行。

2)Windows有个环境变量叫“ComSpec”,用来设置命令行解释器。

时间很快来到9月。回顾8月,唉,太浪费时间了。整个8月都不知道忙了啥,就连工作也是超无聊(每星期都有好几天没具体工作任务)。于是,希望9月来个突破!

今晚赶紧吃完饭后,独自到中大自习。本来想看《系统架构设计师教程》(想到11月去考一下),但精神不佳,于是拿出《高质量程序设计艺术(Code Quality: The Open Source Perspective)》来看。

首先第1章翻了几翻便过去了。到了第2章看到几个挺好的话题:

1)一个8位的硬件计数器是否能用来对一个256字节缓冲区的成员进行计数?
    该问题有点不明白,“硬件计数器”是什么?“一个256字节缓冲区的成员”又是什么?我觉得该提问者想说,8位的处理能力最大是255字节。纯猜测,跳过……

2)多路处理的问题
    处理多个分支的代码(如switch语句)时,作者提出以下几个处理方法:
(1)在每个分支或步骤添加注释,即具体说明;
(2)代码与复合数据成员(结构体与类)之间的关系;
(3)switch中,case的值要覆盖所有可能的情况,或者用defualt来处理可能遗漏的情况;
(4)或者根本不用多路处理,把数据与代码直接联系在一起,可以通过为原本是一个case成员的处理创建一个子类,或者是将各个实现特定接口的函数与每个数据成员相关联来实现。

3)软件发布版中的隐藏功能
    由于软件发布版中的隐藏功能,如调试功能,会带来潜在的危险与额外的资源消耗,所以应该避免。著名的反面教材是“莫里斯蠕虫”病毒,利用Unix的邮件传送代理sendmail中的调试代码,来执行命令,达到自我复制、传播的目的。

4)溢出的问题
    一个简单的表达式子:
int a = b * 100 / 255;
    当b接近int的最大值时就可能溢出,但改为这样就可以避免问题发生:
int a = b  / 255 * 100;