Запах кода - это признак, который указывает на наличие проблемы в проекте, которая потенциально может увеличить количество ошибок: это не относится к регионам, но регионы могут способствовать созданию запахов кода, таких как длинные методы.
Поскольку:
Анти-паттерн (или антипаттерн) - это паттерн, используемый в социальных или бизнес-операциях или разработке программного обеспечения, который может широко использоваться, но неэффективен и / или контрпродуктивен на практике.
регионы - это анти-паттерны. Они требуют больше работы, которая не повышает качество или удобочитаемость кода, не уменьшает количество ошибок и может только усложнить код для рефакторинга.
Не используйте регионы внутри методов; вместо этого рефакторинг
Методы должны быть короткими . Если в методе всего десять строк, вы, вероятно, не использовали бы регионы, чтобы скрыть пять из них при работе с другими пятью.
Кроме того, каждый метод должен выполнять одну-единственную вещь . Регионы, с другой стороны, предназначены для разделения разных вещей . Если ваш метод выполняет A, то B, логично создать две области, но это неправильный подход; вместо этого вы должны изменить метод на два отдельных метода.
Использование регионов в этом случае также может усложнить рефакторинг. Представь, что у тебя есть:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
if (!verification)
{
throw new DataCorruptedException();
}
Do(data);
DoSomethingElse(data);
#endregion
#region Audit
var auditEngine = InitializeAuditEngine();
auditEngine.Submit(data);
#endregion
}
Свернуть первую область, чтобы сконцентрироваться на второй, не только рискованно: мы можем легко забыть об исключении, останавливающем поток ( return
вместо него может быть предложено защитное предложение , которое еще труднее обнаружить), но также возникнет проблема если код должен быть реорганизован таким образом:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
var info = DoSomethingElse(data);
if (verification)
{
Do(data);
}
#endregion
#region Audit
var auditEngine = InitializeAuditEngine(info);
auditEngine.Submit(
verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
#endregion
}
Теперь регионы не имеют смысла, и вы не можете прочитать и понять код во второй области, не глядя на код в первой.
Другой случай, который я иногда вижу, это:
public void DoSomething(string a, int b)
{
#region Validation of arguments
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
#endregion
#region Do real work
...
#endregion
}
Соблазнительно использовать регионы, когда проверка аргументов начинает охватывать десятки LOC, но есть лучший способ решить эту проблему: тот, который используется исходным кодом .NET Framework:
public void DoSomething(string a, int b)
{
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
InternalDoSomething(a, b);
}
private void InternalDoSomething(string a, int b)
{
...
}
Не используйте регионы вне методов для группировки
Некоторые люди используют их для группировки полей, свойств и т. Д. Этот подход неправильный: если ваш код совместим с StyleCop, тогда поля, свойства, частные методы, конструкторы и т. Д. Уже сгруппированы и их легко найти. Если это не так, то пришло время задуматься о применении правил, обеспечивающих единообразие в вашей кодовой базе.
Другие люди используют регионы, чтобы скрыть множество похожих объектов . Например, если у вас есть класс с сотнями полей (который составляет не менее 500 строк кода, если вы подсчитываете комментарии и пробелы), у вас может возникнуть желание поместить эти поля в область, свернуть ее и забыть о них. Опять же, вы делаете это неправильно: с таким количеством полей в классе вам следует лучше подумать об использовании наследования или разбить объект на несколько объектов.
Наконец, некоторые люди испытывают желание использовать регионы для группировки связанных вещей : событие с его делегатом или метод, связанный с IO, с другими методами, связанными с IO, и т. Д. В первом случае это становится беспорядком, который трудно поддерживать читать и понимать Во втором случае лучшим дизайном, вероятно, будет создание нескольких классов.
Есть ли хорошее применение для регионов?
Нет . Было наследство использование: сгенерированный код. Тем не менее, инструменты генерации кода просто должны использовать частичные классы. Если в C # есть поддержка регионов, то это в основном потому, что это устаревшее использование, и теперь, когда слишком много людей использовали регионы в своем коде, было бы невозможно удалить их, не нарушая существующие кодовые базы.
Думайте об этом как о goto
. Тот факт, что язык или IDE поддерживает функцию, не означает, что она должна использоваться ежедневно. Правило StyleCop SA1124 ясно: вы не должны использовать регионы. Никогда.
Примеры
В настоящее время я делаю обзор кода моего коллега кода. Кодовая база содержит много областей, и на самом деле является прекрасным примером того, как не использовать области и почему области приводят к плохому коду. Вот некоторые примеры:
4 000 LOC монстр:
Недавно я где-то читал на сайте Programmers.SE, что, когда файл содержит слишком много using
s (после выполнения команды «Удалить неиспользуемые использования»), это хороший признак того, что класс внутри этого файла делает слишком много. То же самое относится и к размеру самого файла.
Просматривая код, я наткнулся на 4 000 LOC-файлов. Оказалось, что автор этого кода просто копировал один и тот же 15-строчный метод сотни раз, слегка изменяя имена переменных и вызываемый метод. Простое регулярное выражение позволило обрезать файл с 4 000 LOC до 500 LOC, просто добавив несколько обобщений; Я почти уверен, что при более умном рефакторинге этот класс может быть сокращен до нескольких десятков строк.
Используя регионы, автор поощряет себя игнорировать тот факт, что код невозможно поддерживать и плохо написан, и сильно дублировать код вместо его рефакторинга.
Регион «До А», Регион «До Б»:
Другим отличным примером был метод инициализации монстра, который просто выполнял задачу 1, затем задачу 2, затем задачу 3 и т. Д. Было пять или шесть задач, которые были полностью независимыми, каждая из которых инициализировала что-то в классе контейнера. Все эти задачи были сгруппированы в один метод и сгруппированы по регионам.
Это имело одно преимущество:
- Метод был довольно понятен для понимания, глядя на названия регионов. При этом тот же метод после рефакторинга будет таким же понятным, как и оригинал.
Проблемы, с другой стороны, были множественными:
Не было очевидно, были ли зависимости между регионами. Надеемся, что не было повторного использования переменных; в противном случае, обслуживание может стать еще большим кошмаром.
Метод было практически невозможно проверить. Как бы вы легко узнали, что метод, который делает двадцать вещей одновременно, делает их правильно?
Область полей, область свойств, область конструктора:
Рассмотренный код также содержал множество областей, объединяющих все поля вместе, все свойства вместе и т. Д. Это имело очевидную проблему: рост исходного кода.
Когда вы открываете файл и видите огромный список полей, вы более склонны сначала выполнить рефакторинг класса, а затем работать с кодом. С регионами вы берете привычку рушиться и забывать об этом.
Другая проблема заключается в том, что если вы будете делать это повсеместно, вы обнаружите, что создаете области из одного блока, что не имеет никакого смысла. Это было на самом деле в коде, который я рассмотрел, где было много #region Constructor
содержащих один конструктор.
Наконец, поля, свойства, конструкторы и т. Д. Уже должны быть в порядке . Если они совпадают и соответствуют соглашениям (константы, начинающиеся с заглавной буквы и т. Д.), То уже ясно, где останавливается тип элементов и начинается другой, поэтому вам не нужно явно создавать для этого регионы.