criar um código verdadeiramente Modular sem dependências

desenvolver software é ótimo, mas… acho que todos concordamos que pode ser um pouco de uma montanha-russa emocional. No início, está tudo óptimo. Você adiciona novos recursos um após o outro em uma questão de dias, se não horas. Estás em Maré de sorte!

avançar rapidamente alguns meses, e a sua velocidade de desenvolvimento diminui. É porque não estás a trabalhar tanto como antes? Nem por isso. Vamos avançar mais alguns meses, e a sua velocidade de desenvolvimento cai ainda mais. Trabalhar neste projecto já não é divertido e tornou-se uma chatice.A situação piora. Você começa a descobrir vários bugs em sua aplicação. Muitas vezes, resolver um bug cria dois novos. Nesta altura, podes começar a cantar:

99 pequenos insectos no código.99 pequenos insectos.Leve um para baixo, remenda em torno,

…127 pequenos bugs no código.

o que acha de trabalhar neste projecto agora? Se és como eu, deves começar a perder a motivação. É apenas uma dor desenvolver esta aplicação, uma vez que cada mudança de código existente pode ter consequências imprevisíveis.

esta experiência é comum no mundo do software e pode explicar por que tantos programadores querem jogar seu código fonte fora e reescrever tudo.

razões pelas quais o desenvolvimento de Software abranda ao longo do tempo

então qual é a razão para este problema?

a principal causa é a crescente complexidade. Pela minha experiência, o maior contribuinte para a complexidade Global é o fato de que, na grande maioria dos projetos de software, tudo está conectado. Por causa das dependências que cada classe tem, se você mudar algum código na classe que envia e-mails, seus usuários de repente não podem se registrar. Porquê? Porque o seu código de registo depende do código que envia e-mails. Agora não podes mudar nada sem introduzir insectos. Simplesmente não é possível rastrear todas as dependências.

então aí você tem; a verdadeira causa de nossos problemas é aumentar a complexidade vindo de todas as dependências que nosso código tem.

Big Ball of Mud and How to Reduce It

Funny thing is, this issue has been known for years now. É um anti-padrão comum chamado “grande bola de lama”.”Eu vi esse tipo de arquitetura em quase todos os projetos em que trabalhei ao longo dos anos em várias empresas diferentes.

então o que é esse anti-padrão exatamente? Simplesmente falando, você tem uma grande bola de lama quando cada elemento tem uma dependência com outros elementos. Abaixo, você pode ver um gráfico das dependências do bem conhecido Projeto de código aberto Apache Hadoop. A fim de visualizar a grande bola de lama (ou melhor, a grande bola de fio), você desenhar um círculo e colocar classes do projeto uniformemente sobre ele. Basta desenhar uma linha entre cada par de classes que dependem umas das outras. Agora você pode ver a fonte de seus problemas.

uma visualização da grande bola de lama do Apache Hadoop, com algumas dezenas de nós e centenas de linhas conectando-os um ao outro.

o Apache Hadoop, o “grande bola de lama”

Uma Solução Modular com o Código

Então eu perguntei a mim mesmo uma pergunta: Seria possível reduzir a complexidade e ainda divertir-se como no início do projeto? Verdade seja dita, não se pode eliminar toda a complexidade. Se você quiser adicionar novos recursos, você sempre terá que aumentar a complexidade do Código. No entanto, a complexidade pode ser movida e separada.

How Other Industries Are Solving This Problem

Think about the mechanical industry. Quando alguma pequena loja mecânica está criando máquinas, eles compram um conjunto de elementos padrão, criar alguns personalizados, e colocá-los juntos. Eles podem fazer esses componentes completamente separados e montar tudo no final, fazendo apenas alguns ajustes. Como é possível? Eles sabem como cada elemento se encaixará em conjunto por padrões da indústria, como tamanhos de parafusos, e decisões iniciais, como o tamanho dos buracos de montagem e a distância entre eles.

um diagrama técnico de um mecanismo físico e como as suas peças se encaixam. As peças são numeradas por ordem de anexar a seguir, mas essa ordem vai da esquerda para a direita 5, 3, 4, 1, 2.

