Коли функція занадто довга? [зачинено]


130

35 рядків, 55 рядків, 100 рядків, 300 рядків? Коли ви повинні почати розбивати його на частини? Я запитую, бо у мене функція з 60 рядків (включаючи коментарі) і думала про її розрив.

long_function(){ ... }

в:

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

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

Коли ви розділите функцію на більш дрібні? Чому?

  1. Методи повинні робити лише одну логічну річ (подумати про функціональність)
  2. Ви повинні мати можливість пояснити метод в одному реченні
  3. Він повинен відповідати висоті вашого дисплея
  4. Уникайте зайвих накладних витрат (коментарі, які вказують на очевидне ...)
  5. Тестування одиниць легше для малих логічних функцій
  6. Перевірте, чи можна частину функції використовувати повторно іншими класами чи методами
  7. Уникайте надмірної міжкласової зв'язку
  8. Уникайте глибоко вкладених структур управління

Дякую всім за відповіді , відредагуйте список та проголосуйте за правильну відповідь, яку я оберу;)

Я зараз переглядаю ці ідеї на увазі :)


У вашому питанні є помилка друку, я думаю, ви мали на увазі «Коли функція занадто довга?».
Том

1
Ви неправильно запитуєте це питання, ставлячи його у вигляді рядків коду. Визначальні коефіцієнти не вимірюються у рядках коду.
dkretz

це питання може ускладнитися залежно від коду та мови. можливо, ви можете опублікувати його.
Рей Тайек

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

Відповіді:


75

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

Однак, "щось робити" все-таки може бути досить багато рядків, тому я не впевнений, що ряд рядків є правильним показником, який слід використовувати :)

Редагувати: Це єдиний рядок коду, який я надіслав по роботі на минулому тижні (щоб довести свою думку. Це не те, що я маю звичку :)) - Я, звичайно, не хотів би 50-60 цих поганих хлопців у моєму методі : D

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();

1
LOL Добре, що я міг би видалити всі пробіли у своєму методі, і це була б лише одна дуже довга лінія та не довга функція. Виконуючи одне, це, мабуть, відповідь, ніж, дякую

@Movaxes Цей фрагмент коду, який я опублікував, - це одне твердження, але не просто багато рядків на одному рядку ... там немає напівколонок :) Я міг би кожен раз розширювати GetResources (), щоб зробити це ще більше злом: P
Стівен Роббінс

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

Я пам’ятаю, що в старих журналах (я говорю BBC Micro Old) вони мали «10 лінійних програм», які мали по декілька заяв на кожному рядку, аж до максимальної довжини, з якою може справитись BBC. Вони завжди були правильними болями набрати: D
Стівен Роббінс

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

214

Ось список червоних прапорів (не в конкретному порядку), які можуть вказувати на те, що функція занадто довга:

  1. Глибоко вкладені структури управління : наприклад, для-циклів 3 рівня глибокого або навіть просто 2 рівня глибиною з вкладеними операторами if, які мають складні умови.

  2. Занадто багато параметрів, що визначають стан : Під параметром , що визначає стан , я маю на увазі параметр функції, який гарантує певний шлях виконання через функцію. Отримайте занадто багато параметрів цього типу, і у вас є комбінаторний вибудок шляхів виконання (це зазвичай відбувається в тандемі з №1).

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

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

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

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

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

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


1
Гарний пост! Дуже детерміновано
Чак Конвей

2
@PedroMorteRolo Саме так. Стандартний API не завжди є моделлю елегантності. Крім того, більша частина Java API була розроблена з інтимними знаннями про Java-компілятор та JVM, отже, у вас є міркування щодо продуктивності, які можуть пояснити це. Я визнаю, що критичні розділи коду, які не можуть витратити ні одну мілісекунду, можуть порушити деякі з цих правил, але це завжди слід вважати особливим випадком. Витрата додаткового часу на розробку - це початкова інвестиція, яка дозволяє уникнути майбутніх (потенційно калічних) боргів за технологію.
Ryan Delucchi

2
До речі .. Я до думки, що довгі методи - є-погано-евристичні також стосуються класів. ІМХО, тривалі заняття погані, оскільки вони, як правило, порушують єдиний принцип відповідальності. Було б весело мати укладачів, які б випробовували попередження як для довгих занять, так і для методів ....
Педро Роло

3
@PedroMorteRolo Я безумовно погоджуюся на це. Крім того, великі класи, швидше за все, мають більш змінний стан: що призводить до коду, який дуже важко підтримувати.
Райан Делуччі

1
Найкраща відповідь. Ще одна гарна підказка: як виглядають коментарі до коду? Кількість разів я натрапив на чий - то код з лінії , як: // fetch Foo's credentials where Bar is "uncomplete". Це майже напевно ім'я функції саме там, і його слід відокремити. Можливо, хоче переробити щось на зразок: Foo.fetchCredentialWhereBarUncomplete()
Джей Едвардс

28

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


18

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

Те, що ви насправді не хочете робити, - це копіювати та вставляти кожні 10 рядків своєї тривалої функції на короткі функції (як підказує ваш приклад).


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

"робити одну річ" може бути, а може бути і невірною, залежно від деталізації. Якщо функція множить матрицю, це добре. Якщо функція будує віртуальний автомобіль - це "одне", але це теж дуже велика річ. Кілька функцій можна використовувати для побудови автомобіля, складових за компонентами.
void.pointer

