Чи існує таке поняття, як занадто багато приватних функцій / методів?


63

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

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

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

[Редагувати]

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

Проста відповідь: це відчуття кишки.

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

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

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

Тож я прошу цього просвітити себе, щоб вибрати правильний шлях.

[Редагувати]

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

Перша версія:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Друга версія:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

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

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

Тепер ось моє запитання: який? Я вибираю перший чи другий?


1
"занадто багато функцій - це погана практика." Чому? Будь ласка, оновіть своє запитання, щоб пояснити, чому це здається вам поганим.
S.Lott

Я рекомендую ознайомитися з поняттям "класової згуртованості" - Боб Мартін обговорює це в "Чистому кодексі".
JᴀʏMᴇᴇ

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

Якщо хтось може сказати мені, чому "ця відповідь не корисна", я буду вдячний. Завжди можна навчитися. :-)
Пабло Н

@PabloH Відповідь, яку ви надали, дає гарне спостереження та розуміння ОП, проте насправді це не відповідає на поставлене питання. Я перемістив його в поле коментарів.
maple_shaft

Відповіді:


36

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

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

Це означає , що кожен , хто читає ваш код, коли вони потрапляють в startTheEngine()метод в вашому коді, може ігнорувати все нижні деталі рівня , такі як openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), і все іншої складної, маленької, функції , які повинні бути запущені, щоб полегшити набагато більшу, більш абстрактну функцію Росії startTheEngine().

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

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

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

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


3
Отже, ти вважаєш, що добре "інкапсуляція" зробити логіку доступною для всього класу, який викликається лише в одному місці?
Стівен Євріс

9
@Steven Jeuris: Поки ці функції стають приватними, я не бачу проблем з цим; насправді я вважаю, що, як зазначено у цій відповіді, легше читати. Набагато простіше зрозуміти публічну функцію високого рівня, яка викликає лише ряд приватних функцій низького рівня (які, звичайно, були названі відповідним чином). Звичайно, весь цей код міг би бути включений у функцію високого рівня, але тоді знадобиться більше часу, щоб зрозуміти код, оскільки вам доведеться переглядати його набагато більше в одному і тому ж місці.
габлін

2
@gablin: приватні функції все ще доступні для всього класу. У цій (мабуть, суперечливій) статті я розповідаю про те, як втрачається «глобальна читабельність», коли вона має занадто багато функцій. Насправді загальноприйнятий принцип, окрім того, що функції повинні виконувати лише одне, чи не повинно бути занадто багато. Ви можете отримати "локальну читабельність", розділяючи лише на короткий час, що також можна досягти простими коментарями та "абзацами коду". Гаразд, код повинен бути самодокументованим, але коментарі не застаріли!
Стівен Євріс

1
@Steven - Що простіше зрозуміти? myString.replaceAll(pattern, originalData);або я вставляю весь метод zamjeAll у свій код, включаючи масив резервного char, який представляє базовий рядок об'єкта щоразу, коли мені потрібно використовувати функціональність String?
jmort253

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

22

Так і ні. Основний принцип: метод повинен робити одне і лише одне

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

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


1
Приємно читати, що не всі сліпо слідують правилу «одне»! ; p Приємна відповідь!
Стівен Євріс

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

1
Це теж нормально в деяких випадках, коли ви дійсно не можете уникнути великого методу. Наприклад, якщо у вас є метод Initialize, ви можете викликати його InitializeSystems, InitializePlugins тощо. Завжди слід перевіряти себе, що рефакторинг не потрібен
Хом

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

3
@gablin: ще одна перевага , розглянути наступні питання: коли я ламаю речі на більш дрібні функції, не думаю , що «вони служать тільки одну функцію», думаю , «вони служать тільки одну функцію зараз ». Було кілька випадків, коли великі методи повторного факторингу економили мені багато часу при написанні інших методів у майбутньому, оскільки менші функції були більш загальними та багаторазовими.
GSto

17

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


10

Чудова відповідь jmort253 надихнула мене на відповідь "так, але" ...

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

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


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

8

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

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


7

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

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


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

4

Уривок із чистого кодексу Р. Мартіна (стор. 176):

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


3

Деякі варіанти:

  1. Це добре. Просто назвіть приватні методи, а також назвіть ваші загальнодоступні методи. І назвіть свої публічні методи добре.

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


1

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

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

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

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


0

Чи існує таке поняття, як занадто багато приватних функцій / методів?

Так.

У Python поняття "приватне" (як його використовують C ++, Java та C #) насправді не існує.

Існує "своєрідна" конвенція про іменування, але це все.

Приватне може призвести до важкого для тестування коду. Він також може порушити "Принцип відкритого закриття", зробивши код просто закритим.

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


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