cada elemento da montagem acima pode ser fornecido por uma empresa separada que não tem qualquer conhecimento sobre o produto final ou suas outras peças. Desde que cada elemento modular seja fabricado de acordo com as especificações, você será capaz de criar o dispositivo final como planejado.Podemos replicar isso na indústria de software?Claro que podemos! Usando interfaces e princípio de inversão de controle; a melhor parte é o fato de que esta abordagem pode ser usada em qualquer linguagem orientada a objetos: Java, C#, Swift, TypeScript, JavaScript, PHP-a lista continua e continua. Você não precisa de nenhuma estrutura chique para aplicar este método. Só precisas de seguir algumas regras simples e manter-te disciplinado.A inversão do controle é seu amigo, quando ouvi falar pela primeira vez sobre inversão do controle, percebi imediatamente que tinha encontrado uma solução. É um conceito de tomar dependências existentes e invertê-las usando interfaces. Interfaces são simples declarações de métodos. Eles não fornecem nenhuma implementação concreta. Como resultado, eles podem ser usados como um acordo entre dois elementos sobre como conectá-los. Eles podem ser usados como conectores modulares, se você quiser. Enquanto um elemento fornece a interface e outro elemento fornece a implementação para ela, eles podem trabalhar juntos sem saber nada sobre o outro. É brilhante.

vamos ver em um exemplo simples como podemos dissociar nosso sistema para criar código modular. Os diagramas abaixo foram implementados como simples aplicações Java. Você pode encontrá-los neste repositório GitHub.

Problema

Vamos supor que temos uma aplicação muito simples, consistindo apenas de um Main classe, três serviços, e uma única Util classe. Esses elementos dependem uns dos outros de várias maneiras. Abaixo, você pode ver uma implementação usando a abordagem” Grande Bola de lama”. As aulas simplesmente ligam umas às outras. Eles estão bem acoplados, e você não pode simplesmente eliminar um elemento sem tocar nos outros. As aplicações criadas com este estilo permitem-lhe inicialmente crescer rapidamente. Acredito que este estilo é apropriado para projetos de demonstração de conceito, uma vez que você pode brincar com as coisas facilmente. No entanto, não é apropriado para soluções prontas para a produção, porque mesmo a manutenção pode ser perigosa e qualquer mudança pode criar bugs imprevisíveis. O diagrama abaixo mostra esta grande bola de arquitetura de lama.

o Main usa os Serviços A, B E C, que cada um usa Util. O serviço C também utiliza o serviço A.

por que a injeção de Dependência ficou tudo errado

em uma busca por uma melhor abordagem, podemos usar uma técnica chamada injeção de dependência. Este método pressupõe que todos os componentes devem ser utilizados através de interfaces. Li alegações de que decompõe elementos, mas será mesmo? Não. Dê uma olhada no diagrama abaixo.

a arquitetura anterior, mas com injeção de dependência. Agora o Main usa o serviço de Interface A, B E C, que são implementados por seus serviços correspondentes. Os Serviços A E C ambos usam o serviço de Interface B e Interface Util, que é implementado por Util. O serviço C também utiliza o serviço de Interface A. Cada serviço, juntamente com a sua interface, é considerado um elemento.

a única diferença entre a situação atual e uma grande bola de lama é o fato de que agora, em vez de chamar as aulas diretamente, nós as chamamos através de suas interfaces. Melhora ligeiramente a separação dos elementos uns dos outros. Se, por exemplo, você gostaria de reutilizar Service A em um projeto diferente, você poderia fazer isso tirando Service A em si, juntamente com Interface A, bem como Interface B e Interface Util. Como você pode ver, Service A ainda depende de outros elementos. Como resultado, ainda temos problemas em mudar de código em um lugar e estragar o comportamento em outro. Ele ainda cria o problema de que se você modificar Service B e Interface B, você terá que mudar todos os elementos que dependem dele. Esta abordagem não resolve nada; na minha opinião, ela apenas adiciona uma camada de interface em cima de elementos. Você nunca deve Injectar qualquer dependência, mas em vez disso você deve se livrar deles de uma vez por todas. Viva a independência!

the Solution for Modular Code

the approach I believe solves all the main headache of dependencies does it by not using dependencies at all. Você cria um componente e seu ouvinte. Um ouvinte é uma interface simples. Sempre que você precisa chamar um método de fora do elemento atual, você simplesmente adiciona um método para o ouvinte e chamá-lo em vez disso. O elemento só é permitido usar arquivos, métodos de chamada dentro de seu pacote, e usar classes fornecidas pelo framework principal ou outras bibliotecas usadas. Abaixo, você pode ver um diagrama da aplicação modificado para usar a arquitetura do elemento.

