Информация скрывает больше чем соглашение?


20

В Java, C # и многих других строго типизированных, статически проверенных языках мы привыкли писать код следующим образом:

public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }

Некоторые динамически проверяемые языки не предоставляют ключевые слова для выражения уровня «приватности» данного члена класса и вместо этого полагаются на соглашения о кодировании. Например, в Python префикс частных членов ставится с подчеркиванием:

_m(self): pass

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

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

Имея это в виду, мой вопрос:

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

Может ли оно быть использовано для защиты секретного состояния класса от нападения? Может ли рефлексия перекрыть этот механизм? Что могло бы стоить компилятору применять скрытие информации?


10
Извините, Эрик Липперт в отпуске :)
Бенджол

Ваш вопрос немного сложен для понимания. Вы просто спрашиваете, может ли кто-то получить доступ к частному члену извне?
Морган Херлокер


@ironcode: я могу сказать, что я почему-то недостаточно ясно выражаюсь из некоторых ответов. Можете ли вы сказать, что конкретно мешает понять? Я постараюсь уточнить.
blubb

2
«Это бессмысленно предоставлять информацию во время компиляции, а затем проверять ее только во время выполнения» - правда? Лучше расскажите об этом Microsoft, потому что именно на этом основана существенная часть текущей среды .NET: выборочное включение или отключение статической типизации. Я не думаю, что это действительно то, что вы имеете в виду, хотя; вопрос неоднократно говорит о «статической» или «динамической» проверке, но это характеристики системы типов; что на самом деле речь здесь время компиляции против времени выполнения связывания .
Ааронаут

Ответы:


4

Я учусь на сертификацию Java, и в целом это касается того, как управлять модификаторами доступа. И они имеют смысл, и их следует использовать правильно.

Я работал с python, и во время моего обучения я слышал, что в python существует соглашение, потому что люди, которые работают с ним, должны знать, что это значит и как его применять. Тем не менее, в _m(self): passподчеркивании предупредил бы меня не возиться с этим полем. Но все ли будут следовать этой конвенции? Я работаю с Javascript и должен сказать, что нет . Иногда мне нужно проверить проблему, и причина была в том, что человек делал то, чего не должен был делать ...

Прочитайте это обсуждение относительно подчеркивания Python.

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

Из того, что я сказал, да.

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

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

Отредактировано Да, отражение может, проверить комментарии

Последний вопрос интересный, и я готов прочитать ответы на него.


3
Отражение обычно может обойти защиту доступа. Для Java и C # они оба могут получить доступ к частным данным.
Марк Х

Благодарность! Ответы здесь: stackoverflow.com/questions/1565734/… объясните это!
wleao

1
Культура Javascript сильно отличается от культуры Python. Python имеет pep-08, и почти все разработчики Python придерживаются этих соглашений. Нарушения PEP-08 часто считаются ошибкой. Поэтому я думаю, что опыт работы с javascript / php / perl / ruby ​​очень мало переводит на опыт работы с Python в этом аспекте: _private очень хорошо работает в python.
Майк Коробов

1
В некоторых (но не во всех) языках программирования сокрытие информации может использоваться для защиты от враждебного кода. Скрытие информации не может быть использовано для защиты от враждебных пользователей.
Брайан,

2
@ Майк, если нарушения PEP-08 так часто «считаются ошибкой», и ни один разработчик Python не мечтал бы о несоблюдении этих соглашений, то вы также можете применять их на языке! Почему черновая работа, когда язык может сделать это автоматически?
Андрес Ф.

27

«Частный» спецификатор доступа не связан с ошибкой компилятора, он генерируется при первом его отображении. На самом деле речь идет о запрете доступа к чему-то, что все еще может измениться при изменении реализации класса, содержащего закрытый член.

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

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

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

Итак, краткое резюме:

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

3
Что еще более важно, любое (легко узнаваемое, что имеет место, например, с ведущими подчеркиванием) неисполненное соглашение о «деталях реализации, подлежащих изменению» также избегает случайного использования частными членами. Например, в Python единственный раз, когда здравомыслящий программист пишет код с использованием закрытого члена извне (и даже тогда он хмурится), когда это почти неизбежно, он точно знает, что делает, и принимает потенциальную поломку в будущих версиях , То есть ситуация, в которой, например, Java-программисты будут использовать отражение.

