Чи слід витягувати певну функціональність у функції і чому?


29

У мене є великий метод, який виконує 3 завдання, кожне з яких можна витягти в окрему функцію. Якщо я зроблю додаткові функції для кожного з цих завдань, чи зробить мій код кращим чи гіршим і чому?

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

Чи потрібно це робити до того, як я написав увесь код, або повинен залишити його, поки все не буде зроблено, а потім витягнути функції?


19
"Я залишаю це, поки все не буде зроблено", як правило, є синонімом "Це ніколи не буде зроблено".
Ейфорія

2
Це взагалі вірно, але також пам’ятайте протилежний принцип YAGNI (який не застосовується в даному випадку, оскільки він вам вже потрібен).
поштовх


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

Відповіді:


35

Це книга, на яку я часто посилаюсь, але тут я повторююсь: Чистий код Роберта К. Мартіна , глава 3, «Функції».

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

Ви вважаєте за краще читати функцію +150 рядків або функцію, яка викликає 3 +50 лінійних функцій? Я думаю, що я віддаю перевагу другому варіанту.

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

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

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

Нарешті, і це не суперечить тому, що я щойно написав, запитайте себе, чи дійсно вам потрібно робити цей рефакторинг, ознайомтеся з відповідями " Коли робити рефактор ?" (також шукайте ТАКИХ запитань про "рефакторинг", є більше, а відповіді цікаво читати)

Чи потрібно це зробити перед тим, як написати увесь код, або слід залишити його, поки все не буде зроблено, а потім витягнути функції?

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


10
Власне, Боб Мартін кілька разів показав, що він надає перевагу 7 функціям з 2 до 3 рядків над однією функцією з 15 рядками (див. Тут sites.google.com/site/unclebobconsultingllc/… ). І саме там чимало навіть досвідчених дияворів чинять опір. Особисто я вважаю, що багато хто з цих «досвідчених дияволів» просто не можуть прийняти, що вони все-таки можуть вдосконалитись на такій базовій справі, як побудова абстракцій з функціями після> 10 років кодування.
Док Браун

+1 лише для посилання на книгу, яка, на мою скромну думку, повинна знаходитися на полицях будь-якої програмної компанії.
Фабіо Марколіні

3
Я, можливо, перефразовую тут, але фраза з тієї книги, яка майже щодня лунає в моїй голові, - "кожна функція повинна робити лише одне, і робити це добре". Тут здається особливо актуальним, оскільки ОП заявила, що "моя головна функція виконує три речі"
wakjah

Ви абсолютно праві!
Джалайн

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

13

Так, очевидно. Якщо легко побачити і розділити різні "завдання" однієї функції.

  1. Читання - Функції з добрими іменами чітко визначають, який код робить без необхідності читати цей код.
  2. Повторність використання - Простіше використовувати функцію, яка робить одну річ у декількох місцях, ніж функцію, яка робить речі, які вам не потрібні.
  3. Заповітність - простіше перевірити функцію, яка має одну визначену "функцію", та, яка має багато з них

Але з цим можуть виникнути проблеми:

  • Нелегко зрозуміти, як розділити функцію. Це може зажадати спочатку рефакторингу внутрішньої частини функції, перш ніж перейти до розділення.
  • Функція має величезний внутрішній стан, який передається навколо. Зазвичай це вимагає отримання якогось рішення OOP.
  • Важко сказати, яку функцію слід виконувати. Тест перевіряйте та рефакторируйте, поки не дізнаєтесь.

5

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

Чи добре розділяти довгі функції та методи на більш дрібні, навіть якщо їх більше не буде викликано?

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

Про:

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

Контраст:

  • Це займає багато сторінок вашого екрану;
  • Читати його потрібно багато часу;
  • Запам'ятати всі різні кроки непросто;

Тепер давайте уявимо собі поділити довгу функцію на кілька підфункцій і розглянути їх із більш широкою перспективою.

Про:

  • За винятком функцій залишення, кожна функція описує словами (назви підфункцій) різні кроки;
  • Для читання кожної окремої функції / підфункції потрібно дуже короткий час;
  • Зрозуміло, які параметри та змінні впливають на кожну підфункцію (розділення питань);

Контраст:

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

Обидва рішення мають «проти» і «проти». Фактичним найкращим рішенням буде створення редакторів, які дозволяють розширити, вбудувати та на повну глибину кожну функцію викликати у своєму вмісті. Що б зробило розбиття функцій на підфункції єдиним найкращим рішенням.


2

Для мене є чотири причини, щоб витягти кодові блоки у функції:

  • Ви його повторно використовуєте : ви просто скопіювали блок коду в буфер обміну. Замість того, щоб просто вставити його, поставте його у функцію та замініть блок на виклик функції з обох сторін. Отже, коли вам потрібно змінити цей блок коду, вам потрібно змінити лише одну функцію замість того, щоб змінювати код у кількох місцях. Тому щоразу, коли ви копіюєте блок коду, ви повинні зробити функцію.

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

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

  • Ви хочете самодокументувати код : Отже, замість того, щоб писати рядок коментарів через блок коду, який узагальнює те, що він робить, ви витягаєте всю річ у функцію та називаєте її тим, що ви б написали в коментарі. Хоча я і не прихильник цього, тому що мені подобається виписувати назву використовуваного алгоритму, причина, чому я вибрав цей алгоритм, і т.д. Імена функцій були б занадто довгими тоді ...


1

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

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


0

Убік: я написав це у відповідь на запитання Далліна (зараз закрите), але я все ще вважаю, що це може бути корисним для когось, так ось


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

Розмежування проблем

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

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

Скажімо, у JavaScript у вас є функція getMyData (), яка 1) створює мильне повідомлення з параметрів, 2) ініціалізує посилання на службу, 3) викликає службу з мильним повідомленням, 4) аналізує результат, 5) повертає результат. Здається розумним, я писав цю точну функцію багато разів - але насправді це можна розділити на 3 приватні функції, включаючи код для 3 і 5 (якщо це так), оскільки жоден інший код не відповідає безпосередньо за отримання даних із служби .

Покращений досвід налагодження

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

  • Отримати мої дані
    • Побудувати мильне повідомлення
    • Ініціалізація довідкової служби
    • Синтаксична відповідь служби - ПОМИЛКА

було б набагато цікавіше, ніж з'ясувати, що при отриманні даних сталася помилка. Але деякі інструменти ще корисніші для налагодження детальних дерев викликів, ніж це, візьмемо для прикладу Microsofts Canvas Debugger Canvas .

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

FYI - це одна з тих речей "робити так, як я говорю не так, як я", зберігати код атомарним є безглуздим, якщо твій безжально узгоджується з ним ІМХО, чого я не є.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.