Чи реєстрація поруч із впровадженням є порушенням SRP?


19

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

Чи реєстрація поруч із впровадженням є порушенням SRP?

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

Скажімо, у нас є маса компонентів повністю без порушення SRP, і тоді ми хочемо додати журнал.

  • компонент А
  • компонент В використовує A

Ми хочемо вести журнал для A, тому ми створюємо ще один компонент D, прикрашений A, що реалізує інтерфейс I.

  • інтерфейс I
  • компонент L (компонент реєстрації системи)
  • компонент A реалізує I
  • компонент D реалізує I, декорує / використовує A, використовує L для ведення журналів
  • компонент B використовує I

Переваги: ​​- Я можу використовувати A без реєстрації - тестування A означає, що мені не потрібні макети реєстрації - тести простіші

Недолік: - більше компонентів і більше тестів

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



Я вже це прочитав, і відповідь не задовольняє, вибачте.
Aitch


@MarkRogers дякую, що поділилися цією цікавою статтею. Дядько Боб каже в «Чистому коді», що хороший компонент SRP має справу з іншими компонентами на тому ж рівні абстракції. Для мене це пояснення простіше зрозуміти, оскільки контекст також може бути занадто великим. Але я не можу відповісти на запитання, адже що таке контекст або рівень абстракції реєстратора?
Aitch

3
"не відповідь для мене" або "відповідь не задовольняє" трохи зневажливо. Ви можете задуматися над тим, що конкретно не задовольняє (яка вимога у вас не була виконана відповіддю? Що конкретно є унікальним у вашому запитанні?), А потім відредагувати своє запитання, щоб переконатися, що ця вимога / унікальний аспект чітко пояснено. Метою є змусити вас відредагувати своє питання, щоб вдосконалити його, щоб зробити його більш чітким та зосередженим, а не просити твердження, що підтверджує, що ваше питання інше / не слід закривати без обґрунтування чому. (Ви також можете прокоментувати іншу відповідь.)
DW

Відповіді:


-1

Так, це порушення SRP, оскільки лісозаготівля є наскрізною проблемою.

Правильний спосіб - делегувати ведення журналу в клас реєстратора (Перехоплення), єдиною метою якого є ведення журналу - дотримання SRP.

Перейдіть за цим посиланням для хорошого прикладу: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Ось короткий приклад :

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

Переваги включають

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

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

2
Я не бачу, щоб ви ніде не призначали змінну _logger. Чи планували ви використовувати інжектор конструктора і просто забули? Якщо так, ви, ймовірно, отримаєте попередження про компілятор.
користувач2023861

27
Замість того, щоб TenantStore був DIPed із загальнодоступним реєстратором, для якого потрібні N + 1 класи (коли ви додаєте LandlordStore, FooStore, BarStore тощо), у вас є TenantStoreLogger, який поширюється з TenantStore, FooStoreLogger DIPed з FooStore тощо ..., що вимагають 2N-класів. Наскільки я можу сказати, за нульову вигоду. Коли ви хочете зробити тестування блоку без реєстрації, вам потрібно буде повторно запустити N класів, а не просто налаштувати NullLogger. ІМО, це дуже поганий підхід.
user949300

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

9
Захищений. Ви видалили занепокоєння щодо журналу з класу Орендатор, але тепер ваша TenantStoreLoggerзміна буде щоразу TenantStoreзмінюватися. Ви вже не розділяєте проблеми, ніж у початковому рішенні.
Лоран LA RIZZA

61

Я б сказав, що ти сприймаєш СРП занадто серйозно. Якщо ваш код достатньо охайний, що ведення журналу - єдине "порушення" SRP, то ви краще, ніж 99% усіх інших програмістів, і вам слід погладити себе по спині.

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


19
@Aitch: ваш вибір полягає в тому, щоб жорстко провести реєстрацію у вашому класі, передати ручку до лісоруба або взагалі нічого не записувати. Якщо ви будете надто суворими щодо SRP за рахунок всього іншого, я б рекомендував ніколи нічого не реєструвати. Все, що вам потрібно знати про те, що робить ваше програмне забезпечення, можна виправити налагоджувачем. П в СРП означає "принцип", а не "фізичний закон природи, який ніколи не повинен бути порушений".
Blrfl

3
@Aitch: Ви повинні мати можливість відстежувати вхід у свій клас до певної вимоги, інакше ви порушуєте YAGNI. Якщо журнал ведеться на столі, ви надаєте дійсну ручку реєстратора так само, як і для всього іншого, що потрібен класу, бажано класу з класу, який вже пройшов тестування. Незалежно від того, який виробляє фактичні записи в журнал або скидає їх у бітове відро, це питання, що створило екземпляр вашого класу; сам клас не повинен байдуже.
Blrfl

3
@Aitch Щоб відповісти на ваше запитання щодо тестування одиниць: Do you mock the logger?це ТОЧНО те, що ви робите. У вас повинен бути ILoggerінтерфейс, який визначає, що робить реєстратор. Код, що перевіряється, вводиться з вказаним ILoggerвами. Для тестування у вас є class TestLogger : ILogger. Чудова річ у цьому полягає в тому, що TestLoggerможна виявити такі речі, як останній рядок або зафіксовану помилку. Тести можуть перевірити, чи правильно перевіряється код, який перевіряється. Наприклад, тестом може бути тест UserSignInTimeGetsLogged(), де тест перевіряє TestLoggerнаявність зареєстрованих.
CurtisHx

