За последние несколько лет мы постепенно переходили на все более и более качественно написанный код, по несколько шагов за раз. Мы наконец начинаем переключаться на что-то, что, по крайней мере, напоминает SOLID, но мы еще не совсем там. После внесения изменений одна из самых больших претензий разработчиков заключается в том, что они не выносят рецензирования и обхода десятков и десятков файлов, где ранее для выполнения каждой задачи требовалось, чтобы разработчик касался 5-10 файлов.
До начала переключения наша архитектура была организована примерно так: (предоставлено, на один или два порядка больше файлов):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
В отношении файлов все было невероятно линейным и компактным. Было очевидно много дублирования кода, сильной связи и головной боли, однако каждый мог пройти через это и разобраться. Совершенные новички, люди, которые никогда не открывали Visual Studio, могли понять это всего за несколько недель. Отсутствие общей сложности файлов делает относительно простым для начинающих разработчиков и новых сотрудников, чтобы начать вносить свой вклад без слишком большого времени наращивания. Но это почти то, где все преимущества стиля кода выходят за рамки.
Я искренне одобряю каждую попытку, которую мы предпринимаем, чтобы улучшить нашу кодовую базу, но очень часто можно получить отпор от остальной команды в таких масштабных изменениях парадигмы. Пара самых больших точек преткновения в настоящее время:
- Модульные тесты
- Количество классов
- Сложность экспертной оценки
Модульные тесты были невероятно трудными для продажи, поскольку все они считают, что это пустая трата времени, и что они в состоянии справиться с тестированием своего кода намного быстрее, чем каждый фрагмент в отдельности. Использование модульных тестов в качестве одобрения для SOLID в большинстве случаев было бесполезным и на данный момент стало шуткой.
Количество классов, вероятно, является самым большим препятствием для преодоления. Задачи, которые раньше занимали 5-10 файлов, теперь могут занимать 70-100! Хотя каждый из этих файлов служит определенной цели, объем файлов может быть огромным. Ответом команды в основном были стоны и царапины на голове. Ранее задача могла требовать одного или двух хранилищ, модели или двух, логического уровня и метода контроллера.
Теперь, чтобы создать простое приложение для сохранения файлов, у вас есть класс, чтобы проверить, существует ли файл, класс для записи метаданных, класс для абстрагирования, DateTime.Now
чтобы вы могли вводить время для модульного тестирования, интерфейсы для каждого файла, содержащего логику, файлы содержать модульные тесты для каждого класса, и один или несколько файлов, чтобы добавить все в ваш контейнер DI.
Для малых и средних приложений SOLID очень легко продать. Каждый видит выгоду и простоту обслуживания. Однако они просто не видят выгодного предложения для SOLID в очень крупномасштабных приложениях. Поэтому я пытаюсь найти способы улучшить организацию и управление, чтобы помочь нам справиться с растущими проблемами.
Я подумал, что приведу немного более сильный пример объема файла, основанного на недавно выполненной задаче. Мне было поручено реализовать некоторые функции в одном из наших новых микросервисов для получения запроса на синхронизацию файлов. Когда запрос получен, служба выполняет серию поисков и проверок и, наконец, сохраняет документ на сетевой диск, а также в 2 отдельные таблицы базы данных.
Чтобы сохранить документ на сетевой диск, мне понадобилось несколько конкретных классов:
- IBasePathProvider
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect
- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests
- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider
- NewGuidProviderTests
- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests
- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request)
- PatientFileWriter
- PatientFileWriterTests
Таким образом, всего 15 классов (исключая POCO и строительные леса), чтобы выполнить довольно простое сохранение. Это число значительно увеличилось, когда мне нужно было создать POCO для представления сущностей в нескольких системах, создать несколько репозиториев для взаимодействия со сторонними системами, несовместимыми с другими нашими ORM, и создать логические методы для обработки сложностей определенных операций.