繁体   English   中英

从引发的异常中恢复或返回

[英]Recover or return from a thrown exception

多年以来,我的主要语言是Perl,而且我会定期验证用户输入的内容而不会出现问题。 现在,我正在使用大量的C#,并希望朝着验证用户输入并从引发的异常中恢复/返回的引发/捕获样式迁移。 我正在使用一种非常幼稚(即愚蠢)的方法来执行此操作,并且迫切需要转移到更成熟,更不愚蠢的东西上。 我复制了一个从提示返回整数的函数。 我正在使用可怕的GOTO语句从用户错误中恢复。 什么是更好的方法呢?

谢谢,CC。

private static int GetInput(string v)
{
    begin:
    Console.Write(v);
    string strradius = Console.ReadLine();
    int intradius;
    try
    {
        intradius = int.Parse(strradius);
        if (intradius < 1)
            throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentNullException)
    {
        Console.WriteLine("You must enter a value.");
        goto begin;
    }
    catch (FormatException)
    {
        Console.WriteLine("You must enter a valid number.");
        goto begin;
    }
    catch (ArgumentOutOfRangeException)
    {
        Console.WriteLine("Your number is out of range");
        goto begin;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
        goto begin;
    }
    finally
    {
        Console.WriteLine("Okay");
    }
    return intradius;
} 

首先,永远不会有何时使用goto的良好经验法则。 确实,除了少数极少数特殊情况外,您永远都不想使用它。

接下来,对于您的问题,使用异常来验证输入通常是一个坏主意。 正如大多数人指出的那样,这很昂贵。 应该使用异常来处理特殊情况,因此我实际上根本不会使用它们。

相反,您可以使用do-while循环,并在用户输入不正确的输入时重复执行。 一旦获得正确的输入,便退出循环。 如果发生异常,则不应真正继续该过程。 要么在外部处理它(即,方法内部没有try-catch ),否则,如果您必须进行try-catch则只需打印一条消息并退出该方法。 但是我不会对这种方法使用异常处理。 实际将返回类型更改为bool也是一个好主意,因此您可以通过返回类型向外界指示该方法是否成功。 您使用out参数实际返回转换后的int

private static bool GetInput(string msg, out int converted)
{
    bool result = false;
    converted = 0;
    do
    {
        Console.Write(msg);
        string str = Console.ReadLine();
        result = int.TryParse(str, out converted);
        if (result && converted < 1)
        {
            Console.WriteLine("Your number is out of range");
            result = false;
        }
        if (!result && string.IsNullOrEmpty(str))
        {
            Console.WriteLine("You must enter a value.");
        }
        if (!result && !string.IsNullOrEmpty(str))
        {
            Console.WriteLine("You must enter a valid number.");
        }
    } while (!result);

    return result;
}

在C#代码中使用goto语句是高度皱起了眉头,在因为它使代码难以阅读,调试和维护(有关更多信息,请阅读 )。 可以使用循环,if / then语句或方法调用代替goto语句。 另外,应谨慎使用try \\ catch块,以捕获无法处理的异常。

在您的情况下,我们可以使用while循环继续循环直到输入有效数字为止,并且可以使用int.TryParse方法尝试解析字符串并获取整数结果。 此方法返回一个指示成功的Boolean ,并采用将设置为整数结果的out参数。

我对您的方法的建议是让它接受一个字符串,该字符串将用作提示用户(要求他们输入数字),并返回其输入的整数结果。

例如:

private static int GetIntFromUser(string prompt, int minValue = int.MinValue, 
    int maxValue = int.MaxValue)
{           
    int result;
    string errorMsg = $"ERROR: Input must be a valid number from {minValue} to {maxValue}";

    while(true)
    {
        Console.Write(prompt);
        string input = Console.ReadLine();

        if (!int.TryParse(input, out result) || result < minValue || result > maxValue)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(errorMsg);
            Console.ResetColor();
        }
        else
        {
            break;
        }
    }

    return result;
}

