【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

从“用断言检查 null”到“设计单元测试”

  • 2009-03-12
  • 本文字数:2079 字

    阅读完需:约 7 分钟

防御式编程是保护自己的程序不受外部侵害的一种有效方式。代码大全在“防御式编程”这一章中讲到,可以用断言(assert)来检查真实情况是否满足自己的预期,例如指针非空。

上个月 Miško Hevery 写了一篇文章:断言,还是不断言,描述了在构造器中编写断言给单元测试所造成的种种不便。

他先举了一个实际例子:

复制代码
class House {
Door door;
Window window;
Roof roof;
Kitchen kitchen;
LivingRoom livingRoom;
BedRoom bedRoom;
House(Door door, Window window,
Roof roof, Kitchen kitchen,
LivingRoom livingRoom,
BedRoom bedRoom){
this.door = Assert.notNull(door);
this.window = Assert.notNull(window);
this.roof = Assert.notNull(roof);
this.kitchen = Assert.notNull(kitchen);
this.livingRoom = Assert.notNull(livingRoom);
this.bedRoom = Assert.notNull(bedRoom);
}
void secure() {
door.lock();
window.close();
}
}

然后说到,因为 secure 方法只需要 door 和 window 两个对象,所以理所当然的是,在测试这个方法的时候只需要初始化 door 和 window,其他参数用 null 代替,如: 、

House house = new House(door, window,null, null, null, null);

这样的方法才能够让人看明白意图,但是因为构造器中做了参数非空的断言,所以就不得不这样初始化 House 对象:

House house = new House(door, window,
new Roof(),
new Kitchen(),
new LivingRoom(),
new BedRoom());

这样就让人不太能看得懂到底是哪些对象真正在测试方法中被用到了。而当构造器中的参数增多,代码的可读性就会更差。Miško 最后提到:

我不是反对断言,我也在自己的代码中也常用,不过我的大多数断言只是用来检查对象的内部状态,而不是是不是传入了一个 null 值。检查是不是 null 往往会影响到代码的测试,如果要我在完好测试过的代码和有断言但是没测试的代码之间做选择,结果是不言而喻的。

有不少人跟帖反对 Miško 的观点,他们认为这根本不是断言的问题,而是来自于设计本身,例如

Rasmus Kromann-Larsen 就提出,

你为啥不用 mock 框架而非要自己初始化那么一堆东西呢?在.NET 世界里面有个新词,叫做 automocker——你可以把它看作是 mock 框架和 ioc 容器的混合体…… 看看这个链接吧: http://blog.eleutian.com/CommentView,guid,762249da-e25a-4503-8f20-c6d59b1a69bc.aspx

Howie 则说:

不管是我写的代码,还是我看到的代码,都是在调用对象的时候去做断言,而不是在保存对象的时候。这样合理不?所以你应该在 secure() 方法里面写 Assert.notNull(door);Assert.notNull(window)。用到的时候再去检查,而不是提前 就把一切都检查好,以备过几天以后需要用。

zdsbs 提出用 Builder 来取代构造器:

Door door = new Door();
Window window = new Window();
House house = HouseBuilder.new().with(door).with(window); house.secure();

assertTrue(door.isLocked());

assertTrue(window.isClosed());
这样问题就很好的解决了,其他变量都可以在 HouseBuilder 里面提供一些缺省值,你也不会创建出具有非法状态的对象来。

earlNameless 对 Miško 的设计思路直接抨击说:

我不同意你的看法,因为你这个例子里面,人们得先知道你所测试的方法只用到了 door 和 window,没用其他任何东西。 所以你的测试人员就得了解方法的具体实现,而这是不应该的……我如果有个可以滑开的房顶,我就得还给它上锁,可现在房顶是 null。

后面有几个人同意 earlNameLess 的看法:测试代码需要清楚的了解实现代码的内部实现方式实在不是什么好做法。建议根据具体情况提取出接口,或者用一下 Ioc 容器。

不过也有些人对 earlNameless 的看法表示反对,例如 Adam Tybor 说到:

为了写测试,不就是得知道实现方式么。这是 TDD,不是 TAD,不是么?如果房顶还需要加锁,那就让测试失败,然后改实现,直到所有测试变绿。

Christian Gruber 说:

上面有很多讨论都是针对 Misko 的测试代码不得不知道实现细节这个做法的。虽然,测试代码了解内部细节是有点不太稳定,但这是单元测试,单元测试是白盒 测试,不是黑盒。如果你用 mock,那你肯定得知道内部细节……再说的清楚一点,你是在测试基于给定的条件,你的代码能够得到预期的结果。你必须得知道这 些条件是啥,把那些不满足的条件排除掉。如果有人来改了实现细节,那测试就会失败。不过这也暴露出一个问题来,你必须得有一整套单元测试可以做回顾。如果我改了行为,我也就修改了测试的意图。然后我就得修改测试,让它对新的假设起作用。

所以这要看你是想测试行为还是输入输出,这已经是设计测试的哲学范畴了……

跑题跑了一阵子以后, rnaufal 回到原来话题上,直接抛出了 JDK 文档:

JDK 文档中有一篇的标题是“用断言编程”,链接在这里:“ http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html#usage”

里面写着:不要在 public 方法中用断言做变量检查。

读者朋友,你在自己的代码中使用防御式编程么?用过 assert 来检查 null 与否么?你的单元测试是在检查行为,还是检查输入输出呢?欢迎分享你们的意见。

2009-03-12 23:072952
用户头像

发布了 197 篇内容, 共 52.1 次阅读, 收获喜欢 20 次。

关注

评论

发布
暂无评论
发现更多内容

职场浅谈三则

姬翔

9月日更

这个 TCP 问题你得懂:Cannot assign requested address

AlwaysBeta

Linux TCP TCP/IP Linux内核 TCP协议

Kubernetes踩坑问题集

玏佾

Kubernetes k8s k8s文档

FontAwesome图标大全

入门小站

工具

网络攻防学习笔记 Day129

穿过生命散发芬芳

日志分析 9月日更

如何让项目准时上线?

石云升

项目管理 管理 引航计划 内容合集 9月日更

JS完美收官之——作用域

法医

9月日更

阿里P8整理出SQL笔记:收获不止SOL优化抓住SQL的本质,带你领略SQL的世界!

Java MySQL 架构 面试 架构师

linux之ssh命令

入门小站

Linux

【网络安全】记一次挖洞的日常

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

女科学家流失之殇

脑极体

网红郭老师遭全平台账号封禁,违背公序良俗的网红该被封杀

石头IT视角

如何修改 Discourse 的域名

HoneyMoose

去中心化身份务实

CECBC

架构学习模块二

George

我在 InfoQ 创作的思路规划

baiyutang

写作技巧 9月日更

Java设计模式如何优雅的使用本地缓存?

张音乐

Java 缓存 9月日更

谈 C++17 里的 FlyWeight 模式

hedzr

c++ 设计模式 Design Patterns 享元模式 flyweight

模块七课后作业

NewBranSTONE

#架构实战营

国家发改委:利用区块链等新技术开展绿色电力交易试点

CECBC

架构实战营 1 期模块 7 作业——业务异地多活架构

tt

架构实战营

13. AlphaGO带给人类的启示到底是什么

数据与智能

人工智能

Redis集群docker部署

非晓为骁

redis Docker redis集群

TLS协议分析 (七) 安全性分析

OpenIM

【布道API】关于 API 分页

devpoint

API REST API 9月日更

Navicat Premium 查询 x 列时不显示

玄兴梦影

MySQL navicat select

阿里内部流传的JDK源码剖析手册!GitHub已获上千万的访问量

Java 编程 架构 jdk 面试

【架构设计模块七】:王者荣耀商城异地多活架构设计

Ryoma

区块链赋能供应链金融风险管控探析

CECBC

推荐三个实用的 Go 开发工具

AlwaysBeta

Go 语言

模块七作业:王者荣耀商城异地多活架构设计

Felix

从“用断言检查null”到“设计单元测试”_Java_李剑_InfoQ精选文章