Используйте внутренние исключения или создавайте производные типы базовых исключений

#c# #exception

Вопрос:

В тех случаях, когда есть метод, который вызывает множество других, и все они могут создавать различные исключения от ArgumentExceptions до EndOfStreamExceptions, было бы лучше выполнить перехват всех и обернуть его в свое собственное исключение, а затем установить внутреннее исключение или создать исключение базового класса и создать производные типы. Если выбрасывание производных типов противоречит рекомендациям по повторному использованию исключений BCL, где это возможно. Так, например

 public void A()
{
    if (someCondition)
    {
        throw new ArgumentException()
    }
}

public void B()
{
    if (anotherCondition)
    {
        throw new InvalidOperation()
    }
}

public void C()
{
    // Throws format exception
    int.Parse(x);
}

public void DoSomething()
{
    try
    {
        A();
        B();
        C();
    }
    catch(Exception ex)
    {
        throw new DoSomethingException("Could not complete DoSomthing"){ InnerException = ex};
    }
}
 

ИЛИ было бы лучше сделать что-то вроде

 public void DoSomething()
{
    A();
    B();
    C();
    D();
}

public void A()
{
    if (someCondition)
    {
        throw new DoSomethingMissingArg()
    }
}

public void B()
{
    if (anotherCondition)
    {
        throw new DoSomethingCannotStart()
    }
}

public void C()
{
    // Throws format exception
    if (!int.TryParse(x))
    {
        throw new DoSomethingFormatException("x must be in y format");
    }
}

public class DoSomethingException: Exception
{

}

public class DoSomethingMissingArg: DoSomethingException
{
    public DoSomethingMissingArg(){ }
}

public class DoSomethingCannotStart: DoSomethingException
{
    public DoSomethingCannotStart(){ }
}

public class DoSomethingFormatException: DoSomethingException
{
    public DoSomethingFormatException(){ }
}
 

Ответ №1:

Это просто зависит от того, потребуется ли вашим абонентам такой уровень детализации при обнаружении ваших исключений.

Мое предложение состояло бы в том, чтобы сначала реализовать вариант 1, а затем, если требуется более подробная информация, переключиться на следующий вариант 3:

 public void A()
{
    if (someCondition)
    {
        throw new ArgumentException()
    }
}

...    

public void DoSomething()
{
    try
    {
        A();
    }
    catch(ArgumentException ex)
    {
        throw new DoSomethingMissingArgException() { InnerException = ex };
    }

    try
    {
        B();
        C();
    }
    catch(Exception ex)
    {
        throw new DoSomethingException("Could not complete DoSomthing"){ InnerException = ex};
    }
}
 

Вариант 3 имеет следующие преимущества:

  • Это обратно совместимо с вариантом 1, так как исключение DoSomethingMissingArgException является исключением DoSomethingException. И вы можете добавлять дополнительные подклассы исключений по мере необходимости, сохраняя при этом обратную совместимость.
  • У вас есть логика исключений, зависящая от конкретного способа, только в методе doSomething, а не разбросанная вокруг ваших вспомогательных методов, которые могут использовать «естественные» исполнительные функции BCL.