Является ли регистрация рядом с реализацией нарушением SRP?

19

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

Является ли регистрация рядом с реализацией нарушением SRP?

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

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

  • компонент А
  • компонент B использует A

Нам нужна регистрация для A, поэтому мы создаем еще один компонент D, украшенный A и реализующий интерфейс I.

  • интерфейс I
  • компонент L (компонент регистрации системы)
  • компонент А реализует I
  • компонент D реализует I, украшает / использует A, использует L для регистрации
  • Компонент B использует I

Преимущества: - я могу использовать A без регистрации - тестирование означает, что мне не нужны никакие журналы - тесты проще

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

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

крестцовой
источник
Я уже прочитал это, и ответ не удовлетворяет, извините.
Aitch
@MarkRogers спасибо, что поделились этой интересной статьей. Дядя Боб в «Чистом коде» говорит, что хороший компонент SRP имеет дело с другими компонентами на том же уровне абстракции. Для меня это объяснение легче понять, поскольку контекст также может быть слишком большим. Но я не могу ответить на вопрос, потому что каков контекст или уровень абстракции журнала?
Aitch
3
«не является ответом для меня» или «ответ не удовлетворяет» немного пренебрежительно. Вы можете подумать о том, что конкретно является неудовлетворительным (какое у вас требование, которое не было удовлетворено этим ответом? Что конкретно является уникальным в вашем вопросе?), А затем отредактировать свой вопрос, чтобы убедиться, что это требование / уникальный аспект объяснено четко. Цель состоит в том, чтобы заставить вас отредактировать свой вопрос, чтобы улучшить его, чтобы сделать его более понятным и более сфокусированным, а не запрашивать шаблон, утверждающий, что ваш вопрос отличается / не должен закрываться без объяснения причин. (Вы также можете прокомментировать другой ответ.)
DW

Ответы:

-1

Да, это нарушение SRP, поскольку ведение журнала является сквозной проблемой.

Правильный способ - делегировать ведение журнала классу ведения журнала (Interception), единственной целью которого является ведение журнала с помощью 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.
z0mbi3
источник
Спасибо за приятную ссылку. Рисунок 1 на этой странице - это то, что я бы назвал своим любимым решением. Список сквозных задач (ведение журнала, кэширование и т. Д.) И шаблон декоратора - это наиболее общее решение, и я рад, что не ошибаюсь в своих мыслях, хотя более широкое сообщество хотело бы отказаться от этой абстракции и встроенной регистрации. ,
Aitch
2
Я не вижу, чтобы вы где-либо назначали переменную _logger. Вы планировали использовать инжектор конструктора и просто забыли? Если это так, вы, вероятно, получите предупреждение компилятора.
user2023861
27
Вместо того, чтобы TenantStore был DIPed с помощью Logger общего назначения, который требует N + 1 классов (когда вы добавляете LandlordStore, FooStore, BarStore и т. Д.), У вас есть TenantStoreLogger, который DIPed с TenantStore, FooStoreLogger DIPed с FooStore и т.д ... требующих 2N классов. Насколько я могу сказать, для нулевой выгоды. Если вы хотите выполнить модульное тестирование без регистрации, вам нужно перенастроить N классов, а не просто настроить NullLogger. ИМО, это очень плохой подход.
user949300
6
Выполнение этого для каждого отдельного класса, для которого требуется ведение журналов, значительно увеличивает сложность вашей кодовой базы (если только у немногих классов нет ведения журналов, которые вы бы даже не называли это сквозной задачей). Это в конечном итоге делает код менее обслуживаемым просто из-за большого количества поддерживаемых интерфейсов, что идет вразрез со всем, для чего был создан Принцип единой ответственности.
jpmc26
9
Downvoted. Вы удалили проблему с журналированием из класса «Арендатор», но теперь ваши TenantStoreLoggerизменения будут меняться каждый раз TenantStore. Вы не разделяете проблемы больше, чем в первоначальном решении.
Лоран LA RIZZA
61

Я бы сказал, что вы слишком серьезно относитесь к SRP. Если ваш код достаточно аккуратен, что регистрация является единственным «нарушением» SRP, то вы справляетесь лучше, чем 99% всех других программистов, и вам следует погладить себя по пятам.

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

grahamparks
источник
19
@ Aitch: Вы можете жестко подключить регистрацию к вашему классу, передать дескриптор регистратору или вообще ничего не регистрировать. Если вы собираетесь быть очень строгими в отношении SRP за счет всего остального, я бы порекомендовал ничего не регистрировать. Все, что вам нужно знать о том, что делает ваше программное обеспечение, можно исправить с помощью отладчика. P в 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 - это сквозная проблема, связанная с отслеживанием выполнения : ведение журнала входит и выходит в каждом методе. АОП лучше всего подходит для этого.

