W ciągu ostatnich kilku lat powoli przechodziliśmy na stopniowo coraz lepszy kod, kilka kroków naraz. W końcu zaczynamy przestawiać się na coś, co przynajmniej przypomina SOLID, ale jeszcze tam nie jesteśmy. Od czasu dokonania zmiany, jednym z największych zarzutów ze strony programistów jest to, że nie mogą znieść recenzowania i przeglądania dziesiątek plików, w których wcześniej każde zadanie wymagało jedynie od programisty dotknięcia 5-10 plików.
Przed przystąpieniem do zmiany nasza architektura była zorganizowana w sposób podobny do następującego (przyznane, z jednym lub dwoma rzędami wielkości więcej plików):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
Jeśli chodzi o pliki, wszystko było niesamowicie liniowe i kompaktowe. Było oczywiście dużo duplikacji kodu, ścisłego sprzężenia i bólów głowy, jednak każdy mógł to przebrnąć i to rozgryźć. Kompletni nowicjusze, ludzie, którzy nigdy wcześniej nie otwierali Visual Studio, mogli to zrozumieć w ciągu zaledwie kilku tygodni. Brak ogólnej złożoności plików sprawia, że początkujący programiści i nowi pracownicy mogą stosunkowo łatwo rozpocząć pracę bez zbyt długiego czasu przyspieszania. Ale jest to w zasadzie miejsce, w którym wszelkie zalety stylu kodu wychodzą przez okno.
Z całego serca popieram każdą próbę ulepszenia naszej bazy kodu, ale bardzo często zdarza się, że reszta zespołu reaguje na tak masywne zmiany paradygmatu. Kilka największych obecnie utrzymujących się punktów to:
- Testy jednostkowe
- Klasa liczyć
- Złożoność recenzji
Testy jednostkowe były niezwykle trudne do sprzedania zespołowi, ponieważ wszyscy uważają, że to strata czasu i że są w stanie przetestować swój kod znacznie szybciej niż cały element indywidualnie. Używanie testów jednostkowych jako aprobaty dla SOLID było w większości daremne i stało się w tym momencie żartem.
Liczenie klas jest prawdopodobnie największą przeszkodą do pokonania. Zadania, które kiedyś zajmowały 5–10 plików, mogą teraz zająć 70–100! Podczas gdy każdy z tych plików służy odrębnemu celowi, sama ilość plików może być przytłaczająca. Reakcja zespołu to głównie jęki i drapanie głowy. Poprzednio zadanie mogło wymagać jednego lub dwóch repozytoriów, modelu lub dwóch, warstwy logicznej i metody kontrolera.
Teraz, aby zbudować prostą aplikację do zapisywania plików, masz klasę, aby sprawdzić, czy plik już istnieje, klasę do zapisywania metadanych, klasę do wyodrębnienia, DateTime.Now
dzięki czemu możesz wprowadzać czasy do testów jednostkowych, interfejsy dla każdego pliku zawierającego logikę, pliki zawiera testy jednostkowe dla każdej klasy i jeden lub więcej plików, aby dodać wszystko do kontenera DI.
W przypadku małych i średnich aplikacji SOLID to bardzo łatwa sprzedaż. Wszyscy widzą korzyści i łatwość utrzymania. Jednak po prostu nie widzą dobrej oferty dla SOLID w aplikacjach na bardzo dużą skalę. Staram się więc znaleźć sposoby na poprawę organizacji i zarządzania, abyśmy mogli pokonać rosnące problemy.
Pomyślałem, że podam nieco mocniejszy przykład woluminu pliku na podstawie ostatnio ukończonego zadania. Dostałem zadanie zaimplementowania niektórych funkcji w jednej z naszych nowych mikrousług, aby otrzymać żądanie synchronizacji plików. Po otrzymaniu żądania usługa wykonuje serię wyszukiwań i kontroli, a następnie zapisuje dokument na dysku sieciowym, a także 2 osobne tabele bazy danych.
Aby zapisać dokument na dysku sieciowym, potrzebowałem kilku konkretnych klas:
- 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
To łącznie 15 klas (z wyłączeniem POCO i rusztowań), aby wykonać dość proste zapisanie. Liczba ta znacznie wzrosła, kiedy musiałem utworzyć POCO do reprezentowania bytów w kilku systemach, zbudowałem kilka repozytoriów, aby komunikować się z systemami stron trzecich, które są niekompatybilne z naszymi innymi ORM, i zbudowałem metody logiczne do obsługi zawiłości niektórych operacji.