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

45

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

Мой вопрос: насколько необходим этот вид защитного кодирования? Это «правильно» и похоже на правильную практику, но это не значит, что Zone API когда-либо станет частью какой-то публичной библиотеки. Это только для меня, так что вроде как я защищаю свой код от себя, когда я, возможно, мог бы быть более эффективным, просто используя стандартные Коллекции.

Как далеко я должен взять эту идею Зоны? Может ли кто-нибудь дать мне несколько советов о том, как много я должен думать о сохранении контрактов в классах, которые я пишу, особенно для тех, которые на самом деле не будут общедоступными?

нарушитель закона
источник
4
= ~ s / необходимо / рекомендуется / ги
GrandmasterB
2
Типы данных должны быть правильными по построению, или же на чем вы основываетесь? Они должны быть инкапсулированы таким образом, чтобы, изменяемые или нет, они могли быть только в допустимых состояниях. Только если невозможно применить это статически (или неоправданно сложно), вы должны вызвать ошибку во время выполнения.
Джон Перди
1
Никогда не говори никогда. Если ваш код никогда не используется, вы никогда не сможете точно знать, где ваш код окажется в конечном итоге. ;)
Изката
1
Комментарий @codebreaker GrandmasterB - это выражение замены. Это означает: заменить «необходимо» на «рекомендуется».
Рикардо Соуза
1
Код без кода № 116 « Доверяй никому», вероятно, особенно уместен здесь.

Ответы:

72

Я не собираюсь решать проблему дизайна - просто вопрос о том, нужно ли делать вещи «правильно» в непубличном API.

это только для меня, так что вроде как я защищаю свой код от себя

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

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

Майкл К
источник
Хорошо знать. В прошлом я просто программировал настолько эффективно, насколько мог, поэтому иногда мне было трудно привыкнуть к таким идеям. Я рад, что я шел в правильном направлении.
код
15
«Эффективно» может означать много разных вещей! По моему опыту, новички (я не говорю, что вы один) часто упускают из виду, насколько эффективно они смогут поддержать программу. На этапе поддержки жизненного цикла продукта код обычно тратит гораздо больше времени, чем на этапе «написания нового кода», поэтому я считаю, что это эффективность, которую следует тщательно продумать.
Чарли Килиан
2
Я определенно согласен. Вернувшись в колледж, я никогда не думал об этом.
код
25

Я обычно следую некоторым простым правилам:

  • Старайтесь всегда программировать по контракту .
  • Если метод общедоступен или получает информацию от внешнего мира , примените некоторые защитные меры (например IllegalArgumentException).
  • Для всего остального, что доступно только внутри, используйте утверждения (например assert input != null).

Если клиент действительно заинтересован в этом, он всегда найдет способ заставить ваш код плохо себя вести. Они всегда могут сделать это через отражение, по крайней мере. Но это красота дизайна по контракту . Вы не одобряете такое использование своего кода, и поэтому не можете гарантировать его работу в таких сценариях.

Что касается вашего конкретного случая, если Zoneпосторонние не должны использовать его и / или получать к нему доступ, либо сделайте класс package-private (и, возможно, final), либо, предпочтительно, используйте коллекции, которые Java вам уже предоставляет. Они проверены, и вам не нужно изобретать велосипед. Обратите внимание, что это не мешает вам использовать утверждения во всем коде, чтобы убедиться, что все работает как положено.

afsantos
источник
1
+1 за упоминание дизайна по контракту. Если вы не можете полностью запретить поведение (и это трудно сделать), по крайней мере, вы даете понять, что нет никаких гарантий плохого поведения. Мне также нравится создавать исключение IllegalStateException или UnsupportedOperationException.
user949300
@ user949300 Конечно. Мне нравится полагать, что такие исключения были введены с осмысленной целью. Соблюдение контрактов, кажется, соответствует такой роли.
afsantos
16

Оборонительное программирование - это очень хорошая вещь.
Пока это не начинает мешать написанию кода. Тогда это не такая хорошая вещь.


Говоря немного более прагматично ...

Похоже, вы находитесь на грани того, чтобы зайти слишком далеко. Задача (и ответ на ваш вопрос) заключается в понимании бизнес-правил или требований программы.

Используя в качестве примера API-интерфейс вашей карточной игры, в некоторых ситуациях все, что можно сделать для предотвращения мошенничества, имеет решающее значение. Могут быть задействованы большие суммы реальных денег, поэтому имеет смысл поставить большое количество чеков на место, чтобы избежать мошенничества.

С другой стороны, вы должны помнить о принципах SOLID, особенно о единоличной ответственности. Просить контейнерный класс эффективно проверять, куда идут карты, может быть немного. Может быть лучше иметь уровень аудита / контроллера между контейнером карты и функцией, которая получает запросы на перемещение.


