Чому Clean Code пропонує уникати захищених змінних?


168

Чистий код пропонує уникати захищених змінних у розділі "Вертикальна відстань" розділу "Форматування":

Концепції, які тісно пов'язані між собою, повинні триматися вертикально близько один до одного. Зрозуміло, що це правило не працює для понять, що належать до окремих файлів. Але тоді тісно пов’язані поняття не слід розділяти на різні файли, якщо у вас немає дуже вагомих причин. Дійсно, це одна з причин того, що слід уникати захищених змінних .

Яке міркування?


7
покажіть приклад, де ви думаєте, що вам це потрібно
gnat

63
Я б не сприймав багато в цій книзі як євангелію.
Риг

40
@Rig ти межуєш з богохульством у цих частинах з таким коментарем.
Рятал

40
Я з цим все гаразд. Я прочитав книгу і можу мати свою власну думку про неї.
Риг

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

Відповіді:


277

Захищених змінних слід уникати, оскільки:

  1. Вони, як правило, призводять до проблем YAGNI . Якщо у вас є клас нащадків, який насправді робить речі із захищеним членом, зробіть його приватним.
  2. Вони, як правило, призводять до проблем LSP . Захищені змінні, як правило, мають деяку внутрішню інваріантність, пов'язану з ними (інакше вони будуть загальнодоступними). Тоді спадкоємцям потрібно підтримувати ті властивості, які люди можуть викрутити або навмисно порушити.
  3. Вони схильні порушувати OCP . Якщо базовий клас робить занадто багато припущень щодо захищеного члена або спадкодавець занадто гнучкий у поведінці класу, це може призвести до зміни поведінки базового класу цим розширенням.
  4. Вони, як правило, призводять до спадкування для розширення, а не до складу. Це, як правило, призводить до більш жорсткої зв'язку, більшої кількості порушень СРП , більш складного тестування та ряду інших речей, що підпадають під обговорення "складу прихильності над спадщиною".

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


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

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

5
@Matsemann - точно. Якщо я точно запам’ятав книгу, цей розділ зосереджений на читанні та виявленні коду. Якщо змінні та функції працюють над концепцією, вони повинні бути близькими в коді. Захищені члени будуть використовуватися похідними типами, які обов'язково не можуть бути близькими до захищеного члена, оскільки вони знаходяться в зовсім іншому файлі.
Теластин

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

3
YAGNI = Ви Ейнт Збираюся потрібно це LSP = Лісков принцип заміщення OCP = Open / Close Принцип SRP = Single Відповідальність Принцип
Брайан Глейзер

54

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


26

Я не читав книгу, але можу сказати, що мав на увазі дядько Боб.

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

Щоб виправити проблему, ви можете інкапсулювати змінну в захищеній властивості, наприклад:

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

Це дозволяє nameбезпечно встановити з похідного класу за допомогою аргументу конструктора, не піддаючи деталі реалізації базового класу.


Ви створили публічну власність із захищеним сетером. Захищене поле повинно бути вирішене захищеною власністю з приватним сетером.
Енді

2
+1 зараз. Я думаю, що сеттер також може бути захищений, але точка ідентифікації бази може застосувати, якщо нове значення дійсне чи ні.
Енді

Просто для того, щоб запропонувати протилежну точку зору - я роблю всі мої змінні захищеними в базовому класі. Якщо до них можна отримати доступ лише через загальнодоступний інтерфейс, тоді він не повинен бути похідним класом, він повинен містити лише об'єкт baseclass. Але я також уникаю ієрархій глибших 2 класів
Мартін Беккет

2
Існує величезна різниця між protectedта publicчленами. Якщо BaseTypeвикриває публічний член, це означає, що всі похідні типи повинні мати того самого члена, що працює однаково, оскільки DerivedTypeекземпляр може бути переданий до коду, який отримує BaseTypeпосилання, і очікує використання цього члена. На противагу цьому , якщо BaseTypeвиставляє protectedчлен, то DerivedTypeможе розраховувати на доступ до цього члену в base, але baseне може бути нічого іншого , ніж BaseType.
supercat

18

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

