Beatles1692, простите меня, но я начну с решения вашей основной проблемы рефакторинга обработки исключений вместо того, чтобы сразу переходить к части DSL.
В своем вопросе вы говорите следующее:
Я просматриваю часть базы кода и перехожу к части обработки исключений, которая действительно запутана. Я бы хотел заменить его на что-то более элегантное.
что, я полагаю, является вашей главной заботой. Итак, я попытаюсь предоставить вам руководство о том, как сделать его более элегантным - на самом деле фрагмент кода, который вы предоставили, не является элегантным, не о том, что он не является DSL или плавными интерфейсами, а о качествах дизайна. Если в вашем дизайне есть избыточность и связь, то создание плавного интерфейса поверх этой избыточности и связи только сделает его «красивее беспорядка».
Ответ будет длинным, и я упомяну о некоторых качествах кода, поэтому, если вам нужны дополнительные пояснения, просто дайте мне знать. Поскольку в принятии такого решения участвует множество переменных (например, стоимость изменений, владение кодом и т. д.), я постараюсь предоставить вам «самое чистое» решение, а затем то, которое требует наименьших усилий.
В подобных ситуациях полезно воспользоваться советом Gang of Four, авторов классических шаблонов проектирования. Этот совет: «Инкапсулируйте то, что меняется». в вашем случае различается обработка сбоев, а изменение зависит от типа исключения. Как бы я применил это здесь?
Первое решение — полностью убрать запахи из кода.
Первый вопрос, который я бы задал, звучит так: можете ли вы изменить код, который выдает исключения? Если да, я бы попытался инкапсулировать не внутри кода, где вы перехватываете исключения, >но внутри кода, который их выдает. Это может показаться вам странным, но это может позволить вам избежать избыточности. Как бы то ни было, ваш код в настоящее время связан с типом исключения в двух местах.
Во-первых, это то, где вы его выбрасываете (вы должны знать, какое исключение выбрасывать) — сильно упрощенное, это может выглядеть так:
if(someSituationTakesPlace())
{
throw new ExpType1();
}
else if(someOtherSituationTakesPlace()
{
throw new ExpType2();
}
и т. д. Конечно, условия могут быть более сложными, может быть несколько разных объектов и методов, которые вы выбрасываете, но, по сути, это всегда сводится к ряду вариантов, таких как «В ситуации A, выдать исключение X».
Второе место, где у вас есть это сопоставление, — это когда вы перехватываете исключение — вам снова нужно пройти серию if-else, чтобы узнать, в какой ситуации это было, а затем вызвать некоторую логику, которая обработает это.
Чтобы избежать этой избыточности, я бы выбрал обработку, в которой вы выбрасываете исключение - у вас должна быть вся необходимая информация. Таким образом, я бы сначала определил класс исключения следующим образом:
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();
И это все. Дайте мне знать, если это поможет вам в любом случае!
person
Grzesiek Galezowski
schedule
27.01.2013