资讯专栏INFORMATION COLUMN

让 bug 越早暴露越好 (1)

habren / 424人阅读

摘要:典型的缓冲区越界注入等攻击手段,或者网络异常等。这假设实际上往往是错误的,绝大多数系统实际对用户信息的要求不仅仅是满足基本类型信息即可,而有显式或隐式的其他条件。当然对于这种可能出现的异常应该在程序的其他地方捕获并加以恰当的展示。

不管你的水平多高,多么认真仔细,程序员总是会制造出 bug。bug 的根本来源是:

预期之外的用户输入或系统输入。典型的缓冲区越界、SQL 注入等攻击手段,或者网络异常等。它的特点是在通常的用户输入时,程序表现得非常正常;但对于不普通、或者异常的用户输入没有做任何防御。

程序员的疏忽或理解错误。例如不正确地调用了参数,按照不正确地次序调用函数,错误的线程数据共享。

开发组件之外的系统其他部分,如中间件、数据库系统或其他组件本身的 bug,也会给我们本来完美的程序带来 bug。但这种 bug 我们可以称为是衍生错误,本质是上述两种 bug 之一。

所谓防御性编程(Defensive Programming) 一般针对第一类错误,它的原则是:只要有用户或系统输入,就应该对它的合规性进行严格的检查。例如:

javapublic String findUserName(String userId) {
  return db.userNameFrom(db, userId);
}

如果 nameInput 是直接来自于用户输入(不论是通过页面,还是 URL,还是应用程序),上述简单程序意味着:只要 userId 是一个字符串,它就是合法的!而我们就将它作为合法字符串在系统各处传递。这假设实际上往往是错误的,绝大多数系统实际对用户信息的要求不仅仅是满足基本类型信息即可,而有显式或隐式的其他条件。例如,userId 很可能有长度限制,例如长于 6 个,短于20个字符;它很可能有取值限制,例如只能是字符和数字。而我们的编译器,即使是 Java 的强类型编译器,对这种要求一无所知。是程序员的责任来检查这些额外要求:

java//一个特别的类 InputChecker 来检查输入
public void checkUserId(String userIdInput) {
  if (userIdInput.length < 6 || userIdInput.length > 20)
    throw new InvalidInputException("userId", userIdInput);
}

//需要和用户输入直接打交道的地方调用 inputChecker
public String findUserName(String userId) {
  inputChecker.checkUserId(userId);
  return db.userNameFrom(db, userId);
}

注意,由于异常输入是一种异常,用运行时异常来表达它是合理的。当然对于这种可能出现的异常应该在程序的其他地方捕获并加以恰当的展示。

这样的防御性编程代码是可靠设计的一个良好习惯,实际上它也广泛地被使用。但也有很多程序员将它错误地过多使用,或者错误地用它来覆盖第二类 bug (程序员错误)。

javapublic List transferGameResult(GameRoom room, List gameResult) {
 if (null == gameResult || gameResult.size() == 0) {
    return null;
 .....
}

这段程序的目的是将一种游戏结果转化为另一种表达式来方便计算。其中第一句语句的目的是?

看起来这似乎是防御性编程,想要检查 gameResult 的输入错误:如果 gameResult 不正确输入,就返回空值。

但这里的 gameResult 是用户输入吗?不可能是,它是一个 int[],只可能是来自于程序其他地方,也就是说来自于这段代码的调用者,另一个程序员。

用正确的参数来调用是程序员的责任,我们不能假装我们的程序可以正常处理 null 的参数或者长度为0 的输入,实际上对此我们没有处理能力。因此,这里真正防御性编程的处理是:

javapublic List transferGameResult(GameRoom room, List gameResult) {
   assert(gameResult != null && gameResult.size() > 0);
 .....
}

表明我们的这段程序对输入参数的严格要求。有人可能会说,assert 不是不应该出现在运行时的系统中吗?至少对于 Java 语言来说,assert 将立即抛出一个异常(如果你没有停用 assert 的话),调用程序员如果对引用这段代码的程序做了合理的测试,他会立即发现这个错误,就有了纠正它的机会。但按照之前的写法,调用程序员则很可能不能看到任何问题,而使用不正确的参数调用,本身就是一个错误!这样,我们成功地帮助程序员掩盖了他的错误,增加了将 bug 带到生产系统中的机会。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64292.html

相关文章

  • 如何准备校招技术面试

    摘要:网易跨境电商考拉海购在线笔试现场技术面面。如何看待校招面试招聘,对公司而言,是寻找劳动力对员工而言,是寻找未来的同事。 如何准备校招技术面试 标签 : 面试 [TOC] 2017 年互联网校招已近尾声,作为一个非 CS 专业的应届生,零 ACM 经验、零期刊论文发表,我通过自己的努力和准备,从找实习到校招一路运气不错,面试全部通过,谨以此文记录我的校招感悟。 写在前面 写作动机 ...

    MkkHou 评论0 收藏0
  • 程序员,软件测试知多少?

    摘要:单元测试过后,机器状态保持不变。单元测试应该产生可重复一致的结果。然并卵都说国内很多程序员是不写单元测试的,甚至从来都不写,笔者当年做的时候也没写过几次捂脸。回归测试在单元测试的基础上,我们就能够建立关于这一模块的回归测试。 showImg(https://segmentfault.com/img/bVPMPd?w=463&h=312); 送给初级程序员的测试认知文 作为开发同学,一些...

    libxd 评论0 收藏0
  • 大厂难进?(来自双非 非科班 应届生的自述信)

    摘要:关于自己届毕业生一本双非学校,非科班可能和很多人一样,因为小时候喜欢打游戏,所以大学一直想学编程,但因为种种原因,自己来到了一个硬件相关专业,但由于现实和兴趣,自己又从事了软件相关的工作。找实习实习对于之后的秋招来说,是非常非常重要的。 ...

    jerryloveemily 评论0 收藏1

发表评论

0条评论

最新活动
阅读需要支付1元查看
<