【问题标题】:Best way to return status flag and message from a method in Java从 Java 中的方法返回状态标志和消息的最佳方法
【发布时间】:2008-12-10 14:32:31
【问题描述】:

我有一个看似简单的场景,我想要一个简单的解决方案,但不清楚哪个是“最正确”或“最 Java”。

假设我在某个类中有一个小的身份验证(客户端客户端)方法。身份验证可能由于多种原因而失败,我想为控制流返回一个简单的布尔值,但也为用户返回一个字符串消息。这些是我能想到的可能性:

  • 返回一个布尔值,并传入一个 StringBuilder 来收集消息。这是最接近 C 风格的方式。
  • 抛出异常而不是返回 false,并包含消息。我不喜欢这样,因为失败也不例外。
  • 使用布尔值和字符串创建一个名为 AuthenticationStatus 的新类。对于一种小方法来说,这似乎有点过头了。
  • 将消息存储在成员变量中。这会引入一个潜在的竞争条件,我不喜欢它暗示一些实际上并不存在的状态。

还有其他建议吗?

编辑错过了这个选项关闭

  • 返回 null 表示成功 - 这不安全吗?

编辑解决办法:

我选择了最面向对象的解决方案并创建了一个小的 AuthenticationResult 类。我不会用任何其他语言来做这件事,但我喜欢用 Java 做。我也喜欢这个建议 返回 String[] 因为它类似于 null 返回但更安全。 Result 类的一个优点是,如果需要,您可以收到包含更多详细信息的成功消息。

【问题讨论】:

  • 你能解释一下为什么它比返回哨兵值更OO吗?我正在经历同样的情况并使用您的解决方案。但我不知道为什么这个解决方案比其他解决方案更好。
  • @Rumel,我认为其他答案和 cmets 很好地解释了这一点,但简而言之,哨兵值是危险的,因为它滥用了类型系统中的漏洞(您可以为该类型返回的值在任何其他情况下您都不会期望)。使用 null 特别危险,因为缺少 null 检查很容易且很常见,并且可能导致错误。它更加面向对象,因为它使用了一个专门定义类型的类,该类型表示“身份验证结果”或“带有可选消息的状态”,因此不会以牺牲更重量级的类型为代价进行类型滥用。

标签: java exception return-value


【解决方案1】:

返回一个带有布尔标志和内部字符串的小对象可能是最类似于 OO 的方式,尽管我同意这样的简单情况似乎有点过头了。

另一种选择是始终返回一个字符串,并让 null(或空字符串 - 您选择哪个)表示成功。只要在 javadocs 中清楚地解释了返回值,就不应该有任何混淆。

【讨论】:

  • 返回空字符串或 null 表示失败称为返回“哨兵值”。两者都不是特别安全,因为您实际上是在击败类型系统。
  • 伙计们——这里的问题是返回值经常而且很容易被忽略。因此,使用身份验证之类的方法,异常路由更安全。
  • Null 是个坏主意,因为在某些时候你不会期待它。
  • 如果您需要调用者手动检查返回值以确保您的身份验证系统的完整性,则使用哨兵是一个特别糟糕的主意。当用户的凭据无法通过身份验证时,应用程序逻辑应该完全脱轨,无法继续前进。句号。
  • 我强烈认为从方法返回 null 是一种糟糕的做法,因为我厌倦了处理遗留代码中的蹩脚 NullPointerExceptions。请考虑以下几点:c2.com/cgi/wiki?NoNullBeyondMethodScope
【解决方案2】:

你可以使用异常......

try {
    AuthenticateMethod();
} catch (AuthenticateError ae) {         
    // Display ae.getMessage() to user..
    System.out.println(ae.getMessage());
    //ae.printStackTrace();    
}

然后如果您的 AuthenticateMethod 发生错误,您将发送一个新的 AuthenticateError(扩展异常)

