Мої колеги люблять говорити, що "ведення журналів / кешування / тощо. Є суперечливою проблемою", а потім продовжують використовувати відповідний сингл. І все ж вони люблять IoC та DI.
Це справді вагомий привід зламати принцип SOLI D ?
Мої колеги люблять говорити, що "ведення журналів / кешування / тощо. Є суперечливою проблемою", а потім продовжують використовувати відповідний сингл. І все ж вони люблять IoC та DI.
Це справді вагомий привід зламати принцип SOLI D ?
Відповіді:
Немає.
SOLID існує як керівництво для врахування неминучих змін. Ви дійсно ніколи не збираєтесь змінювати свою бібліотеку журналів, або ціль, або фільтрувати, або форматувати, або ...? Ви дійсно не збираєтесь змінювати бібліотеку кешування, або ціль, або стратегію, або масштабування, або ...?
Звичайно, ти є. За принаймні , ви будете хотіти , щоб знущатися ці речі в здоровому чином , щоб ізолювати їх для тестування. І якщо ви хочете ізолювати їх для тестування, ви, швидше за все, зіткнетеся з діловою причиною, де хочете ізолювати їх з реальних причин.
І ви тоді отримаєте аргумент про те , що реєстратор сам буде обробляти зміни. "О, якщо ціль / фільтрування / форматування / стратегія зміниться, ми просто змінимо конфігурацію!" Це сміття. Мало того, що тепер у вас є об'єкт God, який обробляє всі ці речі, ви пишете свій код у XML (або подібному), де не отримуєте статичного аналізу, не отримуєте помилок часу компіляції, і ви не ' t дійсно отримують ефективні одиничні тести.
Чи є випадки порушення інструкцій SOLID? Абсолютно. Іноді все не зміниться (не вимагаючи повного перезапису). Іноді незначне порушення ЛСП є найчистішим рішенням. Іноді створення ізольованого інтерфейсу не надає ніякої цінності.
Але реєстрація та кешування (та інші всюдисущі наскрізні проблеми) - це не такі випадки. Вони, як правило, прекрасні приклади стику та дизайну, які ви отримуєте, коли ігноруєте вказівки.
String
або Int32
навіть List
з вашого модуля. Там всього лише ступеня , в якій це розумно і розсудлива плану змін. І, за винятком найбільш очевидних "основних" типів, розпізнавання того, що ви, можливо, змінить , справді є лише питанням досвіду та судження.
Так
У цьому і полягає вся суть терміна "наскрізний концерн" - це означає щось, що не вписується акуратно в принцип SOLID.
Тут ідеалізм зустрічається з реальністю.
Люди, які є напів новими в SOLID і наскрізних, часто стикаються з цим розумовим завданням. Це добре, не вигадуйте. Намагайтеся все ставити під термін SOLID, але є кілька місць, таких як реєстрація та кешування, де SOLID просто не має сенсу. Наскрізний брат - брат SOLID, вони йдуть рука об руку.
HttpContextBase
(яке було введено саме з цієї причини). Я точно знаю, що моя реальність була б дуже кисла без цього класу.
Для лісозаготівлі я думаю, що це так. Ведення журналу є розповсюдженим та, як правило, не пов'язане з функціональністю сервісу. Загальноприйнято і добре зрозуміло використовувати рамки ведення журналів одиночних шаблонів. Якщо ви цього не зробите, ви створюєте та вставляєте реєстратори скрізь, і цього не хочете.
Одне з вищезазначених питань полягає в тому, що хтось скаже "але як я можу перевірити ведення журналу?" . Мої думки полягають у тому, що я зазвичай не перевіряю ведення журналу, але не заперечую, що я можу насправді читати файли журналів і розуміти їх. Коли я бачу тестування журналу, це зазвичай тому, що хтось повинен стверджувати, що клас насправді щось зробив, і вони використовують повідомлення журналу, щоб отримати такий зворотний зв'язок. Я набагато краще зареєструвати якогось слухача / спостерігача в цьому класі та запевнити в своїх тестах, що викликається. Потім можна помістити журнал подій всередині цього спостерігача.
Я думаю, кешування - це зовсім інший сценарій.
Мої 2 копійки ...
Так і ні.
Ви ніколи не повинні дійсно порушувати принципи ви приймаєте; але ваші принципи завжди повинні бути впорядковані і прийняті на службу до вищої мети. Отже, при правильно обумовленому розумінні деякі видимі порушення можуть не бути фактичними порушеннями "духу" або "тіла принципів в цілому".
Зокрема, принципи SOLID, крім того, що вимагають багато нюансів, в кінцевому підсумку підпорядковуються меті "забезпечення робочого, ремонтованого програмного забезпечення". Отже, дотримання будь-якого конкретного принципу SOLID є самозакоханим та суперечливим, коли це робить, суперечить цілям SOLID. І тут, я часто відзначаю , що поставляючи козирі ремонтопридатність .
Отже, що з D у SOLID ? Ну, це сприяє підвищенню ремонтопридатності, роблячи модуль багаторазового використання відносно агностичним щодо його контексту. І ми можемо визначити "модуль багаторазового використання" як "колекцію коду, який ви плануєте використовувати в іншому окремому контексті". І це стосується єдиних функцій, класів, наборів класів та програм.
І так, зміни реалізацій реєстратора, ймовірно, ставлять ваш модуль у "інший чіткий контекст".
Отже, дозвольте запропонувати два мої великі застереження :
По-перше: малювання рядків навколо блоків коду, які складають "модуль багаторазового використання", - це питання професійного судження. І ваше судження обов'язково обмежується вашим досвідом.
Якщо ви не в даний час планують використовувати модуль в іншому контексті, це , ймовірно , добре для того , щоб безпорадно залежати від нього. Застереження про застереження: Ваші плани, ймовірно, неправильні - але це теж нормально. Чим довше ви пишете модуль за модулем, тим більше і інтуїтивніше і точніше будете відчувати, чи буде "мені знову це потрібно колись". Але, напевно, ви ніколи не зможете ретроспективно сказати: "Я модулював і деакупував все якомога більше, але без зайвого ".
Якщо ви відчуваєте вину за свої помилки в суді, перейдіть до визнання і продовжуйте ...
По-друге: інвертування керування не дорівнює залежності від введення .
Це особливо актуально, коли ви починаєте вводити залежності від нудоти . Ін'єкція залежності - корисна тактика для всеосяжної стратегії ІОК. Але я б заперечив, що DI має меншу ефективність, ніж деякі інші тактики - як, наприклад, використання інтерфейсів та адаптерів - поодинокі точки впливу контексту зсередини модуля.
І давайте дійсно зосередимося на цьому на секунду. Тому що, навіть якщо ви вводите Logger
нудоту реклами , вам потрібно написати код проти Logger
інтерфейсу. Ви не можете почати використовувати новий Logger
від іншого постачальника, який приймає параметри в іншому порядку. Ця здатність походить від кодування в модулі проти інтерфейсу, який існує всередині модуля і в якому є один підмодуль (Адаптер) для управління залежністю.
А якщо ви кодуєте адаптер, то Logger
вводиться чи адаптер у цей адаптер або виявляється адаптером, як правило, є малозначущим для загальної мети щодо ремонту. І що ще важливіше, якщо у вас є адаптер рівня модуля, то, мабуть, просто безглуздо вводити його в що-небудь. Це написано для модуля.
tl; dr - Перестаньте метушитися щодо принципів, не враховуючи, чому ви використовуєте принципи. І, більш практично, просто створити Adapter
для кожного модуля. Використовуйте судження, вирішуючи, де ви намалюєте межі "модуля". Зсередини кожного модуля йдіть далі та зверніться безпосередньо до Adapter
. І обов'язково введіть справжній реєстратор у Adapter
- але не в кожну дрібницю, яка може знадобитися.
Ідея про те, що ведення журналу завжди має здійснюватися як одиночний, - одна з тих брехні, про які говорили так часто, що набрала тяги.
Поки про сучасні операційні системи було розпізнано, ви можете зайти в декілька місць залежно від характеру виводу .
Дизайнери систем повинні постійно ставити під сумнів ефективність попередніх рішень, перш ніж сліпо включати їх у нові. Якщо вони не проводять такої ретельності, то вони не виконують свою роботу.
Справжній журнал - це особливий випадок.
@Telastyn пише:
Ви дійсно ніколи не збираєтесь змінювати свою бібліотеку журналів, або ціль, або фільтрувати, або форматувати, або ...?
Якщо ви передбачаєте, що вам може знадобитися змінити бібліотеку журналів, тоді вам слід використовувати фасад; тобто SLF4J, якщо ви перебуваєте в світі Java.
Що стосується решти, гідна бібліотека ведення журналів піклується про те, щоб змінити, куди йде журнал, які події фільтруються, як форматування подій журналу за допомогою файлів конфігурації реєстратора та (за потреби) спеціальних класів плагінів. Існує ряд альтернатив, що не існують.
Коротше кажучи, це вирішені проблеми ... для ведення журналів ... і, отже, для їх вирішення не потрібно використовувати ін'єкцію залежності.
Єдиний випадок, коли DI може бути корисним (у порівнянні зі стандартними підходами до ведення журналу), якщо ви хочете піддавати реєстрацію вашої програми одиночному тестуванню. Однак я підозрюю, що більшість розробників скажуть, що ведення журналу не є частиною функціональності класів, а не те, що потрібно перевірити.
@Telastyn пише:
І тоді ви отримаєте аргумент, що сам реєстратор впорається зі зміною. "О, якщо ціль / фільтрування / форматування / стратегія зміниться, ми просто змінимо конфігурацію!" Це сміття. Мало того, що тепер у вас є об'єкт God, який обробляє всі ці речі, ви пишете свій код у XML (або подібному), де не отримуєте статичного аналізу, не отримуєте помилок часу компіляції, і ви не ' t дійсно отримують ефективні одиничні тести.
Боюся, що це дуже теоретичний ріпост. На практиці більшість розробників та системних інтеграторів ПОДАЮТЬ той факт, що ви можете налаштувати ведення журналу через конфігураційний файл. І їм подобається той факт, що від них не очікується перевірка реєстрації модуля.
Звичайно, якщо ви складете конфігурації журналів, то у вас можуть виникнути проблеми, але вони виявляться як відмова програми під час запуску, так і занадто багато / занадто мало журналу. 1) Ці проблеми легко усуваються, виправляючи помилку у файлі config. 2) Альтернативою є повний цикл складання / аналізу / тесту / розгортання кожного разу, коли ви вносите зміни до рівнів реєстрації. Це не прийнятно.
Так і Ні !
Так: Я думаю, що розумно, що різні підсистеми (або семантичні шари, або бібліотеки, або інші поняття модульного зв’язку) приймають (той самий або) потенційно різний реєстратор під час їх ініціалізації, а не всі підсистеми, що посилаються на один і той самий спільний синглтон .
Однак,
Ні: водночас нерозумно параметризувати ведення журналів у кожному маленькому об’єкті (за допомогою конструктора чи екземпляра). Щоб уникнути зайвих і безглуздих розривів, більш дрібні об'єкти повинні використовувати одиночний реєстратор свого контексту.
Це одна з причин серед багатьох інших думати про модульність у рівнях: методи згруповані в класи, а класи - у підсистеми та / або семантичні шари. Ці великі пучки є цінним інструментом абстракції; нам слід розглянути різні міркування в модульних межах, ніж при їх перетині.
Спочатку він починається з потужного кеш-сингтона, наступні речі, які ви бачите, - це сильні class
одиночні клавіші для рівня баз даних, що представляють глобальний стан, не описові API- ес і нестабільний код.
Якщо ви вирішили не мати синглтона для бази даних, мабуть, непогано мати синглтон для кешу, зрештою, вони представляють дуже схожу концепцію зберігання даних, лише використовуючи різні механізми.
Використання одиночного класу в класі перетворює клас, який має певну кількість залежностей, до класу, який теоретично має нескінченну кількість, тому що ніколи не знаєш, що насправді ховається за статичним методом.
За останнє десятиліття, що я проводив програмування, був лише один випадок, коли я став свідком зусиль змінити логіку ведення журналу (який тоді був записаний як одиночний). Тож хоча я люблю уколи залежностей, реєстрація справді не є такою великою проблемою. Кеш, з іншого боку, я б завжди вважав залежність.
Так і Ні, але в основному Ні
Я припускаю, що більша частина розмови базується на статичному екземплярі проти введеного екземпляра. Ніхто не пропонує, щоб реєстрація порушила SRP, я припускаю? В основному ми говоримо про "принцип інверсії залежності". Tbh Я здебільшого згоден з невідповіддю Теластина.
Коли добре використовувати статику? Бо однозначно іноді добре. Точка відповідей "так" на користь абстракції, а відповіді "ні" вказують на те, що вони - це те, за що ви платите. Однією з причин вашої роботи є те, що немає однієї відповіді, яку можна записати та застосувати до всіх ситуацій.
Брати:
Convert.ToInt32("1")
Я вважаю за краще це:
private readonly IConverter _converter;
public MyClass(IConverter converter)
{
Guard.NotNull(converter)
_converter = conveter
}
....
var foo = _converter.ToInt32("1");
Чому? Я приймаю, що мені потрібно буде перефактурувати код, якщо мені потрібна гнучкість для заміни коду конверсії. Я погоджуюсь з тим, що я не зможу знущатися над цим. Я вважаю, що простота і лаконічність варті цієї торгівлі.
Дивлячись на інший кінець спектру, якби це IConverter
був a SqlConnection
, я був би дуже жахливо бачити це як статичний дзвінок. Причини, які є досить очевидними. Я хотів би зазначити, що в SQLConnection
додатку може бути досить «наскрізний», тому я б не використовував ці точні слова.
Чи Ведення журналу більше схоже на SQLConnection
чи Convert.ToInt32
? Я б більше сказав, як "SQLConnection".
Ви повинні знущатися з Ведення журналів . Він спілкується із зовнішнім світом. Коли я пишу метод, використовуючи його Convert.ToIn32
, я використовую його як інструмент для обчислення іншого окремо випробуваного виходу класу. Мені не потрібно перевіряти, чи Convert
було викликано правильно, коли перевіряли, що "1" + "2" == "3". Ведення журналів відрізняється, це абсолютно незалежний вихід класу. Я припускаю, що це результат, який має значення для вас, команди підтримки та бізнесу. Ваш клас не працює, якщо ведення журналу не правильне, тому одиничні тести не повинні проходити. Ви повинні тестувати, що записує ваш клас. Я думаю, що це вбивчий аргумент, я дійсно міг би просто зупинитися тут.
Я також думаю, що це щось цілком ймовірно, що зміниться. Хороший журнал не просто друкує рядки, це уявлення про те, чим займається ваша програма (я великий прихильник ведення журналів на основі подій). Я бачив, як базовий журнал перетворюється на досить складні інтерфейси звітування. Очевидно набагато простіше в цьому напрямку, якщо ваш лісозаготівля виглядає _logger.Log(new ApplicationStartingEvent())
і менш схожим Logger.Log("Application has started")
. Можна стверджувати, що це створює інвентар для майбутнього, яке ніколи не може відбутися, це виклик рішення, і я думаю, що воно того варте.
Насправді в особистому проекті я створив інтерфейс без реєстрації, використовуючи лише те, _logger
щоб зрозуміти, що робить додаток. Це означало, що мені не потрібно було писати код, щоб зрозуміти, що робить додаток, і я закінчився рок-сильним журналом. Я відчуваю, що якби моє ставлення до ведення лісозаготівлі було, що це просто і незмінно, ця ідея не сталася б у мене.
Тож я погодився би з Теластином у випадку лісозаготівлі.
Semantic Logging Application Block
. Не використовуйте його, як і більшість кодів, створених командою MS "Шаблони і спостереження", за іронією долі, це антипаттерн.
Перші проблеми, що перетинаються, не є основними складовими і не повинні розглядатися як залежності в системі. Система повинна працювати, якщо, наприклад, Logger не ініціалізований або кеш не працює. Як ви зробите систему менш сполученою та згуртованою? Ось де SOLID відображається в дизайні OO системи.
Збереження об'єкта як одиночного не має нічого спільного з SOLID. Це ваш життєвий цикл об'єкта, скільки часу ви хочете, щоб об’єкт жив у пам'яті.
Клас, який потребує ініціалізації залежності, не повинен знати, чи наданий екземпляр класу є однотонним або тимчасовим. Але tldr; якщо ви пишете Logger.Instance.Log () у кожному методі чи класі, то це проблематичний код (запах коду / жорстке з'єднання) - справді безладний. Це момент, коли люди починають зловживати SOLID. І такі розробники, як ОП, починають задавати справжнє запитання, як це.
Я вирішив цю проблему, використовуючи поєднання успадкування та ознак (які також називаються міксинами в деяких мовах). Характеристики дуже зручні для вирішення такого роду наскрізних проблем. Зазвичай це мовна особливість, тому я думаю, що справжня відповідь полягає в тому, що це залежить від мовних особливостей.