检查是否为空?

9
在Dart中,我们通过构造函数简化了变量的初始化:
例如:
class Foo
{
    Bar _bar;

    Foo(this._bar);
}

乍一看这似乎非常方便。但是根据我的经验,在95%的情况下,您希望传递给构造函数的参数应该是非空的。

例如,在C#中我会写:

public class Foo
{
    private Bar bar;

    public Foo(Bar bar)
    {
         if (bar == null)
             throw new ArgumentNullException("bar");

         this.bar = bar;
    }
}

那么我的问题是,Dart中处理空参数的最佳实践是什么?考虑到我们已经有了一种语言特性基本上会阻止使用它。

5个回答

11

在Dart的源代码中,他们会抛出ArgumentError异常。大多数情况下,他们不会检查null,而是变量类型。

int codeUnitAt(int index) {
  if (index is !int) throw new ArgumentError(index);
  // ...

来源:dart/sdk/lib/_internal/lib/js_string.dart#L17

factory JSArray.fixed(int length)  {
  if ((length is !int) || (length < 0)) {
    throw new ArgumentError("Length must be a non-negative integer: $length");
  }
  // ...

来源:dart/sdk/lib/_internal/lib/js_array.dart#L25


1
有趣啊。虽然有点奇怪?长度除了int以外还可以是其他什么吗?不过我猜在静态和动态参数之间应该是一致的。 - ronag
@ronag 在未经检查的模式下可以实现。类型不受强制执行。 - MarioP
@MarioP:嗯,你确定吗?我的意思是类型在编译时就进行了检查。在什么情况下,使用类型进行编译会导致运行时出现错误的类型呢? - ronag
@ronag 类型“错误”只是警告,如果您忽略它,它仍然可以编译通过。不同之处在于,在检查模式下,类型错误会引发异常,而在未经检查的模式下,它会正常运行,直到遇到检查,如答案所示。或者尝试调用不存在的类方法或类似的东西。 - MarioP
@ronag - 在Dart中,您可以将类型声明视为断言语句,例如assert(length is int)。这些断言在生产模式下被关闭 - 在此模式下,类型可能完全错误,但程序仍将运行。请参阅有关可选类型的文章以获取更多背景信息:https://www.dartlang.org/articles/optional-types/。 - Greg Lowe
我们如何检查 null 呢?我期望如果元素不存在,它必须返回 null...有没有一种方法可以检查是否抛出了异常? - Pushan Gupta

4

这种初始化的方法只是为了免去手动分配参数的麻烦,检查和其他逻辑仍然需要编写代码。不过,我认为这仍然是一个有用的功能。

class Foo {

  var _bar;

  Foo(this._bar) {
    if(this._bar==null) throw new ArgumentError(_bar);
  }

}

哦,我没有考虑到那个。虽然我在检查赋值后的值方面遇到了困难。在常规函数中这样做会使任何异常保证失效。但是我猜在构造函数的情况下应该可以。我觉得这有点不直观。 - ronag
此外,我发现在检查本地变量值之后为无效参数抛出异常更加不直观。 - ronag
@ronag 这确实有点不寻常,但作为一个非常懒的人,我仍然喜欢它。对我来说,它按照我的意图工作 - 执行 f = new Foo(null); 会导致 f 为空,即使异常被捕获并忽略。 - MarioP
应该抛出 新的 ArgumentError(_bar); - Günter Zöchbauer

3

这取决于你的个人喜好。

最常见的代码与您展示的 C# 代码类似:

if (bar == null) throw new ArgumentError("arg is null");

它提供有用的错误信息,并防止以下代码执行不正常(例如在 null 上随意格式化硬盘)。
我会写成:
Foo(Bar bar) : _bar = bar {
  if (bar == null) throw ArgumentError(...);
}

因为相比其他选择,我发现它更易读,但如果你喜欢的话,你甚至可以写:

Foo(Bar bar) : this.bar = bar ?? throw ArgumentError(...);

使用 assert(bar != null) 也可以正常工作。它仅在启用断言时才捕获问题,但是如果只是为了保护自己(例如,在库的内部类中),那就足够了。对于面向公众的函数和类,我更喜欢使用 if-throw。


也许类似于 Guava Preconditions#checkNotNull 这样的工具会很有用(在 quiver 中?)。 - Alexandre Ardhuin
1
自从一段时间以来,<!-- language-all: lang-dart --> 对于语法高亮已经不再必要。 - Günter Zöchbauer
哦,谢谢你提供这个信息。写起来并不是一件轻松的事情 :) - lrn
如果我们有多个参数呢? - WitVault
检查每个参数应该不成问题。我只会在发现第一个问题时抛出异常,而不是尝试收集所有问题,所以代码基本相同。 - lrn

0
你是指“简化初始化”作为“语言特性阻碍空值检查”的一部分吗?我可能误解了你的问题。
无论如何,这是我的解决方案:
class About  {

  final String title;
  final String text;

  const About({    
    customTitle,
    customText,
  }) :
    title = customTitle ?? "",
    text = customText ?? "";
}

-1
你可以使用assert来进行检查。

第二章. Dart语言之旅

assert(text != null);

断言语句仅在检查模式下起作用,在生产模式下没有任何影响。因此,断言对开发很方便,但不会影响生产性能。如果您希望在生产中保持检查状态,可以像在C#中一样操作。
if (bar == null) {
  throw new ArgumentError('Bar must not be null');
}

异常 - Dart 技巧,第9集


1
是的,如果担心性能问题,可以使用断言。无论是使用异常还是断言都不影响问题的解决。 - ronag
我知道在Dart中可以使用异常。请仔细阅读问题,特别是“最佳实践”和“基本上不鼓励使用的语言特性”。 - ronag
我必须承认,我不理解你的问题。 - Günter Zöchbauer
我认为这与最佳实践无关。并非每个字段都是由构造函数参数设置的。您可以在字段上提供默认值,或在构造函数参数上提供默认值,或在构造函数中计算该值并将其分配给字段。 - Günter Zöchbauer
我还没有看到有关于这个特定例子的“最佳实践”指南。我会对重要的公共API使用运行时空值检查和异常,而对于其他方法只使用assert语句或者不做处理。我认为你不能想出一种适用于所有情况的规则。我曾经见过评论,建议在运行时检查类型通常是错误的做法。例如,if (foo is! String) throw new Boom(); - Greg Lowe
也有关于在语言中添加非空类型的讨论。这被推迟到1.0版本之后,正在建立一个ECMA标准委员会,可以讨论语言更改。 - Greg Lowe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接