Наскільки необхідно дотримуватися практики оборонного програмування для коду, який ніколи не буде оприлюднений?


45

Я пишу реалізацію карткової гри на Java, тому створив спеціальний тип колекції, яку я називаю зоною. Усі способи модифікації колекції Java не підтримуються, але є метод в API Zone move(Zone, Card), який переміщує карту з даної зони до себе (виконується за допомогою пакетно-приватних методик). Таким чином я можу гарантувати, що жодна картка не виймається із зони та просто зникає; їх можна перенести лише в іншу зону.

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

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


4
= ~ s / необхідні / рекомендовані / gi
GrandmasterB

2
Типи даних мають бути правильними за побудовою, або ще на чому ви будуєте? Вони повинні бути інкапсульовані таким чином, що, змінюючись чи ні, вони можуть колись знаходитися у дійсних станах. Тільки якщо неможливо застосувати це статично (або необгрунтовано складно), якщо ви зробите помилку виконання.
Джон Перді

1
Ніколи не кажи ніколи. Якщо ваш код ніколи не використовується, ви ніколи не можете точно знати, де ваш кінець опиниться. ;)
Ізката

1
@codebreaker коментар GrandmasterB це вираз заміни. Це означає: замінити "необхідне" на "рекомендовано".
Рікардо Соуза

1
Код без коду №116 Довіра Нікому тут, ймовірно, особливо не підходить.

Відповіді:


72

Я не збираюся займатися дизайнерською проблемою - лише питанням, чи потрібно робити "правильно" в непублічному API.

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

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

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


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

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

2
Я однозначно згоден. Ще в коледжі мені не доводилося думати про це.
кодовий прорив

25

Я зазвичай дотримуюсь простих правил:

  • Намагайтеся завжди програмувати за контрактом .
  • Якщо метод є загальнодоступним або отримує внесок із зовнішнього світу , застосовуйте деякі захисні заходи (наприклад IllegalArgumentException).
  • Для всього іншого, що доступне лише внутрішньо, використовуйте твердження (наприклад assert input != null).

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

Що стосується вашого конкретного випадку, якщо Zoneсторонні користувачі не повинні використовуватись та / або отримувати доступ до них, або зробіть пакет класу приватним (і можливо final), або бажано використовувати колекції, які вже надає вам Java. Вони перевірені, і вам не доведеться винаходити колесо. Зауважте, що це не заважає вам використовувати твердження у всьому коді, щоб переконатися, що все працює так, як очікувалося.


1
+1 для згадування дизайну за контрактом. Якщо ви не можете повністю заборонити поведінку (а це важко зробити), ви принаймні даєте зрозуміти, що жодних гарантій на погану поведінку немає. Мені також подобається кидати IllegalStateException або UnsupportedOperationException.
user949300

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

16

Оборонне програмування - дуже хороша річ.
Поки він не почне заважати писати код. Тоді це не така гарна річ.


Якщо говорити трохи прагматичніше ...

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

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

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


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

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


2
+1 для "Поки вона не починає перешкоджати написанню коду." Особливо для короткострокових особистих проектів кодування захисно може зайняти набагато більше часу, ніж варто.
Корі

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

13

Оборонне кодування - це не просто гарна ідея для публічного коду. Це чудова ідея для будь-якого коду, який не відразу викидається. Звичайно, ви знаєте, як це потрібно називати зараз , але ви не знаєте, наскільки добре ви запам'ятаєте це через півроку, коли повернетесь до проекту.

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

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


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

3
@codebreaker Єдине, що може допомогти в цьому випадку - це інкапсуляція картки в інший об’єкт. Піки туза - це те, що є. Місцезнаходження не визначає його особу, і карта, ймовірно, повинна бути незмінною. Можливо, картки містять зони: можливо, вони CardDescriptorмістять карту, її місцезнаходження, стан обличчя вгору / вниз або навіть обертання для ігор, які турбуються про це. Це все змінні властивості, які не змінюють особу карти.

1

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

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

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


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

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

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

1

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

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

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

Однак ця реалізація звучить як похідний тип більш Collection<Card>схожого набору карт з менш обмежувальним API. Наприклад, коли ви хочете створити калькулятор вартості рук або AI, ви, безперечно, хочете мати можливість вільно вибрати, яку і скільки кожної картки ви повторите.

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

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