Beatles1692, простите меня, но я начну с решения вашей главной задачи рефакторинга обработки исключений вместо того, чтобы сразу переходить к DSL-части.
В своем вопросе вы говорите следующее:
Я рассматриваю часть базы кода, и я перехожу к части обработки исключений, которая действительно грязная.Я хотел бы заменить его на что-то более элегантное.
, которое, как я полагаю, является вашей главной заботой.Поэтому я постараюсь предоставить вам руководство о том, как сделать его более элегантным - на самом деле фрагмент кода, который вы предоставили, не будучи элегантным, не о том, что это не DSL или свободные интерфейсы, а о качестве проектирования.Если у вас есть избыточность и связь в вашем дизайне, то создание плавного интерфейса поверх этой избыточности и связи только сделает это «более красивым беспорядком».
Ответ будет длинным, и я буду ссылатьсяс некоторыми качествами кода, так что если вам нужны дальнейшие объяснения, просто дайте мне знать.Поскольку в принятии такого решения участвуют многие переменные (например, стоимость изменений, владение кодом и т. Д.), Я постараюсь предоставить вам самое «чистое» решение, а затем решение, требующее минимальных усилий.
ВВ таких ситуациях хорошо бы воспользоваться рекомендациями Gang of Four, авторов классических шаблонов проектирования.Этот совет: «Инкапсулируйте то, что меняется».в вашем случае это обработка ошибок, и это зависит от типа исключения.Как бы я применил это здесь?
Первое решение - полностью изменить запах кода
Первый вопрос, который я хотел бы задать, таков: можете ли вы изменить кодчто вызывает исключения? Если это так, я бы попытался инкапсулировать не внутри кода, где вы ловите исключения, , а внутри кода, который их выбрасывает .Это может показаться странным для вас, но это может позволить вам избежать избыточности.На самом деле ваш код в настоящее время связан с типом исключения в двух местах.
Первое место - это место, где вы его выбросили (вы должны знать, какое исключение выбрасывать) - очень упрощенно, это может выглядетьэто:
if(someSituationTakesPlace())
{
throw new ExpType1();
}
else if(someOtherSituationTakesPlace()
{
throw new ExpType2();
}
и т. д.Конечно, условия могут быть более сложными, может быть несколько разных объектов и методов, которые вы бросаете, но, по сути, это всегда сводится к серии выборов, таких как «В ситуации A, выбросить исключение X».
Второе место, где у вас есть это отображение, - это когда вы ловите исключение - вы должны снова пройти серию if-elses, чтобы выяснить, в какой ситуации это было, и затем вызвать некоторую логику, которая справится с этим.
Чтобы избежать этой избыточности, я бы определился с обработкой, в которую вы добавляете исключение - у вас должна быть вся необходимая там информация.Таким образом, я сначала определил бы класс исключений следующим образом:
public class Failure : Exception
{
IFailureHandling _handling;
public Failure(IFailureHandling handling)
{
//we're injecting how the failure should be handled
_handling = handling;
}
//If you need to provide additional info from
//the place where you catch, you can use parameter list of this method
public void Handle()
{
_handling.Perform();
}
}
Затем я бы создал фабрику, которая создает такие исключения, связывая их, уже в этой точке , собработчики.Например:
public class FailureFactory
{
IFailureHandling _handlingOfCaseWhenSensorsAreDown,
IFailureHandling _handlingOfCaseWhenNetworkConnectionFailed
public FailureFactory(
IFailureHandling handlingOfCaseWhenSensorsAreDown,
IFailureHandling handlingOfCaseWhenNetworkConnectionFailed
//etc.
)
{
_handlingOfCaseWhenSensorsAreDown
= handlingOfCaseWhenSensorsAreDown;
_handlingOfCaseWhenNetworkConnectionFailed
= handlingOfCaseWhenNetworkConnectionFailed;
//etc.
}
public Failure CreateForCaseWhenSensorsAreDamaged()
{
return new Failure(_handlingOfCaseWhenSensorsAreDown);
}
public Failure CreateForCaseWhenNetworkConnectionFailed()
{
return new Failure(_handlingOfCaseWhenNetworkConnectionFailed);
}
}
Обычно вы создаете только одну такую фабрику для всей системы и делаете это там, где вы создаете экземпляры всех долго работающих объектов (как правило, в приложении есть одно такое место), поэтому, покасоздавая фабрику, вы должны иметь возможность передавать все объекты, которые вы хотите, чтобы она использовала через конструктор (как это ни странно, это создаст очень примитивный беглый интерфейс. Помните, что беглые интерфейсы - это читабельность и поток, а не только вставка .a.dot.every.method.call: -):
var inCaseOfSensorDamagedLogItToDatabaseAndNotifyUser
= InCaseOfSensorDamagedLogItToDatabaseAndNotifyUser(
logger, translation, notificationChannel);
var inCaseOfNetworkDownCloseTheApplicationAndDumpMemory
= new InCaseOfNetworkDownCloseTheApplicationAndDumpMemory(
memoryDumpMechanism);
var failureFactory = new FailureFactory(
inCaseOfSensorDamagedLogItToDatabaseAndNotifyUser,
inCaseOfNetworkDownCloseTheApplicationAndDumpMemory
);
Таким образом, и место, где вы выбрасываете исключение, и место, где вы его ловите, не связаны с логикой обработки - это то, что меняется в вашемвопрос!Таким образом, мы заключили в капсулу то, что изменяется!Конечно, вы можете предоставить более продвинутый и удобный интерфейс.
Теперь каждое место, куда вы добавляете исключение, будет выглядеть так:
if(sensorsFailed())
{
throw _failureFactory.CreateForCaseWhenSensorsAreDamaged();
}
И место, гдевы ловите все эти исключения будут выглядеть так:
try
{
PerformSomeLogic();
}
catch(Failure failure)
{
failure.Handle();
}
Таким образом, единственное место, где вы знаете, как обрабатывать каждый случай сбоя, - это логика, создающая объект класса FailureFactory.
Второе решение - использовать обработчик
В случае, если вам не принадлежит код, который генерирует исключения, или было бы слишком дорого или слишком рискованно внедрять решение, описанное выше, я бы использовал объект Handler, который выглядел бы как FailureFactory, но вместо этого создания объектов, он сам выполняет обработку:
public class FailureHandlingMechanism
{
_handlers = Dictionary<Type, IFailureHandling>();
public FailureHandler(Dictionary<Type, IFailureHandling> handlers)
{
_handlers = handlers;
}
public void Handle(Exception e)
{
//might add some checking whether key is in dictionary
_handlers[e.GetType()].Perform();
}
}
Создание такого механизма обработки уже дало бы вам очень примитивный свободный интерфейс:
var handlingMechanism = new HandlingMechanism(
new Dictionary<Type, IFailureHandling>()
{
{ typeof(NullPointerException), new LogErrorAndCloseApplication()}},
{ typeof(ArgumentException}, new LogErrorAndNotifyUser() }
};
Если вы хотите еще более плавный и менее шумный способ настройки такого механизма обработки, вы можете создать конструктор на основе HandlingMechanism, который имеет методы для добавления ключей и значений в словарь и метод с именем Build (), который создал объект для вы:
var handlingMechanismThatPerforms = new HandlingMechanismBuilder();
var logErrorAndCloseApplication = new LogErrorAndCloseApplication();
var logErrorAndNotifyUser = new LogErrorAndNotifyUser();
var handlingMechanism = handlingMechanismThatPerforms
.When<NullPointerException>(logErrorAndCloseApplication)
.When<ArgumentException>(logErrorAndNotifyUser)
.Build();
И это все. Дайте мне знать, поможет ли это вам как-нибудь!