какой должна быть позиция регистратора в списке параметров [закрыто]

12

В моем коде я внедряю регистратор во многие мои классы через список параметров их конструктора

Я заметил, что я поставил это случайно: иногда это первый в списке, иногда последний, а иногда между

Есть ли у вас какие-либо предпочтения? Моя интуиция говорит, что в этом случае полезна последовательность, и я предпочитаю ставить ее на первое место, чтобы ее было легче заметить, когда она отсутствует, и легче пропустить, когда она есть.

Ezra
источник

Ответы:

31

Лесорубы - это то, что мы называем «сквозной проблемой». Они уступают методам, таким как Аспектно-ориентированное Программирование; если у вас есть способ украсить свои классы атрибутом или выполнить какое-то переплетение кода, то это хороший способ получить возможности ведения журналов, сохраняя при этом ваши объекты и списки параметров «чистыми».

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

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

Если вы все еще хотите передать регистратор, я предпочитаю, чтобы вы сделали его последним параметром в списке параметров (если это возможно, сделайте его необязательным параметром). Наличие первого параметра не имеет особого смысла; первый параметр должен быть самым важным, наиболее релевантным для работы класса.

Роберт Харви
источник
11
+! не допускать логгера к конструкторам; Я предпочитаю статический логгер, так что я знаю, что все используют одно и то же
Стивен А. Лоу
1
@ StevenA.Lowe Обратите внимание, что использование статических регистраторов может привести к другим проблемам в будущем, если вы не будете осторожны (например, статический порядок инициализации фиаско в C ++). Я согласен, что наличие логгера в качестве общедоступной сущности имеет свои прелести, но следует тщательно оценить, вписывается ли и как такой дизайн в общую архитектуру.
ComicSansMS
@ComicSansMS: конечно, плюс проблемы с потоками и т. Д. Просто личное предпочтение - «как можно проще, но не проще»;)
Стивен А. Лоу,
Наличие статического регистратора может быть проблемой. Это усложняет внедрение зависимостей (если только вы не имеете в виду одноэлементный регистратор, созданный вашим контейнером DI), и если вы когда-нибудь захотите изменить свою архитектуру, это может быть болезненным. Прямо сейчас, например, я использую функции Azure, которые передают регистратор в качестве параметра для каждого выполнения функции, поэтому я жалею, что пропустил свой регистратор через конструктор.
Slothario
@Slothario: в этом вся прелесть статического регистратора. Никаких инъекций не требуется.
Роберт Харви
7

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

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

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

addThreeToList = map (+3)

Однако, как упоминалось в других ответах, вы, вероятно, не хотите явно передавать регистратор в список аргументов каждого класса в системе.

Doval
источник
3

Вы можете определенно потратить много времени на решение этой проблемы.

Для языков с реализациями канонического журналирования просто создайте экземпляр канонического регистратора непосредственно в каждом классе.

Для языков без канонической реализации, попробуйте найти каркас фасада регистрации и придерживаться его. slf4j - хороший выбор в Java.

Лично я предпочел бы придерживаться одной конкретной реализации регистрации и отправить все в системный журнал. Все хорошие инструменты анализа журналов способны объединять журналы sysout с нескольких серверов приложений в единый отчет.

Когда сигнатура функции включает в себя одну или две службы зависимостей, а также некоторые «реальные» аргументы, я ставлю зависимости в последнюю очередь:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

Поскольку мои системы обычно имеют только пять или менее таких служб, я всегда проверяю, чтобы службы были включены в один и тот же порядок для всех сигнатур функций. Алфавитный порядок так же хорош, как и любой. (Кроме того: поддержание этого методологического подхода для обработки мьютексов также снизит ваши шансы на развитие взаимоблокировок.)

Если вы обнаружите, что внедряете в свое приложение более дюжины или около того зависимостей, то систему, вероятно, нужно разделить на отдельные подсистемы (смею сказать, микросервисы?).

Кристиан Уиллман
источник
Мне кажется неудобным использовать инъекцию свойства для вызова calcFooBarSum.
phu
2

Регистраторы - это особый случай, потому что они должны быть доступны буквально везде.

Если вы решили, что хотите передать регистратор в конструктор каждого класса, то вам определенно следует установить согласованное соглашение о том, как вы это делаете (например, всегда первый параметр, всегда передаваемый по ссылке, список инициализации конструктора всегда запускается с m_logger (theLogger) и т. д.). Все, что будет использоваться во всей вашей кодовой базе, когда-нибудь получит пользу от согласованности.

В качестве альтернативы, вы можете сделать так, чтобы каждый класс создавал свой собственный объект logger, без необходимости что-либо передавать. Регистратору может понадобиться знать некоторые вещи «по волшебству», чтобы это работало, но жесткое кодирование пути к файлу в определении класса потенциально Гораздо более легок в обслуживании и менее утомителен, чем правильная передача его сотням различных классов, и, возможно, гораздо менее злым, чем использование глобальной переменной для обхода упомянутой утомительной работы (Следует признать, что регистраторы являются одним из немногих законных вариантов использования глобальных переменных)

