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


9

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

Я не запитую, який найкращий підхід, коли я виконую функцію, я запитую, чи слід я вийти зранку чи я просто не повинен викликати функцію.

Оберніть, якщо навколо дзвінка функції


if (shouldThisRun) {
  runFunction();
}

Увімкніть, якщо ( охоронець ) у функції

runFunction() {
  if (!shouldThisRun) return;
}

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


Ось приклад

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

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



3
@gnat Ні, це питання по суті "Що є кращим синтаксисом при ранньому виході", тоді як мій "Чи повинен я вийти рано чи повинен просто не викликати функцію"
Меттью Маллін

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

"Я хочу лише, щоб статус був оновлений, якщо статус змінився" - ви хочете, щоб статус оновлено (= змінився), якщо той самий статус змінився? Звучить досить кругло. Чи можете ви уточнити, що ви маєте на увазі під цим, і я можу додати змістовний приклад до своєї відповіді на це?
Док Браун

@DocBrown Дозволяє, наприклад, сказати, що я хочу зберегти два різних властивості статусу об'єктів у синхронізації. Коли змінюються будь-які об'єкти, я називаю syncStatuses () - але це може бути ініційовано для багатьох різних змін поля (не тільки поля статусу).
Метью Маллін

Відповіді:


15

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

Застереження про функцію (раннє повернення):
це захист від виклику з недійсними параметрами

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

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

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

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


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

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

7

Ви можете мати як функцію, яка не перевіряє параметри, так і іншу, яка робить, як це (можливо, повертає деяку інформацію про те, чи був виклик):

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

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

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

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


1
Потрібно визнати, ідіома "tryXXX ()" завжди здавалася трохи відхиленою, і тут недоречно. Ви не намагаєтесь зробити щось, очікуючи ймовірної помилки. Ви оновлюєте, якщо воно брудне.
user949300

@ user949300: вибір хорошого імені або схеми іменування залежить від реального випадку використання, реальних імен функцій, а не якогось надуманого імені runFunction. Така функція updateStatus()може супроводжуватися іншою функцією на кшталт updateIfStatusHasChanged(). Але це на 100% залежно від випадку, для цього не існує рішення "один на один", тому так, я згоден, ідіома "спробувати" - це не завжди вдалий вибір.
Док Браун

Немає чогось з назвою "dryRun"? Більш-менш це регулярне виконання без побічних ефектів. Як відключити побічні ефекти - інша історія
Laiv

3

Що стосується того, хто вирішує, чи потрібно балотуватися, відповідь - від GRASP , який є "знавцем інформації".

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

Щось подібне, якщо функція вирішує:

 ensureUpdated()
 updateIfDirty()

Або, якщо абонент повинен вирішити:

 writeStatus()

2

Я хотів би розкрити відповідь @ Baldrickk.

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

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

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

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

Отже, питання:

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

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


2

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

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

І вам потрібно викликати іншу функцію в runFunctionтакому методі:

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

Що ти робитимеш? Ви копіюєте всі перевірки зверху вниз?

someOtherfunction() {
   if (!shouldThisRun) return;
}

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

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

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