um diagrama da aplicação modificado para usar a arquitetura do elemento. Principais usos Util e todos os três serviços. Main também implementa um ouvinte para cada serviço, que é usado por esse serviço. Um ouvinte e serviço juntos são considerados um elemento.

por favor, note que, nesta arquitetura, apenas a classe Main tem várias dependências. Ele conecta todos os elementos juntos e encapsula a lógica de negócios da aplicação.Os Serviços, por outro lado, são elementos completamente independentes. Agora, você pode tirar cada serviço desta aplicação e reutilizá-los em outro lugar. Não dependem de mais nada. Mas espera, fica melhor: não precisas de modificar esses serviços nunca mais, desde que não mudes o comportamento deles. Desde que esses serviços façam o que deviam fazer, podem ficar intocados até ao fim dos tempos. Eles podem ser criados por um engenheiro de software profissional, ou pela primeira vez codificador comprometido do pior código de esparguete que alguém já cozinhou com declarações goto misturadas. Não importa, porque a sua lógica está encapsulada. Por mais horrível que possa ser, nunca se espalhará para outras aulas. Isso também lhe dá o poder de dividir o trabalho em um projeto entre vários desenvolvedores, onde cada desenvolvedor pode trabalhar em seu próprio componente independentemente, sem a necessidade de interromper outro ou mesmo saber sobre a existência de outros desenvolvedores.Finalmente, você pode começar a escrever código independente mais uma vez, assim como no início de seu último projeto.

elemento padrão

vamos definir o elemento estrutural padrão de modo que seremos capazes de criá-lo de uma forma repetível.

a versão mais simples do elemento consiste em duas coisas:: Uma classe de elementos principais e um ouvinte. Se você quer usar um elemento, então você precisa implementar o ouvinte e fazer chamadas para a classe principal. Aqui está um diagrama da configuração mais simples:

um diagrama de um único elemento e seu ouvinte dentro de uma aplicação. Como antes, o aplicativo usa o elemento, que usa seu ouvinte, que é implementado pelo aplicativo.

obviamente, você vai precisar adicionar mais complexidade no elemento eventualmente, mas você pode fazer tão facilmente. Apenas certifique-se de que nenhuma das suas aulas de lógica dependem de outros arquivos no projeto. Eles só podem usar o framework principal, bibliotecas importadas e outros arquivos neste elemento. Quando se trata de arquivos de ativos como imagens, vistas, sons, etc., eles também devem ser encapsulados dentro de elementos para que no futuro eles serão fáceis de reutilizar. Você pode simplesmente copiar a pasta inteira para outro projeto e lá está ele!

abaixo, você pode ver um gráfico de exemplo mostrando um elemento mais avançado. Observe que ele consiste de uma visão que está usando e não depende de quaisquer outros arquivos de Aplicação. Se você quiser saber um método simples de verificar dependências, basta olhar para a seção importação. Existem alguns ficheiros de fora do elemento actual? Se assim for, então você precisa remover essas dependências, movendo-as para o elemento ou adicionando uma chamada apropriada para o ouvinte.

um diagrama simples de um elemento mais complexo. Aqui, o sentido maior da palavra "elemento" consiste em seis partes: View; Logics A, B, And C; Element; and Element Listener. As relações entre os dois últimos e o App são as mesmas de antes, mas o elemento interno também usa lógicas A E C. lógica C usa lógicas A E B. lógica a usa lógica B e visão.

vamos também dar uma olhada em um exemplo simples de” Hello World ” criado em Java.

