Почему Чистый код предлагает избегать защищенных переменных?

168

Чистый код предлагает избегать защищенных переменных в разделе «Вертикальное расстояние» главы «Форматирование»:

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

В чем причина?

Matsemann
источник
7
покажите пример, где вы думаете, что вам это нужно
gnat
63
Я бы не воспринял многое в этой книге как Евангелие.
Буровая установка
40
@ Риг, в этих частях вы граничите с богохульством с таким комментарием.
Ryathal
40
Я в порядке с этим. Я прочитал книгу и могу иметь собственное мнение о ней.
Рог
10
Я прочитал книгу, и хотя я согласен с большинством того, что там, я бы не сказал, что считаю это Евангелием. Я думаю, что каждая ситуация отличается ... и быть способным придерживаться точных указаний, представленных в книге, довольно сложно для многих проектов.
Саймон Уайтхед

Ответы:

277

Защищенных переменных следует избегать, потому что:

  1. Они имеют тенденцию приводить к проблемам ЯГНИ . Если у вас нет класса-потомка, который фактически работает с защищенным членом, сделайте его закрытым.
  2. Они имеют тенденцию приводить к проблемам LSP . Защищенные переменные обычно имеют некоторую внутреннюю инвариантность, связанную с ними (иначе они были бы открытыми). Затем наследникам необходимо поддерживать те свойства, которые люди могут испортить или умышленно нарушить.
  3. Они имеют тенденцию нарушать OCP . Если базовый класс делает слишком много предположений о защищаемом члене или наследник слишком гибок с поведением класса, это может привести к изменению поведения базового класса этим расширением.
  4. Они имеют тенденцию приводить к наследованию для расширения, а не к составу. Это приводит к более тесному взаимодействию, большему количеству нарушений SRP , более сложному тестированию и множеству других вещей, которые попадают в дискуссию «композиция благосклонности над наследованием».

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

Telastyn
источник
Спасибо за много веских причин. Тем не менее, книга упоминает об этом конкретно в контексте форматирования и вертикального расстояния, это та часть, которая меня интересует.
Мацеманн
2
Кажется, что дядя Боб имеет в виду вертикальное расстояние, когда кто-то ссылается на защищенный член между классами, а не в одном классе.
Роберт Харви
5
@ Мацеманн - конечно. Если я точно помню книгу, то в этом разделе основное внимание уделяется удобочитаемости и способности обнаружения кода. Если переменные и функции работают над концепцией, они должны быть близки по коду. Защищенные члены будут использоваться производными типами, которые обязательно не могут быть близки к защищенному члену, поскольку они находятся в совершенно другом файле.
Теластин
2
@ steve314 Я не могу представить сценарий, когда базовый класс и все его наследники будут когда-либо жить только в одном файле. Без примера я склонен считать это злоупотреблением наследством или плохой абстракцией.
Теластин
3
YAGNI = Ты не собираешься это нужно LSP = Принцип замещения Лискова OCP = Принцип открытия / закрытия SRP = Принцип одиночной ответственности
Брайан Глейзер
54

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

Карл Билефельдт
источник
26

Я не читал книгу, но могу понять, что имел в виду дядя Боб.

Если вы надеваете protectedчто-то, это означает, что класс может наследовать это. Но переменные-члены должны принадлежать классу, в котором они содержатся; это часть базовой инкапсуляции. Установка protectedпеременной-члена нарушает инкапсуляцию, поскольку теперь производный класс имеет доступ к деталям реализации базового класса. Это та же проблема, которая возникает, когда вы создаете переменную publicв обычном классе.

Чтобы исправить проблему, вы можете инкапсулировать переменную в защищенное свойство, например так:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Это позволяет nameбезопасно устанавливать его из производного класса, используя аргумент конструктора, не раскрывая подробности реализации базового класса.

Роберт Харви
источник
Вы создали публичное свойство с защищенным сеттером. Защищенное поле должно быть решено с помощью защищенного свойства с частным установщиком.
Энди
2
+1 сейчас. Полагаю, что сеттер тоже может быть защищен, но точка, которую база может установить, если новое значение допустимо или нет.
Энди
Просто чтобы предложить противоположную точку зрения - я защищаю все свои переменные в базовом классе. Если они доступны только через открытый интерфейс, то это не должен быть производный класс, он должен просто содержать объект базового класса. Но я также избегаю иерархий более 2-х классов
Мартин Беккет
2
Существует огромная разница между protectedи publicчленами. Если BaseTypeвыставляется открытый член, это означает, что все производные типы должны иметь один и тот же элемент, работающий одинаково, поскольку DerivedTypeэкземпляр может быть передан в код, который получает BaseTypeссылку и ожидает использовать этот элемент. Напротив, если BaseTypeвыставляет protectedучастника, то DerivedTypeможет ожидать доступа к этому члену base, но baseне может быть ничем иным, кроме BaseType.
суперкат
18