5
99% здається трохи низьким. Ви, мабуть, кращі за 100% усіх програмістів.
Пол Дрейпер

2
+1 для розумності Такого мислення нам потрібно більше: менше зосередження уваги на словах та абстрактних принципах і більше уваги на створенні коду, що підтримується .
jpmc26

15

Ні, це не порушення СРП.

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

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

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

Тоді ваша реалізація також не повинна знати, чи є реєстратор, якому він надсилає повідомлення NullLogger.

Це сказав.

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

Що стосується наскрізних проблем, OTOH, - це відстеження виконання : реєстрація входить та виходить із кожного методу. AOP найкраще робити це.


Скажімо, повідомлення реєстратора - це "реєстраційний користувач xyz", який надсилається до реєстратора, який попереджує часову позначку тощо. Чи знаєте ви, що означає "вхід" для реалізації? Чи розпочинається сеанс із cookie чи будь-яким іншим механізмом? Я думаю, що існує багато різних способів здійснення входу, тому зміна реалізації не має нічого спільного з тим, що користувач входить у систему. Це ще один чудовий приклад декорування різних компонентів (скажімо, OAuthLogin, SessionLogin, BasicAuthorizationLogin), що робить те саме як Loginінтерфейс, прикрашений тим самим реєстратором.
Aitch

Це залежить від того, що означає повідомлення "login user xyz". Якщо він позначає факт успішного входу, відправка повідомлення в журнал належить до випадку використання входу. Конкретний спосіб подання інформації для входу як рядок (OAuth, Session, LDAP, NTLM, відбиток пальців, колесо хом'яка) належить до конкретного класу, що представляє облікові дані або стратегію входу. Немає переконливої ​​необхідності її видаляти. Цей окремий випадок не викликає суперечок. Це специфічно для випадку використання входу.
Лоран LA RIZZA

7

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

Залежно від мови, для якого ви використовуєте перехоплювач або якусь рамку AOP (наприклад, AspectJ на Java).

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


2
Більшість коду AOP, який я бачив, стосувався реєстрації кожного кроку введення та виходу кожного методу. Я хочу лише записати деякі частини бізнес-логіки. Тож, можливо, можна реєструвати лише анотовані методи, але AOP взагалі може існувати лише у мовах скриптів та середовищах віртуальної машини, правда? Наприклад, C ++ це неможливо. Я визнаю, що не дуже задоволений підходами AOP, але, можливо, немає більш чистого рішення.
Aitch

1
@Aitch. "C ++ це неможливо." : Якщо ви перейдете на Google для "aop c ++", ви знайдете статті про це. "... код AOP, який я бачив, стосувався реєстрації кожного кроку введення та виходу кожного методу. Я хочу лише занести деякі частини бізнес-логіки" Aop дозволяє визначити шаблони, щоб знайти способи зміни. тобто всі методи з простору імен "my.busininess. *"
k3b

1
Ведення журналу часто НЕ є суцільним питанням, особливо коли ви хочете, щоб ваш журнал містив цікаву інформацію, тобто більше інформації, ніж міститься у трасі стека винятків.
Лоран LA RIZZA

5

Це звучить прекрасно. Ви описуєте досить стандартний декоратор лісозаготівлі. Ти маєш:

компонент L (компонент реєстрації системи)

Це несе одну відповідальність: реєстрацію інформації, яка передається їй.

компонент A реалізує I

Це несе одну відповідальність: забезпечення реалізації інтерфейсу I (якщо я належним чином сумісний з SRP, тобто).

Це важлива частина:

компонент D реалізує I, декорує / використовує A, використовує L для ведення журналів

Коли це сказано так, це звучить складно, але подивіться на це так: Компонент D робить одне : об'єднувати A і L разом.

  • Компонент D не реєструється; він делегує це L
  • Компонент D не реалізує сам Я; він делегує це А

Тільки відповідальність , що компонент D має, щоб переконатися , що L повідомляється , коли А використовується. Реалізація A і L є і в іншому місці. Це повністю сумісні з SRP, а також є акуратним прикладом OCP і досить звичним використанням декораторів.

Важливий застереження: коли D використовує ваш компонент журналу L, він повинен робити це таким чином, що дозволяє змінити спосіб ведення журналу. Найпростіший спосіб зробити це - мати інтерфейс IL, який реалізується Л. Тоді:

  • Компонент D використовує ІЛ для реєстрації; надається екземпляр L
  • Компонент D використовує I для забезпечення функціональності; надається екземпляр A
  • Компонент B використовує I; надається екземпляр D

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


Я фактично знаю лише C #, яка має підтримку делегації. Ось чому я і писав D implements I. Спасибі за вашу відповідь.
Aitch

1

Звичайно, це порушення СРП, оскільки ви маєте наскрізну стурбованість. Однак ви можете створити клас, який відповідає за складання журналу з виконанням будь-якої дії.

приклад:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}

2
Захищений. Діяльність лісозаготівель справді є актуальною проблемою. Так само є послідовним викликом методу у вашому коді. Це недостатньо підстав, щоб заявити про порушення СРП. Реєстрація виникнення конкретної події у вашій заявці НЕ є суцільною проблемою. ШЛЯХ цих повідомлень передається будь-якому зацікавленому користувачеві насправді є окремою проблемою, і опис цього в коді реалізації є порушенням SRP.
Лоран LA RIZZA

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

Це не детальна реалізація. Це глибоко впливає на форму вашого коду.
Лоран LA RIZZA

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