В связи с этими проблемами вы должны понимать, какие компоненты вашего API публично представлены (и, следовательно, уязвимы) по сравнению с частными и менее уязвимыми. Я не сторонник «твердого внешнего покрытия с мягкой внутренней частью», но лучший результат ваших усилий - укрепить внешний вид вашего API.

Я не думаю, что предполагаемый конечный пользователь библиотеки так же критичен в определении того, сколько защитного программирования вы внедрили. Даже с модулями, которые я пишу для собственного использования, я по-прежнему применяю меры проверки, чтобы убедиться, что в будущем я не совершил непреднамеренную ошибку при вызове библиотеки.


источник
2
+1 за «Пока не начнут мешать писать код». Специально для краткосрочных личных проектов кодирование в обороне может занять гораздо больше времени, чем оно того стоит.
Кори
2
Согласитесь, хотя я хотел бы добавить, что хорошо / уметь / программировать в обороне, но также важно уметь программировать в режиме прототипирования. Способность делать и то, и другое позволит вам выбрать наиболее подходящее действие, которое гораздо лучше, чем многие знакомые мне программисты, способные только программировать (своего рода) в обороне.
Дэвид Малдер
13

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

Базовый синтаксис Java дает вам много встроенной защиты по сравнению с языком нижнего уровня или интерпретируемым языком, таким как C или Javascript соответственно. Предполагая, что вы называете свои методы четко и не имеете внешнего «секвенирования методов», вы, вероятно, можете просто указать аргументы в качестве правильного типа данных и включить разумное поведение, если правильно типизированные данные все еще могут быть недопустимыми.

(С другой стороны, если карты всегда должны быть в зоне, я думаю, что вы получите лучший результат, если все карты в игре будут ссылаться на коллекцию, глобальную для вашего объекта Game, и иметь свойство Zone быть свойством каждую карту. Но поскольку я не знаю, чем занимаются ваши зоны, кроме как держать карты, трудно понять, подходит ли это.)

DougM
источник
1
Я считал, что зона является свойством карты, но так как мои карты лучше работают как неизменяемые объекты, я решил, что этот путь лучше всего. Спасибо за совет.
код
3
@ codebreaker В этом случае может помочь инкапсуляция карты в другом объекте. Пиковый туз - это то, что он есть. Местоположение не определяет его личность, и карта, вероятно, должна быть неизменной. Возможно, у Зоны есть карты: может быть CardDescriptor, есть карта, которая содержит карту, ее местоположение, статус «вверх / вниз» или даже ротацию для игр, которые заботятся об этом. Это все изменяемые свойства, которые не изменяют личность карты.
1

Сначала создайте класс, который хранит список зон, чтобы вы не потеряли зону или карты в ней. Затем вы можете проверить, что перевод находится в вашем ZoneList. Этот класс, вероятно, будет своего рода одноэлементным, так как вам понадобится только один экземпляр, но вы можете захотеть установить наборы зон позже, так что держите ваши параметры открытыми.

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

Есть реальные вопросы о том, что является «правильным». Но как только вы поймете, что это такое, вы захотите поступить таким образом. Через два года вы забудете обо всем этом, и если вы попытаетесь использовать код, то вы будете очень раздражены парнем, который написал его таким нелогичным образом и ничего не объяснил.

RalphChapin
источник
2
Ваш ответ слишком сфокусирован на рассматриваемой проблеме, а не на более широких вопросах, которые ОП задает в отношении защитного программирования в целом.
Я на самом деле передаю Zones методам, которые принимают Collections, поэтому реализация необходима. Однако, своего рода реестр зон в игре - интересная идея.
код
@ GlenH7: работа с конкретными примерами часто помогает больше, чем абстрактная теория. ОП предоставил довольно интересный вариант, и я согласился с этим.
Ральф Чапин
1

Защитное кодирование в дизайне API обычно заключается в проверке правильности ввода и тщательного выбора правильного механизма обработки ошибок. Стоит также отметить и другие ответы.

Это на самом деле не то, что ваш пример. Вы ограничиваете свою поверхность API по очень конкретной причине. Как упоминает GlenH7 , когда набор карт должен использоваться в реальной игре, например, с колодой («используемой» и «неиспользованной»), столом и руками, вы определенно хотите поставить чеки, чтобы убедиться, что каждый Карта из набора присутствует один раз и только один раз.

То, что вы разработали это с «зонами», является произвольным выбором. В зависимости от реализации (в приведенном выше примере зона может быть только рукой, колодой или столом), она вполне может быть тщательной.

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

Так что хорошо бы выставить такой ограничительный API, если единственная цель этого API - убедиться, что каждая карта всегда находится в зоне.

CodeCaster
источник