Чому люди настільки рішуче проти методів #region у методах?


27

Я багато чую про те, щоб тримати методи коротко, і я чув багато програмістів, які говорять про те, що використання тегів #region у методі є вірною ознакою того, що він занадто довгий і його слід переробити на кілька методів. Однак мені здається, що існує багато випадків, коли розділення коду з тегами #region у методі є найкращим рішенням для рефакторингу на кілька методів.

Припустимо, у нас є метод, обчислення якого можна розділити на три досить чіткі фази. Крім того, кожен з цих етапів має значення лише для обчислення цього методу, і тому вилучення їх у нові методи не приносить нам ніякого використання коду. У чому ж полягають переваги вилучення кожної фази у її власний метод? Наскільки я можу сказати, все, що ми отримуємо, - це певна читабельність та окрема змінна область для кожної фази (що допоможе запобігти випадковому порушенню іншої фази модифікацій певної фази).

Однак і того, і іншого можна досягти, не вилучаючи кожну фазу у свій метод. Теги регіонів дозволяють нам згортати код у форму, яка так само читається (з додатковою перевагою, що нам більше не доведеться залишати своє місце в цьому файлі, якщо ми вирішимо розширити і вивчити код), а також просто перевести кожну фазу в {}створює власну сферу роботи.

Користь зробити це таким чином полягає в тому, що ми не забруднюємо область рівня класу трьома методами, які насправді стосуються лише внутрішніх функцій четвертого методу. Мені здається, що негайне рефакторинг довгого методу на низку коротких методів - це повторне використання коду, рівнозначне передчасній оптимізації; ви вводите додаткову складність, щоб вирішити проблему, яка в багатьох випадках ніколи не виникає. Ви завжди можете витягти одну з фаз у свій метод пізніше, якщо з’явиться можливість повторного використання коду.

Думки?


4
Зараз я працюю з досить великим класом. Він дотримується принципу єдиної відповідальності; все в класі вимагає функціональності. У ньому багато приватних методів, в основному завдяки рефакторингу. Я використовую #region, щоб поділити ці методи на функціональні категорії, що дуже добре вийшло для нас. У цьому проекті є багато інших класів, які не потребували такого лікування. Я кажу, що це правильний інструмент для правильної роботи.
Роберт Харві

4
@RobertHarvey, використання їх у класі відрізняється від використання їх у методі.
CaffGeek

18
@Chad: Ах, цього не помітили. Використання їх у методі здається трохи екстремальним. Якщо метод настільки довгий, що він вимагає #regions, я перефактую його на окремі методи.
Роберт Харві

8
Насправді не відповідь, але я не лише ненавиджу всі #regionтеги, я взагалі вимикаю складку коду у Visual Studio. Мені не подобається код, який намагається приховати від мене.
Даніель Приден

2
"Крім того, кожен з цих етапів має значення лише для обчислення цього методу, і тому вилучення їх у нові методи не приносить нам ніякого повторного використання коду". ОТО, я знаходжу, що якщо я розділю функцію на 3, я часто виявляю пізніше, що окремі частини <i> згодом стануть корисними, наприклад. якщо деякі вхідні дані змінюють лише одну фазу, ви можете перевірити це ізольовано і т. д.
Джек В.

Відповіді:


65

Все, про що ви повинні колись турбуватися, - це те, щоб ваш код був корисним, не використовувався повторно. Мавпа може перетворити корисний код на код багаторазового використання, якщо взагалі є якісь перетворення.

Аргумент "мені це потрібно тільки тут" поганий, щоб сказати це ввічливо. Техніку, яку ви описуєте, часто називають технікою заголовків і, як правило, нахмуріться.

  1. Ви не можете перевірити регіони, але ви можете перевірити справжні методи ізольовано.
  2. Регіони - це коментарі, а не синтаксичні елементи. У гіршому випадку вкладення ваших регіонів та ваших блоків суперечать одне одному. Ви завжди повинні прагнути представляти семантику вашої структури синтаксичними елементами мови, якою ви користуєтесь.
  3. Після рефакторингу на методи більше не потрібно складати текст, щоб прочитати текст. Можливо, ви дивитеся на вихідний код у будь-якому інструменті, який не має складання, як-от термінал, поштовий клієнт, веб-перегляд для вашого VCS або розглядача розгляду.
  4. Після рефакторингу на методи отриманий метод принаймні такий же хороший, як і з регіонами, плюс простіше переміщати окремі частини навколо.
  5. Принцип єдиної відповідальності передбачає, що будь-який підрозділ повинен мати одне завдання і одне завдання. Завдання «основного» методу - скласти методи «помічники», щоб отримати бажаний результат. Методи «помічників» вирішують дискретну, просту проблему ізольовано. Клас, що містить усі ці методи, також повинен виконувати лише одне завдання і містити лише методи, пов'язані з цим завданням. Таким чином, допоміжні методи або належать до області класу, або код не повинен бути в класі в першу чергу, а принаймні в якомусь глобальному методі або, ще краще, слід вводити .

