项目49:检查参数的有效性

ITEM 49: CHECK PARAMETERS FOR VALIDITY
  大多数方法和构造函数对传入参数的值有一些限制。例如,索引值必须是非负的,对象引用必须是非空的,这种情况并不少见。您应该清楚地记录所有这些限制,并在方法体的开头进行检查。这是一般原则的一个特例,即您应该尝试在错误发生后尽快检测它们。如果不这样做,就不太可能检测到错误,而且一旦检测到错误,就很难确定错误的来源。
  如果一个无效的参数值被传递给一个方法,并且该方法在执行之前检查它的参数,那么它将快速而干净地失败,并出现一个适当的异常。如果方法未能检查其参数,可能会发生以下几件事情。该方法可能会在处理过程中出现令人困惑的异常而失败。更糟糕的是,该方法可能会正常返回,但会默默地计算错误的结果。最糟糕的是,该方法可能会正常返回,但会使某个对象处于折衷状态,从而在将来某个不确定的时间在代码中某个不相关的点上导致错误。换句话说,如果未能验证参数,则可能导致破坏故障原子性(item 76)。
  对于 public 和 protected 方法,使用 Javadoc @throw 标记来记录如果违反了对参数值的限制将会引发的异常(item 74)。通常,生成的异常是 IllegalArgumentException、IndexOutOfBoundsException 或 NullPointerException (item 72)。一旦您记录了方法参数上的限制,并且记录了如果违反这些限制将引发的异常,那么执行这些限制就很简单了。这里有一个典型的例子:

/**
* Returns a BigInteger whose value is (this mod m). This method
* differs from the remainder method in that it always returns a
* non-negative BigInteger. *
* @param m the modulus, which must be positive
* @return this mod m
* @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {
    if (m.signum() <= 0)
        throw new ArithmeticException("Modulus <= 0: " + m);
    ... // Do the computation 
}

  请注意,doc 注释并没有说 “mod在m为null时抛出NullPointerException”,尽管该方法确实是这样做的,这是调用 m.signum() 的副产品。这个异常记录在类级别的 BigInteger 类的 doc 注释中。类级注释适用于类的所有公共方法中的所有参数。这是避免在每个方法上分别记录每个NullPointerException 的混乱的好方法。它可以与 @Nullable 或类似的注释结合使用,以指示某个特定参数可能为 null,但这种做法并不标准,为此使用了多个注释。
  在Java 7中添加的Objects.requireNonNull 方法非常灵活方便,因此没有必要再手动执行null 检查。如果愿意,您可以指定自己的异常详细信息。该方法返回它的输入,所以你可以执行一个空检查,同时你使用一个值:

// Inline use of Java's null-checking facility
this.strategy = Objects.requireNonNull(strategy, "strategy");

  您还可以忽略返回值并使用对象。 Objects.requireNonNull 是一个独立的null检查,满足您的需要。
  在 Java 9 中,一个范围检查功能被添加到 Java.util.objects 中。这个工具由三个方法组成:checkFromIndexSize、checkFromToIndex 和 checkIndex。这个工具不如空检查方法灵活。它不允许您指定自己的异常详细信息,而且它只用于列表和数组索引。它不处理封闭范围(其中包含它们的两个端点)。但如果它能满足你的需要,那就很方便了。
  对于未导出的方法,作为包的作者,您可以控制调用该方法的环境,因此您可以并且应该确保只传递有效的参数值。因此,非公共方法可以使用断言检查它们的参数,如下所示:

// Private helper function for a recursive sort 
private static void sort(long a[], int offset, int length) {
    assert a != null;
    assert offset >= 0 && offset <= a.length;
    assert length >= 0 && length <= a.length - offset;
    ... // Do the computation 
}

  本质上,这些断言是断言的条件将为真,而不管其客户端如何使用封装的包。与常规的有效性检查不同,如果断言失败,则会抛出 AssertionError。与普通的有效性检查不同,它们没有效果,而且基本上没有成本,除非您启用它们,这是通过向 java 命令传递 -ea(或 -enableassertion)标志来实现的。有关断言的更多信息,请参阅教程 [Asserts]。
  特别重要的是,要检查方法没有使用但存储起来供以后使用的参数的有效性。例如,考虑第101页中的静态工厂方法,它接受 int array 并返回该数组的列表视图。如果客户机传递 null,该方法将抛出 NullPointerException,因为该方法有一个显式的检查(调用 object.requirenonnull)。如果忽略了检查,该方法将返回对新创建的 Listinstance 的引用,该引用将在客户端试图使用它时抛出 NullPointerException。到那时,列表实例的来源可能很难确定,这可能会使调试任务变得非常复杂。
  构造函数代表了一种特殊的情况,即您应该检查要存储起来供以后使用的参数的有效性。检查构造函数参数的有效性对于防止构造违反其类不变量的对象是至关重要的。
  在执行方法的计算之前,应该显式地检查方法的参数,这条规则也有例外。一个重要的例外是有效性检查是昂贵的或不切实际的,并且检查是在执行计算的过程中隐式执行的。例如,考虑一个对对象列表进行排序的方法,例如 Collections.sort(list)。
  列表中的所有对象必须是相互可比的。在对列表排序的过程中,列表中的每个对象都将与列表中的其他对象进行比较。如果对象不是相互可比的,其中一个比较将抛出 ClassCastException,这正是 sort 方法应该做的。因此,事先检查列表中的元素是否具有可比性是没有意义的。但是,请注意,不加选择地依赖隐式有效性检查可能会导致失败的原子性损失(item 76)。
  有时,计算隐式执行所需的有效性检查,但如果检查失败则抛出错误的异常。换句话说,由于无效的参数值而导致计算自然抛出的异常与方法记录为抛出的异常不匹配。在这种情况下,您应该使用 item 73 中描述的异常翻译习语来将自然异常转换为正确的异常。
  不要从这一项推断对参数的任意限制是一件好事。相反,你应该把方法设计得既通用又实用。对参数的限制越少越好,假设该方法可以对它所接受的所有参数值进行合理的处理。然而,通常一些限制是实现抽象的固有的。
  总而言之,每次编写方法或构造函数时,都应该考虑其参数存在哪些限制。您应该记录这些限制,并在方法主体的开头使用显式检查来强制执行它们。养成这样做的习惯很重要。它所包含的少量工作将在有效性检查第一次失败时连本带利地偿还。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容