3
@Simon Stelling - нет, я говорю, что оригинальный разработчик класса, который сделал что-то приватное, говорит вам «не используйте это, любое поведение здесь может измениться в будущем». Например, закрытая переменная-член класса, которая сначала содержит расстояние между двумя точками, может позже содержать расстояние между двумя точками в квадрате по соображениям производительности. Все, что полагалось бы на старое поведение, молча сломалось бы.
Йорис Тиммерманс

1
@MadKeithV: Тогда чем ключевое слово на уровне языка, говорящее «не трогай, потому что ...», отличается от соглашения, означающего то же самое? Только компилятор обеспечивает его выполнение, но тогда мы вернемся к старой и динамической дискуссии.

7
@ Симон Стеллинг (и Делнан) - для меня это все равно, что спросить: «Чем ограничитель двигателя отличается от знака ограничения скорости».
Йорис Тиммерманс

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

11

Если вы пишете код, который будет использоваться кем-то другим, то сокрытие информации может обеспечить гораздо более простой интерфейс. «Кто-то другой» может быть другим разработчиком в вашей команде, разработчиками, использующими API, который вы написали на коммерческой основе, или даже вашим будущим пользователем, который «просто не может вспомнить, как работает эта чертова штука». Работать с классом, в котором доступно только 4 метода, намного проще, чем с классом 40.


2
Я полностью согласен, но это не отвечает на мой вопрос. Использую ли я _memberили private memberв своем классе ничтожно мало, если другой программист понимает, что он должен смотреть только на publicчленов без префиксов или без них.
blubb

6
Да, но общедоступная документация класса, написанная на динамическом языке, также не бросает в вас эти 36 закрытых частей (по крайней мере, по умолчанию).

3
@Simon Утверждение, что это «просто соглашение», вводит в заблуждение, поскольку имеет реальные преимущества. «Просто соглашение» - это что-то вроде фигурных скобок на следующей строке. Соглашение с преимуществами - это что-то вроде использования значимых имен, которые, я бы сказал, совершенно разные. Помогает ли установка чего-то в приватный режим, когда кто-то видит вас "секретными методами"? Нет, если они не определены.
Морган Херлокер

1
@ Simon - также, только показ методов, которые безопасны в использовании, увеличивает вероятность того, что пользователь не сломает что-либо при попытке использовать класс. Там могут быть легко методы, которые могут вызвать сбой веб-сервера, если вызвать тысячу раз. Вы не хотите, чтобы потребители вашего API могли это делать. Абстракция - это больше, чем просто соглашение.
Морган Херлокер

4

Я хотел бы предложить, что для справки, вы начинаете с чтения об инвариантах класса .

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

Давайте использовать очень простой пример C #:

public class EmailAlert
{
    private readonly List<string> addresses = new List<string>();

    public void AddRecipient(string address)
    {
        if (!string.IsNullOrEmpty(address))
            addresses.Add(address);
    }

    public void Send(string message)
    {
        foreach (string address in addresses)
            SendTo(address, message);
    }

    // Details of SendTo not shown
}

Что тут происходит?

  • Член addressesинициализируется по классу строительства.
  • Это личное, поэтому ничто извне не может коснуться его.
  • Мы также сделали это readonly, так что ничто изнутри не может коснуться его после построения (это не всегда правильно / необходимо, но здесь полезно).
  • Таким образом, Sendметод может сделать предположение, addressesкоторое никогда не будет null. Он не должен выполнять эту проверку, потому что нет возможности изменить значение.

Если бы другим классам было разрешено писать в addressesполе (т. Е. Если бы это было так public), то это предположение больше не было бы верным. Каждый другой метод в классе, который зависит от этого поля, должен будет начать выполнять явные проверки на ноль или рискнуть сбой программы.

Так что да, это намного больше, чем «конвенция»; Все модификаторы доступа для членов класса вместе образуют набор предположений о том, когда и как это состояние может быть изменено. Эти предположения впоследствии включаются в зависимые и взаимозависимые члены и классы, чтобы программистам не приходилось рассуждать обо всем состоянии программы одновременно. Способность делать предположения является критически важным элементом управления сложностью программного обеспечения.

На эти другие вопросы:

Может ли оно быть использовано для защиты секретного состояния класса от нападения?

Да и нет. Защитный код, как и большая часть кода, будет опираться на определенные инварианты. Модификаторы доступа, безусловно, полезны в качестве указателей доверенным абонентам, которые не должны связываться с ним. Вредоносный код не будет заботиться, но вредоносный код также не должен проходить через компилятор.

Может ли рефлексия перекрыть этот механизм?

Конечно может. Но рефлексия требует, чтобы вызывающий код имел такой уровень привилегий / доверия. Если вы используете вредоносный код с полным доверием и / или правами администратора, значит, вы уже проиграли эту битву.

Что могло бы стоить компилятору применять скрытие информации?

Компилятор уже делает жизнь это. Так же как и среда выполнения в .NET, Java и других подобных средах - код операции, используемый для вызова метода, не будет успешным, если метод является закрытым. Единственные способы обойти это ограничение требуют наличия доверенного / повышенного кода, а повышенный код всегда может просто записываться непосредственно в память программы. Он применяется настолько, насколько это возможно, без специальной операционной системы.


1
@Simon: нам нужно уточнить здесь «невежество». Префикс метода с _указанием контракта, но без его принудительного исполнения. Это может затруднить невежество в отношении контракта, но не усложнит его нарушение , особенно если сравнивать с утомительными и часто ограничительными методами, такими как Reflection. Конвенция гласит: «Вы не должны нарушать это». Модификатор доступа говорит: «Вы не можете сломать это». Я не пытаюсь сказать, что один подход лучше другого - они оба хороши в зависимости от вашей философии, но, тем не менее, это очень разные подходы.
Аарона

2
Как насчет аналогии: модификатор доступа private/ protectedдоступа похож на презерватив. Вам не нужно использовать один, но если вы не собираетесь, то вам лучше быть чертовски уверены в вашем времени и ваших партнерах. Это не предотвратит злонамеренный поступок и может даже не сработать каждый раз, но определенно снизит риски, вызванные небрежностью, и сделает это гораздо эффективнее, чем выражение «пожалуйста, будьте осторожны». О, и если у вас есть, но не используйте его, то не ожидайте от меня сочувствия.
11:00

3

Сокрытие информации - это больше, чем просто соглашение; попытка обойти это может фактически нарушить функциональность класса во многих случаях. Например, довольно распространенной практикой является сохранение значения в privateпеременной, выставление его с использованием свойства protectedили publicтого же времени, а в получателе, проверьте наличие нуля и выполните любую необходимую инициализацию (т. Е. Отложенную загрузку). Или сохраните что-нибудь в privateпеременной, предоставьте ее с помощью свойства и в установщике проверьте, изменилось ли значение и запустили ли PropertyChanging/ PropertyChangedсобытия. Но не увидев внутреннюю реализацию, вы никогда не узнаете всего, что происходит за кулисами.


3

Сокрытие информации развилось из нисходящей философии дизайна. Python был назван восходящим языком .

Сокрытие информации обеспечивается на уровне классов в Java, C ++ и C #, поэтому на данном уровне это не совсем соглашение. Сделать класс «черным ящиком» очень просто, с открытыми интерфейсами и скрытыми (частными) деталями.

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

Даже с Java, C ++ или C # в какой-то момент сокрытие информации становится соглашением. На высших уровнях абстракции нет элементов управления доступом, используемых в более сложных программных архитектурах. Например, в Java вы можете найти использование «.internal». названия пакетов. Это чисто соглашение об именах, потому что нелегко реализовать такую ​​информацию, скрывающуюся, только за счет доступности пакетов.

Одним из языков, который стремится определить формально доступ, является Eiffel. В этой статье рассказывается о некоторых других слабостях, скрывающих информацию, таких как Java.

Справочная информация : скрытие информации было предложено в 1971 году Дэвидом Парнасом . В этой статье он указывает, что использование информации о других модулях может «катастрофически увеличить связность структуры системы». Согласно этой идее, отсутствие сокрытия информации может привести к тому, что системы будут сложно поддерживать. Он продолжает с:

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


1
+1 за цитату «использование информации программистом», вот и все (хотя в конце у вас есть лишняя запятая).
Jmoreno

2

Ruby и PHP имеют его и применяют во время выполнения.

Точка сокрытия информации - фактически показ информации. «Скрывая» внутренние детали, цель становится очевидной с внешней точки зрения. Есть языки, которые охватывают это. В Java доступ по умолчанию для внутреннего пакета, в haXe для защищенного. Вы явно объявляете их публичными, чтобы разоблачить их.

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

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


1
Если ваша библиотека используется в той же компании, в которой вы работаете, вам будет все равно ... Если он сломает свое приложение, вам будет трудно его починить.
wleao

1
Зачем притворяться пристегнутым ремнем безопасности, если вы можете его надеть? Я бы перефразировал вопрос и спросил бы, есть ли причина для компилятора не применять его, если ему доступна вся информация. Вы не хотите ждать, пока у вас не случится авария, чтобы выяснить, стоил ли этот ремень безопасности носить.
Марк Х