Також актуально: думки Джеффа Етвудса щодо складання коду


9
1. Більшість програмістів не перевіряють усі приватні методи. 2. Назва методу не може передати все, що потрібно знати про цей метод; які винятки можуть бути кинуті? Чи недійсний вхід? Іноді нормально використовувати несинтаксичні конструкції, щоб зробити ваш код більш зрозумілим. У багатьох мовах файли є прикладом несинтаксичного елемента, який досить широко сприймається як засіб організації коду. 3. Зануритися у внутрішню розробку методу найкраще намагатися в IDE з різних причин; Ситуації, коли регіони можуть вкусити вас у вашому СКС або поштовому клієнті, є досить рідкісними.
jjoelson

3
4. Але ви лише експортували якусь серйозну складність у решту свого класу. Чи варто того? Також регіони можна переміщувати так само легко, як виклик методу. 5. Тепер ви говорите про забруднення простору імен більшою кількістю класів, які будуть використовуватися лише в одному місці. Чому класи та методи - єдині одиниці інкапсуляції, які ви приймете? Ви завжди можете створити цей новий клас пізніше, коли (або якщо він) стане необхідним мати його окремим.
jjoelson

7
@jjoelson: 1. Так що? 2. Точно моя думка: нормально використовувати несинтаксичні конструкції, якщо (і лише тоді) синтаксис одного недостатньо. 3. "Рідкісний?" - Я гадаю, у вас є цифри, щоб це показати. Я можу вам точно сказати, що інструмент diff (або звинувачувати чи будь-що з цього приводу) в комплекті з TortoiseSVN не має складних. 4. Як це пов’язано з точкою, яку я зробив. 5. Ось чому більшість сучасних мов мають нестабільні простори імен. Ви скористаєтесь мистецтвом ASCII, а не використовуєте широку палітру доступних мовних елементів для структури свого коду.
back2dos

4
@jjoelson: 1. Це ваша думка і виходить за межі цього питання. 2. За допомогою мого аргументу вкладені функції краще, ніж регіони, так. 3. Тому що мені іноді потрібно переглянути ряд змін, щоб відстежити проблему. Так чи інакше, вихідний код пишеться для людей, а не IDE. 4. Для одного аргумент не пов'язаний з моєю думкою, по-друге, це знову ж таки лише ваша думка, по-третє, це поза сферою цього питання. 5. C # має інші засоби структурування коду, ніж функція введення. Вам слід використовувати ці мови або мову, яка дозволяє вкладати функції введення.
back2dos

13
Ви, хлопці, повинні взяти це за чат, якщо ви все ще хочете обговорити ... У мене нічого не варто сказати ОП, він типовий самовпевнений молодший розробник, встановлений у своїх переконаннях. Ніщо не змінить його думку, але більше досвіду IMO.
maple_shaft

15

Це не проблема регіонів у методах, а випадок, коли необхідність (або навіть використання) вводити регіони в методи.

Довелось налагодити багато 200-300 лінійних методів, я можу вам сказати, що я б цього не бажав нікому. Якщо ви пишете і доглядаєте свій власний код, це добре - робіть все, що завгодно. Але як тільки хтось ще дивиться на це, йому слід негайно зрозуміти, що таке метод.

"Спочатку він дістає речі, а потім переміщує їх навколо, потім, якщо вони потребують гудіння, це робить це, інакше він перевіряє, чи вони сині, і перетворює їх значення Коперника. Потім, якщо друга річ більша за першу, ми потрібно дістати коробку з соком і вставити ... "