实际上,我们现在可以调用此方法从用户那里获取数字,我们将知道它们是有效的,而无需进行任何其他验证:

private static void Main()
{
    // Age must be between 5 and 100
    var age = GetIntFromUser("Please enter your age: ", 5, 100);

    // Weight must be a positive number (minimum of zero)
    var weight = GetIntFromUser("Please enter your weight: ", 0);

    // No restrictions on favorite number
    var favNum = GetIntFromUser("Enter your favorite whole number: ");

    // This is a similar method I wrote to pause the program with a prompt
    GetKeyFromUser("\nDone! Press any key to exit...");
}

产量

![在此处输入图片描述

我会这样写(尽管我可能会给用户一个放弃的机会):

 private static int GetInput(string v)
 {
     int intradius = 0;   //needs to be initialized to keep compiler happy
     while (true)
     {
         Console.Write($"{v}: ");
         string strradius = Console.ReadLine();
         if (!int.TryParse(strradius, out intradius))
         {
             Console.WriteLine($"An integer is required: [{strradius}] is not an integer");
         }
         else if (intradius < 1)
         {
             Console.WriteLine($"The entered number [{intradius}] is out of range, it must be one or greater");
         }
         else
         {
             break;      //breaking out of the while loop, the input is good
         }
     }

     return intradius;
 }

对于可恢复的验证,请使用条件代码/检查,而不要依赖异常。 从性能的角度来看,异常的代价很高,主要是因为异常将生成调用堆栈。

相反,请看以下内容:

private static int GetInput(string v)
{
    Console.Write(v);
    string strradius = Console.ReadLine();

    if (string.IsNullOrEmpty(strradius)
    {
        Console.WriteLine("You must enter a value.");
        return 0;
    }

    int intradius;

    bool result = int.TryParse(strradius, out intradius);
    if (!result)
        Console.WriteLine("You must enter a valid number.");
    else if (intradius < 1)
        Console.WriteLine("Your number is out of range");

    Console.WriteLine("Okay");
    return intradius;
} 

就个人而言,我喜欢包装业务逻辑结果:

// Result container.
private class RadiusValidationResults
{
   public bool IsSuccessful {get; private set;}
   public int Radius {get; private set;}
   public string FailureReason {get; private set;}

   private RadiusValidationResults()
   { }

   public static RadiusValidationResults Success(int result)
   {
      return new RadiusValidationResults { IsSuccessful = true, Radius = result };
    }

    public static RadiusValidationResults Failure(string failureReason)
    {
      return new RadiusValidationResults { FailureReason = failureReason };
    }
}

// Validation business logic.
private static RadiusValidationResult ValidateRadius(string input)
{
    if (string.IsNullOrEmpty(input)
        return RadiusValidationResult.Failure("You must enter a value.");

    int radius;

    if (!int.TryParse(strradius, out radius))
        return RadiusValidationResult.Failure("You must enter a valid number.");
    else if (intradius < 1)
        return RadiusValidationResult.Failure("Your number is out of range");

    return RadiusValidationResult.Success(radius);   
}

然后您与控制台交互的调用方法:

private static int GetInput()
{
   try
   {
      var result = ValidateRadius(Console.ReadLine());
      if(!result.IsSuccessful)
         Console.WriteLine(result.FailureReason);
      else
         Console.WriteLine("Okay");

      return result.Radius;
   catch // Here you can handle specific exception types, or bubble to a higher level. Log exception details and either terminate or present users with a generic "Whoops" and let them retry the operation.
   {
       Console.WriteLine("An unexpected error occurred.")
   }
}

这意味着您可以对业务逻辑(验证)进行单元测试,而无需严格依赖数据源或输出。 (控制台)代码应简洁明了,易于理解正在执行的操作。 可以将Try / Catch添加到GetInput以处理特殊情况。 通常,让异常冒到足够高的水平来处理它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM