Мои коллеги любят говорить «ведение журнала / кеширование и т. Д. - сквозная проблема», а затем везде использовать соответствующий синглтон. Все же они любят IoC и DI.
Действительно ли это оправданное нарушение принципа SOLI D ?
Мои коллеги любят говорить «ведение журнала / кеширование и т. Д. - сквозная проблема», а затем везде использовать соответствующий синглтон. Все же они любят IoC и DI.
Действительно ли это оправданное нарушение принципа SOLI D ?
Ответы:
Нет.
SOLID существует в качестве руководства для учета неизбежных изменений. Вы действительно никогда не собираетесь менять свою библиотеку журналов, или цель, или фильтрацию, или форматирование, или ...? Вы действительно не собираетесь менять свою кеширующую библиотеку, или цель, или стратегию, или область видимости, или ...?
Конечно же. По крайней мере , вы захотите издеваться над этими вещами в разумной форме, чтобы изолировать их для тестирования. И если вы хотите изолировать их для тестирования, вы, скорее всего, столкнетесь с бизнес-причинами, когда вы хотите изолировать их по причинам реальной жизни.
И вы тогда получите аргумент о том , что регистратор сам будет обрабатывать изменения. «О, если цель / фильтрация / форматирование / стратегия изменятся, тогда мы просто изменим конфигурацию!» Это мусор. Мало того, что теперь у вас есть объект God, который обрабатывает все эти вещи, вы пишете свой код в XML (или аналогичный), где вы не получаете статический анализ, вы не получаете ошибки времени компиляции, и вы не ' Т действительно получить эффективные юнит-тесты.
Есть ли случаи, когда нарушаются правила SOLID? Абсолютно. Иногда вещи не меняются (без необходимости переписывать в любом случае). Иногда небольшое нарушение LSP является самым чистым решением. Иногда создание изолированного интерфейса не имеет значения.
Но регистрация и кэширование (и другие повсеместные проблемы) не являются такими случаями. Они, как правило, являются отличными примерами проблем с соединением и дизайном, которые возникают при игнорировании рекомендаций.
String
или Int32
даже List
из вашего модуля. Есть только степень, в которой разумно и разумно планировать изменения. И, помимо наиболее очевидных «основных» типов, определение того, что вы, вероятно, измените , действительно является вопросом опыта и суждений.
да
В этом весь смысл термина «сквозная забота» - это означает то, что не вписывается в принцип SOLID.
Здесь идеализм встречается с реальностью.
Люди, полу новички в SOLID и сквозные люди, часто сталкиваются с этой мысленной проблемой. Все в порядке, не волнуйтесь. Стремитесь представить все в терминах SOLID, но есть несколько мест, таких как логирование и кэширование, где SOLID просто не имеет смысла. Сквозной является братом SOLID, они идут рука об руку.
HttpContextBase
(которое было введено именно по этой причине). Я точно знаю, что моя реальность была бы очень кислой без этого класса.
Для регистрации я думаю, что это так. Ведение журнала широко распространено и, как правило, не связано с функциональностью сервиса. Распространено и понятно, что используются одноэлементные шаблоны каркаса ведения журнала. Если вы этого не сделаете, вы создаете и внедряете регистраторы везде, и вы не хотите этого.
Одна из вышеперечисленных проблем заключается в том, что кто-то скажет: «Но как я могу проверить логирование? , Я думаю, что обычно я не тестирую логи, кроме того, что я могу на самом деле читать файлы логов и понимать их. Когда я видел проверенные журналы, это обычно потому, что кто-то должен утверждать, что класс действительно что-то сделал, и они используют сообщения журнала, чтобы получить эту обратную связь. Я бы предпочел зарегистрировать слушателя / наблюдателя в этом классе и утверждать в моих тестах, что он вызывается. Затем вы можете поместить журнал событий в этом наблюдателе.
Однако я думаю, что кэширование - это совершенно другой сценарий.
Мои 2 цента ...
Да и нет.
Вы никогда не должны действительно нарушать принципы, которые вы принимаете; но ваши принципы всегда должны быть нюансированы и приняты в служении более высокой цели. Таким образом, при правильно обусловленном понимании некоторые очевидные нарушения могут не быть фактическими нарушениями «духа» или «совокупности принципов в целом».
Принципы SOLID, в частности, помимо большого количества нюансов, в конечном счете, подчиняются цели «предоставления работоспособного, поддерживаемого программного обеспечения». Таким образом, соблюдение какого-либо конкретного принципа SOLID является самоубийственным и противоречивым, когда это противоречит целям SOLID. И здесь я часто отмечаю, что доставляет козыри ремонтопригодность .
Итак, что насчет D в SOLID ? Что ж, это способствует повышению удобства обслуживания, делая ваш повторно используемый модуль относительно независимым от контекста. И мы можем определить «модуль многократного использования» как «набор кода, который вы планируете использовать в другом отдельном контексте». И это относится к отдельным функциям, классам, наборам классов и программ.
И да, изменение реализаций логгера, вероятно, переводит ваш модуль в «другой особый контекст».
Итак, позвольте мне предложить два моих больших замечания :
Во-первых: рисование линий вокруг блоков кода, которые составляют «модуль многократного использования», является вопросом профессионального суждения. И ваше суждение обязательно ограничено вашим опытом.
Если вы в настоящее время не планируете использовать модуль в другом контексте, вероятно, он может беспомощно зависеть от него. Предостережение к предостережению: Ваши планы, вероятно, ошибочны, но это тоже нормально. Чем дольше вы будете писать модуль за модулем, тем более интуитивным и точным будет ваше понимание того, «когда-нибудь мне это понадобится снова». Но вы, вероятно, никогда не сможете ретроспективно сказать: «Я модульный и отделил все в максимально возможной степени, но без излишеств ».
Если вы чувствуете вину за свои ошибки в суждениях, идите на исповедь и продолжайте ...
Во-вторых: Инвертирующий контроль не равен инъекционным зависимостям .
Это особенно верно, когда вы начинаете вводить зависимости до тошноты . Внедрение зависимостей - полезная тактика для всеобъемлющей стратегии IoC. Но я бы сказал, что DI обладает меньшей эффективностью, чем некоторые другие тактики - например, использование интерфейсов и адаптеров - отдельные точки воздействия контекста внутри модуля.
И давайте действительно сосредоточимся на этом на секунду. Потому что, даже если вы вводите Logger
рекламную тошноту , вам нужно написать код для Logger
интерфейса. Вы не можете начать использовать новый Logger
от другого поставщика, который принимает параметры в другом порядке. Эта способность происходит от кодирования внутри модуля с интерфейсом, который существует внутри модуля и который имеет единственный субмодуль (адаптер) для управления зависимостями.
И если вы пишете код для Адаптера, то, Logger
введен ли он в этот Адаптер или обнаружен Адаптером, вообще чертовски незначительно для общей цели обслуживания. И что более важно, если у вас есть адаптер уровня модуля, возможно, просто абсурдно внедрять его во что-либо. Это написано для модуля.
tl; dr - Хватит суетиться о принципах, не задумываясь о том, почему вы используете эти принципы. И, более практично, просто построить Adapter
для каждого модуля. Используйте свое суждение, решая, где вы проводите границы «модуля». Внутри каждого модуля, идти вперед и обратиться непосредственно к Adapter
. И конечно, впрысните настоящий регистратор в Adapter
- но не в каждую мелочь, которая может понадобиться.
Идея о том, что ведение журнала всегда должно быть реализовано как единое целое, является одной из лжи, о которой так часто говорили, что она набирает обороты.
Поскольку современные операционные системы были в курсе, было признано, что вы можете войти в несколько мест в зависимости от характера вывода .
Разработчики систем должны постоянно подвергать сомнению эффективность прошлых решений, прежде чем вслепую включать их в новые. Если они не проводят такое усердие, то они не выполняют свою работу.
Ведение журнала действительно является особым случаем.
@Telastyn пишет:
Вы действительно никогда не собираетесь менять свою библиотеку журналов, или цель, или фильтрацию, или форматирование, или ...?
Если вы ожидаете, что вам может понадобиться изменить библиотеку журналов, то вам следует использовать фасад; т.е. SLF4J, если вы находитесь в мире Java.
В остальном, приличная библиотека журналов заботится о том, чтобы изменить место ведения журнала, какие события фильтруются, как события журнала форматируются с использованием файлов конфигурации регистратора и (при необходимости) пользовательских классов плагинов. Есть много готовых альтернатив.
Короче говоря, это решенные проблемы ... для ведения журнала ... и, следовательно, нет необходимости использовать Dependency Injection для их решения.
Единственный случай, когда DI может быть полезен (по сравнению со стандартными подходами к ведению журналов), - это если вы хотите подвергнуть ведение журнала вашего приложения модульному тестированию. Тем не менее, я подозреваю, что большинство разработчиков скажут, что ведение журнала не является частью функциональности классов и не является чем-то, что требует тестирования.
@Telastyn пишет:
И тогда вы получите аргумент, что сам регистратор будет обрабатывать изменения. «О, если цель / фильтрация / форматирование / стратегия изменятся, тогда мы просто изменим конфигурацию!» Это мусор. Мало того, что теперь у вас есть объект God, который обрабатывает все эти вещи, вы пишете свой код в XML (или аналогичный), где вы не получаете статический анализ, вы не получаете ошибки времени компиляции, и вы не ' действительно получить эффективные юнит-тесты.
Боюсь, это очень теоретический рипост. На практике большинству разработчиков и системных интеграторов нравится тот факт, что вы можете настроить ведение журнала через файл конфигурации. И им нравится тот факт, что от них не ожидается модульного тестирования журналирования модуля.
Конечно, если вы заполняете конфигурацию регистрации, вы можете получить проблемы, но они будут проявляться либо как сбой приложения во время запуска, либо слишком много / слишком мало регистрации. 1) Эти проблемы легко исправить, исправив ошибку в файле конфигурации. 2) Альтернативой является полный цикл сборки / анализа / тестирования / развертывания каждый раз, когда вы вносите изменения в уровни ведения журнала. Это не приемлемо.
Да и нет !
Да: я думаю, что разумно, чтобы разные подсистемы (или семантические слои, или библиотеки, или другие понятия модульного объединения) каждый принимали (одинаковые или) потенциально разные регистраторы во время их инициализации, а не все подсистемы, использующие один и тот же общий синглтон .
Тем не мение,
Нет: в то же время нецелесообразно параметризовать ведение журнала в каждом маленьком объекте (методом конструктора или экземпляра). Чтобы избежать ненужного и бессмысленного раздувания, меньшие объекты должны использовать одноэлементный регистратор своего окружающего контекста.
Это одна из причин, по которой многие думают о модульности уровней: методы объединяются в классы, а классы объединяются в подсистемы и / или семантические уровни. Эти большие связки являются ценными инструментами абстракции; мы должны дать другие соображения в рамках модульных границ, чем при их пересечении.
Сначала он начинается с сильного одноэлементного кэша, следующие вещи, которые вы видите, это сильные синглеты для уровня базы данных, представляющие глобальное состояние, неописательные API-интерфейсы class
es и непроверяемый код.
Если вы решите не использовать синглтон для базы данных, вероятно, не очень хорошо иметь синглтон для кеша, в конце концов, они представляют собой очень похожую концепцию хранения данных, используя только разные механизмы.
Использование синглтона в классе превращает класс, имеющий определенное количество зависимостей, в класс, имеющий теоретически бесконечное их количество, потому что вы никогда не знаете, что действительно скрыто за статическим методом.
За прошедшее десятилетие я потратил программирование, был только один случай, когда я стал свидетелем попытки изменить логику логирования (которая тогда была написана как синглтон). Поэтому, хотя мне нравятся инъекции зависимостей, ведение журналов на самом деле не такая уж большая проблема. Кеш, с другой стороны, я бы определенно всегда делал как зависимость.
Да и нет, но в основном нет
Я предполагаю, что большая часть разговора основана на статическом и внедренном экземпляре. Никто не предлагает, чтобы регистрация сломала SRP, который я предполагаю? В основном мы говорим о «принципе инверсии зависимостей». Я в основном согласен с тем, что Теластин не ответил.
Когда можно использовать статику? Потому что ясно, что иногда все в порядке. Ответы «да» указывают на преимущества абстракции, а ответы «нет» указывают на то, что вы платите за них. Одна из причин, по которой ваша работа тяжелая, заключается в том, что нет ни одного ответа, который вы могли бы записать и применить ко всем ситуациям.
Возьмите:
Convert.ToInt32("1")
Я предпочитаю это:
private readonly IConverter _converter;
public MyClass(IConverter converter)
{
Guard.NotNull(converter)
_converter = conveter
}
....
var foo = _converter.ToInt32("1");
Зачем? Я согласен с тем, что мне потребуется рефакторинг кода, если мне понадобится гибкость для замены кода конвертации. Я принимаю это, что я не смогу издеваться над этим. Я считаю, что простота и краткость стоит этой торговли.
Глядя на другой конец спектра, если бы IConverter
был SqlConnection
, я был бы довольно испуган, чтобы видеть это как статический вызов. Причины этого очевидны. Я бы SQLConnection
отметил, что a может быть довольно «сквозным» в приложении, поэтому я бы не использовал эти точные слова.
Ведение журнала больше похоже на 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 входит в картину в дизайне ОО системы.
Хранение объекта как синглтона не имеет ничего общего с SOLID. Это жизненный цикл вашего объекта, как долго вы хотите, чтобы объект жил в памяти.
Класс, для инициализации которого требуется зависимость, не должен знать, является ли предоставленный экземпляр класса одноэлементным или временным. Но тлдр; если вы пишете Logger.Instance.Log () в каждом методе или классе, то это проблемный код (запах кода / жесткая связь), он действительно очень грязный. Это момент, когда люди начинают злоупотреблять SOLID. И коллеги-разработчики, такие как OP, начинают задавать такие вопросы.
Я решил эту проблему, используя комбинацию наследования и признаков (также называемых миксинами в некоторых языках). Черты очень удобны для решения этого сквозного вопроса. Обычно это языковая функция, поэтому я думаю, что реальный ответ заключается в том, что она зависит от языковых возможностей.