大自然的BUG、人的BUG、软件的疑难杂症
2006-02-04 13:48:28 来源:WEB开发网核心提示:“每逢春夏,千鹤云集”的青城山,大自然的BUG、人的BUG、软件的疑难杂症,在此栖居了上千年的仙鹤纷纷飞走,从此失踪了,后者可能因为使用堆栈中的变量没做初始化等,写安全的代码就像人练就金刚不坏之身,当地居民纷纷指责:这都是房地产大开发惹的祸!也许不是这个原因,也许是;全球多个国家出现气候异常
“每逢春夏,千鹤云集”的青城山,在此栖居了上千年的仙鹤纷纷飞走,从此失踪了。当地居民纷纷指责:这都是房地产大开发惹的祸!也许不是这个原因,也许是;全球多个国家出现气候异常,有没有根本的方法来防止呢,可能没有。
人类的最大BUG就是癌症,以及艾滋病、SARS等的出现。没有根本的防治方法,也找不出根本的原因。
搞软件的也总会碰到许多疑难杂症,有的解决了,有的无法解决。我们来对比分析几个原因以及解决:
1. 不良习惯
有背自然规律,不良生活习惯的积累导致自然生病、人生病。不良的编程习惯也会导致程序出现疑难杂症。
例1.1 中间件内存问题
一Delphi中间件执行批量数据处理时内存剧增,几个小时后内存占到近1G,处理完了内存也不下降。用Turbo MemorySluth等内存工具查没查到问题,最后采用最原始的方法:在一段代码前后执行AllocMemSize,看其差值,正常应该为零,查出一段代码每处理一条记录就会泄漏100K左右内存,只是因为该代码自己创建的类实例没有释放,释放了就好了。这个问题查了很久,如果该段代码的作者习惯于自己创建的对象就自己释放,就不会需要多个人费劲心机来跟踪查找,还怀疑到ADO、Midas、三层结构有问题。
对于程序员,很多事情都没有对错,只是习惯,比如遵循命名惯例书写惯例、写注释、使用try...finally...end来保证资源释放等,坏习惯的累积最后导致软件出现一些不容易查找的疑难杂症,好的生活习惯会意味着好的生活质量,好的编程习惯也会意味者好的产品质量。
2. 存在未知
相对于大自然、人来说,软件简直是太简单了,但都可称为复杂系统。一个人在一个特定的时间只可能了解一个复杂系统的一部分,如果要用未知的东西,就很可能出现问题,就象人,虽然我们对自己很多未知,却活着,天天用,当然也是经常生病,经常出问题。
对于软件的未知东西,我们能够能做的就是信任并按自然的方式用之并了解之转未知为已知、或者直接转用已知的东西、再有就是直觉判断该不那么做、应这么做。
例2.1 MDB不能打开问题
一单机版软件使用access数据库发布到用户后运行总是提示不能独占打开数据库,但确实没有其他人使用,问题也是找了很长时间,怀疑过病毒、杀毒程序等,最后知道只是因为MDB文件属性是只读的,发布时刻的光盘,直接拷贝到用户的机器后就仍然是只读的。
不知道是因为只读的原因,问题就很难搞定。当然这也可以说是软件的缺陷,它应该有更友好的提示,也许写Jet引擎这段代码的作者也没考虑到这么多,也许文档里写有,但我们不知道。程序员的未知总是很多,遇到的问题总是很多,但是我们还是要用这些自己无法全部把握的东西。
这个世界,什么都必须去重用,什么都重用,比如女人、还有男人。
例2.2 safecall问题
一中间件远程模块采用Wrapper模式(或者说委托模式),把调用转给相应的类来处理,如
// 远程模块代码不实现方法
PRocedure TrdmMyRemoteDataModule.MyProc; // 在 interface 中必须声明为safecall
begin
FMyClass.MyProc;
end;
// 具体的实现在另一个类中
procedure TMyClass.MyProc; // 在 interface 中也声明为safecall
begin
raise Exception.Create('Just a exception');
end;
结果发现异常无法返回给客户端。另一个类写同样类似的代码则没有问题,异常能正常抛到客户端,结果发现差别在于没问题的类的方法没有声明为safecall,把有问题的方法声明中safecall去掉就对了。写正确代码的写出来时不明白自己为什么都不加safecall,可能只是因为直觉做了正确的事情。
最后查Delphi帮助,才都懂了:“The safecall convention implements exception "firewalls." ...”。
在同一个时间点,不同程序员可能存在相同的未知,有的能直接走正确的路,有的走错误的路,不知道是什么道理,只能靠多年经验积累的直觉。
例2.3 ADO问题
v2.7或更低版本的ADO,Delphi中执行以下带参数的SQL会出错:“select * (select * from t where f=:p) a”,2.8的就没有问题。查找出这个问题费了很多周折。先是一个很复杂的SQL总报错,然后简化为上述的还是报错,怀疑跟ADO版本有关,发现主文件版本2.70的有问题,2.71的就没问题,想直接替换ADO的支持文件(Program Files\Common Files\System\ado),结果替换成功了,再试时发现文件又被恢复成老版本了,最后在去掉shell后在命令行中替换文件成功,操作系统真是一个巨大的病毒。直接升级到ADO2.8也可以。
其实开发人员、用户、客服人员都一样,发现有问题时都是想者升级软件到高版本,不明白是什么原因,也许问题就解决了。不明白的事情太多,但未知不能阻止程序员去解决问题,许多时候解决问题都是不需要也不知道道理的,只有说有效果比有道理重要。
所述问题确认是ADO处理参数问题后,我们换成基于Zeos的代码来处理参数把参数值直接插入到SQL中形成完整字符串,这样也方便调试、优化SQL,同时解决另一个更复杂的SQL在ADO2.8中执行时导致MSVCRT.dll出现Access Violatioin错的问题。
3. 结构问题
一个大的自然工程,如大坝等,如果结构存在问题,那影响的可能不只是环境,也是很难改的。如果人的结构存在问题,比如生下来就缺少点什么,那就很痛苦。软件如果存在结构问题,也很痛苦,不过,软件与自然、人不一样的就是软件容易推倒重来。
例3.1 98内存问题
一Delphi程序在98下运行出现内存不足提示无法运行,但在其它操作系统都没问题;另一个程序可执行文件更大但没问题。发现是程序中表单太多的问题,减掉几个就可以。后来找出原因是可执行文件中资源太多,资源需要占系统的句柄空间,而98只有64K的User Resource句柄空间,这样,只要把可执行文件中不用的其它位图等资源删除一些也就可以了。
Delphi提供的RAD开发模式,除了VCL是很不错的东东,其它都不是好东西:表单使用单独的资源保存、事件代码直接写在表单的单元中等,后者直接导致界面和逻辑不分离,前者在Delphi8和.NET中都已不这么做。界面与逻辑分离可以为程序带来优良的结构,实际上Delphi已经提供了这样的机制,比如基于Action。我们在新的开发中,还引入了动态表单,把界面和界面逻辑分离到表单中,业务逻辑写在Action中,更清晰的结构会意味着更大的扩展空间和更少的问题、更小的维护成本。
4. 环境变化
动物和人在环境变化时都会出现异常的行为,人在新的环境中可能水土不服,软件的错误在不同环境中也会有不同的表现。
例4.1 “灾难性故障”
三层软件在客户端报中文的“灾难性故障”错误,查出都是因为中间件出现AV(Access violatioin)错误,AV错大都是因为不良习惯或者不安全的代码引起的,比如空对象(指针)访问、释放空对象或对象重复释放等。
另一个可能导致客户端报“灾难性故障”的是中间件出现浮点数操作错误,比如除零错,还有浮点值太大无法填写到数据库字段的“Invalid floating point Operation”错,这两种错也是因为不安全的代码,前者是浮点数除操作前没做判断,后者可能因为使用堆栈中的变量没做初始化等。
写安全的代码就像人练就金刚不坏之身,在外部环境变化时都不会出故障。
人类的最大BUG就是癌症,以及艾滋病、SARS等的出现。没有根本的防治方法,也找不出根本的原因。
搞软件的也总会碰到许多疑难杂症,有的解决了,有的无法解决。我们来对比分析几个原因以及解决:
1. 不良习惯
有背自然规律,不良生活习惯的积累导致自然生病、人生病。不良的编程习惯也会导致程序出现疑难杂症。
例1.1 中间件内存问题
一Delphi中间件执行批量数据处理时内存剧增,几个小时后内存占到近1G,处理完了内存也不下降。用Turbo MemorySluth等内存工具查没查到问题,最后采用最原始的方法:在一段代码前后执行AllocMemSize,看其差值,正常应该为零,查出一段代码每处理一条记录就会泄漏100K左右内存,只是因为该代码自己创建的类实例没有释放,释放了就好了。这个问题查了很久,如果该段代码的作者习惯于自己创建的对象就自己释放,就不会需要多个人费劲心机来跟踪查找,还怀疑到ADO、Midas、三层结构有问题。
对于程序员,很多事情都没有对错,只是习惯,比如遵循命名惯例书写惯例、写注释、使用try...finally...end来保证资源释放等,坏习惯的累积最后导致软件出现一些不容易查找的疑难杂症,好的生活习惯会意味着好的生活质量,好的编程习惯也会意味者好的产品质量。
2. 存在未知
相对于大自然、人来说,软件简直是太简单了,但都可称为复杂系统。一个人在一个特定的时间只可能了解一个复杂系统的一部分,如果要用未知的东西,就很可能出现问题,就象人,虽然我们对自己很多未知,却活着,天天用,当然也是经常生病,经常出问题。
对于软件的未知东西,我们能够能做的就是信任并按自然的方式用之并了解之转未知为已知、或者直接转用已知的东西、再有就是直觉判断该不那么做、应这么做。
例2.1 MDB不能打开问题
一单机版软件使用access数据库发布到用户后运行总是提示不能独占打开数据库,但确实没有其他人使用,问题也是找了很长时间,怀疑过病毒、杀毒程序等,最后知道只是因为MDB文件属性是只读的,发布时刻的光盘,直接拷贝到用户的机器后就仍然是只读的。
不知道是因为只读的原因,问题就很难搞定。当然这也可以说是软件的缺陷,它应该有更友好的提示,也许写Jet引擎这段代码的作者也没考虑到这么多,也许文档里写有,但我们不知道。程序员的未知总是很多,遇到的问题总是很多,但是我们还是要用这些自己无法全部把握的东西。
这个世界,什么都必须去重用,什么都重用,比如女人、还有男人。
例2.2 safecall问题
一中间件远程模块采用Wrapper模式(或者说委托模式),把调用转给相应的类来处理,如
// 远程模块代码不实现方法
PRocedure TrdmMyRemoteDataModule.MyProc; // 在 interface 中必须声明为safecall
begin
FMyClass.MyProc;
end;
// 具体的实现在另一个类中
procedure TMyClass.MyProc; // 在 interface 中也声明为safecall
begin
raise Exception.Create('Just a exception');
end;
结果发现异常无法返回给客户端。另一个类写同样类似的代码则没有问题,异常能正常抛到客户端,结果发现差别在于没问题的类的方法没有声明为safecall,把有问题的方法声明中safecall去掉就对了。写正确代码的写出来时不明白自己为什么都不加safecall,可能只是因为直觉做了正确的事情。
最后查Delphi帮助,才都懂了:“The safecall convention implements exception "firewalls." ...”。
在同一个时间点,不同程序员可能存在相同的未知,有的能直接走正确的路,有的走错误的路,不知道是什么道理,只能靠多年经验积累的直觉。
例2.3 ADO问题
v2.7或更低版本的ADO,Delphi中执行以下带参数的SQL会出错:“select * (select * from t where f=:p) a”,2.8的就没有问题。查找出这个问题费了很多周折。先是一个很复杂的SQL总报错,然后简化为上述的还是报错,怀疑跟ADO版本有关,发现主文件版本2.70的有问题,2.71的就没问题,想直接替换ADO的支持文件(Program Files\Common Files\System\ado),结果替换成功了,再试时发现文件又被恢复成老版本了,最后在去掉shell后在命令行中替换文件成功,操作系统真是一个巨大的病毒。直接升级到ADO2.8也可以。
其实开发人员、用户、客服人员都一样,发现有问题时都是想者升级软件到高版本,不明白是什么原因,也许问题就解决了。不明白的事情太多,但未知不能阻止程序员去解决问题,许多时候解决问题都是不需要也不知道道理的,只有说有效果比有道理重要。
所述问题确认是ADO处理参数问题后,我们换成基于Zeos的代码来处理参数把参数值直接插入到SQL中形成完整字符串,这样也方便调试、优化SQL,同时解决另一个更复杂的SQL在ADO2.8中执行时导致MSVCRT.dll出现Access Violatioin错的问题。
3. 结构问题
一个大的自然工程,如大坝等,如果结构存在问题,那影响的可能不只是环境,也是很难改的。如果人的结构存在问题,比如生下来就缺少点什么,那就很痛苦。软件如果存在结构问题,也很痛苦,不过,软件与自然、人不一样的就是软件容易推倒重来。
例3.1 98内存问题
一Delphi程序在98下运行出现内存不足提示无法运行,但在其它操作系统都没问题;另一个程序可执行文件更大但没问题。发现是程序中表单太多的问题,减掉几个就可以。后来找出原因是可执行文件中资源太多,资源需要占系统的句柄空间,而98只有64K的User Resource句柄空间,这样,只要把可执行文件中不用的其它位图等资源删除一些也就可以了。
Delphi提供的RAD开发模式,除了VCL是很不错的东东,其它都不是好东西:表单使用单独的资源保存、事件代码直接写在表单的单元中等,后者直接导致界面和逻辑不分离,前者在Delphi8和.NET中都已不这么做。界面与逻辑分离可以为程序带来优良的结构,实际上Delphi已经提供了这样的机制,比如基于Action。我们在新的开发中,还引入了动态表单,把界面和界面逻辑分离到表单中,业务逻辑写在Action中,更清晰的结构会意味着更大的扩展空间和更少的问题、更小的维护成本。
4. 环境变化
动物和人在环境变化时都会出现异常的行为,人在新的环境中可能水土不服,软件的错误在不同环境中也会有不同的表现。
例4.1 “灾难性故障”
三层软件在客户端报中文的“灾难性故障”错误,查出都是因为中间件出现AV(Access violatioin)错误,AV错大都是因为不良习惯或者不安全的代码引起的,比如空对象(指针)访问、释放空对象或对象重复释放等。
另一个可能导致客户端报“灾难性故障”的是中间件出现浮点数操作错误,比如除零错,还有浮点值太大无法填写到数据库字段的“Invalid floating point Operation”错,这两种错也是因为不安全的代码,前者是浮点数除操作前没做判断,后者可能因为使用堆栈中的变量没做初始化等。
写安全的代码就像人练就金刚不坏之身,在外部环境变化时都不会出故障。
更多精彩
赞助商链接