Чи є винятки як контрольний потік серйозним антипаттером? Якщо так, то чому?


129

Ще в кінці 90-х я досить багато працював з кодовою базою, яка використовувала винятки як контроль потоку. Він реалізував машину з кінцевим станом для управління програмами телефонії. Останнім часом мені нагадують ті дні, бо я робив веб-додатки MVC.

Вони обоє мають Controllerрішення, куди йти далі, і надаватимуть дані в логіку призначення. Дії користувачів із домену старого шкільного телефону, як тони DTMF, стали параметрами методів дій, але замість того, щоб повернути щось на зразок a ViewResult, вони кинули знак a StateTransitionException.

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

Це так, і якщо так, то чому?

Оновлення: коли я задав питання, я вже мав на увазі відповідь @ MasonWheeler, тому я пішов з відповіддю, яка найбільше додала до моїх знань. Я думаю, що його також є надійною відповіддю.


2
Питання, пов’язані з цим: programmers.stackexchange.com/questions/107723
Eric King

25
Ні. У Python, використовуючи винятки, оскільки контрольний потік вважається "пітонічним".
user16764

4
Якби я робив таку справу я Java, я, звичайно, не став би виняток за це. Я виходив би з якоїсь ієрархії не винятку, не помилки, підкидаються.
Томас Едінг

2
Щоб додати до існуючих відповідей, ось короткий настанов, який добре мені послужив: - Ніколи не використовуйте винятки для "щасливого шляху". Щасливий шлях може бути як (для Інтернету) усім запитом, так і просто одним об'єктом / методом. Усі інші здорові правила все ще діють, звичайно :)
Хоуен

8
Чи не винятки завжди контролюють потік програми?

Відповіді:


109

Докладне обговорення цього питання у Вікі Уорда . Як правило, використання винятків для управління потоком є анти-моделлю, з багатьма помітними ситуаціями - і для конкретної мови (див , наприклад , Python ) кашель виняток кашлю .

Як короткий підсумок, чому, як правило, це анти-шаблон:

  • Виняток - це, по суті, складні заяви GOTO
  • Програмування з винятками, таким чином, призводить до складності для читання та розуміння коду
  • Більшість мов мають існуючі структури управління, розроблені для вирішення ваших проблем без використання винятків
  • Аргументи ефективності, як правило, суперечать сучасним компіляторам, які, як правило, оптимізуються з припущенням, що виключення не використовуються для управління потоком.

Прочитайте дискусію на Вікі Уорда, щоб отримати більш детальну інформацію.


Дивіться також дублікат цього питання тут


18
У Вашій відповіді звучить так, ніби винятки погані для всіх обставин, тоді як питання зосереджене на винятках як контролі потоку.
whatsisname

14
@MasonWheeler Різниця полягає в тому, що цикли для / while містять свої зміни управління потоком чітко і роблять код легким для читання. Якщо ви побачите у своєму коді оператор для виписки, вам не доведеться намагатися з’ясувати, який файл містить кінець циклу. Гото - це не погано, тому що якийсь бог сказав, що вони були, вони погані просто тому, що їх важче наслідувати, ніж циклічні конструкції. Винятки подібні, не неможливі, але досить жорсткі, що можуть сплутати речі.
Білл К

24
@BillK, тоді аргументуйте це, і не робіть надмірних тверджень про те, як винятки становлять готи.
Вінстон Еверт

6
Гаразд, але серйозно, що стосується помилок на стороні сервера та додатків, які поховають помилки з порожніми заявами про вилов у JavaScript? Це гидкий феномен, який коштував мені багато часу, і я не знаю, як просити без зйомки. Помилки - твій друг.
Erik Reppen

12
@mattnz: ifа foreachтакож є складними GOTOs. Чесно кажучи, я вважаю, що порівняння з goto не є корисним; це майже як зневажливий термін. Використання GOTO не є суттєво злим - у нього є реальні проблеми, і винятки можуть поділити їх, але вони можуть цього не зробити. Було б корисніше почути ці проблеми.
Еймон Нербонна

110

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

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

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


18
Однак це не відповідає на питання. Те, для чого вони були розроблені , не має значення; Єдине, що є релевантним, чому погано використовувати їх для управління потоком, це тема, яку ви не торкалися. Наприклад, шаблони C ++ були розроблені для однієї речі, але вони прекрасно використовуються для метапрограмування - використання, яке дизайнери ніколи не очікували.
Томас Боніні

6
@Krelp: Дизайнери ніколи не сподівались на багато речей, як -от випадково закінчитися системою шаблонів Тьюрінга - цілком випадково! Шаблони C ++ навряд чи є хорошим прикладом для використання тут.
Мейсон Уілер

15
@Krelp - Шаблони C ++ НЕ "ідеально підходять" для метапрограмування. Вони - кошмар, поки ви не зрозумієте їх, і тоді вони прагнуть до коду лише для запису, якщо ви не геніальний шаблон. Ви можете вибрати кращий приклад.
Майкл Коне