【讨论】:

  • 我同意这一点。身份验证失败并不是您希望调用者能够轻松忽略的错误。
  • 我也同意。也许这不是应用程序的例外情况,但它肯定是此连接“子系统”的例外情况。这就是为什么我建议不仅使用异常抛出,还要为不同类型的失败制作不同的异常。
  • 永远不要使用异常来实现业务逻辑。
  • @Peter... 您正在寻找的格言是“永远不要使用异常来实现流控制”。这不是流量控制。用户提供了期望登录的凭据,但凭据无法进行身份验证。为什么?也许不存在这样的用户名。可能密码已过期。谁知道?
  • 身份验证将提供的凭据与用户匹配。有两个(一般)预期结果:找到匹配项和未找到匹配项。当任何一种情况发生时,身份验证业务逻辑应该向应用程序的其余部分发出信号。认证无法“成功”的已知方式应使用正常的控制流程进行处理;未知方式可以通过异常处理(例如无法连接到底层子系统)。
【解决方案3】:

避免返回“哨兵值”,尤其是 null。您最终将得到一个代码库,其中调用者在不阅读实现的情况下无法理解方法。在 null 的情况下,如果调用者忘记(或不知道)您的方法可能返回 null,则调用者可能会以 NullPointerExceptions 结束。

如果我想从一个方法返回多个值,Bas Leijdekkers 的元组建议是一个很好的建议。我们使用的是来自Functional Java 库的P2<A, B>。这种类型是其他两种类型的联合联合(它包含每种类型的一个值)。

为控制流抛出异常有点代码味道,但检查异常是从方法中获取多种类型值的一种方式。不过,还有其他更清洁的可能性。

  1. 您可以拥有一个带有两个子类Some<T>None<T>Option<T> 抽象类。这有点像 null 的类型安全替代方案,也是实现部分函数(未为某些参数定义返回值的函数)的好方法。 Functional Java 库有一个功能齐全的 Option 类,它实现了 Iterable<T>,所以你可以这样做:

    public Option<String> authenticate(String arg) {
       if (success(arg))
          return Option.some("Just an example");
       else
          return Option.none();
    }
    
    ...
    
    for(String s : authenticate(secret)) {
       privilegedMethod();
    }
    
  2. 或者,您可以使用两种类型的不相交并集,作为Either&lt;L, R&gt; 类。它包含一个类型为LR 的值。这个类为LR 实现了Iterable&lt;T&gt;,所以你可以这样做:

    public Either<Fail, String> authenticate(String arg) {
       if (success(arg))
          return Either.right("Just an example");
       else
          return Either.left(Fail.authenticationFailure());
    }
    
    ...
    
    Either<Fail, String> auth = authenticate(secret);
    for(String s : auth.rightProjection()) {
       privilegedMethod();
    }
    for(Fail f : auth.leftProjection()) {
       System.out.println("FAIL");
    }
    

所有这些类,P2OptionEither 在各种情况下都很有用。

【讨论】:

  • Java8 有 Optional,它也提供了 (1.) 中描述的那种功能。
【解决方案4】:

更多选择:

  • 为每种类型的故障返回一个单独的枚举值。枚举对象可以包含消息
  • 返回一个 int 并有一个单独的方法从数组中查找适当的消息
  • 创建一个可以包含两个值的通用实用程序元组类。这样的类可以在更多的地方有用。

简单的元组示例,实际实现可能需要更多:

class Tuple<L, R> {

    public final L left;
    public final R right;

    public Tuple( L left, R right) {
        this.left = left;
        this.right = right;
    }
}

【讨论】:

  • 我同意 PairTuples 是非常有用的类,它们消除了创建所有那些杂乱无章的 DTO 的需要。我会覆盖 equals、hashcode 和 toString,它可以在任何地方使用,在集合等中。
  • 1) 枚举 + 解码器函数
【解决方案5】:

您可以返回错误消息集合,为空表示没有问题。这是对您的第三个建议的改进。

