君子之行,静以修身,俭以养德。非淡泊无以明志,非宁静无以致远。ESFramework,超强悍的通信框架! 武汉傲瑞科技有限公司 www.oraycn.com
很久以前我就考虑这样一个问题:有这样一个函数,它的功能是从一个整数集合中返回最大的那个数,如何设计这个函数的签名了?当时没有得出令自己满意的答案,所以就搁浅了。今天重新思考,终于有所悟!现在把我思索的整个过程展现于此。最直观的函数签名设计如下:
乍看之下,很好,很直接的反映了意图。稍微深入一点就发现,如果eleList为null或者其中元素个数为0,GetMaxElement返回什么了?第一反应,修改签名为下面的形式:
我问了很多程序员,几乎都是这样处理的。我觉得这样设计函数很别扭,我喜欢直观简单的解决方案,无疑我最喜欢第一种签名形式,它很好的反应的函数的意图。而第二种了,它的设计不够好,除了不够直观外,还有什么更重要的缺陷?今天我知道了答案。 首先,我们想想看,当eleList为null或者其中元素个数为0时,GetMaxElement知道怎么处理这件事吗?当然,不知道!因为在此函数中所有相关的上下文已经丢失了,那么谁知道处理的方法了?对,是调用GetMaxElement的调用方。调用方在调用GetMaxElement之前就应该检查eleList 是否满足条件。那么,这个条件是在哪里定义的了,目前的解决方案是在GetMaxElement函数说明文档中。调用方这么做:
由于调用方知道怎么处理eleList为空和个数为0的错误的上下文,所以它很容易解决这个问题。如果采用第二种签名,有何缺陷了?在第二种签名情况下,调用方通常这么做:
到if(!succeed)语句时,调用方已经不知道GetMaxElement返回的错误是不是由eleList为空和个数为0引发的,错误的根源丢失了,所以调用方对返回的false真是爱莫能助、唯有叹息了!
到这里,我总结了一个设计原则: 不要让错误传播,在错误出现的发源地(萌芽期)就解决它!错误越是传播到最后,关于处理它的上下文就丢失得越多,对于错误的蔓延就越是爱莫能助!
为GetMaxElement方法加上注释后是这个样子:
这实际上是限制了一个前置条件,关于前置条件和后置条件的更多资料可参考“契约式设计”!讲到这里,我考虑在.NET平台上实现一个契约设施Dbc.net,该设施将在运行时自动检测前置条件、后置条件等。很有可能像下面的样子:
对于实现Dbc.net有什么好的建议,欢迎和我讨论。我已经在博客园申请了Dbc.net的专栏http://www.cnblogs.com/DbcNet,欢迎你的加入!
posted on 2005-09-10 11:07 zhuweisky 阅读(2280) 评论(20) 编辑 收藏
个人而言,我更喜欢第二种签名设计,我觉得这样对于调用方来说,可以更自由一点 回复 引用
有道理,降异常逻辑下放到另外级别中的确很危险。 但是这些问题应该在你设计函数的时候就必须预见到。 现在知道异常的重要性了 回复 引用
经历跟作者一样, 回复 引用 查看
作者说得不错,但这也就带来了安全隐患,可能被有不良意图的人利用.其实我觉得应当看情况而定.有时候需要整个体系建立在一个非常坚实的基础上,那就必须要在最底层的模块处理绝大多数的错误. @fffly 我不清楚你的意图是支持那方的. 回复 引用 查看
抛出异常不好吗 回复 引用 查看
我一般在函数开头都先检查参数的合法性,如不合法,则抛出异常。 回复 引用 查看
其实dbc的思想已经很老很老了, 为什么没有被采用呢? 说明它本身有缺陷. http://www.alphatom.com/content/view/246/43/ NContract早就出来了, 连对此感兴趣的楼主都不知道, 更证明了它的失败. 回复 引用
为什么要把什么事情都放到函数内处理;我一般使用第一种,调用者应该先检查再调用,而不是什么都丢给函数;加入你的GetMaxElement函数由别人完成,你能保证别人能完善的处理好一切吗? 回复 引用
我喜欢第一种啊.第二种winAPI里面好多都是第二种方法. 还指针一下一下搞得C#调用起来感觉晕晕的. 回复 引用
还是觉得在函数里抛出一个异常比较好,这样函数签名清晰,错误信息可自定义而且不丢失! 回复 引用 查看
除了扔exception, 也可以用assert来做. 回复 引用 查看
我比较喜欢第一种函数签名. 但是我一般把验证放在函数内部,对于无法返回最大值的情况抛出异常给调用者. Public Function GetMaxElement(ByVal eleList As ArrayList) As Integer If eleList Is Nothing Then Throw New ArgumentNullException("eleList") End If If eleList.Count = 0 Then Throw New ArgumentOutOfRangeException("eleList", "eleList集合的数量不能为0") End If Dim maxValue As Integer '...... Return maxValue End Function 如果eleList集合的元素不是int型的,是字符串或者其它类型的呢,或者是int型与其它类型的混合呢,如果不使用异常的话,那么调用者还需要先做个循环把集合的元素都检查一遍才能调用的话,还不如把检查工作放在函数内部,检查到非int型就抛出一个异常给调用者. Throw New Exception(String.Format("eleList集合的第{0}个元素不是int型", index)) 这仅仅是我个人喜欢的用法,至于这样做到底好不好,很想听听大家的意见. 回复 引用 查看
用Assert多好! 回复 引用
看到前几段,脑子里就出现一个概念,不错,和楼主想的一样,就是DBC,契约式编程,记得似乎在前两年的时候,接触过这个东西,可惜好像除了Effiel,其它语言都没有在语言层实现DBC,直到现在也是这样。当时还认为DBC也是OO的一项重要分支。 想问问idior,它本身的缺陷是什么? 我记得DBC的作者写过一本书,被认为是有史以来最好一本OO的书。 回复 引用 查看
如果在基本架构上就不支持DBC这样的东西,我觉得还是按。NET自己的习惯处理比较好,习惯处理是什么呢?其实咱们都用过string的一些方法,初学者用这些方法最容易得到一堆的越界异常等,所以,这里我想还是得抛出个异常,比如一个ArgumentNullException 回复 引用 查看
其实对于你这种情况用 最直观的函数签名设计如下: int GetMaxElement(ArrayList eleList) ; 是正确的, 针对eleList != null,eleList.Count > 0在.NET中应以异常的机制来处理 这是.net 企业级示例的推荐 回复 引用
我总结了一个设计原则: 不要让错误传播,在错误出现的发源地(萌芽期)就解决它!错误越是传播到最后,关于处理它的上下文就丢失得越多,对于错误的蔓延就越是爱莫能助! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这个总结明显有问题吧 错误如果不能被正确的处理 就应该让它传播 回复 引用 查看
if((eleList==null) || (eleList.Count==0)) { throw new UserDefinedException("element is null, or what ever") } try { int max = GetMaxElement(eleList) ; } catch (UserDefinedException ex) { //Handle your Exception here } 回复 引用
似乎違背單元測試的做法,一般考慮reuse,必需正反的使用方式都考慮,既然是元件那就必需經的起考驗,誰知道以後誰會用這個元件.或許沒注意,這產生的bug將造成後續維護很大問題.不建議使用者自己先做確認.倒是丟出異常除了performance有些疑慮外,應該是不錯的方式 回复 引用
你们有没有发现,有时候我们经常丢NULL值或空或不可转换类型(显/隐式)的对象/值给WINDOWS或.NET里的一些API呢? 它是让你先转换好或保证绝对正确的值,然后再传给它?还是在运行时抛出错误呢? 或者二种都有等等? (纯属个人看法--菜鸟) 回复 引用
人的灵魂要强,
接受人的脆弱。