16

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

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

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

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

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

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


2
+1 "Використовуваний показник - це функціональність, а не рядки коду"
Коді Пірсолл

Ще одна важлива метрика - кількість рівнів блокування вкладень. Зведіть до мінімуму. Порушення функції на більш дрібні частини часто допомагає. Також можуть допомогти інші речі, такі як багаторазова віддача.
користувач2367418

10

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

Чому я можу порушити функцію:

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

3
Хороші моменти, я погоджуюся, також якщо вам доведеться назвати функцію DoThisAndThisAndAlsoЦе, мабуть, це робить занадто багато. дякую :)

2
Ви просто вийшли з ладу з цим товаришем. 60 рядків завжди буде занадто багато. Я б сказав, що якщо ви закриваєтесь на 10 рядках, ви, ймовірно, близько до межі.
willcodejavaforfood

Але інша функція все ще викликає ці функції і є по суті тією самою DoThisAndThisAndAlsoThisфункцією, але з великою кількістю абстракцій, які вам все-таки доведеться називати якось
Тимо Хуовінен

6

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


4
... поки встановили розмір шрифту 5?
EricSchaefer

5

Розмір приблизно за розміром екрана (тому займіться великим широким екраном та поверніть його ... :-)

Жарт убік, одна логічна річ на функцію.

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

/ Йохан


Хороший момент про тестування блоку :)

5

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

function build_address_list_for_zip($zip) {

    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

набагато приємніше:

function fetch_addresses_for_zip($zip) {
    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_zip($zip) {

    $addresses = fetch_addresses_for_zip($zip);

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

Цей підхід має дві переваги:

  1. Кожен раз, коли вам потрібно отримати адреси для певного поштового коду, ви можете використовувати легко доступну функцію.

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

[З іншого боку (заперечу, я говорив вам про це, навіть під тортурами): Якщо ви багато читаєте про оптимізацію PHP, ви можете отримати ідею зберегти кількість функцій якомога меншим, оскільки виклик функції дуже, дуже дорого в PHP. Я не знаю про це, оскільки ніколи не робив жодних орієнтирів. У такому випадку вам, мабуть, було б краще не дотримуватися жодного з відповідей на ваше запитання, якщо заявка дуже "чутлива до продуктивності" ;-)]


дякую хороший приклад :) Я буду google за орієнтири функцій у php

5

Погляньте на цикломатику МакКейба, в якій він розбиває свій код на графік, де: «Кожен вузол у графі відповідає блоку коду в програмі, де потік є послідовним, а дуги відповідають гілкам, взятим у програмі. "

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

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

Щоб визначити, якого розміру мають бути ваші методи щодо кількості блоків на метод, одне запитання, яке ви можете задати собі: скільки методів я повинен мати для мінімізації максимальної потенційної кількості залежностей (MPE) між усіма блоками?

Ця відповідь дається рівнянням. Якщо r - кількість методів, що мінімізує MPE системи, а n - кількість блоків у системі, то рівняння дорівнює: r = sqrt (n)

І може бути показано, що це дає також кількість блоків для методу, яка також буде sqrt (n).


4

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

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

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


2

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

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

Ви впевнені, що там немає деталей, які були б корисні в іншому місці? Що це за функція?


Функція створює кеш-файл із шаблону на основі URL-адреси, наприклад post_2009_01_01.html з URL-адреси / post /

2

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


2

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


Мені подобається коментувати свій код, здебільшого не для мене, а для інших, що виключає безліч питань щодо того, де була визначена змінна $, але мені також подобається, що код має пояснення. Чи брешуть коментарі?

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

Я щойно натрапив на цю відповідь: stackoverflow.com/questions/406760/… заявивши, що "Більшість коментарів у коді насправді є згубною формою дублювання коду". Також - довгий ряд коментарів.
Олаф Кок

1

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

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

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


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

1

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

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

Див . Оптимальний розмір класу для об'єктно-орієнтованого програмного забезпечення для подальшого обговорення.


дякую за посилання, читаючи :)

1

Я раніше писав 500 лінійних функцій, однак це були просто великі заяви про перемикання для декодування та відповіді на повідомлення. Коли код для одного повідомлення став складнішим, ніж для одного if-then-else, я витяг його.

По суті, хоча функція складала 500 ліній, незалежно підтримувані регіони в середньому складали 5 ліній.


1

Я зазвичай використовую тестовий підхід до написання коду. У такому підході розмір функції часто пов'язаний із деталізацією ваших тестів.

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

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

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


1

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

Кожен для циклу, якщо оператор тощо, тоді не розглядається як гілка методу виклику.

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

Якщо функція / метод має лише три гілки, вона отримає три на цій метриці, що дуже добре.

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

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


0

Я підозрюю, що ви знайдете багато відповідей на це.

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

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

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


0

якщо припустити, що ви робите одне , тривалість буде залежати від:

  • що ти робиш
  • якою мовою ви користуєтесь
  • скільки рівнів абстракції вам потрібно розібратися в коді

60 рядків може бути занадто довгим, або це може бути правильним. я підозрюю, що це може бути занадто довгим.


Я роблю кешування в PHP, так, напевно, 60 рядків занадто багато, рефакторинг ...

0

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


0

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


0

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

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