Чи використання цього умовного режиму є антидіаграмою?


14

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

bool todo = false;
if(cond1)
{
  ... // lots of code here
  if(cond2)
    todo = true;
  ... // some other code here
}

if(todo)
{
  ...
}

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

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

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


2
Як це зробити рефактор?
Tamás Szelei

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

3
@ammoQ: +1; якщо справи складні, то так вони і є. Змінна прапора може мати набагато більше сенсу в деяких обставинах, оскільки це стає зрозумілішим, що рішення було прийнято, і ви можете шукати його, щоб знайти, де було прийнято це рішення.
Дональні стипендіати

1
@ Дональд стипендіатів: Якщо пошук причини потрібний, я зміню список змінної; до тих пір, поки він порожній, він "помилковий"; незалежно від того, де встановлено прапор, до списку додається код причини. Тож ви можете закінчити таким списком, ["blacklisted-domain","suspicious-characters","too-long"]який показує, що застосовано кілька причин.
користувач281377

2
Я не думаю, що це анти-візерунок, але це, безумовно, запах
Binary Worrier

Відповіді:


23

Я не знаю про анти-візерунок, але я витягну з цього три методи.

Перший виконав би якусь роботу і повернув булеве значення.

Другий буде виконувати будь-яку роботу, виконану "якимсь іншим кодом"

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

Витягнуті методи, ймовірно, будуть приватними, якби важливо, щоб другий (і завжди) був викликаний, якщо перший метод повернеться істинним.

Називаючи методи добре, я сподіваюся, що це зробить код яснішим.

Щось на зразок цього:

public void originalMethod() {
    bool furtherProcessingRequired = lotsOfCode();
    someOtherCode();
    if (furtherProcessingRequired) {
        doFurtherProcessing();
    }
    return;
}

private boolean lotsOfCode() {
    if (cond1) {
        ... // lots of code here
        if(cond2) {
            return true;
        }
    }
    return false;
}

private void someOtherCode() {
    ... // some other code here
}

private void doFurtherProcessing() {
    // Do whatever is needed
}

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

Справа в тому, що намір коду стає зрозумілішим, що добре ...

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


Розщеплення на 2 функції все-таки вимагатиме todoзмінної і, ймовірно, буде складніше зрозуміти.
Паббі

Так, я б це зробив, але моє питання стосувалося використання прапора "todo".
Крикет

2
Якщо ви закінчите if (check_if_needed ()) do_whatever ();, явного прапора там немає. Я думаю, що це може занадто сильно порушити код і потенційно зашкодити читабельності, якщо код досить простий. Зрештою, подробиці того, що ви робите, do_whateverможуть вплинути на тестування check_if_needed, так що корисно буде тримати весь код разом в одному екрані. Крім того, це не гарантує, що check_if_neededможна уникнути використання прапора - і якщо це станеться, він, ймовірно, використовуватиме кілька returnзаяв для цього, можливо, засмучуючи суворих захисників одного виходу.
Steve314

3
@ Pubby8 сказав, що "витягнути з цього 2 способи" , в результаті чого отримано 3 способи. 2 методи, що виконують фактичну обробку, і оригінальний метод, що координує робочий процес. Це було б набагато чистішим дизайном.
MattDavey

Це не стосується ... // some other code hereвипадків раннього повернення
Калет

6

Я думаю, що потворність пов’язана з тим, що в одному методі є багато коду, і / або змінні називаються погано (обидва вони пахнуть кодом самостійно - анти-візерунки - це більш абстрактні та складні речі ІМО).

Тож якщо ви вилучите більшу частину коду методами нижчого рівня, як пропонує @Bill, решта стає чистою (як мінімум, для мене). Напр

bool registrationNeeded = installSoftware(...);
if (registrationNeeded) {
  registerUser(...)
}

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

calculateTaxRefund(isTaxRefundable(...), ...)

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


4

todo - це дійсно погана назва змінної, але я думаю, що це може бути все, що не так. Без контексту важко бути повністю впевненим.

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

