
В этой короткой заметке я поделюсь наблюдениями о проблемах концепции exception в языке C#, именно о тех, которые возникают от самого факта наличия такой ее реализации. И оставлю “за скобками" проблемы, которые появляются от ее неправильного использования. Ниже я перечислю и опишу их.
Непредсказуемость
Недостаток заключается в том, что при вызове метода по интерфейсу нельзя сказать, выбросит ли он exception-ы и каких типов они могут быть.
Рассмотрим код ниже:
public void Method(IMapper mapper) { ... mapper.Save(); ... }
Во-первых, автор такого кода вынужден принять тот факт, что из метода Save может вылететь абсолютно любой exception.
Во-вторых, если до и после кода "mapper.Save()" есть какая-то логика, то мы не знаем, будет ли она выполнена вся, потому что может случиться исключение.
Не наглядность
Даже когда вы имеете дело с определенным методом, из класса без виртуальных методов, единственный способ узнать, какие exception могут быть выброшены из вызываемого метода -- полностью исследовать его исходный код вплоть до самого мелкого метода, которые вызываются внутри него.
Но это не решает задачу полностью, т.к. при любом обновлении библиотек работу по исследованию метода придется проделывать заново, что в целом невозможно в реальных приложениях.
Хорошо, что современные IDE умеют подкачивать исходники или даже декомпилировать код, если не удалось найти исходник. Как люди раньше жили, не очень понятно.
Runtime
Выше описанные проблемы приводят нас к ситуации, в которой мы вынуждены на самом верхнем уровне приложения ловить все exception-ы, записывать их в лог и затем уже на этапе эксплуатации из лога вычитывать, что же там случилось и решать, как не допустить исключения в будущем.
Выводы
К сожалению, концепция Exception сидит глубоко в самом языке C#, и еще глубже и обширнее она проникла в сам фреймворк. Остается только смириться с тем, что не предсказуемы exception будут возникать на production-серверах в наших приложениях.
Я бы рекомендовал как минимум избегать в своих библиотеках использование этой концепции в пользу функционального стиля, когда все возможные варианты возвращаемых значений описываются через discriminant union. В C# его аналогом, для этой задачи, можно считать паттерн OneOf. Ну а как максимум - оборачивать внешние библиотеки в свои обертки, которые гарантируют exception free код.
Надеюсь, когда-нибудь появится движение Exception Free Code, которое будет публиковать обертки над стандартными библиотеками, гарантирующие, что вызов метода не приведет к exception, и все возможные значения, которые могут быть возвращены из метода будут описываться явно через возвращаемый тип.
Приглашение к дискуссии
Мне кажется, что проблему можно было бы решить через анализаторы кода, которые, во-первых подсказывали бы какие exception возможны там, где это известно, и во-вторых, обязывали бы на этапе компиляции, когда уже точно известны все зависимости, обработать все exception на уровне приложения. Не ясно, как быть с динамическим созданием типа по его имени. Но тут можно обязать в месте создания и использование такого типа оборачивать все в общий catch.
