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


23

Маючи справу зі складними алгоритмами на мовах із підтримкою вкладених функцій (таких як Python та D), я часто пишу величезні функції (оскільки алгоритм складний), але пом'якшую це за допомогою вкладених функцій для структурування складного коду. Чи величезні (100+ рядкові) функції все ще вважаються злими, навіть якщо вони добре структуровані внутрішньо за допомогою використання вкладених функцій?

Редагувати: Для тих, хто вам не відомий Python або D, вкладені функції на цих мовах також дозволяють отримати доступ до зовнішньої області функцій. У D цей доступ дозволяє мутувати змінні у зовнішній області. У Python це дозволяє лише читання. У D ви можете явно відключити доступ до зовнішньої області вкладеної функції, оголосивши її static.


5
Я регулярно пишу 400+ лінійних функцій / методів. Поклав десь заяву про перемикання випадку десь :)
Callum Rogers

7
@Callum Rogers: У Python замість того, щоб мати заяву про перемикання випадку, ви б використовували словник пошуку / відображення. Я вважаю, що вони є вищими, ніж те, що мають випадок із 500-кратним переключенням справи. Вам все ще потрібно 500 рядків визначень словника (як альтернатива, в Python ви можете використовувати відображення для динамічного їх перерахування), але визначення словника - це дані, а не код; і є набагато менше труднощів щодо визначення великого визначення даних, ніж визначення великої функції. Крім того, дані є більш надійними, ніж код.
Лі Лі Райан

Я щоразу, коли я бачу функції з великою кількістю LOC, змушує замислюватися, яка мета модульності. Простіше слідувати логіці та налагоджувати менші функції.
Aditya P

Стародавня і знайома мова Паскаля також дозволяє отримати доступ до зовнішньої області, і це також розширює функцію вкладеної функції в GNU C. (Ці мови дозволяють лише нижчі фунарги, однак: тобто аргументи, які є функціями, що несуть сферу застосування, можуть бути лише передано вниз і не повернулося, що вимагало б повного лексичного супроводу закриття).
Каз

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

Відповіді:


21

Завжди пам’ятайте правило, функція робить одне і робить це добре! Якщо ви можете це зробити, уникайте вкладених функцій.

Це перешкоджає читанню та тестуванню.


9
Я завжди ненавиджу це правило, тому що функція, яка виконує "одну річ", коли дивиться на високому рівні абстракції, може виконувати "багато речей", якщо дивитися на нижчий рівень. Якщо застосовувати неправильно, правило "одне" може призвести до надмірно дрібного API.
dimimcha

1
@dsimcha: Яке правило не можна застосовувати неправильно і призводить до проблем? Коли хтось застосовує "правила" / позиції повз точку, де вони корисні, просто виведіть інше: всі узагальнення помилкові.

2
@Roger: Обґрунтована скарга, за винятком того, що правило IMHO часто неправильно застосовується на практиці для виправдання надмірно дрібнозернистих, переобладнаних API. Це має конкретні витрати, оскільки API стає складнішим і більш детальним для використання у простих, звичайних випадках використання.
dimimcha

2
"Так, правило неправильно застосовується, але правило неправильно застосовується!"? : P

1
Моя функція робить одне, і це робить дуже добре: а саме займати 27 екранів. :)
Каз

12

Деякі стверджують, що короткі функції можуть бути більш схильними до помилок, ніж довгі функції .

Card and Glass (1990) зазначають, що складність дизайну дійсно передбачає два аспекти: складність усередині кожного компонента та складність взаємозв'язків між компонентами.

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

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


Ви читали "Чистий код"? Це обговорюється досить довго. amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/…
Мартін Вікман

9

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

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

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


3
Вам просто потрібно роздрукувати його на матричному принтері - дивіться, 10 метрів коду все одночасно :)

Або придбайте монітор з більшою роздільною здатністю
frogstarr78

І використовувати його у вертикальній орієнтації.
Кальмарій

4

Навіщо використовувати вкладені функції, а не звичайні зовнішні функції?

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

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}

2
Дві можливі причини: захаращення простору імен і те, що я називаю "лексична близькість". Це можуть бути добрими чи поганими причинами, залежно від вашої мови.
Френк Ширар

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

3

У мене в даний момент немає книги перед собою (для цитування), але згідно з Code Code, "солодощі" для функції довжина становила близько 25-50 рядків коду згідно з його дослідженнями.

Бувають випадки, коли нормально мати довгі функції:

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

Часи, коли не нормально мати довгі функції:

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