1
@Simon: зачем загромождать интерфейс вещами, которые вы не хотите, чтобы люди использовали. Я ненавижу, что C ++ требует, чтобы каждый член присутствовал в определении класса в заголовочном файле (я понимаю, почему). Интерфейсы предназначены для других людей и не должны быть завалены вещами, которые они не могут использовать.
phkahler

1
@phkahler: pydocудаляет _prefixedMembersиз интерфейса. Беспорядочные интерфейсы не имеют ничего общего с выбором ключевого слова, проверяемого компилятором.
blubb

2

Модификаторы Acces определенно могут делать то, чего не может конвенция

Например, в Java вы не можете получить доступ к закрытому члену / полю, если не используете отражение.

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

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

Если пользователь запускает мой интерфейс в своей среде, он имеет контроль и, следовательно, может обойти модификаторы доступа.


2

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

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

Если у меня есть библиотека

class C {
  public void foo() { ... }

  private void fooHelper() { /* lots of complex code */ }
}

Я могу хотеть иметь возможность заменить fooреализацию и, возможно, fooHelperрадикально измениться . Если группа людей решила использовать fooHelperнесмотря на все предупреждения в моей документации, то я не смогу сделать это.

privateпозволяет авторам библиотеки разбивать библиотеки на методы управляемых размеров (и privateвспомогательные классы), не опасаясь, что они будут вынуждены хранить эти внутренние детали в течение многих лет.


Что могло бы стоить компилятору применять скрытие информации?

Напомним, что в Java privateне применяется компилятор, а проверяется байт-код Java .


Может ли рефлексия перекрыть этот механизм? Что могло бы стоить компилятору применять скрытие информации?

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

 public class C {
   private int i = 42;

   public class B {
     public void incr() { ++i; }
   }
 }

Поскольку класс B(действительно именованный C$B) использует i, компилятор создает синтетический метод Bдоступа, который позволяет получить доступ C.iтаким образом, чтобы он прошел проверку байт-кода. К сожалению, поскольку ClassLoaderпозволяет вам создать класс из a byte[], довольно просто получить доступ к частным лицам, которые Cоткрылись для внутренних классов, создав новый класс в Cпакете внутри , что возможно, если Cjar не был запечатан.

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


Может ли оно быть использовано для защиты секретного состояния класса от нападения?

Да. «Безопасная декомпозиция» возможна, когда программисты могут сотрудничать, сохраняя при этом свойства безопасности своих модулей - мне не нужно доверять автору другого модуля кода, чтобы не нарушать свойства безопасности моего модуля.

Языки возможностей объектов, такие как Joe-E, используют скрытие информации и другие средства, чтобы сделать возможной безопасную декомпозицию:

Joe-E - это подмножество языка программирования Java, разработанное для поддержки безопасного программирования в соответствии с дисциплиной объектных возможностей. Joe-E предназначен для облегчения построения защищенных систем, а также для облегчения анализа безопасности систем, построенных в Joe-E.

Ссылка на эту страницу дает пример того, как privateправоприменение делает возможной безопасную декомпозицию.

Обеспечение безопасной инкапсуляции.

Рисунок 1. Средство регистрации только для добавления.

public final class Log {
  private final StringBuilder content;
  public Log() {
    content = new StringBuilder();
  }
  public void write(String s) {
    content.append(s);
  }
}

Рассмотрим рис. 1, который иллюстрирует, как можно создать средство регистрации только для добавления. При условии, что остальная часть программы написана на Joe-E, рецензент кода может быть уверен, что записи журнала могут быть только добавлены, но не могут быть изменены или удалены. Этот обзор является практичным, поскольку он требует только проверки Log класса и не требует проверки какого-либо другого кода. Следовательно, проверка этого свойства требует только локальных рассуждений о коде регистрации.


1

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


1

Делая защиту частью языка, вы получаете нечто: разумную уверенность.

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

Есть ли способы обойти синтаксическую защиту? Абсолютно; у большинства языков есть они. В C ++ вы всегда можете привести класс к другому типу и ткнуть его битами. В Java и C # вы можете отразить это в себе. И так далее.

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

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

Без синтаксической защиты ответственность лежит не на тех людях, которые используют класс. Они должны следовать соглашению, иначе произойдет неуказанное зло. Поцарапайте это: может произойти неуказанная плохость .

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.