Я пишу много кода, который включает в себя три основных шага.
- Получить данные откуда-то.
- Преобразуйте эти данные.
- Положите эти данные куда-нибудь.
Я обычно заканчиваю тем, что использую три типа классов - вдохновленные их соответствующими шаблонами дизайна.
- Фабрики - построить объект из какого-то ресурса.
- Посредники - использовать фабрику, выполнить трансформацию, затем использовать командира.
- Командиры - разместить эти данные где-то еще.
Мои классы, как правило, довольно малы, часто представляют собой один (общедоступный) метод, например, получение данных, преобразование данных, выполнение работы, сохранение данных. Это приводит к увеличению числа классов, но в целом работает хорошо.
Когда я прихожу на тестирование, я бьюсь о том, чтобы в конечном итоге я получал тесно связанные тесты. Например;
- Factory - читает файлы с диска.
- Commander - записывает файлы на диск.
Я не могу проверить одно без другого. Я мог бы написать дополнительный «тестовый» код для чтения и записи на диск, но потом я повторяюсь.
Глядя на .Net, класс File использует другой подход, он объединяет обязанности (моей) фабрики и командира вместе. Он имеет функции для создания, удаления, существования и чтения всего в одном месте.
Должен ли я последовать примеру .Net и объединить - особенно при работе с внешними ресурсами - мои занятия вместе? Код все еще связан, но он более намеренный - это происходит в исходной реализации, а не в тестах.
Является ли моя проблема здесь в том, что я применил принцип единой ответственности несколько слишком усердно? У меня есть отдельные классы, ответственные за чтение и запись. Когда я мог иметь комбинированный класс, который отвечает за работу с конкретным ресурсом, например системным диском.
источник
Looking at .Net, the File class takes a different approach, it combines the responsibilities (of my) factory and commander together. It has functions for Create, Delete, Exists, and Read all in one place.
- Обратите внимание, что вы связываете «ответственность» с «делом». Ответственность больше похожа на «проблемную область». Класс File отвечает за выполнение файловых операций.File
библиотеке из C # является то, что, насколько мы знаем,File
класс может быть просто фасадом, помещающим все файловые операции в одном месте - в классе, но может внутренне использовать классы чтения / записи, аналогичные вашим, что на самом деле содержат более сложную логику для обработки файлов. Такой класс (theFile
) все еще будет придерживаться SRP, потому что процесс фактической работы с файловой системой будет абстрагирован за другим уровнем - скорее всего, с объединяющим интерфейсом. Не сказать, что это так, но это может быть. :)Ответы:
Следование принципу Единой Ответственности, возможно, было тем, что направляло вас сюда, но где вы находитесь, имеет другое имя.
Распределение ответственности по командным запросам
Изучите это, и я думаю, вы найдете его по знакомой схеме, и вы не одиноки в размышлениях о том, как далеко вы можете это сделать. Кислотный тест заключается в том, что следование этому дает вам реальную выгоду, или если вы просто следите за слепой мантрой, чтобы вам не приходилось думать.
Вы выразили беспокойство по поводу тестирования. Я не думаю, что следование CQRS исключает написание тестируемого кода. Вы можете просто следовать CQRS таким образом, чтобы ваш код не был тестируемым.
Это помогает узнать, как использовать полиморфизм для инвертирования зависимостей исходного кода без необходимости изменения потока управления. Я не совсем уверен, где ваши навыки написание тестов.
Предостережение: следовать привычкам, которые вы найдете в библиотеках, не оптимально. Библиотеки имеют свои потребности и, откровенно говоря, старые. Так что даже самый лучший пример - это только лучший пример того времени.
Это не значит, что не существует вполне достоверных примеров, которые не соответствуют CQRS. После этого всегда будет немного больно. Это не всегда стоит платить. Но если вам это нужно, вы будете рады, что использовали это.
Если вы используете его, прислушайтесь к этому слову предупреждения:
источник
Вам нужна более широкая перспектива, чтобы определить, соответствует ли код принципу единой ответственности. На это нельзя ответить, просто проанализировав сам код, вы должны рассмотреть, какие силы или действующие лица могут привести к изменению требований в будущем.
Допустим, вы храните данные приложения в файле XML. Какие факторы могут заставить вас изменить код, связанный с чтением или письмом? Некоторые возможности:
Во всех этих случаях вам придется изменить как чтение и логику написания. Другими словами, они не являются отдельными обязанностями.
Но давайте представим другой сценарий: ваше приложение является частью конвейера обработки данных. Он считывает некоторые CSV-файлы, сгенерированные отдельной системой, выполняет некоторый анализ и обработку, а затем выводит другой файл для обработки третьей системой. В этом случае чтение и письмо являются самостоятельными обязанностями и должны быть отделены друг от друга.
Итог: вы вообще не можете сказать, если чтение и запись файлов являются отдельными обязанностями, это зависит от ролей в приложении. Но, исходя из вашего намека на тестирование, я полагаю, что в вашем случае это единственная ответственность.
источник
Вообще у вас правильная идея.
Похоже, у вас есть три обязанности. ИМО «Медиатор» может многое сделать. Я думаю, что вы должны начать с моделирования ваших трех обязанностей:
Тогда программа может быть выражена как:
Я не думаю, что это проблема. IMO много маленьких связных, тестируемых классов лучше, чем большие, менее связные классы.
Каждая часть должна быть независимо проверяемой. По модели выше вы можете представить чтение / запись в файл как:
Вы можете написать интеграционные тесты, чтобы проверить эти классы, чтобы убедиться, что они читают и записывают в файловую систему. Остальная часть логики может быть записана как преобразования. Например, если файлы в формате JSON, вы можете преобразовать
String
s.Затем вы можете преобразовать в соответствующие объекты:
Каждый из них независимо проверяется. Вы можете также модульное тестирование
program
выше, насмехаясьreader
,transformer
иwriter
.источник
FileWriter
, читая непосредственно из файловой системы вместо использованияFileReader
. Это действительно зависит от ваших целей. Если вы используетеFileReader
, тест будет прерван, если одинFileReader
илиFileWriter
сломан - что может занять больше времени для отладки.Итак, основное внимание здесь уделяется тому, что их объединяет . Вы передаете объект между ними (например
File
,?) Тогда это файл, с которым они связаны, а не друг с другом.Из того, что ты сказал, ты отделил свои классы. Ловушка в том, что вы тестируете их вместе, потому что это проще или «имеет смысл» .
Зачем вам нужен вход,
Commander
чтобы прийти с диска? Все, что его волнует - это запись с использованием определенного ввода, а затем вы можете проверить, правильно ли он записал файл, используя то, что находится в тесте .Фактическая часть, которую вы проверяете,
Factory
это «правильно ли он прочитает этот файл и выведет правильную»? Так что издевайтесь над файлом, прежде чем читать его в тесте .В качестве альтернативы, проверка того, что Factory и Commander работают, когда они связаны друг с другом, - это хорошо - это вполне устраивает Integration Testing. Вопрос здесь больше в том, может ли ваш юнит тестировать их по отдельности.
источник
Это типичный процедурный подход, о котором Дэвид Парнас писал в 1972 году. Вы концентрируетесь на том, как идут дела. Вы принимаете конкретное решение вашей проблемы как модель более высокого уровня, которая всегда неверна.
Если вы придерживаетесь объектно-ориентированного подхода, я бы лучше сконцентрировался на вашем домене . О чем это все? Каковы основные обязанности вашей системы? Каковы основные понятия, которые представлены на языке ваших экспертов домена? Итак, разберитесь в своем домене, разложите его, относитесь к областям ответственности более высокого уровня как к своим модулям , рассматривайте низкоуровневые концепции, представленные как существительные, как ваши объекты. Вот пример, который я привел к недавнему вопросу, он очень актуален.
И есть очевидная проблема со сплоченностью, вы упомянули об этом сами. Если вы вносите какие-либо изменения в логику ввода и пишете на ней тесты, это никоим образом не доказывает, что ваша функциональность работает, поскольку вы можете забыть передать эти данные на следующий уровень. Видите, эти слои неразрывно связаны. А искусственная развязка делает вещи еще хуже. Я знаю это сам: 7-летний проект со 100 человеко-годами за плечами, полностью написанный в этом стиле. Убегай от него, если сможешь.
И в целом вещь SRP. Это все о сплоченности, примененной к вашему проблемному пространству, то есть домену. Это основной принцип SRP. В результате объекты становятся умными и выполняют свои обязанности за себя. Никто не контролирует их, никто не предоставляет им данные. Они объединяют данные и поведение, выставляя только последнее. Таким образом, ваши объекты сочетают в себе проверку необработанных данных, преобразование данных (т. Е. Поведение) и постоянство. Это может выглядеть следующим образом:
В результате есть довольно много связанных классов, представляющих некоторую функциональность. Обратите внимание, что проверка обычно идет к объектам-значениям - по крайней мере, в подходе DDD .
источник
Следите за утечками абстракций при работе с файловой системой - я слишком часто пренебрегал ею, и у нее есть симптомы, которые вы описали.
Если класс оперирует данными, которые поступают из этих файлов или поступает в них, тогда файловая система становится деталью реализации (I / O) и должна быть отделена от нее. Эти классы (фабрика / командир / посредник) не должны знать о файловой системе, если их единственной задачей является хранение / чтение предоставленных данных. Классы, которые имеют дело с файловой системой, должны инкапсулировать специфичные для контекста параметры, такие как пути (могут передаваться через конструктор), чтобы интерфейс не раскрывал свою природу (слово «Файл» в имени интерфейса в большинстве случаев является запахом).
источник
На мой взгляд, звучит так, как будто вы начали идти по правильному пути, но недостаточно далеко. Я думаю, что правильное разбиение функциональности на разные классы, которые делают одно и делают это хорошо.
Чтобы продвинуться дальше, вы должны создать интерфейсы для ваших классов Factory, Mediator и Commander. Затем вы можете использовать макеты версий этих классов при написании ваших модульных тестов для конкретных реализаций других. С помощью макетов вы можете проверить, что методы вызываются в правильном порядке и с правильными параметрами, и что тестируемый код работает правильно с различными возвращаемыми значениями.
Вы также можете посмотреть на абстрагирование чтения / записи данных. Сейчас вы переходите на файловую систему, но, возможно, захотите в будущем обратиться к базе данных или даже к сокету. Ваш класс-посредник не должен изменяться, если изменяется источник / назначение данных.
источник