Laurent LA RIZZA
источник
Скажем, сообщение регистратора - это «login user xyz», которое отправляется регистратору, который добавляет метку времени и т. Д. Знаете ли вы, что означает «логин» для реализации? Это начало сеанса с cookie или любым другим механизмом? Я думаю, что существует много разных способов реализации входа в систему, поэтому изменение реализации логически не имеет ничего общего с тем, что пользователь входит в систему. Это еще один прекрасный пример украшения различных компонентов (например, OAuthLogin, SessionLogin, BasicAuthorizationLogin), выполняющих одно и то же. как Login-интерфейс, украшенный тем же регистратором.
Aitch
Это зависит от того, что означает сообщение «login user xyz». Если это отмечает тот факт, что вход в систему успешен, отправка сообщения в журнал принадлежит в случае использования входа в систему. Конкретный способ представления информации для входа в систему в виде строки (OAuth, Session, LDAP, NTLM, fingerprint, wheel of hamster) принадлежит конкретному классу, представляющему учетные данные или стратегию входа в систему. Нет необходимости убирать его. Этот отдельный случай не является сквозной проблемой. Это зависит от варианта использования входа в систему.
Laurent LA RIZZA
7

Поскольку ведение журнала часто считается сквозной задачей, я бы предложил использовать АОП для отделения ведения журнала от реализации.

В зависимости от языка вы используете для этого перехватчик или некоторую инфраструктуру AOP (например, AspectJ в Java).

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

Оливер Вейлер
источник
2
Большая часть кода AOP, который я видел, была о регистрации каждого шага входа и выхода каждого метода. Я только хочу записать некоторые части бизнес-логики. Так что, возможно, можно регистрировать только аннотированные методы, но AOP вообще может существовать только в языках сценариев и средах виртуальных машин, верно? Например, в C ++ это невозможно. Я признаю, что я не очень доволен подходами АОП, но, возможно, нет более чистого решения.
Aitch
1
@Aitch. «C ++ это невозможно». : Если вы гуглите «aop c ++», вы найдете статьи об этом. «... код AOP, который я видел, был для регистрации каждого шага входа и выхода каждого метода. Я хочу записать только некоторые части бизнес-логики». Aop позволяет вам определять шаблоны, чтобы найти методы для модификации. т.е. все методы из пространства имен "my.busininess. *"
k3b
1
Ведение журнала часто НЕ является сквозной задачей, особенно когда вы хотите, чтобы ваш журнал содержал интересную информацию, т.е. больше информации, чем содержится в трассировке стека исключений.
Лоран LA RIZZA
5

Это звучит нормально. Вы описываете довольно стандартный логаринг-декоратор. У тебя есть:

компонент L (компонент регистрации системы)

Это имеет одну ответственность: регистрация информации, которая передается ему.

компонент А реализует I

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

Это важная часть:

компонент D реализует I, украшает / использует A, использует L для регистрации

Когда это так, это звучит сложно, но посмотрите на это так: Компонент D делает одну вещь: объединяя A и L вместе.

  • Компонент D не регистрируется; это делегирует это L
  • Компонент D не реализует сам I; это делегирует это

Только ответственность , что компонент D имеет, чтобы убедиться , что L уведомляется , когда А используется. Реализации A и L оба в другом месте. Это полностью SRP-совместимый, а также является отличным примером OCP и довольно распространенным использованием декораторов.

Важное предостережение: когда D использует ваш компонент журналирования L, он должен делать это таким образом, чтобы вы могли изменить способ ведения журнала. Самый простой способ сделать это - иметь интерфейс IL, который реализуется L. Затем:

  • Компонент D использует IL для регистрации; предоставляется экземпляр L
  • Компонент D использует I для обеспечения функциональности; предоставляется экземпляр A
  • Компонент B использует I; экземпляр D предоставляется

Таким образом, ничто не зависит напрямую от чего-либо другого, что облегчает их обмен. Это облегчает адаптацию к изменениям и позволяет имитировать части системы, чтобы можно было проводить модульное тестирование.

Анаксимандр
источник
Я на самом деле знаю только C #, который имеет поддержку собственных делегаций. Вот почему я написал D implements I. Спасибо за ваш ответ.
Aitch
1

Конечно, это нарушение SRP, поскольку у вас есть сквозная проблема. Однако вы можете создать класс, который будет отвечать за составление журналов с выполнением любого действия.

пример:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}
Павел Никонович
источник
2
Downvoted. Лесозаготовка - действительно междисциплинарная проблема. То же самое относится и к вызовам методов последовательности в вашем коде. Это не достаточная причина, чтобы требовать нарушения ПСП. Регистрация возникновения конкретного события в вашем приложении НЕ является сквозной задачей. СПОСОБ, в котором эти сообщения передаются любому заинтересованному пользователю, действительно является отдельной задачей, и описание этого в коде реализации является нарушением SRP.
Лоран LA RIZZA
«вызовы методов секвенирования» или функциональная композиция - это не сквозная проблема, а скорее деталь реализации. Ответственность за созданную мной функцию состоит в том, чтобы составить лог-оператор с действием. Мне не нужно использовать слово «и», чтобы описать, что делает эта функция.
Павел Никонович,
Это не деталь реализации. Это оказывает глубокое влияние на форму вашего кода.
Лоран LA RIZZA
Я думаю, что я смотрю на SRP с точки зрения «ЧТО делает эта функция», где, как вы смотрите на SRP с точки зрения «КАК эта функция делает это».
Пол Никонович,