【问题标题】:Generate and handle user messages inside methods?在方法中生成和处理用户消息?
【发布时间】:2011-10-21 00:08:31
【问题描述】:

处理可能偶尔无法评估的函数的最佳方法是什么,但即使它确实失败也不需要停止父例程,尽管有时可能需要向用户解释?

我的代码深处有这样一个函数,它返回一个数字。在编码方面,最简单的方法是使函数可以为空,并在无法评估时返回 null。这样,调用例程可以继续,同时还知道函数未能返回数字。

但是,根据具体情况,有时我想向用户显示失败的原因,以便他们知道要修复什么。显然只返回 null 是不够的信息。我是否应该在函数本身内部引发消息,因为它会评估要捕获的匿名侦听器,以便在需要时显示?
(对于那些正确指出逻辑函数不应该负责创建用户消息的人,我并不是要暗示该函数生成全文消息,只是以某种方式传输问题的原因,以便 UI 可以稍后解析进入消息)

我想的另一个选项是在函数内部抛出异常,如果它无法评估,然后在需要时捕获并解释给用户消息。但是,如前所述,无法评估通常并不意味着例程的停止,现在我每次使用函数调用时都必须在函数调用周围放置一个 Try...Catch 块。

【问题讨论】:

  • 我觉得“TryX”模式可能是最好的解决方案。这样我就可以调用该函数而不必担心停止例程,并在需要时获取失败原因。扩展返回集以包含成功/失败信息的建议解决方案可能也可行,但我个人认为它不经常使用,并且可能会使处理返回更加麻烦。

标签: c# vb.net design-patterns .net-3.5


【解决方案1】:

不应将异常处理用于流控制。只在真正异常的情况下抛出异常。

...咳咳。是时候离开我的高马了。

说真的,我不知道您要解决的问题的性质。第一个问题是,您的算法中的失败是否真的失败,或者无法评估是否可以用 NaN 值或 0 表示?如果它确实是一个基本的概念故障,算法是否能够在继续之前检查输入?如果是这样,抛出 ArgumentException 或其派生类 - 最好是后者。这意味着任何其他消费代码都可以处理一般情况(例如,在 IoC 场景中),而您的代码可以处理特定情况,这是可以合理预期的。如果你这样做,我建议任何包含此功能的程序集也应该提供一些静态验证函数,允许调用者在调用可能引发异常的东西之前验证入站参数是否有效。

【讨论】:

  • 是的,我认为在直接调用函数之前进行验证检查可能会起作用,因为我可以避免停止异常。然后我可以让支票返回失败背后的原因,如果是这样的话。
  • 计算函数计算结果失败属于异常情况。但是,我同意在这种情况下,似乎可以通过之前验证用户输入来避免异常情况。
  • @jdmichal:但“失败”是什么意思? “函数未使用这些参数定义”和“尝试使用这些参数进行计算没有意义”之间存在概念上的差异。未定义的区域在数学上是可以接受的,并且可能适合返回 NaN。
  • @Tom W 失败意味着该函数无法生成有意义的值。我完全同意你所说的,只要你处理数学概念。但诚实的事实是,应用程序程序员很少处理已经定义的数学函数。取而代之的是,我们通常在进行时进行编造,并自己定义输入和输出空间。而且,在这种情况下,函数统一“失败”通常比在某些情况下异常和在其他情况下出现 NaN / null 更有帮助。
【解决方案2】:

您可以将out 参数“例如字符串”传递给函数,因此每当函数失败时,通过返回falsenull 将原因打印给用户。就像微软对TryParse.. 所做的那样,但在这里我们也得到了失败的原因:

public bool TrySomeFunction(out string errorMessage) 
{
    try
    {
        //code that may cause exception

        return true;//operation completed successfully
    }
    catch (Exception exception)
    {
        errorMessage = exception.Message;
    }

    return false; 
}

【讨论】:

  • 我认为这可能是最好的方法,因为它符合现有的 Try 模式,可以在没有停止异常的情况下调用函数,并且可以设计为返回有关失败原因的信息。
【解决方案3】:

如果您的调用代码应该知道如何处理 NULL 值,则返回 NULL 很好。在这种情况下,我将记录从函数内部返回 NULL(意外)值的时间。也许该功能可以通过消息框通知用户。我猜这真的取决于你如何设置一切。

编辑:

