Чи слід розміщувати функції, які використовуються лише в одній іншій функції, в межах цієї функції?


30

Зокрема, я пишу на JavaScript.

Скажімо, моя основна функція - це функція A. Якщо функція A робить кілька викликів до функції B, але функція B не використовується більше ніде, то я повинен просто розмістити функцію B у функції A?

Це хороша практика? Або я все-таки повинен розміщувати функцію B в тій же області, що і функція A?

Відповіді:


32

Я взагалі прихильник вкладених функцій, особливо в JavaScript.

  • В JavaScript єдиний спосіб обмежити видимість функції - це вклавши її всередину іншої функції.
  • Допоміжна функція - це приватна деталь реалізації. Розміщення його в однаковій мірі схоже на оприлюднення приватних функцій класу.
  • Якщо він виявляється більш загальним, його легко висунути, тому що ви можете бути впевнені, що помічник наразі використовується лише цією однією функцією. Іншими словами, це робить ваш код більш згуртованим.
  • Ви можете часто усунути параметри, що робить підпис функції та реалізацію менш багатослівним. Це не те саме, що робити змінні глобальними. Це більше схоже на використання члена класу в приватному методі.
  • Ви можете надати функції помічників кращі, простіші імена, не турбуючись про конфлікти.
  • Функцію помічника простіше знайти. Так, є інструменти, які можуть вам допомогти. Ще простіше просто трохи перенести очі вгору по екрану.
  • Код, природно, утворює своєрідне дерево абстракції: декілька загальних функцій в корені, розгалужуючись на кілька деталей реалізації. Якщо ви використовуєте редактор із складанням / згортанням функції, вкладення створює ієрархію тісно пов'язаних функцій на одному рівні абстракції. Це дозволяє дуже легко вивчити код на потрібному рівні та приховати деталі.

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


14

Ви повинні поставити це на глобальний обсяг з кількох причин.

  • Введення функції помічника в абонента збільшує довжину абонента. Довжина функції майже завжди є негативним показником; короткі функції легше зрозуміти, запам’ятати, налагодити і підтримувати.

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

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

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

Редагувати

Це виявилося більш суперечливим питанням, ніж я думав. Для уточнення:

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

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


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

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

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

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

1
"Виконання ролі класів" намагається написати JS так, ніби це щось інше. Що таке "роль занять"? Перевірка типу? Модульність? Поетапна компіляція? Спадщина? Питання передбачає підбір результатів, тому я припускаю, що ви маєте на увазі грубі правила класифікації занять. Ось лише здача долара: як слід оцінювати заняття ? PHP робить їх глобальними, що не забезпечує інкапсуляції. Python охоплює класи лексично, але якщо ви знаходитесь в блоці з лексичним кодом, навіщо обгортати все в інший блок, що охоплює класи? JS не має такої надмірності: просто використовуйте стільки лексичного обсягу, скільки вам потрібно.
Варбо

8

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

var functionA = (function(){
    function functionB() {
        // do stuff...
    }

    function functionA() {
        // do stuff...
        functionB();
        // do stuff...
    }

    return functionA;
})();

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

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


0

Визначення функції B у функції A надає функції B доступ до внутрішніх змінних A.

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

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


-1

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


-3

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


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