为什么MongoDB Java驱动程序在条件条件下使用随机数生成器?


211

我在此提交中看到了以下代码,用于MongoDB的Java Connection驱动程序乍一看似乎是在开玩笑。以下代码做什么?

if (!((_ok) ? true : (Math.random() > 0.1))) {
    return res;
}

(编辑:发布此问题以来,代码已更新


13
它的哪一部分让您感到困惑?
奥利弗·查尔斯沃思

4
我认为这令人困惑。此代码在catch块中执行!
2013年

11
@MarkoTopolnik:是吗?它可以写得更清晰if (!ok || Math.random() < 0.1)(或类似)。
奥利弗·查尔斯沃思

5
github.com/mongodb/mongo-java-driver/commit/…您不是第一位,请参阅对该行的评论
msangel

3
@msangel那些家伙似乎在批评逻辑,而不是编码风格。
Marko Topolnik

Answers:


279

在检查了这条线的历史之后,我的主要结论是工作中有些不称职的编程。

  1. 那条线是令人费解的。一般形式

    a? true : b

    for boolean a, b等同于简单

    a || b
  2. 周围的否定和过多的括号进一步使事情复杂化。牢记De Morgan的定律,这是一个琐碎的观察,这段代码相当于

    if (!_ok && Math.random() <= 0.1)
      return res;
  3. 最初引入此逻辑的提交有

    if (_ok == true) {
      _logger.log( Level.WARNING , "Server seen down: " + _addr, e );
    } else if (Math.random() < 0.1) {
      _logger.log( Level.WARNING , "Server seen down: " + _addr );
    }

    —不称职的编码的另一个示例,但请注意逻辑相反:如果一个事件_ok或其他情况的10%,则记录该事件,而2.中的代码将返回 10%的时间,并记录90%的时间。因此,以后的提交不仅破坏了清晰度,而且破坏了正确性本身。

    我认为,在您发布的代码中,我们实际上可以看到作者是如何将if-then字面意义从字面上转换为早期return条件所必需的。但是后来他搞砸了,并通过扭转不平等现象插入了有效的“双重否定”。

  4. 除了编码风格方面的问题,随机记录本身就是一个相当可疑的做法,尤其是因为日志条目没有记录其自身的特殊行为。显然,这样做的目的是减少相同事实的重述:服务器当前已关闭。合适的解决方案是仅记录服务器状态的变化,而不记录每个服务器状态的变化,更不用说随机选择10%的这种状态了。是的,这只需要一点点的工作,所以让我们来看一些。

我只希望仅通过检查三行代码而积累的所有这些不称职的证据,并不能完全代表整个项目,并且希望这项工作能够尽快得到清理。


26
此外,这似乎是,据我所知,MongoDB的官方10gen的Java驱动程序,以便除了具有对Java驱动程序的意见,我觉得它给我上的MongoDB的代码意见
克里斯·特拉弗斯

5
对仅几行代码的出色分析,我可能会将其变成面试问题!您的第四点是真正的关键,为什么这个项目存在根本上的问题(其他人可能会因为不幸的程序员错误而被驳回)。
亚伯(Abel)2013年

1
@ChrisTravers这 mongo的官方mongo java驱动程序。
assylias 2013年

17

https://github.com/mongodb/mongo-java-driver/commit/d51b3648a8e1bf1a7b7886b7ceb343064c9e2225#commitcomment-3315694

gareth-rees 11小时前:

大概的想法是仅记录大约1/10的服务器故障(因此避免大量发送日志),而不会产生维护计数器或计时器的成本。(但是确定维护计时器是否可以负担得起?)


13
不是nitpick,而是:1/10的时间将返回res,因此它将记录其他9/10的时间。
愚蠢的

23
@Supericy绝对不是挑剔。这只是该人糟糕的编码实践的更多证据。
阿诺罗夫

7

添加一个初始化为否定的类成员1:

  private int logit = -1;

在try块中,进行测试:

 if( !ok && (logit = (logit + 1 ) % 10)  == 0 ) { //log error

这始终记录第一个错误,然后记录随后的第十个错误。逻辑运算符“短路”,因此logit仅在发生实际错误时才递增。

如果你想的第一和第十所有错误错误,无论连接如何,请使logit类为静态对象,而不是aa成员。

如前所述,这应该是线程安全的:

private synchronized int getLogit() {
   return (logit = (logit + 1 ) % 10);
}

在try块中,进行测试:

 if( !ok && getLogit() == 0 ) { //log error

注意:我认为抛出90%的错误不是一个好主意。


1

我以前见过这种东西。

有一段代码可以回答来自另一个“黑匣子”代码的某些“问题”。万一它不能回答他们,它将把他们转发到另一个非常慢的“黑匣子”代码。

因此,有时会出现以前看不见的新“问题”,并且它们会成批出现,例如连续出现100个。

程序员对程序的运行方式感到满意,但他希望将来可能会发现一些新问题,以某种方式改进软件。

因此,解决方案是记录未知问题,但事实证明,有1000个不同的问题。日志太大,由于没有明显的答案,因此无法加快日志的速度。但是每隔一段时间,就会出现一批可以回答的问题。

由于日志变得太大,并且日志记录妨碍了他获得此解决方案真正重要的东西:

仅随机记录5%,这将清除日志,但从长远来看仍会显示可以添加哪些问题/答案。

因此,如果发生未知事件,则在这些情况的随机情况下,将记录该事件。

我认为这与您在这里看到的类似。

我不喜欢这种工作方式,因此我删除了这段代码,只是将这些消息记录到另一个文件中,因此它们都存在,但不破坏常规日志文件。


3
除了我们在这里讨论数据库驱动程序外……问题空间错了,IMO!
Steven Schlansker

@StevenSchlansker我从未说过这是一个好习惯。我删除了这段代码,并将这些消息记录到另一个文件中。
Jens Timmerman 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.