Ixrec
источник
1

Я согласен с теми, кто предполагает, что логгер должен быть статически доступен, а не передан в классы. Однако, если есть веская причина, по которой вы хотите передать его (возможно, разные экземпляры захотят войти в другое место или что-то в этом роде), я бы посоветовал вам не передавать его с помощью конструктора, а сделать отдельный вызов для этого, например, Class* C = new C(); C->SetLogger(logger);скорее чемClass* C = new C(logger);

Причина, по которой этот метод является предпочтительным, заключается в том, что средство ведения журнала является не частью класса, а скорее внедренной функцией, используемой для каких-то других целей. Размещение его в списке конструктора делает его обязательным для класса и подразумевает, что оно является частью фактического логического состояния класса. Например, разумно ожидать (с большинством классов, хотя и не со всеми), что если X != Yтогда, C(X) != C(Y)но маловероятно, что вы протестируете неравенство регистратора, если будете сравнивать слишком много экземпляров одного и того же класса.

Джек Эйдли
источник
1
Конечно, у этого есть недостаток, что регистратор недоступен для конструктора.
Бен Фойгт
1
Мне очень нравится этот ответ. Это делает регистрацию второстепенной задачей класса, о которой вам нужно заботиться отдельно от простого его использования. Скорее всего, если вы добавляете регистратор в конструктор большого числа классов, вы, вероятно, используете внедрение зависимостей. Я не могу говорить на всех языках, но я знаю, что в C # некоторые реализации DI также будут внедряться непосредственно в свойства (getter / setter).
jpmc26
@BenVoigt: Это правда, и это может быть убийственная причина, чтобы не делать это таким образом, но, как правило, вы можете сделать запись, которую вы в противном случае сделали бы в конструкторе в ответ на установленный регистратор.
Джек Эйдли
0

Стоит упомянуть, что я не видел, чтобы другие ответы касались здесь, что ввод логгера с помощью свойства или статики затрудняет (э) тестирование класса модулем. Например, если вы вводите свой регистратор через свойство, теперь вам придется вводить этот регистратор каждый раз, когда вы тестируете метод, который использует регистратор. Это означает, что вы могли бы также установить его как конструкторскую зависимость, потому что классу это требуется.

Статика поддается той же проблеме; если регистратор не работает, то весь ваш класс выходит из строя (если ваш класс использует регистратор) - даже если регистратор не обязательно является «частью» ответственности класса - хотя это не так плохо, как внедрение свойства, потому что вы по крайней мере, знать, что регистратор всегда "там" в некотором смысле.

Просто пища для размышлений, особенно если вы используете TDD. По моему мнению, логгер не должен быть частью тестируемой части класса (когда вы тестируете класс, вам также не следует тестировать логирование).

Дэн Кладовая
источник
1
хммм ... так что вы хотите, чтобы ваш класс выполнял ведение журнала (ведение журнала должно быть в спецификации), но вы не хотите тестировать с использованием средства ведения журнала. Это возможно? Я думаю, что ваша точка зрения не годится. Ясно, что если ваши инструменты тестирования сломаны, вы не сможете их протестировать - проектирование таким образом, чтобы не полагаться на инструмент тестирования, является излишним ИМХО
Хоган,
1
Моя точка зрения заключается в том, что если я использую конструктор и вызываю метод для класса, и он все равно не работает, потому что я не установил свойство, тогда разработчик класса неправильно понял концепцию, лежащую в основе конструктора. Если класс требует регистрации, он должен быть вставлен в конструктор - для этого и нужен конструктор.
Дэн Кладовая
ммм .. нет, не совсем. Если вы рассматриваете систему ведения журнала как часть «фреймворка», то она не имеет смысла как часть конструктора. Но об этом было ясно сказано в других ответах.
Хоган
1
Я спорю против введения собственности. Я не обязательно защищаю использование инъекции в конструкторе. Я просто говорю, что, по моему мнению, это предпочтительнее, чем инъекция собственности.
Дэн Кладовая
"Это возможно?" Кроме того, да, ткачество IL - вещь, которая существует и была упомянута в главном ответе ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
Дэн
0

Мне лень передавать объект logger каждому экземпляру класса. Итак, в моем коде такие вещи находятся либо в статическом поле, либо в локальной переменной потока в статическом поле. Последнее довольно круто и позволяет вам использовать разные средства ведения журнала для каждого потока и позволяет добавлять методы для включения и выключения регистрации, которые делают что-то значимое и ожидаемое в многопоточном приложении.

Atsby
источник