Ні, це недостатньо добре. Розбийте його на регіони все, що вам хочеться, але я все ще трясу головою, тільки думаю про це.

Якщо ви втручаєтесь у методи, ви отримуєте багато переваг:

  • Чітке розуміння того, що відбувається де, навіть якщо тільки через назву методу. І ви неправильно , що метод не може бути повністю документовані - додати блок документації (HIT ///) і введіть опис, параметри, тип значення, а також exception, example, remarksвузли детально ці сценарії. Ось куди йде, ось для чого!
  • Ніяких побічних ефектів або проблем зі сферами дії немає. Ви можете сказати, що ви декларуєте всі свої змінні у під-області з {}, але чи я повинен перевірити кожен метод, щоб переконатися, що ви не «обдурили»? Це dodgyObjectя використовую тут лише тому, що використовується в цьому регіоні, чи він прийшов з іншого місця? Якби я був за власним методом, я легко міг би зрозуміти, чи був він переданий мені чи я створив його сам (а точніше, чи ти створив його).
  • Мій журнал подій повідомляє мені, у якому методі сталася виняток. Так, я можу знайти код порушника з номером рядка, але знаючи ім'я методу (особливо, якщо я їх правильно назвав), дає мені дуже хороший показник що сталося і що пішло не так. "О, TurnThingsUpsideDownметод не вдався - він, мабуть, невдалий під час повороту поганої речі", набагато краще, ніж "О, DoEverythingметод не вдався - це могло бути 50 різних речей, і мені доведеться копати протягом години, щоб знайти його" .

Це все до будь-яких питань щодо повторного використання чи неможливості. Правильно відокремлені методи, очевидно, полегшують повторне використання, а також дозволяють легко замінити неефективний метод (занадто повільний, занадто глючний, змінена залежність тощо). Ви коли-небудь пробували рефакторинг будь-якого з цих величезних методів? Немає способу знати, що те, що ти зміниш, не вплине ні на що інше.

Будь ласка, належним чином інкапсулюйте свою логіку методами розумного розміру. Я знаю, що їм легко вийти з ладу, і стверджувати, що не варто переробляти один раз в цей момент - ще одне питання. Але ви повинні змиритися з тим, що немає правильної інкапсульованої, чітко написаної та просто спроектованої методики не шкода та хоча б потенційна користь.


Розбір коментарів XML Visual Studio разом із тегами #region підпадає під категорію функцій Visual Studio. Цілий мій сенс у тому, що не правильно покладатися на ці можливості, якщо всі у вашому проекті використовують Visual Studio. Що стосується побічних ефектів та проблем із визначенням масштабів, ви все ще можете накрутити речі на глобальних полях. В будь-якому випадку ви повинні розраховувати на програмістів, щоб не робити дурних речей з побічними ефектами.
jjoelson

5
@jjoelson Ви все ще можете читати коментарі XML без візуальної студії, але теги регіону майже повністю марні поза VS. Логічне розширення вашого аргументу - це один з гігантських методів, який запускає всю вашу програму, добре, доки ви розділите її на регіони!
Кірк Бродхерст

2
@jjoelson, будь ласка, не запускайте нас із того, наскільки погані глобальні поля. Говорити щось не корисно, тому що у вас є "обхід", щоб все-таки викрутити це просто нерозумно. Просто слідкуйте за тим, щоб ваші методи були короткими та зміненими.
ОліверС

@OliverS, Кірк був тим, хто вивів спільний стан як проблему з моєю схемою "регіональні методи". В основному, я говорив саме те, що ви говорите: той факт, що програміст може зловживати станом, поділяючи занадто багато змінних між регіонами, не є обвинуваченням у всій схемі, як і можливість викручувати стан, поділене між методами за допомогою глобальних мереж. t обвинувальний висновок щодо багатопроцесорної схеми.
jjoelson

7

Якщо ваш метод досить складний, що його можна розділити на три різні фази, то не тільки вони повинні бути трьома окремими методами ... але ваш метод насправді повинен бути окремим класом, присвяченим цьому обчисленню.

"Але тоді мій клас матиме лише один публічний метод!"

Ти маєш рацію, і в цьому немає нічого поганого. Ідея принципу єдиної відповідальності полягає в тому, щоб ваш клас робив одне .

Ваш клас може по черзі викликати кожен із трьох методів і повертати результат. Одна річ.

Як бонус, тепер ваш метод перевіряється, оскільки це вже не приватний метод.

Але не заважайте одному класу; насправді у вас повинно бути чотири : по одному на кожен фрагмент вашого методу, плюс один, який сприймає кожну з трьох за залежність і називає їх у належному порядку, повертаючи результат.

Це робить ваші заняття ще більш відчутними. Це також дозволяє вам легко змінити одну з цих трьох частин. Просто напишіть новий клас, успадковуючи від старого, і перекрийте змінену поведінку. Або створити інтерфейс для поведінки кожного з ваших трьох творів; а потім, щоб замінити один, просто напишіть новий клас, що реалізує цей інтерфейс, і замініть його на старий у вашій конфігурації контейнера для введення залежності.

Що? Ви не використовуєте ін'єкцію залежності? Ну, ви, мабуть, ще не бачили потреби, тому що ви не дотримуєтесь принципу єдиної відповідальності. Після запуску ви побачите, що найпростіший спосіб надати кожному класу залежність - це використовувати контейнер DI.

Редагувати :

Добре, давайте відкладемо питання рефакторингу. Мета #region- приховати код , що в основному означає код, який не потрібно редагувати ні за яких нормальних обставин. Створений код - найкращий приклад. Він повинен бути прихованим у регіонах, оскільки зазвичай його не потрібно редагувати. Якщо вам це потрібно, то акт розширення регіону дає вам трохи попередження в стилі "Тут бути драконами", щоб переконатися, що ви знаєте, що ви робите.

Тому я б сказав, що #regionйого не слід використовувати в середині методу. Якщо я відкрию метод, я хочу побачити код. Використання #region дає мені ще один рівень прихованості, який мені не потрібен, і це стає роздратуванням: я розгортаю метод ... і все ще не бачу жодного коду.

Якщо ви переживаєте за те, щоб люди бачили структуру методу (і ви відмовляєтесь від рефакторації), додайте коментарі банерів, як це:

//
// ---------- COMPUTE SOMETHING OR OTHER ----------
//

Це допоможе комусь, хто переглядає код, бачити окремі частини вашого методу, не маючи справу з розширенням та згортанням #regionтегів.


2
Все це для методу, який викликається в одному місці? Навіть більшість хардкорних Lispers не абстрагують функцію вищого порядку, поки вони не визначать принаймні два місця, де її можна використовувати для легшого визначення функції.
jjoelson

1
@jjoelson Чи так важко скласти новий клас? Що це, одна лінія, одна відкриваюча дужка та одна фіксація. О, і ти повинен натиснутиctrl+n
Кірк Бродхерст

2
Справа не в тому, що скласти новий клас важко , але є додаткові витрати, щоб мати додатковий клас. Це ще один шар непрямості, який укладач коду ускладнює пошук того, що вона шукає. Це не така вже й велика угода, але вона також нічого не купує вам у випадку, коли цей біт коду навряд чи зателефонує з будь-якого місця. І, як ви згадали, це досить легко зробити , що новий клас і помістити код там , якщо з'ясовується, ви робите необхідність викликати цей біт коду в декількох місцях.
jjoelson

2
@jjoelson, це досить поширена реакція. Це було моє, коли я вперше дізнався про такі речі, як SRP та DI, і це була реакція моїх колег, коли ми почали вибувати залежності. Якщо ви дивитесь на один окремий випадок , це, здається, не варто. Вигода полягає в сукупності. Як тільки ви починаєте робити SRP з усіма своїми класами, ви виявите, що змінити частину коду набагато простіше, не змінюючи, як проступає половина програми.
Kyralessa

@Kyralessa, це методи, які використовуються лише в одному місці. Зміна такого методу не прошиватиме половину вашої програми. Якщо такий код реалізований як регіон у методі, який його використовує, ви маєте ідеальну інкапсуляцію; тільки метод, що містить код, використовує код, і тому ви можете змінювати все, що завгодно, не турбуючись про його вплив, за винятком цього методу.
jjoelson

4

Я думаю, що приклад, який ви наводите у своєму аргументі, стосується менше YAGNI (вам це не знадобиться) та більше про написання коду належної якості, який легко досяжний.

На самих ранніх курсах програмування ми дізнаємось, що якщо метод має довжину 700 LOC, він, ймовірно, занадто великий, і, безумовно, було б величезним болем спробувати і налагодити.

По-друге, якщо я записую методи A, B і C, які використовуються лише в рамках методу D, я можу легше поділити тести AB і C незалежно від D, що дозволить зробити більш надійні одиничні тести у всіх 4-х методах.


Тестування одиниць, безумовно, є справедливим моментом, але я не впевнений, що це виправдовує забруднення області класу у кожному випадку. Реально кажучи, чи насправді хтось перевіряє кожен маленький приватний метод, який пише? Я б сказав, що люди схильні перевіряти приватні методи, які дуже часто використовуються, тобто приватні методи, які в будь-якому разі будуть кандидатами на повторне використання коду.
jjoelson

@jjoelson Якщо метод має значущу логіку і не перетворює якусь іншу функціональність, то я завжди пишу одиничний тест проти нього. Методи тестування блоків не мають нічого спільного з повторним використанням коду. Вам слід встановити методи тестування, щоб переконатися, що для заданих входів ви отримуєте очікувані результати послідовно та повторювано. Якщо ви помічаєте, що сфера класу забруднюється, як ви її називаєте, можливо, ваш клас порушує Принцип єдиної відповідальності.
maple_shaft

2
Більшість програмістів, яких я зустрічав, не згодні з ідеєю одиничного тестування кожного приватного методу. Це просто не варто часу, який потрібно для впровадження та підтримки тестів на всіх приватних методах.
jjoelson

3

Припустимо, у нас є метод, обчислення якого можна розділити на три досить чіткі фази. Крім того, кожен з цих етапів має значення лише для обчислення цього методу, і тому вилучення їх у нові методи не приносить нам ніякого використання коду. У чому ж полягають переваги вилучення кожної фази у її власний метод? Наскільки я можу сказати, все, що ми отримуємо, - це певна читабельність та окрема змінна область для кожної фази (що допоможе запобігти випадковому порушенню іншої фази модифікацій певної фази).

Це дві переваги. Але найважливішим, для мене, все-таки є налагодження . Якщо вам доведеться простежити цей код, набагато простіше мати змогу перейти до двох з трьох розділів і простежити лише той, який вам цікавий. Рефакторинг на більш дрібні методи дозволяє це; Теги регіонів не мають.


1
ctrl-f10- бігти до курсору
Стівен Єуріс

2

Моє особисте правило: якщо метод довший одного екрана, скажімо, 40 рядків, він занадто довгий. Якщо клас довший 500 рядків, він занадто великий. Я порушую ці правила іноді, іноді навіть великим кратним, але, як правило, шкодую про це протягом року.

Навіть метод, який становить від 20 до 30 рядків, стає більш довгим у більшості випадків. Тому я б сказав, що використання регіонів у методі зазвичай є поганою ідеєю, просто тому, що майже ніколи не було б сенсу згортати область з 20 до 30 рядків на кілька підрегіонів.

Розбиття функцій, які давно визначені цим визначенням, має принаймні дві переваги:

  1. Ви можете назвати те, що роблять ці підфункції, що роз'яснює ваше теперішнє мислення та спрощує завдання пізніших службовців.

  2. Розкладання на більш дрібні функції змушує вас передавати лише ті параметри, які потрібні меншим функціям, тому обсяг даних, які вони потребують та змінюють, дуже зрозумілий. Це передбачає, що ви не маєте доступу до та змінює стан об'єкта в сотнях різних функцій аркуша, що я також заважаю.

На мій досвід, ці правила створюють код, який легше написати, не роблячи загальних помилок, і пізніше їх можна зрозуміти та змінити, не порушуючи тонкої поведінки. Не має значення, чи є людина, яка має зрозуміти / змінити код, хтось інший, або я після того, як я все проспав і забув.

Тому я б вважав регіони методів "кодовим запахом", що вказує на те, що застосовуються нестійкі практики. Як завжди, можуть бути винятки, але вони трапляються настільки рідко, що майже не варто обговорювати їх.


2

Ось ми знову ... Є багато інших тем про програмістів, які опинилися в одній дискусії.

Ви вказуєте кілька дуже цікавих аргументів, що стосуються коротких функцій. Це не популярне твердження, але я з вами з цього приводу . Після того, як це часто обговорювалося раніше, мені здається, що дискусія зазвичай зводиться до того, чи орієнтуєтесь ви в першу чергу на правильну інкапсуляцію, чи приймаєте тест-розробку до максимуму. Це два головних претендента на те, що, здається, виявляється ще однією святою війною, і я боюся, що ми перебуваємо під слабким боком.

Однак ...

На мою думку, рішення, безумовно, не завершує "фази" в регіонах. Складання коду використовується для підмітання коду під килим. Залиште код там таким, який він є, він може нагадати вам, що ви можете пізніше переробити його! Щоб пов’язати це з аргументом у вашому запитанні: як ви зможете побачити можливість повторного використання коду, якщо ви не бачите жодного коду? Довгі сегменти коду повинні бути саме такими, шип в очах, який змушує вас переробити його.

В якості відповідної заміни для регіонів я пропоную використовувати абзаци коду, оскільки Алекс Пападімуліс називає їх у коментарі . Ви, можливо, вже використовуєте подібний підхід. Це просто блоки коду із заголовком коментаря, що пояснює, що робить весь блок. Ви маєте читабельність функцій, але можете використовувати нормально розташовані англійські речення, і ви не втрачаєте жодної інкапсуляції.


Ну, я по суті використовую регіони для розмежування абзаців коду (у регіонах можуть бути коментарі, які видно навіть при складенні коду). Мені подобається використовувати регіони для цього, оскільки це надає користувачеві можливість вибрати код, якщо вона хоче перевірити реалізацію або скласти її, якщо вона хоче бачити лише "велику картину".
jjoelson

1

Хоча я згоден, що зазвичай краще рефакторний код, ніж використовувати #region, це не завжди можливо.

Для підтвердження цього твердження дозвольте навести приклад.

class Foo : IComparable
{
  private String name;
  private int foo;
  private Bar bar;

  public int CompareTo(Foo other)
  {
#region Ascending on name
     if (this.name < other.name) return -1;
     if (this.name > other.name) return 1;
#endregion
#region Usual order on bar
     int compBar = bar.compareTo(other.bar);
     if (compBar != 0) return compBar;
#endregion
#region Descending on foo
     if (this.foo > other.foo) return -1;
     if (this.foo < other.foo) return 1;
#endregion
     return 0; // equal
  }

Регулювати регіони легко, щоб змінити порядок або розширити, коли додаткові поля додаються до класу. У будь-якому разі потрібні коментарі, щоб швидко побачити, як передбачається працювати замовлення. І найбільше: я не бачу, як рефакторинг допоможе читати в цьому випадку.

Однак ці винятки рідкісні. Зазвичай можливий рефактор, як говорять багато інших відповідей.


0

Я не думаю, що є однозначна відповідь, чому певні люди чи команди вирішують не використовувати, #regionале якщо мені доведеться перерахувати декілька, це були б основні причини, які я бачив.

  • Назви та порядок регіонів чіткіше впорядковують і впорядковують код, який застосовує певний стиль, який може стати джерелом суперечок та скоромовки.
  • Більшість концепцій не чітко вписуються в невелику кількість регіонів. Як правило, ви починаєте з регіонів, використовуючи модифікатори доступу public , private, можливо, за типом учасника (наприклад, метод, властивість, поле), але тоді як щодо конструкторів, які охоплюють ці модифікатори, статичних різновидів усіх цих, захищених членів, внутрішніх членів, захищених внутрішніх члени, неявні члени інтерфейсу, події тощо. Зрештою, ви мали б найкраще розроблені класи, де у вас є єдиний або метод у кожному регіоні через детальну категоризацію.
  • Якщо ви в змозі зберегти прагматичні і лише групувати речі, наприклад, три-чотири послідовні типи регіонів, то це може спрацювати, але яке значення це насправді додає? Якщо ви добре розробляєте класи, вам дійсно не потрібно просіювати сторінки коду.
  • Як натякано вище, регіони можуть приховувати некрасивий код, який може здатися менш проблематичним, ніж це є. Збереження його як болю в очах може допомогти вирішити його.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.