Винятки завдають шкоди функціональному складу, зокрема, і стислості коду в цілому. Наприклад, в той час як я був недосвідченим, я використовував виняток у проекті, і це спричиняло неприємності тривалий час, тому що 1) люди повинні пам’ятати, щоб його наздогнати; 2) ти не можеш писати, const myvar = theFunctionтому що Myvar має бути створений із спроб лову, таким чином, вона більше не є постійною. Це не означає, що я не використовую його в C #, оскільки він є мейнстрімом там з будь-якої причини, але я намагаюся їх зменшити в будь-якому випадку.
Привіт-Ангел

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

29

Винятки такі потужні, як Продовження та GOTO. Вони є універсальною конструкцією потоку управління.

У деяких мовах вони є єдиною універсальною конструкцією потоку управління. Наприклад, JavaScript не має ні Продовження, ні GOTOнавіть не має належних викликів Tail. Отже, якщо ви хочете реалізувати складний потік управління в JavaScript, ви повинні використовувати Винятки.

Проект Microsoft Volta був дослідницьким проектом (який зараз припинено) для збирання довільного коду .NET у JavaScript. .NET має винятки, семантика яких не точно позначає JavaScript, але, що ще важливіше, він має нитки , і ви повинні якось відобразити їх у JavaScript. Volta зробила це, застосувавши Volta Continuations за допомогою винятку JavaScript, а потім реалізувавши всі конструкції потоку управління .NET з точки зору продовження Volta. Вони повинні були використовувати Винятки як контрольний потік, оскільки немає іншої конструкції керуючого потоку, достатньо потужної.

Ви згадали про державні машини. SMs є тривіальною для реалізації за допомогою правильних викликів Tail: кожен стан є підпрограмою, кожен перехід стану - викликом підпрограми. SM-повідомлення також можуть бути легко реалізовані за допомогою GOTOабо Coroutines або Continuations. Однак, Java не має якогось - якого з цих чотирьох, але це дійсно є винятки. Отже, цілком прийнятно використовувати такі як контрольний потік. (Ну, насправді правильним вибором було б, мабуть, використання мови з правильною конструкцією потоку управління, але іноді ви можете застрягти з Java.


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

20
@ErikReppen: Я розумію, що ти просто саркастичний, але. . . Я дійсно не думаю, що той факт, що ми "домінували над розробкою веб-сторінок на базі клієнта за допомогою JavaScript", не має нічого спільного з особливостями мови. Вона має монополію на цьому ринку, тому змогла позбутися багатьох проблем, які не можна списати з сарказмом.
ruakh

1
Хвостова рекурсія була б чудовим бонусом (вони знецінюють функції функцій, щоб мати її в майбутньому), але так, я б сказав, що вона виграла проти VB, Flash, аплетів тощо ... з причин, пов’язаних з функціями. Якби це не зменшило складність і нормалізувалося так легко, як це було, в певний момент була б справжня конкуренція. Я зараз запускаю Node.js для обробки перезапису 21 конфігураційних файлів для огидного стека Java C # +, і я знаю, що я не єдиний, хто це робить. Це дуже добре, у чому це добре.
Erik Reppen

1
Багато мов не мають ні gotos (Goto вважається шкідливим!), Ні процедурами, ні продовженнями, і реалізують ідеально правильний контроль потоку, просто використовуючи ifs, whiles та виклики функцій (або gosubs!). Більшість із них насправді можуть бути використані для емуляції один одного, так само як продовження можна використовувати для представлення всього вищезазначеного. (Наприклад, ви можете використовувати деякий час для виконання if, навіть якщо це смердючий спосіб кодування). Тож жодних винятків НЕ потрібно для здійснення розширеного контролю потоку.
Шейне

2
Ну так, ви можете мати рацію, якщо. Але "for" у своїй формі стилю C, однак, може бути використаний як незручно заявлений час, а деякий час потім може бути використаний у поєднанні з if, щоб реалізувати машину з кінцевим станом, яка може потім імітувати всі інші форми управління потоком. Знову смердючий спосіб кодування, але так. І знову, goto вважається шкідливим. (І я заперечую, так само і з використанням винятків у не виняткових обставинах). Майте на увазі, що існують цілком дійсні та дуже потужні мови, які не забезпечують ні гото, ні винятків і працюють чудово.
Шейне

19

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

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

Це пояснює, як всередині jOOQ (бібліотеки абстракції SQL для Java) (відмова: я працюю для постачальника) такі винятки періодично використовуються для припинення процесу візуалізації SQL на ранніх термінах, коли виконується якась "рідкісна" умова.

Прикладами таких умов є:

  • Зустрічається занадто багато значень прив'язки. Деякі бази даних не підтримують довільну кількість значень прив'язки у своїх операторах SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). У цих випадках jOOQ перериває фазу візуалізації SQL і повторно надає оператор SQL з вбудованими значеннями прив’язки. Приклад:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }
    

    Якщо ми явно дістали значення прив’язки з запиту AST, щоб їх підраховувати кожен раз, ми витратили б цінні цикли процесора на ті 99,9% запитів, які не страждають від цієї проблеми.

  • Деяка логіка доступна лише опосередковано через API, який ми хочемо виконати лише "частково". UpdatableRecord.store()Метод генерує INSERTабо UPDATEзаяву, в залежності від Record«з внутрішніми прапорами. Ззовні ми не знаємо, яка саме логіка міститься store()(наприклад, оптимістичне блокування, обробка слухачем подій тощо), тому ми не хочемо повторювати цю логіку, коли ми зберігаємо кілька записів у пакетному операторі, там, де нам хотілося б store()генерувати лише оператор SQL, а не виконувати його. Приклад:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }
    

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