Змінні захищених членів розкидані в двох місцях і, схоже, схожі на магію. Ви посилання на цю змінну, але вона не визначена тут ... де це вона визначається? І таким чином починається полювання. Краще protectedвзагалі уникати його аргументу.

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


9

Деякі дуже хороші відповіді вже тут. Але додати одне:

У більшості сучасних мов OO є доброю звичкою (у Java AFAIK це необхідно) класти кожен клас у свій власний файл (будь ласка, не розумійте C ++, де у вас часто є два файли).

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


1
Не у Свіфті. Цікаво, що у Swift немає "захищеного", але він має "fileprivate", який замінює "захищений" та C ++ "друг".
gnasher729

@ gnasher729 - звичайно, у Свіфта багато спадчинок. Smalltalk не має нічого іншого, крім приватних змінних та публічних методів. А приватне в Smalltalk означає приватне: два об'єкти навіть одного класу не можуть отримати доступ до змінних один одного.
Жуль

4

Основна ідея полягає в тому, що "поле" (змінна на рівні екземпляра), яке оголошується захищеним, ймовірно, більш видиме, ніж воно повинно бути, і менш "захищене", ніж ви могли б хотіти. У C / C ++ / Java / C # немає модифікатора доступу, який є еквівалентом "доступного лише дочірнім класам в межах однієї збірки", таким чином, ви надаєте можливість визначати своїх власних дітей, які можуть отримати доступ до поля у вашій збірці, але не дозволяти дітям, створеним в інших зборах, однакового доступу; C # має внутрішні та захищені модифікатори, але їх поєднання робить доступ "внутрішнім або захищеним", а не "внутрішнім і захищеним". Отже, поле, яке захищається, є доступним для будь-якої дитини, незалежно від того, написали ви цю дитину чи хтось інший. Таким чином, захищеними є відкриті двері для хакера.

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

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

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


4

Це цікава дискусія.

Якщо чесно кажучи, хороші інструменти можуть пом’якшити багато з цих «вертикальних» питань. Насправді, на мій погляд, поняття "файл" насправді є тим, що перешкоджає розробці програмного забезпечення - над тим, що працює над проектом Light Table (http://www.chris-granger.com/2012/04/12/light- таблиця --- a-new-ide-concept /).

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

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

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


0

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

Однак головна причина, що захищені змінні не рекомендуються, полягає в тому, що вони, як правило, порушують інкапсуляцію. Змінні та методи повинні мати максимально обмежену видимість; для подальшого ознайомлення див. Ефективну Java Джошуа Блоха , пункт 13 - "Мінімізуйте доступність класів та членів".

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


1
Не вважайте, що оскільки мова це дозволяє, це нормально робити. Я не впевнений, що дійсно є богові причини, щоб дозволити захищені поля; ми фактично забороняємо їх у мого роботодавця.
Енді

2
@Andy Ви не прочитали, що я сказав; Я сказав, що захищені поля не рекомендуються і причини цього. Ясно існують сценарії, в яких необхідні захищені змінні; ви можете мати постійне остаточне значення лише в підкласах.
m3th0dman

Ви можете потім переробити частину своєї публікації, оскільки ви, здається, використовуєте змінну та поле без змін і чітко робите, що ви вважаєте, що такі речі, як захищені, є винятком.
Енді

3
@Andy Досить очевидно, що оскільки я мав на увазі захищені змінні, я говорив не про локальні чи параметри змінних, а про поля. Я також згадав сценарій, коли непостійні захищені змінні корисні; Імхо, це неправильно, щоб компанія застосовувала спосіб написання коду програмістами.
m3th0dman

1
Якби це було очевидно, я б тебе не збентежив і не образив тебе. Це правило було прийнято нашими старшими розробниками, як і наше рішення запустити StyleCop, FxCop та вимагати тестування одиниць та перевірки коду.
Енді

0

Я вважаю, що використання захищених змінних для тестів JUnit може бути корисним. Якщо ви зробите їх приватними, ви не можете отримати доступ до них під час тестування без роздумів. Це може бути корисно, якщо ви протестуєте складний процес через багато змін стану.

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


-1

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

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

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

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