Протягом останніх декількох років ми поступово переходимо до прогресивно вдосконаленого письмового коду, кілька кроків дитини одночасно. Ми нарешті починаємо переходити на щось, що принаймні нагадує СОЛІД, але ми ще не зовсім там. З моменту переходу, одна з найбільших скарг розробників полягає в тому, що вони не витримують експертного огляду та проходження десятків і десятків файлів, де раніше кожна задача вимагала, щоб розробник торкнувся 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 для представлення сутностей у кількох системах, створив кілька репостів для спілкування із сторонніми системами, несумісними з іншими нашими ОРМ, та побудував логічні методи для управління тонкощами певних операцій.