Guard классы — использовать или нет?
На днях мне довелось делать довольно крупные изменения в одном C# проекте — удаление сторонней сборки. Самое «замечательное», что львиную долю времени я потратил на изменение мест, где использовались helper’ы из этой сборки (так сказать бонус к основной функциональности).
Helper’ы такого вида:
Guard.ArgumentNotNull(myobject, "myobject");
Такие классы встречаются очень часто, они могут оформляться в common libraries и кочевать из проекта в проект. Но нужны ли они? Распространенное мнение, что подобные guard’ы уменьшают количество строк кода и увеличивают ясность, но, как по мне, это очень субъективное мнение. Вот пример:
class Manager(IEventProvider eventProvider, IDataSource dataSource)
{
Guard.ArgumentNotNull(eventProvider, "eventProvider");
Guard.ArgumentNotNull(dataSource, "dataSource");
this.eventProvider = eventProvider;
this.dataSource = dataSource;
}
class Manager(IEventProvider eventProvider, IDataSource dataSource)
{
if (eventProvider == null)
throw new ArgumentNullException("eventProvider");
if (dataSource == null)
throw new ArgumentNullException("dataSource");
this.eventProvider = eventProvider;
this.dataSource = dataSource;
}
Да количество строк кода больше, но пострадала ли ясность? Нет.
Еще одно мнение, что при необходимости выполнить проверки для большого количества зависимостей, конструкция if (…) throw превращает код в кашу. Согласен, но это симптомы проблем в дизайне, а не конструкции if (…) throw.
Интересное рассуждение на эту тему есть в блоге Марка Симана.
А что вы думаете?
P.S. Есть еще code contracts, которые в самом простом варианте могут использоваться для подобного рода проверок.
Комментарии (6)
11 апреля 2017 в 10:17
0↑
↓
Лично я думаю, что писать, как у вас во втором примере, не следует. Или заключайте тело if в операторные скобки, или пишите всё на одной строке — это защищает от типовой ошибки, когда к телу if что-то добавили, но в скобки не взяли.
Ну и нетрудно заметить, что со скобками получается 3–4 строки на проверку — довод в пользу хелперов. Хотя агитировать за них и не буду, думаю, тут нет серебряной пули.Кстати, расскажите всё же подробней, почему ушло много времени на борьбу с хелперами? Не вижу ни одной причины для этого.
11 апреля 2017 в 10:17
0↑
↓
крупные изменения в одном C# проекте — удаление сторонней сборки.
Вы считаете разницу между гардом через класс и явной проверкой на null таким уж техническим долгом, который реально надо лечить?
Я не считаю.11 апреля 2017 в 10:18
0↑
↓
Гвард позволяет реагировать на некорректные параметры единообразно, и это важно, на мой взгляд.Если в вашем стайлгайде прописана стандартная реакция на null аргументы, на ourofrange, на все частые кейсы — то этого вполне достаточно.
Ну и, как обычно, любая стандартизация (в случае в гвардом) позволяет легко добавять туда логгирование, игнорирование (ну, а вдруг?), какую-нибудь хитрую обработку и т.д. входных параметров.
11 апреля 2017 в 10:30
+2↑
↓
C# 7 позволяет это делать в одну строчку, btw.this.eventProvider = eventProvider ?? throw new ArgumentNullException(nameof(eventProvider));
11 апреля 2017 в 10:32
+1↑
↓
Использую методы расширенияpublic static void CheckArgumentNotNull
(this T arg, string paramName) where T : class { if (arg == null) { throw new ArgumentNullException(paramName: paramName); } } public static void CheckArgumentNotNull (this T arg, string paramName, string message) where T : class { if (arg == null) { throw new ArgumentNullException(paramName: paramName, message: message); } } public static void CheckArgumentNotNull (this T arg, string paramName, Func message) where T : class { if (arg == null) { throw new ArgumentNullException(paramName: paramName, message: message()); } } public static async Task CheckArgumentNotNull (this T arg, string paramName, Func > message) where T : class { if (arg == null) { throw new ArgumentNullException(paramName: paramName, message: await message()); } } public static void CheckNotNull (this T arg, string message) where T : class { if (arg == null) { throw new NullReferenceException(message: message); } } public static void CheckNotNull (this T arg, Func message) where T : class { if (arg == null) { throw new NullReferenceException(message: message()); } } public static async Task CheckNotNull (this T arg, Func > message) where T : class { if (arg == null) { throw new NullReferenceException(message: await message()); } } 11 апреля 2017 в 11:25 (комментарий был изменён)
0↑
↓
Не очень знаком с C#, может быть, там это невозможно, но на мой взгляд, если функция требует, чтобы аргументы у нее были не null, нужно, в идеале, явно запретить туда передавать null на этапе компиляции. Скажем, в c++ это было бы
вместоManager(IEventProvider& eventProvider, IDataSource& dataSource) { this->eventProvider = eventProvider; this->dataSource = dataSource; }
Manager(IEventProvider* eventProvider, IDataSource* dataSource) { Guard.ArgumentNotNull(eventProvider, "eventProvider"); Guard.ArgumentNotNull(dataSource, "dataSource"); this->eventProvider = eventProvider; this->dataSource = dataSource; }
Такой подход повышает readability, переносит проверку ошибок из run-time в compile-time и заставляет программиста, который хочет вызвать этот метод, но имеет на руках лишь указатели, задуматься, может ли там быть nullptr.