Обязательные атрибуты в Smalltalk

Я пишу курсы на Pharo Smalltalk, но полагаю, что этот вопрос актуален и для других реализаций Smalltalk.

Я знаю способ принудительного применения экземпляров с определенными атрибутами — предоставить метод класса для создания экземпляра, а затем предложить использовать метод создания класса. Но любой пользователь знает, что new или basicNew можно использовать в любое время.

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

Есть ли другая библиотека или механизм для принудительного выполнения этих конкретных атрибутов?


person user1000565    schedule 25.07.2017    source источник


Ответы (2)


Нет. И это хорошо.

Вот некоторые подходы, которым вы можете следовать:

  1. Убедитесь, что ваши объекты действительны, предоставив для них проверки. Это очень широкая тема, поэтому я просто скажу, что фреймворк проверки — это тот, который может исследовать ваши объекты и каким-то образом сделать явными любые правила, которые они не соблюдают.

    В зависимости от того, что вы делаете, вы можете ограничить проверки графическим интерфейсом, когда объекты рождаются или изменяются. Однако более сложный подход должен позволять любому объекту решать, является ли он действительным или нет (в данном контексте).

  2. Исключите отдельные установщики из протокола на стороне экземпляра. Предоставьте только несколько установщиков ключевых слов, которые не будут работать, если что-то отсутствует или не подходит. Это означает, что вы должны предоставить такие методы, как Point >> #x:y:, но не Poit >> #x: или Point >> #y:.

    Как видно из примера, вам будет трудно использовать эту практику в полной мере, потому что базовые классы не следуют этому стилю. Также обратите внимание, что эта практика потребует некоторой проверки, потому что проверка только для notNil обычно слишком наивна.

  3. Расслабьтесь и ничего не делайте, пока не возникнет необходимость в решении подобных вопросов.

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

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

person Leandro Caniglia    schedule 25.07.2017
comment
Я бы рекомендовал вариант №3. Единственное, что вы, как автор объекта, могли бы (и фактически должны) сделать, это добавить свое предположение в комментарий к классу. Если вы добавите что-то вроде Я ожидаю, что экземпляр будет создан с использованием #newWith: - если вы создадите экземпляр меня с помощью #new или #basicNew, могут произойти странные вещи, тогда вы сделали свое дело, и любые проблемы, возникающие из-за неуместного /неожиданные экземпляры являются ответственностью человека, использующего ваш объект. Попытка предвидеть все возможные способы того, как кто-то может сделать что-то странное, означает начать битву, которую вы, скорее всего, проиграете. ;-) - person Amos M. Carpenter; 26.07.2017
comment
isValidInContext: anApplicationContext действительно очень полезен на уровне приложения. - person Stephan Eggermont; 11.08.2017

Я согласен с ответом Леандро. Защитное программирование редко используется в языке Smalltalk. Нет ни статической типизации для принудительного выполнения чего-либо, ни личных сообщений. Smalltalk является открытым и поздним. Но с другой стороны, ошибки редко бывают катастрофическими (Smalltalk вообще не падает при ошибке).

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

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

Стратегия, которую вы предлагаете, возможна благодаря использованию Exception. Идея состоит в том, что иногда лучше иметь раннее исключение, наиболее близкое к первопричине, чем позднее исключение, которое сложнее отлаживать и исправлять. Посмотрите, например, на сообщение #shouldNotImplement и его отправителей: вы увидите, что иногда оно используется для предотвращения использования #new.

Также не забывайте, что существует сообщение #initialize, которое можно использовать для присвоения разумного значения по умолчанию переменным экземпляра (что-то вроде конструктора по умолчанию в C++). Есть варианты, когда вы хотите передать дополнительную информацию: для коллекций, которые обычно создаются через #new:, есть сообщение #initialize:, принимающее размер в качестве аргумента. Вы можете по желанию усовершенствовать подобные механизмы для передачи другой информации при создании.

Но не пытайтесь предотвратить использование #basicNew. Вы причините себе боль, сломав некоторые службы, полагающиеся на эту низкоуровневую функцию, например, изменяя существующие экземпляры при изменении макета класса, например, копируя, сохраняя во внешних файлах и т. д.... например, см. #adoptInstance:.

Вместо этого вы должны научиться доверять пользователям вашей библиотеки (включая себя): пользователи не будут использовать basicNew, если они не знают, что они делают (они узнают об этом рано или поздно). И, как сказал Амос, документируйте контракты и ожидания в комментариях к классам или сообщениям или модульных тестах, чтобы люди могли учиться быстрее, и, возможно, используйте не такие привлекательные имена, как basicNew, когда вы хотите выразить, что сообщение предназначено только для осведомленного использования.

EDIT кстати, если вы запретите basicNew, вы вообще не сможете создавать экземпляры, поэтому вам придется создать новое сообщение, вызывающее примитив для создания экземпляра, и в конце концов вы будет просто запутан/усложнен код ни за что, потому что вы просто сместили проблему. Если вы не делаете очень неприятных вещей, таких как:

basicNew
    thisContext sender method selector == #mySepcialCreationMessageWithParameter: ifTrue: [^super basicNew].
    ^self error: 'new instances should be created with mySepcialCreationMessageWithParameter:'

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

РЕДАКТИРОВАТЬ 2 Другая точка зрения состоит в том, что программирование — это социальная деятельность, и код, который вы пишете на Smalltalk, предназначен не только для программирования автомата. Он предназначен для чтения и повторного использования людьми. В этом контексте защитное программирование чем-то похоже на управление посредством принуждения. Вместо того, чтобы тратить время на попытки ограничить, более эффективно потратить время на создание простых и логических абстракций, которые можно легко понять и использовать повторно, возможно, в других контекстах, которые вы не предусмотрели.

person aka.nice    schedule 27.07.2017