Sou desenvolvedor júnior entre os idosos e estou lutando muito com a compreensão de seus pensamentos, raciocínios.
Estou lendo DDD ( Domain-Driven Design ) e não consigo entender por que precisamos criar tantas classes. Se seguirmos esse método de criação de software, terminaremos com 20 a 30 classes que podem ser substituídas por no máximo dois arquivos e 3-4 funções. Sim, isso pode ser confuso, mas é muito mais sustentável e legível.
Sempre que eu quiser ver o que algum tipo de ação EntityTransformationServiceImpl
faz, preciso seguir muitas classes, interfaces, suas chamadas de função, construtores, sua criação e assim por diante.
Matemática simples:
- 60 linhas de código fictício vs 10 classes X 10 (digamos que tenhamos lógicas totalmente diferentes) = 600 linhas de código confuso vs. 100 classes + mais algumas para envolvê-las e gerenciá-las; não esqueça de adicionar injeção de dependência.
- Lendo 600 linhas de código confuso = um dia
- 100 aulas = uma semana, ainda se esqueça de quem faz o quê, quando
Todo mundo está dizendo que é fácil de manter, mas para quê? Toda vez que você adiciona novas funcionalidades, adiciona mais cinco classes com fábricas, entidades, serviços e valores. Eu sinto que esse tipo de código se move muito mais devagar que o código confuso.
Digamos que, se você escrever um código confuso de 50K LOC em um mês, o DDD precisará de várias revisões e alterações (eu não me importo com os testes nos dois casos). Uma simples adição pode levar uma semana, se não mais.
Em um ano, você escreve muitos códigos confusos e pode reescrevê-los várias vezes, mas com o estilo DDD, você ainda não possui recursos suficientes para competir com códigos confusos.
Por favor explique. Por que precisamos desse estilo DDD e de muitos padrões?
UPD 1 : Eu recebi tantas respostas ótimas, vocês podem adicionar um comentário em algum lugar ou editar sua resposta com o link para a lista de leitura (não sei por onde começar, DDD, Design Patterns, UML, Code Complete, Refactoring, Pragmatic,. ... tantos bons livros), é claro com sequência, para que eu também possa começar a entender e me tornar mais velho, como alguns de vocês.
fonte
Respostas:
Este é um problema de otimização
Um bom engenheiro entende que um problema de otimização não tem sentido sem um alvo. Você não pode simplesmente otimizar, você precisa otimizar para alguma coisa. Por exemplo, suas opções do compilador incluem otimizar a velocidade e otimizar o tamanho do código; esses são objetivos às vezes opostos.
Gosto de dizer à minha esposa que minha mesa está otimizada para acrescentar. É apenas uma pilha e é muito fácil adicionar coisas. Minha esposa preferiria que eu otimizasse a recuperação, ou seja, organizei um pouco minhas coisas para que eu pudesse encontrar as coisas. Isso dificulta, é claro, adicionar.
Software é da mesma maneira. Você certamente pode otimizar a criação de produtos - gerar uma tonelada de código monolítico o mais rápido possível, sem se preocupar em organizá-lo. Como você já notou, isso pode ser muito, muito rápido. A alternativa é otimizar para manutenção - tornar a criação um toque mais difícil, mas tornar as modificações mais fáceis ou menos arriscadas. Esse é o objetivo do código estruturado.
Eu sugeriria que um produto de software de sucesso fosse criado apenas uma vez, mas modificado muitas e muitas vezes. Engenheiros experientes viram bases de código não estruturadas ganhar vida própria e tornarem-se produtos, crescendo em tamanho e complexidade, até que pequenas mudanças são muito difíceis de serem feitas sem introduzir um grande risco. Se o código foi estruturado, o risco pode ser contido. É por isso que vamos a todo esse problema.
Complexidade vem de relações, não de elementos
Percebo na sua análise que você está analisando quantidades - quantidade de código, número de classes, etc. Embora isso seja interessante, o impacto real vem das relações entre os elementos, que explodem combinatoriamente. Por exemplo, se você tem 10 funções e não tem idéia de qual depende, você tem 90 relações possíveis (dependências) com as quais precisa se preocupar - cada uma das dez funções pode depender de qualquer uma das nove outras funções e 9 x 10 = 90. Você pode não ter idéia de quais funções modificam quais variáveis ou como os dados são transmitidos, para que os codificadores tenham um monte de coisas com que se preocupar ao resolver qualquer problema específico. Por outro lado, se você tem 30 classes, mas elas são organizadas de maneira inteligente, elas podem ter apenas 29 relações, por exemplo, se estiverem em camadas ou organizadas em uma pilha.
Como isso afeta o rendimento da sua equipe? Bem, há menos dependências, o problema é muito mais tratável; os programadores não precisam manipular um zilhão de coisas na cabeça sempre que fazem uma alteração. Portanto, minimizar as dependências pode ser um grande impulso à sua capacidade de raciocinar sobre um problema de forma competente. É por isso que dividimos as coisas em classes ou módulos, e as variáveis de escopo da forma mais rígida possível e usamos os princípios do SOLID .
fonte
EntityTransformationServiceImpl
, você será obrigado a aprender como tudo funciona antes de corrigi-lo - mas se, por exemplo, algo estiver errado com o formato e umaFormatter
classe lidar com isso, você só precisará aprender como essa parte funciona . ler o seu próprio código após ~ 3 meses é como ler o código de um estranho.) Sinto que a maioria de nós subconscientemente pensou nisso, mas isso pode ser por causa da experiência. Não tenho 100% de certeza disso.Bem, antes de tudo, a legibilidade e a manutenção estão frequentemente nos olhos de quem vê.
O que é legível para você pode não ser para o seu vizinho.
A capacidade de manutenção geralmente se resume à capacidade de descoberta (com que facilidade um comportamento ou conceito é descoberto na base de código) e a capacidade de descoberta é outra coisa subjetiva.
DDD
Uma das maneiras pelas quais o DDD ajuda as equipes de desenvolvedores é sugerir uma maneira específica (ainda subjetiva) de organizar seus conceitos e comportamentos de código. Essa convenção facilita a descoberta de coisas e, portanto, a manutenção do aplicativo.
Esse arranjo não é objetivamente mais fácil de manter. No entanto, é mensurável mais fácil de manter quando todos entendem que estão operando em um contexto DDD.
Aulas
As aulas ajudam na manutenção, legibilidade, descoberta, etc ... porque são uma convenção bem conhecida .
Nas configurações orientadas a objetos, as Classes geralmente são usadas para agrupar comportamentos estreitamente relacionados e encapsular o estado que precisa ser controlado com cuidado.
Sei que parece muito abstrato, mas você pode pensar assim:
Com Classes, você não precisa necessariamente saber como o código nelas funciona. Você só precisa saber pelo que a turma é responsável.
As classes permitem que você raciocine sobre seu aplicativo em termos de interações entre componentes bem definidos .
Isso reduz a carga cognitiva ao raciocinar sobre como seu aplicativo funciona. Em vez de ter que lembrar o que 600 linhas de código realizam, você pode pensar em como 30 componentes interagem.
E, considerando que esses 30 componentes provavelmente abrangem 3 camadas de seu aplicativo, você provavelmente apenas deve raciocinar sobre aproximadamente 10 componentes por vez.
Isso parece bastante administrável.
Sumário
Essencialmente, o que você está vendo os desenvolvedores seniores fazerem é o seguinte:
Eles estão dividindo o aplicativo em fácil raciocínio sobre classes.
Em seguida, eles estão organizando-os para facilitar o raciocínio sobre as camadas.
Eles estão fazendo isso porque sabem que, à medida que o aplicativo cresce, fica cada vez mais difícil argumentar sobre isso como um todo. Dividi-lo em Camadas e Classes significa que eles nunca precisam raciocinar sobre o aplicativo inteiro. Eles só precisam raciocinar sobre um pequeno subconjunto dele.
fonte
AddressServiceRepositoryProxyInjectorResolver
quando você pode resolver o problema com mais elegância? Padrões de design por causa dos padrões de design apenas levam a complexidade e verbosidade desnecessárias.Primeiro, uma observação: a parte importante do DDD não são os padrões , mas o alinhamento do esforço de desenvolvimento com os negócios. Greg Young observou que os capítulos do livro azul estão na ordem errada .
Mas para sua pergunta específica: há muito mais classes do que você esperaria, (a) porque está sendo feito um esforço para distinguir o comportamento do domínio do encanamento e (b) porque está sendo feito um esforço extra para garantir que os conceitos no modelo de domínio são expressos explicitamente.
Sem rodeios, se você tiver dois conceitos diferentes no domínio, eles deverão ser distintos no modelo, mesmo que compartilhem o mesmo na representação de memória .
Com efeito, você está criando uma linguagem específica de domínio que descreve seu modelo no idioma da empresa, de forma que um especialista em domínio possa examiná-lo e detectar erros.
Além disso, você vê um pouco mais de atenção dada à separação de preocupações; e a noção de isolar consumidores de alguma capacidade a partir dos detalhes da implementação. Veja DL Parnas . Os limites claros permitem que você altere ou estenda a implementação sem que os efeitos se espalhem por toda a solução.
A motivação aqui: para um aplicativo que faz parte da competência principal de uma empresa (ou seja, um local em que obtém uma vantagem competitiva), você poderá substituir de forma fácil e barata um comportamento de domínio por uma variação melhor. De fato, você tem partes do programa que deseja evoluir rapidamente (como o estado evolui com o tempo) e outras partes que deseja alterar lentamente (como o estado é armazenado); as camadas extras de abstração ajudam a evitar acoplar inadvertidamente uma à outra.
Para ser justo: alguns deles também são danos cerebrais orientados a objetos. Os padrões originalmente descritos por Evans são baseados em projetos Java dos quais ele participou há mais de 15 anos; estado e comportamento estão fortemente associados a esse estilo, o que leva a complicações que você pode preferir evitar; veja Percepção e ação, de Stuart Halloway, ou Inlining Code, de John Carmack.
fonte
Existem muitos pontos positivos nas outras respostas, mas acho que eles perdem ou não enfatizam um importante erro conceitual que você comete:
Você está comparando o esforço para entender o programa completo.
Esta não é uma tarefa realista com a maioria dos programas. Mesmo programas simples consistem em tanto código que é simplesmente impossível gerenciar tudo na cabeça a qualquer momento. Sua única chance é encontrar a parte de um programa que é relevante para a tarefa em questão (consertar um bug, implementar um novo recurso) e trabalhar com isso.
Se o seu programa consiste em grandes funções / métodos / classes, isso é quase impossível. Você precisará entender centenas de linhas de código apenas para decidir se esse pedaço de código é relevante para o seu problema. Com as estimativas que você forneceu, fica fácil passar uma semana apenas para encontrar o trecho de código em que você precisa trabalhar.
Compare isso com uma base de código com pequenas funções / métodos / classes nomeadas e organizadas em pacotes / espaços para nome que tornam óbvio onde encontrar / colocar uma determinada parte da lógica. Quando bem feito, em muitos casos, você pode pular direto para o local correto para resolver seu problema, ou pelo menos para um local de onde o acionador do depurador o levará ao local certo em alguns saltos.
Eu trabalhei nos dois tipos de sistemas. A diferença pode ser facilmente duas ordens de magnitude no desempenho para tarefas comparáveis e tamanho de sistema comparável.
O efeito que isso tem em outras atividades:
fonte
Porque testar código é mais difícil do que escrever código
Muitas respostas deram um bom raciocínio da perspectiva de um desenvolvedor - que a manutenção pode ser reduzida, com o custo de tornar o código mais árduo para escrever em primeiro lugar.
No entanto, há outro aspecto a considerar - o teste só pode ser refinado como seu código original.
Se você escreve tudo em um monólito, o único teste eficaz que você pode escrever é "dadas essas entradas, a saída está correta?". Isso significa que todos os erros encontrados têm o escopo definido como "em algum lugar nessa pilha gigante de código".
Obviamente, você pode ter um desenvolvedor sentado com um depurador e descobrir exatamente onde o problema começou e trabalhar em uma correção. Isso requer muitos recursos e é um mau uso do tempo de um desenvolvedor. Imagine que um bug cada vez menor, resulta em um desenvolvedor precisando depurar todo o programa novamente.
A solução: muitos testes menores que identificam uma falha específica em potencial cada.
Esses pequenos testes (por exemplo, testes de unidade) têm a vantagem de verificar uma área específica da base de código e ajudar a encontrar erros dentro de um escopo limitado. Isso não apenas acelera a depuração quando um teste falha, mas também significa que, se todos os seus pequenos testes falharem - você poderá encontrar mais facilmente a falha nos testes maiores (ou seja, se não estiver em uma função específica testada, deverá estar na interação entre eles).
Como deve ficar claro, para fazer testes menores, sua base de código precisa ser dividida em pequenos pedaços testáveis. A maneira de fazer isso, em uma grande base de código comercial, geralmente resulta em código parecido com o que você está trabalhando.
Apenas como uma nota lateral: isso não quer dizer que as pessoas não levem as coisas "longe demais". Mas há uma razão legítima para separar as bases de código em partes menores / menos conectadas - se isso for feito com sensatez.
fonte
Muitos (a maioria ...) de nós realmente não precisam deles. Teóricos e programadores experientes e muito avançados escrevem livros sobre teorias e metodologias como resultado de muitas pesquisas e sua profunda experiência - isso não significa que tudo o que escrevem seja aplicável a todos os programadores em sua prática cotidiana.
Como desenvolvedor júnior, é bom ler livros como o que você mencionou para ampliar suas perspectivas e informá-lo sobre certos problemas. Isso também evitará que você se sinta envergonhado e confuso quando seus colegas mais velhos usam a terminologia que você não conhece. Se você encontrar algo muito difícil e não parece fazer sentido ou parecer útil, não se mate por isso - basta arquivar na sua cabeça que existe esse conceito ou abordagem.
No seu desenvolvimento do dia a dia, a menos que você seja um acadêmico, seu trabalho é encontrar soluções viáveis e sustentáveis. Se as idéias encontradas em um livro não o ajudarem a atingir esse objetivo, não se preocupe com isso agora, desde que seu trabalho seja considerado satisfatório.
Pode chegar um momento em que você descobrirá que pode usar parte do que leu, mas não conseguiu "entender" a princípio, ou talvez não.
fonte
AccountServiceFacadeInjectResolver
(exemplo real que acabei de encontrar em um sistema em funcionamento) - a resposta provavelmente não é.Como sua pergunta está cobrindo muito terreno, com muitas suposições, vou destacar o assunto da sua pergunta:
Nós não. Não existe uma regra geralmente aceita que diga que deve haver muitas classes nos padrões de design.
Existem dois guias principais para decidir onde colocar o código e como cortar suas tarefas em diferentes unidades de código:
Por que esses dois devem ser importantes?
Esses dois aspectos são os "drivers" básicos para qualquer escolha de "onde colocar o quê" em qualquer linguagem de programação e em qualquer paradigma (não apenas no OO). Nem todo mundo está explicitamente ciente deles, e leva tempo, muitas vezes anos, para obter uma sensação realmente arraigada e automática de como isso influencia o software.
Obviamente, esses dois conceitos não dizem nada sobre o que realmente fazer . Algumas pessoas erram do lado de muito, outras do lado de muito pouco. Algumas linguagens (olhando para você aqui, Java) tendem a favorecer muitas classes devido à natureza extremamente estática e pedante da própria linguagem (isso não é uma declaração de valor, mas é o que é). Isso se torna especialmente perceptível quando você o compara com linguagens dinâmicas e mais expressivas, por exemplo, Ruby.
Outro aspecto é que algumas pessoas aderem à abordagem ágil de apenas escrever código necessário no momento e refatorar mais tarde, quando necessário. Nesse estilo de desenvolvimento, você não criaria um
interface
quando tiver apenas uma classe de implementação. Você simplesmente implementaria a classe concreta. Se, mais tarde, você precisar de uma segunda classe, refatoraria.Algumas pessoas simplesmente não funcionam dessa maneira. Eles criam interfaces (ou, geralmente, classes básicas abstratas) para qualquer coisa que possa ser usada de maneira mais geral; isso leva a uma explosão de classe rapidamente.
Novamente, existem argumentos a favor e contra, e não importa qual eu ou você prefira. Em sua vida como desenvolvedor de software, você encontrará todos os extremos, desde métodos longos de espaguete, passando por projetos de classe iluminados e com tamanho suficiente, até esquemas de classe incrivelmente ampliados e com muita engenharia. À medida que for adquirindo experiência, você se tornará mais um papel "arquitetônico" e poderá começar a influenciar isso na direção que deseja. Você descobrirá um meio de ouro para si mesmo e continuará descobrindo que muitas pessoas vão discordar de você, faça o que fizer.
Portanto, manter uma mente aberta é a parte mais importante aqui, e esse seria o meu conselho principal para você, visto que você parece sentir muita dor com o problema, a julgar pelo resto da sua pergunta ...
fonte
Codificadores experientes aprenderam:
fonte
As respostas até agora, todas elas boas, começaram com a suposição razoável de que o autor está faltando alguma coisa, o que o autor também reconhece. Também é possível que o solicitante esteja basicamente certo, e vale a pena discutir como essa situação pode ocorrer.
A experiência e a prática são poderosas, e se os idosos obtiveram experiência em projetos grandes e complexos, onde a única maneira de manter as coisas sob controle é com muitos
EntityTransformationServiceImpl
, eles se tornam rápidos e confortáveis com os padrões de design e a estreita adesão ao DDD. Eles seriam muito menos eficazes usando uma abordagem leve, mesmo para programas pequenos. Como o estranho, você deve se adaptar e será uma ótima experiência de aprendizado.Embora se adapte, você deve entender isso como uma lição no equilíbrio entre aprender profundamente uma única abordagem, a ponto de fazê-la funcionar em qualquer lugar, em vez de permanecer versátil e saber quais ferramentas estão disponíveis sem necessariamente ser um especialista em qualquer uma delas. Existem vantagens para ambos e ambos são necessários no mundo.
fonte
Tornar mais classe e função conforme o uso será uma ótima abordagem para resolver problemas de uma maneira específica que ajudará no futuro a resolver quaisquer problemas.
Várias classes ajudam a identificar como seu trabalho básico e qualquer classe pode ser chamada a qualquer momento.
No entanto, se você tiver muitas classes e funções a partir do nome de obtenção, elas serão fáceis de chamar e gerenciar. Isso é chamado de código limpo.
fonte