C# 使用返回的错误消息确定是否存在错误

C# 使用返回的错误消息确定是否存在错误,c#,return-type,C#,Return Type,我最近和一个朋友讨论了返回值只具有单一含义的问题。在我以前的工作中,我们使用C++,并使用Type DeWiD WBOL,0是WFALE,1是WTURE。建筑师说我们还可以返回2,3,4。。。更多信息,我认为这是一个可怕的想法。如果我们期望wTRUE=1,wFALSE=0,wBOOL={wTRUE,wFALSE},那么应该避免返回任何其他内容。。。现在,来看看今天的C 我最近查看了一段代码,其中有一组函数确定是否存在错误并将字符串返回给用户: private bool IsTestReady(o

我最近和一个朋友讨论了返回值只具有单一含义的问题。在我以前的工作中,我们使用C++,并使用Type DeWiD WBOL,0是WFALE,1是WTURE。建筑师说我们还可以返回2,3,4。。。更多信息,我认为这是一个可怕的想法。如果我们期望wTRUE=1,wFALSE=0,wBOOL={wTRUE,wFALSE},那么应该避免返回任何其他内容。。。现在,来看看今天的C

我最近查看了一段代码,其中有一组函数确定是否存在错误并将字符串返回给用户:

private bool IsTestReady(out string errorMessage)
{
  bool isReady = true;
  errorMessage = string.Empty;
  if(FailureCondition1)
  {
    isReady = false;
    errorMessage = FailureMessage1;
  }
  else if(FailureCondition2)
  {
    isReady = false;
    errorMessage = FailureMessage2;
  }
  //... other conditions
  return isReady;
}
然后,要使用这些函数

private enum Tests
{ TestA, TestB, TestC }
private void UpdateUI()
{
  string error = string.Empty;
  bool isTestReady;
  switch(this.runningTest) // which test are we running (TestA, TestB, or TestC)
  {
    case Tests.TestA:
      isTestReady = IsTestAReady(error);
      break;
    case Tests.TestB:
      isTestReady = IsTestBReady(error);
      break;
    case Tests.TestC:
      isTestReady = IsTestCReady(error);
      break;
  }
  runTestButton.Enabled = isTestReady;
  runTestLabel.Text = error;
}
我想把它们分为两种方法:

private string GetTestAErrorMessage()
{
  //same as IsTestReady, but only returns the error string, no boolean stuffs
}

private bool IsTestAReady
{
  get{ return string.IsNullOrEmpty(GetTestAErrorMessage()); }
}
这是否违反了没有返回值意味着不止一件事的原则?例如,在本例中,如果有错误消息IsNullOrEmpty,则不存在错误。我认为这并没有违反这一原则;我的同事工作过。对我来说,这和这没什么不同:

class Person
{
  public int Height {get;}
  public bool IsTall() { return Height > 10; }
}

对这个问题有什么想法或建议吗?我认为out参数是最糟糕的解决方案。

我会使用异常来传递有关故障状态的信息,而不是依赖调用者来知道如何使用错误消息字段(即使它是私有的)。

我不太喜欢让null或空返回值指示没有错误。比您给出的更好的比较是:

class Person
{
    public int Height {get;}
    public bool IsBorn() { return Height > 0; }
}

在.NET中,通常使用您在原始方法中看到的“bool return with out parameter”模式(例如,请参见各种
TryParse
方法)。但是,如果您愿意,另一种解决方案是创建一个
TestReadyCheck
类,将布尔值和字符串作为属性。我在下面的课上也做了类似的事情,并且非常满意

public class RequestFilterResult
{
    public static readonly RequestFilterResult Allow = new RequestFilterResult(true, null);
    public static RequestFilterResult Deny(string reason) { return new RequestFilterResult(false, reason); }
    protected RequestFilterResult(bool allowRequest, string denialReason)
    {
        AllowRequest = allowRequest;
        DenialReason = denialReason;
    }

    public bool AllowRequest { get; private set; }
    public string DenialReason { get; private set; }
}
这允许以下用途:

public RequestFilterResult Filter(...)
{
    if (FailureCondition1) return RequestFilterResult.Deny(FailureMessage1);
    if (FailureCondition2) return RequestFilterResult.Deny(FailureMessage2);
    return RequestFilterResult.Allow();
}
它很简洁,同时强调失败结果会提供失败消息,而成功结果不会


另一方面,我觉得您的
switch
语句的结构像是一种代码味道。您可能想考虑如何利用多态性。可能会使每个测试都有自己的类,并带有
istestrady
方法?

返回值和错误消息在技术上不绑定在一起。您可以让一名开发人员在稍后的时间到来,并将一个新的失败条件添加到IsTestReady,并且该失败条件可能不会设置错误消息。或者,可能存在一条消息,但它并不确切地表示故障(例如,可能是警告或其他),因此可以设置error message参数,但返回值为true

异常在这种情况下也不会真正起作用,这正是StriplingWarrior在其评论中写道的原因——异常应该用于非正常操作状态,而非就绪测试是正常状态

一种解决方案可能是删除错误消息参数,并让IsTestReady函数返回一个类:

public class TestReadyResult {
    public bool IsReady { get; set; }
    public string Error { get; set; }
}

对于测试状态,只需检查一个属性-TestReadyResult.IsReady,如有必要,可以将Error属性用于非就绪状态。函数调用也没有额外的参数可管理。

如果它确实是异常状态,我同意。在这种情况下,它看起来更像是测试尚未准备好运行的预期情况。我同意,因为这不是异常情况,因此异常将不合适。“在.NET中,使用您看到的“bool return with out parameter”模式是一种常见做法”--真的是这样吗?我认为特里帕斯等是方便的方法,因为他们扔。如果替代方法(Parse)没有抛出,那么就没有理由使用TryParse——至少这是我的理解。非常感谢,我非常喜欢您在这里提出的公共静态方法预打包结果的Deny和Allow版本的想法。我也同意代码有味道——谢谢你的帮助@user652688:关于
TryParse
,有些情况下,您希望字符串有效,因此,如果未满足您的期望,您宁愿引发异常,但有些情况下,您希望字符串可能无效,而宁愿在一个操作中检查并执行解析(因为它基本上是相同的逻辑——没有必要重复两次)。通过返回bool,该方法非常适合
if(int.TryParse(…){}
结构。您的代码不使用此结构,但经过良好的重构,就可以了。(例如,您可以避免在成功时设置错误标签)…所以我想我的观点是,在失败是预期逻辑流的一部分的地方使用这种模式是一种常见的做法。另一方面,当抛出异常更有意义时,最好避免使用这种模式。谢谢,我想知道为什么我不喜欢创建这样的回报类型,但我确实同意这可能是最重要的适当的解决方案。顺便说一句,我没有添加关于Person类的最后一点,这对我来说也不一样。在这种情况下,高度的存在并不意味着天生的属性;但错误消息的存在确实意味着错误的存在。