软件robust性思考

一、robust概念探讨

程序需要有robust性,这已经是软件界的共识了。
但是robust究竟是什么意思呢?目前一般翻译成健壮性或者鲁棒性,但个人人为这两种翻译都是不准确的。
先要搞懂robust这个词的准确含义,才能应用到软件开发中。

来自Merriam-Webster的解释:

对于软件robust而言,其意义应该是d那个解释:在各种环境下运行都不失败。
毫无疑问,robust有健壮的含义,但是这个意思是用在生命体上的,指健康和活力,将软件robust翻译成健壮性有偏差。
将robust直译为鲁棒性是没有什么问题,但这样等于是用一个新的词替代了旧的词,并没有表达出robust的内涵。
个人认为,robust最准确的含义是皮实。

皮实是什么意思?比如这里有一辆自行车,车闸已经坏了,只能脚刹;一个脚蹬子没了,只剩个杆;车把歪了;轮轴有断的。但是还是能骑。这种情况下,我们说这辆车很皮实。
也就是说,皮实指的是虽然有些组件已经坏了,但是东西还能用。
说得学术一点就是局部的损坏不影响系统的整体运转。
这个解释是最贴近英文d的解释的。所谓各种环境就是许多地方都出毛病了,所谓不失败就是整个软件还能运行。

所以软件robust并不是指不出错,这个做不到,而是指在局部挂掉的情况下软件整体还能运转。

二、如何实现robust?

让我们先来看看最robust的东西:人体。
人体真的是很robust,缺个胳膊少个腿,哑巴聋子少个肾,人体都还可以继续运转。不过如果脑子或者心脏坏了,人就不行了。
这告诉我们一个道理:局部也是有轻重缓急的。
了解了这点,就可以开始设计软件的robust性了。

为了让说明更加易懂,我们设想一个实际的项目场景。
现在有这样一个场景:用户登陆网页,在网页上填入一些数据,然后服务器根据用户填入数据进行计算,将结果返回网页。
这个场景还是很具有一般性的,许多网页项目抽象出来都是这个原型。

第一步,当然是先把系统分成各个局部,这点应该没有什么疑问。
因为既然robust概念包含了局部和系统,那么划分是自然的事。
如何划分?按照单一职责原则。每个模块只负责一件事。

在我们这个项目中,如何划分呢?很显然有一个网页和一个服务器,网页接收用户数据并传给服务器,服务器接收数据并计算然后回传网页。
两个模块?不好,服务器模块违反了单一职责原则。按照这个设计,服务器干了两件事:传输数据+计算。这在后期会遇到很多麻烦。应该划分成网页(作为界面显示)、监听服务(作为信息枢纽)、计算后台(作为计算核心)。
三者之间的关系如下:

只通过socket直接联系,数据由文件传递。
注意监听服务和计算后台是部署在同一台服务器上的。

第二步,确定各个局部的优先级,优先级高的要重点关注。
但这里面就有讲究了,确定优先级的标准是什么呢?
个人认为,要从用户视角而不是程序员视角确定优先级。
什么意思呢?还是以这个项目为例。从程序员的视角出发,最重要的无疑是计算后台。如果计算后台挂了,整个系统就无法运转了,因为给不出任何有意义的结果。但是从用户视角出发,最重要的不是计算后台,因为用户根本不关心也不知道计算后台,他们关注的是网页的反馈。当网页返回了结果,他们就认为系统还是在运作的;如果提交计算后网页卡死了,他们就会认为系统挂了。

注意!!!这里非常有意思。
从用户视角出发和从程序员视角出发,二者对系统运转的定义不同!!!
robust的定义是局部出问题,系统依然运转。但现在对于什么叫系统运转发生了分歧。关于robust性最深刻的思考就是从这里开始的。

个人认为,应该从用户视角出发,这是因为软件最终是给用户使用的,用户满意才能说明软件成功。
按照这个思路,对局部的优先级排序就是:网页显示>网页响应用户>计算结果。
这里就没有出现监听服务和计算后台的事,这是很自然的,因为用户不知道这两个模块的存在。
到这一步后,我们要把优先级排序映射到编程的角度,并设想各种异常情况。比如这个图:

于是我们首先集中精力确保网页能正常显示。
然后增加监听服务的robust,即不管计算后台如何,都能够响应网页。
可以采取的策略是设计一个socket时限,超过时限没有接收到计算后台的消息,直接给网页返回错误信息。
这样不管计算后台如何,网页都是能响应用户的,即虽然局部失败,系统还是运转的。当然返回的结果不是用户想要的,但至少比不响应要好多了,可以给出错误提示,让用户安心。
最后才是增强计算模块的robust性。

到这里你可能会有异议:只要计算后台不出错,其他的问题都不会存在,因此还是应该集中精力增加计算后台的robust。
如果你能够做到计算后台不出错,那么确实可以这样做。但是很遗憾并不行。
你应该牢记墨菲定律(Murphy’s Law):如果事情有变坏的可能,不管这种可能性有多小,它总会发生。计算后台是最复杂的,所以一定会出错。
另外认为robust就是不出错,是对robust的错误理解。我们承认每一个模块都可能出错,当一个或几个模块出错了,系统还能够运行,这才叫robust。

通过这样的一种设计,网页总是能够响应用户的操作,至少用户不会认为整个系统挂了,我们就实现了初步的robust。最后才是想想如何让计算后台从错误中恢复的问题了。
按优先级思考,这才是增强系统robust的正确思路。

三、设计与代码

需要指出的是刚才我们探讨robust并没有涉及任何代码。
实际上robust性来自两方面:设计和编码。
写一段代码做参数检测、捕捉异常、返回错误值等,这些被强调的很多,但是这些只是robust的一部分。如果代码中全是这些东西,可读性会很差,更重要的是,做了这些也并不能保证系统还能运转。良好的设计才是robust的活水源泉。

不过这并不是说编码不重要,关于这方面的探讨可见《c++异常机制》。