Функції, які викликають лише інші функції. Це хороша практика?


13

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

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

Мені б хотілося розбити ці великі функції на менші шматки, але я боюся, що у мене просто з’являться десятки функцій, які не використовуються повторно, і подібна функція "Зроби все тут", єдиною роботою якої є викликати всі ці менші функції, як-от так:

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

Або, якщо ми підемо на крок далі:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

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

Думки?


Врешті-решт ... це читабельніше? Так, це краще рішення.
Сильванаар

Відповіді:


18

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

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

Зауважте, що це невдалий вибір імен:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

Ви бачите callAllSection1Functions, це ім'я, яке насправді не дає абстракції, оскільки воно насправді не говорить про те, що він робить, а скоріше, як це робить. Її слід називати processSection1замість того, що має значення в контексті.


6
Я принципово погоджуюся з цією відповіддю, за винятком того, що я не можу зрозуміти, як "processSection1" є значущою назвою. Це практично еквівалентно "callAllSection1Functions".
Ерік Кінг

2
@EricKing: callAllSection1Functionsпротікає детальна інформація про реалізацію. З зовнішньої точки зору, не важливо, чи processSection1відводить свою роботу на "всі розділи1 функції", чи це вона робить безпосередньо, чи використовує піксі-пил для виклику єдинорога, який буде виконувати фактичну роботу. Важливо лише те, що після виклику до розділуprocessSection1 , розділ1 можна вважати обробленим . Я погоджуюся, що обробка - це загальне ім'я, але якщо ви маєте високу згуртованість (до якої завжди слід прагнути), ваш контекст, як правило, досить вузький, щоб роз'єднати його.
back2dos

2
Я мав на увазі лише те, що методи під назвою "processXXX" - майже завжди жахливі, марні назви. Усі методи "обробляти" речі, тому називати їх "processXXX" приблизно так само корисно, як іменування "doSomethingWithXXX" або "manipulateXXX" тощо. Майже завжди є краща назва методів під назвою "processXXX". З практичної точки зору, це так само корисно (марно), як "callAllSection1Functions".
Ерік Кінг

4

Ви сказали, що використовуєте C #, тому мені цікаво, чи можна було б побудувати структуровану ієрархію класів, скориставшись тим, що ви використовуєте об'єктно-орієнтовану мову? Наприклад, якщо є сенс розділити звіт на розділи, мати Sectionклас та розширити його на конкретні вимоги клієнта.

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


3

Це залежить. Якщо всі створені вами функції - це справді одна одиниця роботи, то це добре, якщо вони є лише поганими лініями 1-15, 16-30, 31-45 своєї функції виклику, що погано. Довгі функції не є злими, якщо вони роблять одне, якщо у вас є 20 фільтрів для вашого звіту, які мають перевірочну функцію, яка перевіряє всі двадцять, буде довго. Це просто чудово, розбиваючи його на фільтр або групи фільтрів, це просто додає додаткових складностей без реальної вигоди.

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


3
На мій досвід, довгі функції - це зло.
Бернард

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

3

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

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

Подумайте про клас ReportBuilder, куди можна передати дані звіту. Якщо ви можете розбити ReportBuilder на окремі класи, зробіть це також.


2
Процедури служать ідеально хорошою функцією.
DeadMG

1

Так.

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

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


1

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

processSection1HeaderData();
processSection2HeaderData();

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


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

1

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

Так, так, це гарний стиль - це може здатися дивним, але, враховуючи обмежене повторне використання опису, я б рекомендував подумати над тим, ЩО ДОПОМОГАТИ повторне використання. Будьте впевнені, що використання справді однакове, а не просто збіг однакове. Спроба впоратися з усіма справами в одному блоці часто полягає в тому, як ви стикаєтеся з великою невдалою функцією в першому місці.


0

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

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


Так, я працюю в C #. Цілком можливо, що я зможу повторно використовувати деякі функції, але для багатьох цих звітів різні розділи мають дуже різні дані та макети (вимога замовника).
Ерік К.

1
+1, крім регіонів, що пропонують. Я б не використовував регіони.
Бернард

@ Бернар - Якась конкретна причина? Я припускаю, що запитувач розглядає можливість використання #regions лише тоді, коли він вже виключає розподіл функції.
Джошуа Кармоді

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

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