重读这篇文章后,我似乎错过了代码不在任何 UI 类中。与 cmets 一样,任何用户显示消息都应该留给 UI 层来呈现,而不是来自任何其他代码。因此,您需要将排序标志返回给 UI,以便它可以显示消息。如果这个实例不是函数破坏器,那么我会说异常链是不可能的。我想这让你不得不在每个函数调用的返回值中确定它(就像你在第一个实例中使用 NULL 返回一样) - 可能是用于返回值的自定义 DataType,带有“警告”标志或其他东西

【讨论】:

  • 最后一点我不推荐,“也许函数可以通过消息框通知用户。” 让计算函数显示用户消息框是失败的单独的关注点。函数应该只做一件事且只做一件事。
  • @jdmichal,如果函数在设计类之外,这当然不好。但这就是为什么我说这取决于设置。如果该函数位于某种逻辑类中(重新阅读时似乎是这种情况),那么您可能希望将某些东西一直传递回设计级别以指示警告(或其他)
【解决方案4】:

我不会抛出异常,除非在特殊情况下。抛出异常非常昂贵。将它们保存为非常糟糕的东西,例如提到的 missingrequiredfield jd。相反,我会返回一个对象,该对象具有成功标志、可为空的结果以及调用例程随后可以显示给用户的消息列表(或传递回下一层以显示给用户)

【讨论】:

    【解决方案5】:

    您可以使用通用结果类包装您需要的信息。

    public class MyClass
    {
        public static void Main()
        {
            var result = new MyClass().TrySomeFunction();
    
            if (result.Succeeded)
            {
                // use it
                MyReturningResultType resultValue = result.GetResultValue();
            }
            else
            {
                // use message
                string message = result.ResultMessage;
            }
    
        }
    
        Result<MyReturningResultType> TrySomeFunction()
        {
            try
            {
                MyReturningResultType value = CalculateIt();
                return new Result<MyReturningResultType>(value) { Succeeded = true };
            }
            catch (Exception exception)
            {
                return new Result<MyReturningResultType>(default(MyReturningResultType))
                {
                    Succeeded = false,
                    ResultMessage = exception.Message
                };
            }
        }
    }
    
    public class Result<T>
    {
        public Result(T resultValue) { this.ResultValue = resultValue; }
        public bool Succeeded { get; set; }
        public string ResultMessage { get; set; }
        public T GetResultValue()
        {
            if (ResultValue is T)
            {
                return (T)this.ResultValue;
            }
    
            return default(T);
        }
    
        private T ResultValue;
    }
    

    【讨论】:

      【解决方案6】:

      对我来说,这听起来真的是关于异常处理。

      您可以定义自己的异常类型,并且可以在满足(或不满足)一个条件时抛出一种异常,在另一种情况下抛出另一种异常。

      调用者会根据抛出的异常类型来决定做什么,比如通知用户或者将失败方法的结果设置为null并继续等等......

      【讨论】:

        【解决方案7】:

        为每种失败方法创建一个异常类型。根据这些异常类型创建用户消息。不要害怕将字段添加到您的异常类型中,这样您就可以完全重新创建有价值的消息。这是我在异常编程中经常看到的最大失败。

        例如,如果您有一个用户未填写的必填字段,则抛出一个具有MissingFieldName(s) 属性的MissingRequiredFieldException。这样,在堆栈中,您可以向用户输出关于缺少哪些字段的有意义的消息。

        这种方法最好的部分是它不依赖计算函数来生成用户可读的字符串。这将在更高的地方,在适当的地方完成。当你必须国际化时会发生什么?您是否要重构您的功能以根据用户在语言之间切换?听起来像是混合关注点的主要案例......处理用户输出代码的计算函数是什么?

        【讨论】:

        • 对不起,我不是说该函数会生成用户输出文本。我的意思只是该功能需要以某种方式给出失败背后的原因。我已经编辑了原始问题。
        • @Tekito 对不起。我也不是要暗示这就是你的意图。我只是简单地给出我的想法以及为什么我认为这是一个好的想法。
        【解决方案8】:

        我同意@Tom 的观点,即您不应该仅仅为了控制程序流而抛出异常,因为它们很昂贵。您可能会考虑的另一种方法是传递一个委托(这不是我的想法):

        public static void Main()
        {
            Action<string> errorTarget = delegate(string s) { Console.WriteLine(s); }; 
        
            SomeFunction(errorTarget);
        }
        
        private static void SomeFunction(Action<string> errorTarget)
        {
            ...
            // Send non-fatal errors to the errorTarget
            if (result == null)
            {
                // Build the error message, then call errorTarget
                errorTarget(errorMessage);
            }
            ...
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-10-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-21
          • 1970-01-01
          相关资源
          最近更新 更多