朋友问这样的问题:
写了一个函数,从网页源代码中抽取一个结点的文本,函数本身没有问题,但同事在code review时指出,如果提取不成功,应该返回None,而不是空字符串""。
提问的朋友跟我说:你之前说过,从数据库里取出一批数据,如果不存在,要返回一个空的Collection,而不是None(这样才能正确表示“需要的数据不存在”),那么这里,是否应该听取同事的意见呢?
当时我的回答是,因为从这个函数接受数据的代码期望获得的是一个字符串,所以,如果提取不成功,应该返回""。
但是,这样,就无法区分下面两种情况了:存在这个结点,但是一个空结点;不存在这个结点。
这几天晚上一直在思考这个问题,并与
Patrick讨论,最后的结论是:
之所以会出现这个问题,原因在于函数的定义是不明确的——虽然函数名看来很清楚,但它的意义是不确定的。
按照
契约式设计(Design by Contract)的原则,每个模块都应该有前驱条件(pre-condition)和后继条件(post-condition)。正是因为两位开发人员对这一点的认识存在分歧,问题才会出现:
- 如果这个函数的前驱条件是,必须提供一段包含此结点的源代码(也就是说,这个结点必须存在),就不能返回None,返回"",就表示这个结点内容为空,如果提供的源代码不包含这个结点,应抛出异常;
- 如果前驱条件是,提供的源代码不必包含这个结点,那么,这个函数其实就履行了两项职责——检查是否存在结点,以及提取结点的文本,必须与调用方有一个约定,来表示这两重含义(此时可以用None表示“不存在结点”的情况,用""表示结点内容为空的情况,当然我不推荐这么做)。
同样,按照契约式设计的原则,数据验证的职责在调用方,也就是说,约定了这个函数的前驱和后继条件之后,调用方在调用之前,必须保证前驱条件:如果函数要求源代码必须包含这个结点,那么验证是否存在的职责就应该由调用方来承担。
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=1201383