Запах коду - симптом, який вказує на те, що в дизайні є проблема, яка потенційно може збільшити кількість помилок: це не стосується регіонів, але регіони можуть сприяти створенню запахів коду, як довгі методи.
З моменту:
Антидіаграма (або антипаттерн) - це модель, що використовується в соціальних або ділових операціях або інженерії програмного забезпечення, яка може широко застосовуватися, але є неефективною та / або контрпродуктивною на практиці
регіони є анти-моделями. Вони потребують більшої роботи, яка не підвищує якість чи читабельність коду, що не зменшує кількість помилок, і це може лише зробити код більш складним для рефактора.
Не використовуйте регіони в методах; натомість рефактор
Методи повинні бути короткими . Якщо в методі є лише десять рядків, ви, ймовірно, не використовували б регіони, щоб приховати п'ять з них, працюючи над іншими п'ятьма.
Також кожен метод повинен робити одне і одне єдине . Регіони, навпаки, покликані розділяти різні речі . Якщо ваш метод робить 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 (після виконання команди "Видалити невикористані користування"), це хороший знак, що клас у цьому файлі робить занадто багато. Те саме стосується розміру самого файлу.
Переглядаючи код, я натрапив на файл 4000 LOC. Виявилося, що автор цього коду просто копіював вставку того ж 15-рядкового методу сотні разів, трохи змінивши назви змінних та названий метод. Простий регулярний вираз дозволив обрізати файл від 4000 LOC до 500 LOC, лише додавши кілька загальних даних; Я майже впевнений, що при більш розумному рефакторингу цей клас може бути скорочений до кількох десятків рядків.
Використовуючи регіони, автор закликав себе ігнорувати той факт, що код неможливо підтримувати і погано записати, і сильно дублювати код замість рефактора.
Регіон "Зробіть", регіон "зробіть B":
Іншим чудовим прикладом був метод ініціалізації монстра, який просто робив завдання 1, потім завдання 2, потім завдання 3 і т. Д. Було п'ять-шість завдань, які були абсолютно незалежними, кожна з яких ініціалізувала щось у класі контейнерів. Усі ці завдання були згруповані в один метод і згруповані в регіони.
Це мала одну перевагу:
- Метод був досить зрозумілий, щоб зрозуміти, переглядаючи назви регіонів. Якщо говорити, той самий метод, що відбудеться після відновлення, був би таким же зрозумілим, як і оригінал.
Проблем, з іншого боку, було багато:
Не було очевидно, чи існували залежності між регіонами. Сподіваємось, повторного використання змінних не було; інакше обслуговування може бути кошмаром ще більше.
Метод було майже неможливо перевірити. Як ви легко дізнаєтесь, якщо метод, який робить двадцять речей одночасно, робить їх правильно?
Область полів, область властивостей, область конструктора:
Розглянутий код також містив багато регіонів, які групують усі поля разом, усі властивості разом тощо. У цього була очевидна проблема: зростання вихідного коду.
Коли ви відкриєте файл і побачите величезний список полів, ви схильні спочатку перефактурувати клас, а потім працювати з кодом. У регіонах ви звикаєте згортати речі і забувати про них.
Інша проблема полягає в тому, що якщо ви зробите це скрізь, ви опинитеся, що створюєте одноблокові регіони, що не має сенсу. Це було насправді в коді, який я розглянув, де було багато #region Constructor
містять одного конструктора.
Нарешті, поля, властивості, конструктори тощо повинні вже бути в порядку . Якщо вони є, і вони відповідають умовам (константи, починаючи з великої літери тощо), вже зрозуміло, де на типі елементів зупиняється і починається інше, тому не потрібно чітко створювати регіони для цього.