Аргумент дяди Боба - это, в первую очередь, аргумент расстояния: если у вас есть концепция, важная для класса, объедините эту концепцию вместе с классом в этом файле. Не разделены между двумя файлами на диске.

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

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

Дж. Полфер
источник
9

Некоторые очень хорошие ответы уже здесь. Но одна вещь, которую нужно добавить:

В большинстве современных ОО-языков хорошая привычка (в Java AFAIK это необходимо) помещать каждый класс в отдельный файл (пожалуйста, не думайте о C ++, где у вас часто есть два файла).

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

Док Браун
источник
1
Не в Свифте. Интересно, что Swift не имеет «protected», но имеет «fileprivate», который заменяет как «protected», так и C ++ «друг».
gnasher729
@ gnasher729 - конечно, Swift имеет много Smalltalk в своем наследии. Smalltalk не имеет ничего, кроме личных переменных и открытых методов. А частный в Smalltalk означает частный: два объекта даже одного и того же класса не могут получить доступ к переменным друг друга.
Жюль
4

Основная идея заключается в том, что «поле» (переменная уровня экземпляра), которое объявлено как защищенное, вероятно, более заметно, чем должно быть, и менее «защищено», чем вам может показаться. В C / C ++ / Java / C # отсутствует модификатор доступа, который эквивалентен «доступным только дочерним классам в одной сборке», что дает вам возможность определять свои собственные дочерние элементы, которые могут получить доступ к полю в вашей сборке, но не разрешать детям, созданным в других сборках, одинаковый доступ; C # имеет внутренние и защищенные модификаторы, но их объединение делает доступ «внутренним или защищенным», а не «внутренним и защищенным». Таким образом, защищенное поле доступно любому ребенку, независимо от того, написали ли вы этот ребенок или кто-то другой. Защищен, таким образом, открытая дверь для хакера.

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

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

Другой подход, который отвечает на вопрос, это спросить себя; почему код в дочернем методе должен иметь возможность напрямую изменять данные моего состояния? Что это говорит об этом коде? Это аргумент "вертикального расстояния" на его лице. Если в дочернем элементе есть код, который должен напрямую изменять родительское состояние, то, возможно, этот код должен в первую очередь принадлежать родителю?

Keiths
источник
4

Это интересная дискуссия.

Честно говоря, хорошие инструменты могут смягчить многие из этих «вертикальных» проблем. На самом деле, на мой взгляд, понятие «файл» на самом деле препятствует разработке программного обеспечения - над чем работает проект Light Table (http://www.chris-granger.com/2012/04/12/light- таблица --- а-новый-ида-концепция /).

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

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

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

spronkey
источник
0

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

Однако главная причина того, что защищенные переменные не рекомендуются, заключается в том, что они имеют тенденцию нарушать инкапсуляцию. Переменные и методы должны иметь как можно более ограниченную видимость; для дальнейшей ссылки см. « Эффективная Java» Джошуа Блоха , пункт 13 - «Минимизируйте доступность классов и членов».

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

m3th0dman
источник
1
Не думайте, что, поскольку язык это позволяет, это нормально. Я не уверен, что на самом деле есть веская причина разрешить охраняемые поля; мы фактически запрещаем их моему работодателю.
Энди
2
@ Энди, ты не прочитал то, что я сказал; Я сказал, что защищенные поля не рекомендуются и причины для этого. Очевидно, есть сценарии, в которых необходимы защищенные переменные; Вы можете хотеть постоянное окончательное значение только в подклассах.
m3th0dman
Возможно, вы захотите перефразировать некоторые из ваших постов, так как вы, кажется, используете переменные и поля взаимозаменяемо и явно заявляете, что вы рассматриваете такие вещи, как защищенные контраста, как исключение.
Энди
3
@ Andy Довольно очевидно, что, поскольку я имел в виду защищенные переменные, я говорил не о локальных или параметрических переменных, а о полях. Я также упомянул сценарий, в котором непостоянные защищенные переменные полезны; imho, это неправильно для компании обеспечивать соблюдение способа, которым программисты пишут код.
m3th0dman
1
Если бы это было очевидно, я бы тебя не смутил и не проголосовал. Правило было принято нашими старшими разработчиками, так же как и наше решение запустить StyleCop, FxCop и потребовать модульных тестов и проверок кода.
Энди
0

Я считаю, что использование защищенных переменных для тестов JUnit может быть полезным. Если вы сделаете их приватными, вы не сможете получить к ним доступ во время тестирования без размышлений. Это может быть полезно, если вы тестируете сложный процесс с помощью множества изменений состояния.

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

Ник
источник
-1

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

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

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

CurtainDog
источник