Nos últimos anos, passamos lentamente a mudar para um código escrito progressivamente melhor, alguns pequenos passos de cada vez. Finalmente estamos começando a mudar para algo que pelo menos se assemelha ao SOLID, mas ainda não chegamos lá. Desde a mudança, uma das maiores reclamações dos desenvolvedores é que eles não suportam a revisão por pares e a passagem de dezenas e dezenas de arquivos, onde anteriormente todas as tarefas exigiam apenas que o desenvolvedor tocasse de 5 a 10 arquivos.
Antes de começar a fazer a troca, nossa arquitetura era organizada da seguinte maneira (concedida, com uma ou duas ordens de magnitude a mais de arquivos):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
Em termos de arquivo, tudo era incrivelmente linear e compacto. Obviamente, havia muita duplicação de código, acoplamento rígido e dores de cabeça, no entanto, todos podiam atravessar e descobrir. Iniciantes completos, pessoas que nunca abriram o Visual Studio, conseguiram descobrir isso em apenas algumas semanas. A falta de complexidade geral de arquivos torna relativamente simples para desenvolvedores iniciantes e novos contratados começarem a contribuir sem muito tempo de aceleração. Mas é aqui que praticamente qualquer benefício do estilo de código sai pela janela.
Eu sinceramente apoio todas as tentativas que fazemos para melhorar nossa base de código, mas é muito comum receber alguma reação do resto da equipe em mudanças de paradigma massivas como essa. Atualmente, alguns dos maiores pontos de discórdia são:
- Testes unitários
- Contagem de turmas
- Complexidade da revisão por pares
Os testes de unidade têm sido incrivelmente difíceis de vender para a equipe, pois todos acreditam que são uma perda de tempo e que podem testar seu código com muito mais rapidez como um todo do que cada peça individualmente. O uso de testes de unidade como um endosso ao SOLID tem sido inútil e tornou-se uma piada neste momento.
A contagem de turmas é provavelmente o maior obstáculo a superar. As tarefas que costumavam levar de 5 a 10 arquivos agora podem demorar de 70 a 100! Embora cada um desses arquivos tenha uma finalidade distinta, o grande volume de arquivos pode ser esmagador. A resposta da equipe tem sido principalmente gemidos e coçar a cabeça. Anteriormente, uma tarefa pode ter exigido um ou dois repositórios, um modelo ou dois, uma camada lógica e um método de controlador.
Agora, para criar um aplicativo simples para salvar arquivos, você tem uma classe para verificar se o arquivo já existe, uma classe para gravar os metadados, uma classe para abstrair, DateTime.Now
para que você possa injetar horários para testes de unidade, interfaces para cada arquivo que contém lógica, arquivos para conter testes de unidade para cada classe existente e um ou mais arquivos para adicionar tudo ao seu contêiner de DI.
Para aplicações de pequeno e médio porte, o SOLID é uma venda super fácil. Todo mundo vê o benefício e a facilidade de manutenção. No entanto, eles não veem uma boa proposta de valor para o SOLID em aplicativos de grande escala. Por isso, estou tentando encontrar maneiras de melhorar a organização e o gerenciamento para superar as dores do crescimento.
Eu achei que daria um exemplo um pouco mais forte do volume do arquivo com base em uma tarefa concluída recentemente. Foi-me dada a tarefa de implementar algumas funcionalidades em um de nossos microsserviços mais recentes para receber uma solicitação de sincronização de arquivos. Quando a solicitação é recebida, o serviço executa uma série de pesquisas e verificações e, finalmente, salva o documento em uma unidade de rede, além de 2 tabelas de banco de dados separadas.
Para salvar o documento na unidade de rede, eu precisava de algumas classes específicas:
- 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
Portanto, são 15 classes (excluindo POCOs e andaimes) para realizar um salvamento bastante direto. Esse número aumentou significativamente quando eu precisei criar POCOs para representar entidades em alguns sistemas, criei alguns repositórios para se comunicar com sistemas de terceiros incompatíveis com nossos outros ORMs e criei métodos lógicos para lidar com os meandros de certas operações.