Що є загальним способом обробки видимості в бібліотеках?


12

Це питання про те, коли використовувати приватні та коли використовувати захищені в класах, змусило мене задуматися. (Я поширю це питання також на заключні класи та методи, оскільки це пов'язано. Я програмую на Java, але думаю, що це стосується кожної мови OOP)

Прийнята відповідь говорить:

Добре правило: зробити все максимально приватним.

І ще один:

  1. Зробіть усі класи остаточними, якщо вам не потрібно відразу підкласифікувати їх.
  2. Зробіть усі методи остаточними, якщо вам не потрібно відразу підкласифікувати їх і замінити.
  3. Зробіть усі параметри методу остаточними, якщо вам не потрібно їх змінювати в тілі методу, який у будь-якому разі є незручним у більшості випадків.

Це досить просто і зрозуміло, але що робити, якщо я в основному пишу бібліотеки (Open Source на GitHub) замість програм?

Я міг би назвати багато бібліотек та ситуацій, де

  • Бібліотеку розширюють таким чином, про який розробники ніколи не думали б
  • Це довелося робити з "магією завантажувача класу" та іншими хаками через обмеження видимості
  • Бібліотеки звикли таким чином, щоб вони не були побудовані, і необхідний функціональний спосіб "зламався"
  • Бібліотеки не можна було використовувати через невелику проблему (помилка, відсутність функціональності, "неправильна" поведінка), яку не вдалося змінити через зменшену видимість
  • Проблема, яку не вдалося виправити, призвела до величезних, потворних і невдалих способів вирішення проблеми, коли переосмислення простої функції (приватної чи остаточної) могло б допомогти

І я насправді почав називати їх, поки питання не стало занадто довгим, і я вирішив їх усунути.

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

Оскільки великі бібліотеки з відкритим кодом - це не нова річ, який найпоширеніший спосіб обробки видимості в таких проектах об'єктно-орієнтованими мовами?



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

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

1
@piegames: Я повністю погоджуюся на те, що ви бачите тут проблеми, які ви помітили, набагато більше шансів виникнути в закритих джерелах - якщо це ОС lib з дозволеною ліцензією, якщо технічне обслуговування ігнорує запит на зміну, ви можете розправити ліб і змінити видимість самостійно, якщо необхідно.
Док Браун

1
@piegames: я не розумію вашого запитання. "Java" - це мова, а не lib. І якщо "ваша маленька бібліотека з відкритим кодом" має занадто сувору видимість, розширення видимості після цього, як правило, не порушує зворотну сумісність. Тільки навпаки.
Док Браун

Відповіді:


15

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

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

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

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

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

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

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


Тому мені потрібно додати гачки для розширення, оскільки просто зробити його загальнодоступним / перезавантажуваним недостатньо. І мені також потрібно подумати про те, коли слід випустити зміни / новий API через зворотну сумісність. Але як щодо наочності методів у спеціальних?
piegames

@piegames Завдяки видимості ви визначаєте, які частини є загальнодоступними (частина вашого стабільного API), а які - приватними (можуть змінюватися). Якщо хтось обходить це з допомогою рефлексії, це їхня проблема, коли ця функція в майбутньому порушується. До речі, точки розширення часто бувають у формі методу, який можна перекрити. Але є різниця між методом, який можна переосмислити, і методом, який призначено перекрити (див. Також Шаблон методу шаблону).
амон

8

Кожен відкритий і розширюваний клас / метод - це частина вашого API, яку потрібно підтримувати. Обмеження цього набору розумним набором бібліотеки дозволяє забезпечити найбільшу стабільність і обмежує кількість речей, які можуть піти не так. Це рішення управління (і навіть проекти OSS керуються певною мірою) на основі того, що ви можете розумно підтримати.

Різниця між OSS та закритим джерелом полягає в тому, що більшість людей намагаються створити та розробити спільноту навколо коду, щоб більше однієї людини підтримувала бібліотеку. Однак, існує ряд інструментів управління:

  • Списки розсилки обговорюють потреби користувача та способи їх здійснення
  • Системи відстеження випусків (проблеми JIRA або Git тощо) відслідковують помилки та запити на функції
  • Контроль версій управляє вихідним кодом.

У зрілих проектах те, що ви побачите, є чимось таким чином:

  1. Хтось хоче зробити щось із бібліотекою, яку вона спочатку не була створена
  2. Вони додають квиток до відстеження проблем
  3. Команда може обговорити це питання у списку розсилки чи у коментарях, а запитуючий завжди запрошений приєднатися до дискусії
  4. Зміна API приймається та визначається пріоритетом або відхиляється з якоїсь причини

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

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


1
Повністю згоден, і я успішно практикував процес запиту змін, який ви промальовували і для сторонніх закритих джерел, а також для відкритих джерел.
Док Браун

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

Це завжди багато роботи. Майже в кожному проекті, до якого я сприяв, є процес, подібний цьому. Ще в дні моїх Apache ми могли б днями щось обговорювати, тому що ми захоплювалися тим, що ми створили. Ми знали, що багато людей збираються користуватися бібліотеками, тому нам довелося обговорити такі речі, як, чи запропонована зміна буде порушувати API, чи пропонується запропонована функція, коли ми повинні це зробити? п.
Берін Лорич

0

Я переформулюю свою відповідь, оскільки, здається, вона вразила нерв у кількох людей.

Властивість / метод видимості класу не має нічого спільного ні з безпекою, ні з відкритістю джерела.

Причина того, що видимість існує, полягає в тому, що об’єкти неміцні до 4 конкретних проблем:

  1. одночасність

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

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

  1. Допоможіть перешкодити користувачам стріляти в користування інтерфейсом. По суті, це допомагає вам контролювати інваріанти об’єкта.

У кожному об'єкті є маса речей, які потрібно мати істину, щоб бути в послідовному стані. На жаль, ці речі живуть у видимому для клієнта просторі, оскільки дорого переносити кожен об'єкт у свій процес і спілкуватися з ним через повідомлення. Це означає, що дуже легко об'єкт зламати всю програму, якщо користувач має повну видимість.

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

Ще один приємний приклад, коли ви можете покращити зручність використання своїх модулів - це зробити конструктор приватним; тому що якщо конструктор кине виняток, він вб'є програму. Один із ледачих підходів до вирішення цього питання - змусити конструктор викинути помилку часу компіляції, що ви не можете його сконструювати, якщо це не в блоці спробу / лову. Зробивши приватний конструктор і додавши загальнодоступний метод статичного створення, ви можете мати метод створення методу return null, якщо він не зможе його сконструювати, або скористатися функцією зворотного виклику для обробки помилки, зробивши програму більш зручною для користувачів.

  1. Область забруднення

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

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

  1. прив’язаність до залежностей

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

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


1
"Немає сенсу нічого приховувати" - тоді навіщо навіть думати про інкапсуляцію? У багатьох контекстах рефлексія вимагає особливих привілеїв.
Френк Хілеман

Ви думаєте про інкапсуляцію, оскільки вона дає дихаючий простір під час розробки модуля та зменшує ймовірність неправильного використання. Наприклад, якщо у вас є 4 потоки, що безпосередньо змінюють внутрішній стан класу, це легко спричинить проблеми, в той час як робить змінну приватною, спонукає користувача використовувати загальнодоступні методи маніпулювання світовою державою, які можуть використовувати монітори / блокування для запобігання проблем . Це єдина реальна користь від капсулювання.
Дмитро

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

Гаразд, що має більше сенсу, хоча безпеку можна досягти за допомогою контролю доступу, якщо середовище це підтримує, і це було однією з оригінальних цілей в розробці мови OO. Також ви рекламуєте інкапсуляцію і говорите, що не використовуйте її одночасно; трохи заплутано
Френк Хілеман

Я ніколи не мав на увазі не використовувати його; Я мав на увазі не використовувати це задля безпеки; використовувати його стратегічно, щоб покращити досвід своїх користувачів та забезпечити собі більш плавне середовище розробки. моя думка полягає в тому, що це не має нічого спільного ні з безпекою, ні з відкритістю джерела. Клієнтські об'єкти за визначенням вразливі до самоаналізу, а переміщення їх із простору користувальницького простору робить некапсульовані речі настільки ж неприступними, як інкапсульовані.
Дмитро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.