Висновок

По суті, наше використання цих нелокальних gotos - це лише те, що сказав Мейсон Уілер у своїй відповіді:

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

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

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


11

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

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

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

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

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

Але інші мови, наприклад Ruby, мають синтаксис, що нагадує виняток для потоку управління. Виняткові ситуації обробляються з raise/ rescueоператорами. Але ви можете використовувати throw/ catchдля конструкцій керуючого потоку, подібних до винятків **.

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

* Приклад використання винятків, що дорого використовує продуктивність: колись я налаштований оптимізувати неякісний додаток для веб-форм ASP.NET. Виявилося, що візуалізація великої таблиці закликала int.Parse()ок. тисяча порожніх рядків на середній сторінці, в результаті чого прибл. обробляється тисяча винятків. Замінивши код на int.TryParse()я побрив одну секунду! Для кожного запиту на одну сторінку!

** Це може бути дуже заплутаним для програміста підходить до Рубі з інших мов, як throwі catchключові слова , пов'язані з винятками в багатьох інших мовах.


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

8

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

status = getValue(&inout);
if (status < 0)
{
    logError("message");
    return status;
}

doSomething(*inout);

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

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

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


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

5

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

Через відсутність багаторівневих перерв або оператора goto в Python, я часом використовував винятки:

class GOTO(Exception):
  pass

try:
  # Do lots of stuff
  # in here with multiple exit points
  # each exit point does a "raise GOTO()"
except GOTO:
  pass
except Exception as e:
  #display error

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

3
@ 9000 Я збирався прокоментувати абсолютно те саме ... Тримайте try:блоки в 1 або 2 рядках, будь ласка, ніколи try: # Do lots of stuff.
Вім

1
@ 9000, у деяких випадках, звичайно. Але тоді ви втрачаєте доступ до локальних змінних і пересуваєте свій код ще в інше місце, коли процес є цілісним лінійним процесом.
gahooa

4
@gahooa: Раніше я думав, як ти. Це було ознакою поганої структури мого коду. Коли я більше замислювався над цим, я помітив, що локальні контексти можна розплутати, і весь безлад перетворити на короткі функції з невеликими параметрами, кількома рядками коду та дуже точним значенням. Я ніколи не оглядався.
9000

4

Давайте накреслимо таке використання виключення:

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

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

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


Хм, чи є спроба за дійсно протилежну позицію, чи за недостатню чи коротку аргументацію? Мені справді цікаво.
Joop Eggen

Я зіткнувся саме з цим загрозою, я сподівався, що ви зможете побачити, що ви думаєте про моє наступне запитання? Рекурсивно використовуючи винятки, щоб накопичити причину (повідомлення) для винятку вищого рівня codereview.stackexchange.com/questions/107862/…
CL22

@Jodes Я щойно прочитав вашу цікаву техніку, але зараз я досить зайнятий. Сподіваюся, що хтось інший проллє її / його світло на цю проблему.
Joop Eggen

4

Програмування - це робота

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

Кожен раз, коли викликається метод, абонент каже: "Я не знаю, як зробити цю роботу, але ви знаєте як", тому ви робите це для мене ".

Це спричинило складність: що відбувається, коли викликаний метод загалом знає, як виконати роботу, але не завжди? Нам потрібен був спосіб спілкування: «Я хотів допомогти вам, я справді це зробив, але просто не можу цього зробити».

Ранньою методологією довести це було просто повернути значення "сміття". Можливо, ви очікуєте додатне ціле число, тому викликаний метод повертає від’ємне число. Ще один спосіб досягти цього - десь встановити значення помилки. На жаль, обидва способи призвели до того, що кодовий код « перевірте-перевірте-тут-щоб переконатися» - це кошерний код. У міру ускладнення справи ця система розпадається.

Виняткова аналогія

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

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

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

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

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

Отже ... антидіаграма?

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

Щоб кваліфікуватись як анти-модель, потрібно

  • вирішити проблему
  • мають остаточно негативні наслідки

Перший момент легко виконати - система спрацювала, правда?

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

Але це не остаточна шкода. Це поганий спосіб робити речі, і дивно, але анти-шаблон? Ні. Просто ... дивно.


Є один поганий наслідок, принаймні в мовах, що забезпечують повний слід стека. Оскільки код сильно оптимізований з великою кількістю вбудованих даних, справжній слід стека і той, який розробник хоче бачити, сильно відрізняються, і тому генерація сліду стека коштує дорого. Перевикористання винятків дуже погано працює на таких мовах (Java, C #).
maaartinus

"Це поганий спосіб робити речі" - Хіба цього не повинно бути достатньо, щоб класифікувати його як анти-шаблон?
Можливо, фактор

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