public class Main { interface ElementListener { void printOutput(String message); } static class Element { private ElementListener listener; public Element(ElementListener listener) { this.listener = listener; } public void sayHello() { String message = "Hello World of Elements!"; this.listener.printOutput(message); } } static class App { public App() { } public void start() { // Build listener ElementListener elementListener = message -> System.out.println(message); // Assemble element Element element = new Element(elementListener); element.sayHello(); } } public static void main(String args) { App app = new App(); app.start(); }}

inicialmente, definimos ElementListener para especificar o método que imprime a saída. O elemento em si é definido abaixo. Ao chamar sayHello sobre o elemento, ele simplesmente imprime uma mensagem usando ElementListener. Observe que o elemento é completamente independente da implementação do método printOutput. Ele pode ser impresso no console, uma impressora física, ou um UI de fantasia. O elemento não depende dessa implementação. Por causa desta abstração, este elemento pode ser reutilizado em diferentes aplicações facilmente.

agora dê uma olhada na classe principal App. Implementa o ouvinte e reúne o elemento juntamente com a implementação concreta. Agora podemos começar a usá-lo.

Você também pode executar este exemplo em JavaScript aqui

arquitetura de elementos

vamos dar uma olhada usando o padrão de elementos em uma grande escala aplicações. Uma coisa é mostrá—lo em um pequeno projeto-outra é aplicá-lo ao mundo real.

a estrutura de uma aplicação web full-stack que eu gosto de usar parece como se segue:

src├── client│ ├── app│ └── elements│ └── server ├── app └── elements

em uma pasta de código fonte, inicialmente dividimos os arquivos cliente e servidor. É uma coisa razoável de se fazer, já que eles rodam em dois ambientes diferentes: o navegador e o servidor back-end.

depois dividimos o código em cada camada em pastas chamadas app e elementos. Elementos consistem em pastas com componentes independentes, enquanto a pasta app fios todos os elementos juntos e armazena toda a lógica de Negócio.

dessa forma, elementos podem ser reutilizados entre diferentes projetos, enquanto toda a complexidade específica da aplicação é encapsulada em uma única pasta e muitas vezes reduzida a simples chamadas para elementos.

Hands-on Example

Believing that practice always trump theory, let’s have a look at a real-life example created in Node.js e dactilografia.

exemplo da vida real

é uma aplicação web muito simples que pode ser usada como ponto de partida para soluções mais avançadas. Ele segue a arquitetura do elemento, bem como usa um padrão de elementos extensivamente estruturais.

dos destaques, você pode ver que a página principal foi distinguida como um elemento. Esta página inclui sua própria Vista. Então quando, por exemplo, você quer reutilizá-lo, você pode simplesmente copiar a pasta inteira e deixá-la em um projeto diferente. Junta tudo e estás pronto.

é um exemplo básico que demonstra que você pode começar a introduzir elementos em sua própria aplicação hoje. Você pode começar a distinguir componentes independentes e separar a sua lógica. Não importa o quão confuso o código que você está trabalhando atualmente é.

Desenvolver Mais Rápido, Reutilizar Mais Frequentemente!

espero que, com este novo conjunto de ferramentas, você seja capaz de desenvolver mais facilmente um código que seja mais sustentável. Antes de saltar para o uso do padrão de elementos na prática, vamos rapidamente recapitular todos os pontos principais:

  • muitos problemas no software acontecem por causa de dependências entre vários componentes.

  • ao fazer uma mudança em um lugar, você pode introduzir um comportamento imprevisível em outro lugar.

três abordagens arquitectónicas comuns são::

  • a grande bola de lama. É ótimo para o desenvolvimento rápido, mas não tão grande para fins de produção estável.

  • Injecção de Dependência. É uma solução mal cozida que devias evitar.

  • Arquitectura de elementos. Esta solução permite criar componentes independentes e reutilizá-los em outros projetos. É sustentável e brilhante para lançamentos de produção estáveis.

o padrão de elementos básicos consiste de uma classe principal que tem todos os métodos principais, bem como um ouvinte que é uma interface simples que permite a comunicação com o mundo externo.

a fim de alcançar a arquitetura de elementos de pilha completa, primeiro você separa sua front-end do código de back-end. Em seguida, você cria uma pasta em cada um para um aplicativo e elementos. A pasta de elementos consiste de todos os elementos independentes, enquanto a pasta do aplicativo fios tudo em conjunto.

Agora você pode ir e começar a criar e compartilhar seus próprios elementos. A longo prazo, irá ajudá-lo a criar produtos facilmente realizáveis. Boa sorte e deixe-me saber o que você criou!Também, se você se encontrar prematuramente otimizando seu código, leia Como Evitar A maldição da otimização prematura pelo companheiro de Toptaler Kevin Bloch.

Deixe uma resposta

O seu endereço de email não será publicado.