Prostsze rzucanie wyjątków, gdy nie mamy obsłużonego Enuma

Chcemy rzucić wyjątkiem, gdy mamy przypadek nieobsłużony poprzez case’y. Na przykład gdy pojawiła się nowa wartość enuma Shape.

public string GetDescription(Shape shape)
{
    switch (shape)
    {
        case Shape.Round:
            return "Round";
        case Shape.Square:
            return "Square";
        default:
            throw EnumGuard.CreateMissingEnumException(nameof(shape), shape);
    }
}

Zalety

  • dużo informacji do wyszukania w logach:
    • dostajemy nazwę parametru, który sprawił problem
    • dostajemy Typ, który wyszedł poza zakres
    • dostajemy wartość, która wyszła poza zakres
      (gdy jedyne co mamy to Exception z loga, bug się trudno reprodukuje, to każda dodatkowa informacja jest na wagę złota)
  • łatwa weryfikacja podczas CodeReview: ten oneliner jest zawsze taki sam, zmienia się tylko argument powtórzony w argument i nameof(argument).
  • w definicji nie ma żadnego ręcznie pisanego stringa

Alternatywy pisania „każdy po swojemu”

    throw new ArgumentException("Unhandled type", nameof(arguments.SomethingStrategy));
    throw new NotSupportedException($"Following configuration is not supported: {configuration}");

gdy poniższy kod zostanie przekopiowany to raz na jakiś czas ktoś zapomni że trzeba zmienić stringa, bo już inny typ sprawdzamy

    throw new InvalidOperationException($"Unknown something solution method: '{input.SomethingSolution}'");

czyli za każdym razem trochę inaczej.

Problem i rozwiązanie do rozważenia

Ktoś może zapomnieć wpisać throw i kod

EnumGuard.CreateMissingEnumException(nameof(shape), shape);

się skompiluje. Dlatego metoda ma atrybut [Pure], więc tylko jej wywołanie (brak throw) zwraca uwagę ReSharpera.

5 Comments on “Prostsze rzucanie wyjątków, gdy nie mamy obsłużonego Enuma

  1. Pingback: dotnetomaniak.pl

  2. „throw EnumGuard.CreateMissingEnumException” jest bardzo nieintuicyjne. Myślę, że dużo lepszym rozwiązaniem byłoby po prostu stworzenie własnego typu wyjątku dziedziczącego po np. ArgumentOutOfRangeException i wedle potrzeby przeciążenie np. property Message. Nie mówiąc już o tym, że klasa EnumGuard właściwie nie wiadomo czym jest i jaką ma odpowiedzialność. Potencjalnie jest jakimś kontenerem na wszystko co może dotyczyć enuma, czyli powszechnie znanym złem.

    • @janek
      Można stworzyć własny „class EnumOutOfRangeException : ArgumentOutOfRangeException”, nawet coś mi tam świta że kiedyś coś takiego miałem.
      Dlaczego teraz mam to uproszczone? (tldr KISS) 1. Ponieważ prosto jest wrzucić jedną klasę, a wrzucanie nowej klasy dla wyjątku rodzi dodatkowe pytania typu gdzie ją umieścić. 2. Nikt patrząc w logi nie powiedział że coś jest nieczytelne (zawsze jest wspomniane „Enum”.

    • @janek

      Co do nazwy „EnumGuard”: jest Guardem służącym do walidowania odpowiednich wartości w enumach. Nic więcej tu nie pasuje. Klasa „EnumHelper” byłaby kontenterem na wszystko.
      Jak byś nazwał „EnumGuard”?

  3. Pingback: Jaki wyjątek rzucać gdy mamy nieobsłużonego enuma | Show me the code