Суть полягає в тому, що технічне обслуговування має бути одним з найвищих пріоритетів у вашому списку. Якщо інший розробник не може переглянути ваш код і отримати «суть» того, що робить код, менше ніж за 5 секунд, ур-код не дає достатньо «метаданих», щоб сказати, що він робить. Інші розробники повинні мати можливість розповісти, що робить ваш клас, просто переглянувши браузер об’єктів у вибраному вами IDE, а не прочитавши 100+ рядків коду.

Менші функції мають такі переваги:

  • Переносимість: набагато простіше переміщувати функціональність (або в межах класу, що займається рефакторингом, на інший)
  • Налагодження: Коли ви дивитеся на стек стеження, набагато швидше точно визначити помилку, якщо ви дивитесь на функцію з 25 рядками коду, а не 100.
  • Читання - Назва функції вказує на те, що робиться весь блок коду. Розробник вашої команди, можливо, не захоче прочитати цей блок, якщо він не працює з ним. Плюс до цього, в більшості сучасних IDE ще один розробник може краще зрозуміти, чим займається ваш клас, читаючи імена функцій у браузері об’єктів.
  • Навігація - більшість IDE дозволять шукати назви функцій. Крім того, більшість сучасних IDE мають можливість переглядати джерело функції в іншому вікні, це дає можливість іншим розробникам дивитись на вашу довгу функцію на 2 екранах (якщо багатомонітори), а не змушувати їх прокручувати.

Список продовжується .....


2

Відповідь - це залежить, проте ви, мабуть, повинні перетворити це на клас.


Якщо ви не пишете в OOPL, в такому випадку, можливо, немає :)
Френк Шірар

dsimcha згадував, що вони використовують D та Python.
frogstarr78

Заняття занадто часто милі для людей, які ніколи не чули про такі речі, як лексичні закриття.
Каз

2

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

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

Я вважаю, що функція повинна робити свою річ. Робити інші речі - це те, що виконують інші функції. Отже, якщо у вас є 200-лінійна функція "Робити свою річ", і вона все протікає, це добре.


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

1

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

Редагувати: Я не бачив нічого про вкладені функції. Особисто я б не користувався ними. Я б замість цього використовував звичайні функції.


1

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

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

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

У деяких мовах цю проблему легко усунути: локальний розтягнення коду може бути зафіксований у власному блоці, як-от "{...}", в межах якого будь-які нововведені змінні видимі лише для цього блоку. У деяких мовах, таких як Python, відсутня ця функція, і в цьому випадку локальні функції можуть бути корисними для застосування регіонів меншого обсягу.


1

Ні, багатосторінкові функції не бажані, і вони не повинні проходити огляд коду. Раніше я писав і довгі функції, але прочитавши « Рефакторинг» Мартіна Фаулера , я зупинився. Довгі функції важко правильно написати, важко зрозуміти і важко перевірити. Я ніколи не бачив функції навіть з 50 рядків, які не було б легше зрозуміти і перевірити, якби її розділити на набір менших функцій. У багатосторінковій функції майже напевно є цілі класи, які слід враховувати. Важко бути більш конкретним. Можливо, ви повинні опублікувати одну із своїх довгих функцій до перегляду коду, і хтось (може, я) може показати вам, як її вдосконалити.


0

Коли я програмую в python, мені подобається відступати після того, як я написав функцію, і запитаю себе, чи дотримується вона "Zen of Python" (введіть "імпортувати це" у вашому інтерпретаторі python):

Красиве краще, ніж потворне.
Явне краще, ніж неявне.
Простий - краще, ніж складний.
Комплекс краще, ніж складний.
Квартира краще, ніж вкладена.
Розріджений краще, ніж щільний.
Читання рахується.
Особливі випадки недостатньо спеціальні для порушення правил.
Хоча практичність перемагає чистоту.
Помилки ніколи не повинні проходити мовчки.
Якщо тільки явно не замовкли.
В умовах неоднозначності відмовтеся від спокуси здогадатися.
Повинно бути один - і бажано лише один - очевидний спосіб це зробити.
Хоча спочатку це може бути не очевидним, якщо ви не голландці.
Зараз краще, ніж ніколи.
Хоча ніколи не буває краще, ніж правильнозараз.
Якщо реалізацію важко пояснити, це погана ідея.
Якщо реалізацію легко пояснити, це може бути хорошою ідеєю.
Простори імен - це чудова ідея - давайте зробимо більше таких!


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

0

Покладіть їх в окремий модуль.

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

  1. Покладіть їх у модуль із лише однією "загальнодоступною" функцією.
  2. Поставте їх у клас із лише однією "загальнодоступною" (статичною) функцією.
  3. Вкладіть їх у функції (як ви описали).

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

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