bool requiresSorting = false;
if(cond1)
{
    ... // lots of code here
    if(cond2)
        requiresSorting = true;
    ... // some other code here
}

if(requiresSorting)
{
    ...
}

Однак пропозиція Білла також є правильною. Це ще читабельніше:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

Чому б просто не піти на крок далі: якщо (BuildList (список)) SortList (список);
Phil N DeBlanc

2

Модель штатної машини мені добре виглядає. Анти-шаблони є "todo" (неправильне ім'я) та "багато коду".


Я впевнений, що це лише для ілюстрації.
Лорен Печтел

1
Домовились. Що я намагався донести, це те, що хороші зразки, потоплені в поганому коді, не повинні звинувачуватись у якості коду.
ptyx

1

Це дійсно залежить. Якщо код, який захищається todo(я сподіваюся, ви не використовуєте це ім'я справжнє, оскільки воно абсолютно не мнемічне!) Є концептуально чистим кодом, то у вас є антидіапазон і вам слід використовувати щось на зразок RAII C або+ C ++ usingпобудувати для того, щоб обробляти речі.

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


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

1

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

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

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


1

Використовуючи приклад pdr, наведений вище, як гарний приклад, я піду на крок далі.

Він мав:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

Тож я зрозумів, що буде працювати наступне:

if(BuildList(list)) 
    SortList(list)

Але це не так зрозуміло.

Отже, до початкового питання, чому б не мати:

BuildList(list)
SortList(list)

І нехай SortList вирішить, чи потребує сортування?

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


І звичайно, наступний крок - запитати, чому це двоступеневий процес. Де б я не бачив такий код, я перетворююсь на метод під назвою BuildAndSortList (список)
Ян

Це не відповідь. Ви змінили поведінку коду.
D Drmmr

Не зовсім. Знову ж таки, я не можу повірити, що я відповідаю на щось, що я опублікував 7 років тому, але що, чорт забираю :) Те, що я сперечався, це те, що SortList міститиме умовне. Якщо у вас був одиничний тест, який стверджував, що список відсортований лише у випадку дотримання умови x, він все одно пройде. Переміщаючи умовне в SortList, ви уникаєте завжди писати (якщо (щось), то SortList (...))
Ян

0

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

Також багаті доменні моделі, в цьому випадку лише один вкладиш буде робити великі речі всередині об'єкта.


0

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

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

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

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


що додає цей пост, що відсутні відповіді?
esoterik

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

0

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

// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(valuesExist) {
    try {
      // Attempt insertion
      trx.commit();
    } catch (DatabaseException dbe) {
      trx.rollback();
      throw dbe;
    }
  } else {
    closeConnection(db);
    throwException();
  }
} else {
  closeConnection(db);
  throwException();
}

Це можна спростити, відокремивши перевірку від фактичного процесу виконання операції, тож ви побачите більше подібних даних:

boolean proceed = true;
// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(!valuesExist) {
    proceed = false;
  }
} else {
  proceed = false;
}

// The moment of truth
if(proceed) {
  try {
    // Attempt insertion
    trx.commit();
  } catch (DatabaseException dbe) {
    trx.rollback();
    throw dbe;
  }
} else {
  if(db.isOpen()) {
    closeConnection(db);
  }
  throwException();
}

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


У наведеному вами прикладі, я думаю, я вважаю за краще мати функцію mustIDoIt (poljaValidated, valuesExist), яка повертає відповідь. Це тому, що визначення "так / ні" визначається відразу, на відміну від коду, який я бачу тут на роботі, де рішення про продовження розкидається на декілька різних непомітних плям.
Крикет

@KelseyRider, саме в цьому і полягав. Відокремлення перевірки від виконання дозволяє ввести логіку в метод, щоб спростити загальну логіку програми в if (isValidated ()) doOperation ()
Neil

0

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

Тепер ваш конкретний приклад можна переписати як:

if(cond1)
{
    ... // lots of code here
    ... // some other code here
    if (cond2)
    {
        ...
    }
}

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


-1

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

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

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

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