MethodReturnObject:Antipattern?怎么重构呢?

问题描述 投票:0回答:1

在我最近加入的项目中,我们有一个如下所示的类:

class MethodReturn {
  public int    status    = C_OK
  public string message   = ""
  public string str_value = ""
  public int    int_value = 0
  public double dbl_value = 0
  // and some more

  // also some methods that set/get these values
}

此类几乎在任何地方都用作返回类型。所以方法签名看起来像这样:

public MethodReturn doSomething( int input, int output )

调用代码如下所示:

returnObj = doSomething( input, output )
if( returnObj.status != C_OK ) return returnObj

returnObj2 = doSomethingElse( input2, output2)
if( returnObj2.status != C_OK ) return returnObj2

MethodReturn对象声称是“天才”,但对我来说它看起来像一个反模式,因为它隐藏了代码的实际功能并为代码增加了很多开销,可以通过异常处理得更好。此外,我认为操纵参数是一种应该避免的副作用。

不幸的是,这些MethodReturn对象被赋予GUI以显示操作的结果或在发生错误时显示消息。

那么,这个“模式”是否有实际名称?它只是一种你可以忍受的代码气味,只要你保持一致吗?或者我应该尝试将该对象重构出项目以避免将来出现重大问题?可以命名哪些与此模式相关的具体问题?

PS:上面的代码是伪代码,我们实际上是用VB.NET编写的。

design-patterns anti-patterns
1个回答
2
投票

首先,我应该说这个问题主要是基于意见的,你不应该期望获得明确的答案。话虽如此,这是我的意见。

错误代码与异常

基于错误代码和基于异常的方法都不是完美的。这就是为什么即使在今天,一些高级语言仍然没有例外(请参阅Go以获得一个新的例子)。就个人而言,我倾向于同意Raymond Chen的文章"Cleaner, more elegant, and harder to recognize",如果对所有错误进行适当的错误处理至关重要,那么编写正确的错误代码处理比正确的基于异常的代码更容易。 ZeroMQ作者"Why should I have written ZeroMQ in C, not C++ (part I)"也有一篇类似论点的文章。 AFAIU两篇文章的主要观点是,当您使用基于错误代码的方法时,通常会更容易看到一段代码何时无法正确处理错误,因为错误处理代码的位置更好。有了例外,您通常无法看到某些方法调用是否可以引发异常,但是未处理它(不分析整个调用层次结构的代码)。实际上,这就是为什么“检查异常”被添加到Java的原因,但由于各种设计限制,它只取得了有限的成功。

但是,错误代码不是免费的,您支付的价格的一方是您通常应该编写更多代码。在许多业务应用程序中,100%正确的错误处理并不重要,使用异常通常更容易获得“足够好”的代码。这就是为什么许多业务应用程序使用基于异常的语言和框架编写的原因。

一般来说,这类似于垃圾收集器与手动内存管理决策。在某些情况下,您确实应该使用错误代码或手动内存管理,但对于大多数用例,异常和GC都足够好。

特殊代码

尽管我认为错误代码与异常是一个悬而未决的问题,并且应该根据特定的项目上下文和目标做出设计决策,但我真的不喜欢你在问题中展示的代码。我真的不喜欢的是有单个MethodReturn包含所有可能的返回类型。这是一件坏事,因为它隐藏了开发人员真正的方法返回类型。

如果我想在.Net中使用错误代码,我的第一个想法是以类似于标准Double.TryParse的方式使用“out参数”。显然,你可以使用一些比普通的Boolean更复杂的返回类型来返回有关错误的更多细节(例如错误代码+消息)。这种方法再次有利有弊。对我来说最大的问题是VB.NET不像C#那样明确支持out(只有ByRef是双面的)所以它也可能会让开发人员对实际发生的事情感到困惑。

我的第二种方法是使用类似于你所展示的通用包装类来封装所有与错误相关的东西,但不包括返回值。这样的事情(我会用C#,因为我对它更熟悉):

class MethodReturn<TValue> {
  private readonly int    _status;  
  private readonly string _errorMessage;
  private readonly TValue _successValue;

  private MethodReturn(int status, string errorMessage, TValue successValue) {
        _status = status;
        _errorMessage = errorMessage;
        _successValue = successValue;
  }

  public static MethodReturn<TValue> Success(TValue successValue)  {
      return new MethodReturn(C_OK, null, successValue);
  }

  public static MethodReturn<TValue> Failure(int status, string errorMessage)  {
      return new MethodReturn(status, errorMessage, default(TValue));
  }

  public int Status { get { return _status; } }
  public string ErrorMessage { get { return _errorMessage; } }
  public int SuccessValue { get { return _successValue; } }

  public bool IsSuccess {
      get {
          return _status == C_OK;
      }
  }
}

如果我想变得更迂腐,如果SuccessValueIsSuccess时访问false并且如果ErrorMessageIsSuccess时访问true,我可能也会提出异常,但这也可能成为许多误报的来源。

如果你想要起作用,你可能会注意到这个类实际上几乎是一个类似于MonadScala Try,这是EitherScala中定义的Haskell monad或来自Chessie F#library的类似Result的特例。所以你可以使用mapflatMap扩展这个类,并使用它们来做一些monad组合,但我怀疑在VB.NET语法中看起来很难看。

如果你只有几个不同的上下文和你想要在类型系统中编码的不同错误代码列表,那么从简单的Try切换到更通用的Either可能是有意义的,从而使错误处理代码更安全(参见this F#/Result article的例子)。

© www.soinside.com 2019 - 2024. All rights reserved.