【讨论】:

  • 我实际上在应用程序的其他地方使用了它,但这里永远不会有 >1 条消息。但是,空数组可能是一个很好的成功指标。
  • 每当你想到“成功指标”时,将其改为“失败指标”并使用异常;-)
【解决方案6】:

我个人认为使用布尔值和字符串创建一个名为 AuthenticationStatus 的新类是最类似于 Java 的方式。虽然这看起来有点矫枉过正(很可能是这样),但它对我来说似乎更干净,更容易理解。

【讨论】:

  • 四行类定义对我来说似乎不是一个小价值:-)
  • 当你不断地重新制作同一个文件时,它变得有点矫枉过正,只是每个应用程序略有不同,而 Y 开发人员可能选择不在其他应用程序中使用它。此外,它会在某些 EAR 构建环境中造成更多的膨胀,在这些环境中您可能有重复但不相等的 POJO。
【解决方案7】:

仅仅因为失败的身份验证很常见并不意味着它不是例外。

在我看来,身份验证失败是检查异常的poster-child用例。 (嗯......也许文件不存在是典型的用例,但身份验证失败是关闭的#2。)

【讨论】:

  • 有一个例外情况。他们不作曲。例如,如果您想对整个用户列表进行身份验证并收集失败信息,则必须捕获异常会使这变得不必要地困难。
  • 有趣。同时验证多个用户的用例是什么?
  • 即使有一个用例,批量处理身份验证意味着它们应该全部成功或失败,作为一个批处理(在这种情况下,异常仍然是最好的选择)。否则,我认为您并没有真正在批处理模式下运行,每个身份验证都应该有自己的 try/catch。
  • 这可能是一个测试,或者您可能正在代表多个用户执行后台作业。该原则适用于任意数量的用例,其中许多可能不涉及列表。您可能希望传递一个授权模块,该模块对经过身份验证和未经身份验证的用户执行不同的操作。
【解决方案8】:

我自己使用“小类”,通常使用内部类。我不喜欢使用参数来收集消息。

另外,如果可能失败的方法是“低级别”——比如来自应用服务器或数据库层,我更愿意返回一个带有返回状态的 Enum,然后将其转换为字符串图形用户界面级别。如果您打算将代码国际化,请不要在低级别传递用户字符串,因为这样您的应用服务器一次只能以一种语言响应,而不是让不同的客户端以不同的语言工作。

【讨论】:

  • 关于消息的公平点,但在这里并不重要。我应该明确表示这是一条系统级消息,仅用于调试/记录目的。它不是真正的用户可见。
【解决方案9】:

这是您有这种要求的唯一方法吗?如果没有,只需生成一个带有 isSuccessful 标志和消息字符串的通用 Response 类,并在任何地方使用它。

或者您可以让方法返回 null 以显示成功(不漂亮,并且不允许返回成功和消息)。

【讨论】:

    【解决方案10】:

    我很可能会选择类似的东西:

    
    class SomeClass {
    public int authenticate (Client client) {
    //returns 0 if success otherwise one value per possible failure
    }
    public String getAuthenticationResultMessage (int authenticateResult) {}
    //returns message associated to authenticateResult
    }
    

    通过这种“设计”,您可以仅在身份验证失败时询问消息(我希望这是 99.99% 的时间发生的场景;))

    将消息解析委托给另一个类也可能是一种很好的做法。但这取决于您的应用程序需求(主要是,它需要 i18n 吗?)

    【讨论】:

    • 这类似于枚举解决方案,但带有整数。枚举会更像 java-y
    【解决方案11】:

    这似乎是其他编程语言中的一个常见习语,但我无法弄清楚是哪一个(我在问题中读到的 C 我猜)。

    herehere 发布了几乎相同的问题

    试图从一个函数返回两个值,可能会产生误导。但正如这样做的尝试所证明的那样,它也可能非常有用。

    如果这是之前发布的应用程序中的常见流程,那么绝对创建带有结果的小类应该是正确的方法。

    这是关于从函数返回两个值的引用:

    作为编程风格的问题,这个想法不是 在面向对象的编程语言中很有吸引力。 返回对象表示计算结果 是返回多个值的习语。一些 建议您不必声明类 对于不相关的值,但也不应该不相关 值从单个方法返回。

    我在 java 的功能请求中发现它允许 multiple return values

    查看日期为:2005-05-06 09:40:08 的“评估”部分

    【讨论】:

      【解决方案12】:

      身份验证成功应该是“正常”情况,因此身份验证失败是例外情况。

      无论如何,用户的不同状态字符串是什么。我只能看到两个,成功或失败。任何进一步的信息都是潜在的安全问题。 异常解决方案的另一个优点是不能以错误的方式调用,并且失败的情况更明显。没有例外,你写:

      if (authenticate()) {
        // normal behaviour...
      }
      else {
        // error case...
      }
      

      您可能会不小心调用忽略返回值的方法。然后在没有成功验证的情况下执行“正常行为”代码:

      authenticate();
      // normal behaviour...
      

      如果您使用异常,则不会发生这种情况。如果您决定不使用异常,请至少命名该方法,以便清楚地知道它返回一个状态,例如。 g.:

      if (isAuthenticated()) {
      //...
      }
      

      【讨论】:

        【解决方案13】:

        这里有很多很好的答案,所以我会尽量简短。

        我认为用户身份验证失败可以被认为是检查异常的有效案例。如果您的编程风格倾向于处理异常,那么没有理由不这样做。它还删除了“如何从一个方法返回多个值,我的方法只做一件事它对用户进行身份验证”

        如果您要返回多个值,则花 10 分钟创建一个通用的 PairTuple(也可以是多个 TripleTuple,我不会重复上面列出的示例)并以这种方式返回您的值。 我讨厌让小 dto 样式的对象返回各种多个值,它们只会把地方弄得一团糟。

        【讨论】:

          【解决方案14】:

          返回一个字符串怎么样。 Empty 或 Null 表示成功。失败时的错误消息。 最简单的方法。但不确定它是否读得好。

          【讨论】:

            【解决方案15】:

            返回对象。如果需要,它允许您将其他功能放入类中。 Java 中的短期对象可以快速创建和收集。

            【讨论】:

              【解决方案16】:

              我会首先选择例外选项。

              但是,其次,我更喜欢 C 风格的技术:

              public boolean authenticate(Client client, final StringBuilder sb) {
                  if (sb == null)
                      throw new IllegalArgumentException();
                  if (isOK()) {
                      sb.append("info message");
                      return true;
                  } else {
                      sb.append("error message");
                      return false;
                  }
              }
              

              这并不奇怪,它在框架的很多地方都做了。

              【讨论】:

                【解决方案17】:

                我通常只返回一个数组,而不是为返回类型创建一个特殊的对象,其中存储了所有返回的信息。好处是您可以使用新元素扩展此数组,而无需创建新类型和混乱。不利的一面是,您必须确切知道从特定方法返回数组时应该显示哪些元素才能正确解析它。通常我同意某些结构,比如第一个元素总是布尔指示成功,第二个是带有描述的字符串,其余的都是可选的。 示例:

                public static void main(String[] args)
                {
                    Object[] result = methodReturningStatus();
                    if(!(Boolean)result[0])
                        System.out.println("Method return: "+ result[1]);
                }
                
                static Object[] methodReturningStatus()
                {
                    Object[] result = new Object[2];
                
                    result[0] = false;
                    result[1] = "Error happened";
                
                    return result;
                }
                

                【讨论】:

                  猜你喜欢
                  • 2015-05-16
                  • 2011-04-05
                  • 2014-10-15
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-06-11
                  • 2013-03-30
                  • 2014-06-18
                  • 1970-01-01
                  相关资源
                  最近更新 更多