O acoplamento frouxo sem casos de uso é um antipadrão?


22

O acoplamento fraco é, para alguns desenvolvedores, o santo graal do software bem-projetado. Certamente, é uma coisa boa quando torna o código mais flexível diante de mudanças que provavelmente ocorrerão no futuro próximo ou evita a duplicação de código.

Por outro lado, os esforços para acoplar componentes frouxamente aumentam a quantidade de indireção em um programa, aumentando sua complexidade, dificultando o entendimento e tornando-o menos eficiente.

Você considera um foco no acoplamento solto sem nenhum caso de uso para o acoplamento solto (como evitar duplicação de código ou planejar mudanças que provavelmente ocorrerão no futuro próximo) como um antipadrão? O acoplamento solto pode cair sob a égide da YAGNI?


1
A maioria dos benefícios tem um custo. Use se o benefício exceder o custo.
precisa saber é o seguinte

4
você esqueceu o outro lado da moeda do acoplamento frouxo, e esse é high cohesionum sem o outro é um desperdício de esforço e ilustrações fundamental falta de compreensão de ambos.

Respostas:


13

Um pouco.

Às vezes, o acoplamento solto que ocorre sem muito esforço é bom, mesmo se você não tiver requisitos específicos exigindo que algum módulo seja dissociado. A fruta baixa, por assim dizer.

Por outro lado, o excesso de engenharia para quantidades ridículas de mudança exigirá muita complexidade e esforço desnecessários. YAGNI, como você diz, bate na cabeça.


22

A prática de programação X é boa ou ruim? Claramente, a resposta é sempre "depende".

Se você está olhando seu código, se perguntando quais "padrões" você pode injetar, está fazendo errado.

Se você está construindo seu software para que objetos não relacionados não se mexam, está fazendo o que é certo.

Se você está "projetando" sua solução para que ela possa ser ampliada e alterada infinitamente, então você está realmente tornando-a mais complicada.

Acho que, no final das contas, você fica com a única verdade: é mais ou menos complicado separar os objetos? Se for menos complicado combiná-los, essa é a solução correta. Se for menos complicado dissociá-los, essa é a solução certa.

(Atualmente, estou trabalhando em uma base de código bastante pequena que faz um trabalho simples de uma maneira muito complicada, e parte do que a torna tão complicada é a falta de entendimento dos termos "acoplamento" e "coesão" por parte do original. desenvolvedores.)


4
Eu fiz isso outro dia: pensei que precisaria de algum indireção entre duas aulas "por precaução". Então machuquei minha cabeça tentando depurar alguma coisa, arranquei o indireto e fiquei muito mais feliz.
Frank Shearar

3

Eu acho que o que você está vendo aqui é o conceito de coesão . Esse código tem um bom objetivo? Posso internalizar esse objetivo e entender o "quadro geral" do que está acontecendo?

Isso pode levar a códigos difíceis de seguir, não apenas porque há muito mais arquivos de origem (supondo que sejam classes separadas), mas porque nenhuma classe parece ter um propósito.

De uma perspectiva ágil, eu poderia sugerir que esse acoplamento frouxo seria um antipadrão. Sem a coesão, ou mesmo casos de uso, não é possível escrever testes de unidade sensíveis nem verificar a finalidade do código. Agora, o código ágil pode levar a um acoplamento frouxo, por exemplo, quando o desenvolvimento orientado a teste é usado. Mas se os testes corretos foram criados, na ordem certa, é provável que exista boa coesão e baixo acoplamento. E você está falando apenas dos casos em que claramente não existe coesão.

Novamente da perspectiva ágil, você não deseja esse nível artificial de indireção, porque é um esforço desperdiçado em algo que provavelmente não será necessário. É muito mais fácil refatorar quando a necessidade é real.

No geral, você deseja um acoplamento alto nos seus módulos e um acoplamento solto entre eles. Sem o acoplamento alto, você provavelmente não tem coesão.


1

Para a maioria das perguntas como esta, a resposta é "depende". No geral, se eu puder criar um design logicamente fracamente acoplado de maneiras que façam sentido, sem uma grande sobrecarga para fazê-lo, farei. Evitar acoplamentos desnecessários no código é, a meu ver, um objetivo de projeto totalmente válido.

Quando se trata de uma situação em que parece que os componentes devem estar logicamente acoplados, procurarei um argumento convincente antes de começar a dividi-los.

Acho que o princípio em que trabalho com a maioria desses tipos de prática é de inércia. Tenho uma ideia de como gostaria que meu código funcionasse e se posso fazê-lo dessa maneira sem dificultar a vida, então farei. Se isso dificultar o desenvolvimento, mas a manutenção e o trabalho futuro forem mais fáceis, tentarei adivinhar se haverá mais trabalho ao longo da vida útil do código e utilizarei isso como meu guia. Caso contrário, precisaria ser um ponto de design deliberado para valer a pena.


1

A resposta simples é que o acoplamento solto é bom quando feito corretamente.

Se o princípio de uma função, um propósito for seguido, deve ser fácil o suficiente seguir o que está acontecendo. Além disso, o código de pares soltos segue naturalmente, sem nenhum esforço.

Regras simples de design: 1. não construa o conhecimento de vários itens em um único ponto (conforme indicado em todos os lugares, depende), a menos que você esteja construindo uma interface de fachada. 2. uma função - uma finalidade (essa finalidade pode ser multifacetada como em uma fachada) 3. um módulo - um conjunto claro de funções inter-relacionadas - uma finalidade clara 4. se você não puder simplesmente testá-lo por unidade, ele não terá um propósito simples

Todos esses comentários sobre mais fácil refatorar mais tarde são uma carga de bacalhaus. Uma vez que o conhecimento é incorporado em muitos lugares, principalmente em sistemas distribuídos, o custo de refatoração, a sincronização de distribuição e quase todos os outros custos é tão desconsiderado que, na maioria dos casos, o sistema acaba sendo descartado por causa disso.

O triste do desenvolvimento de software hoje em dia é que 90% das pessoas desenvolvem novos sistemas e não têm capacidade de compreender sistemas antigos, e nunca estão por perto quando o sistema atinge um estado de saúde tão ruim devido à refatoração contínua de bits e partes.


0

Não importa o quão fortemente acoplado uma coisa seja à outra, se ela nunca mudar. Ao longo dos anos, achei geralmente mais produtivo concentrar-me em buscar menos razões para as coisas mudarem, buscar estabilidade do que torná-las mais fáceis de mudar, tentando obter a forma mais flexível possível de acoplamento. Desacoplamento que achei muito útil, a ponto de às vezes favorecer duplicação de código modesta para desacoplar pacotes. Como exemplo básico, eu tive a opção de usar minha biblioteca de matemática para implementar uma biblioteca de imagens. Eu não fiz e apenas duplicei algumas funções matemáticas básicas que eram triviais para copiar.

Agora, minha biblioteca de imagens é completamente independente da biblioteca de matemática de uma maneira que, independentemente do tipo de alteração que eu faça na minha biblioteca de matemática, não afetará a biblioteca de imagens. Isso coloca a estabilidade em primeiro lugar. A biblioteca de imagens está mais estável agora, por ter drasticamente menos razões para mudar, uma vez que é dissociada de qualquer outra biblioteca que possa mudar (além da biblioteca padrão C, que, espero, nunca deve mudar). Como bônus, também é fácil de implantar quando é apenas uma biblioteca autônoma que não exige a coleta de várias outras bibliotecas para compilá-lo e usá-lo.

A estabilidade é muito útil para mim. Gosto de criar uma coleção de códigos bem testados, com cada vez menos razões para mudar no futuro. Isso não é um sonho; Eu tenho código C que tenho usado e usado novamente desde o final dos anos 80, que não mudou desde então. É admitidamente coisas de baixo nível, como código orientado a pixels e relacionado à geometria, enquanto muitas das minhas coisas de nível superior se tornaram obsoletas, mas é algo que ainda ajuda muito a ter por perto. Isso quase sempre significa uma biblioteca que depende de cada vez menos coisas, se não de nada externas. A confiabilidade aumenta e aumenta se o seu software depender cada vez mais de fundações estáveis ​​que encontram poucas ou nenhuma razão para mudar. Menos partes móveis são realmente boas, mesmo que na prática as partes móveis sejam muito maiores em número do que as partes estáveis.

O acoplamento frouxo está na mesma linha, mas acho frequentemente que o acoplamento frouxo é muito menos estável do que nenhum acoplamento. A menos que você esteja trabalhando em uma equipe com designers e clientes de interface muito superiores que nunca mudam de idéia do que eu já trabalhei, mesmo as interfaces puras geralmente encontram razões para mudar de maneiras que ainda causam falhas em cascata no código. Essa idéia de que a estabilidade pode ser alcançada direcionando dependências para o abstrato e não para o concreto é útil apenas se o design da interface for mais fácil de acertar na primeira vez que a implementação. Costumo achar que é invertida onde um desenvolvedor pode ter criado uma implementação muito boa, se não maravilhosa, considerando os requisitos de design que eles deveriam cumprir, apenas para descobrir no futuro que os requisitos de design mudam completamente.

Por isso, gosto de favorecer a estabilidade e a dissociação completa, para poder dizer pelo menos com confiança: "Essa pequena biblioteca isolada usada há anos e protegida por testes completos quase não tem probabilidade de exigir mudanças, independentemente do que se passa no caótico mundo exterior. . " Isso me dá um pouco de sanidade, não importa que tipo de mudanças de design sejam necessárias para o exterior.

Acoplamento e estabilidade, exemplo ECS

Também adoro sistemas de componentes de entidades e eles introduzem muito acoplamento rígido, porque o sistema para dependências de componentes acessa e manipula dados brutos diretamente, da seguinte maneira:

insira a descrição da imagem aqui

Todas as dependências aqui são bastante restritas, pois os componentes apenas expõem dados brutos. As dependências não estão fluindo para abstrações, elas estão fluindo para dados brutos, o que significa que cada sistema tem a quantidade máxima de conhecimento possível sobre cada tipo de componente que eles solicitam para acessar. Os componentes não têm funcionalidade com todos os sistemas acessando e violando os dados brutos. No entanto, é muito fácil argumentar sobre um sistema como esse, pois é muito simples. Se uma textura sair esquisita, você saberá imediatamente com esse sistema que apenas o sistema de renderização e pintura acessa os componentes de textura e provavelmente pode descartar rapidamente o sistema de renderização, pois ele lê apenas texturas conceitualmente.

Enquanto isso, uma alternativa fracamente acoplada pode ser a seguinte:

insira a descrição da imagem aqui

... com todas as dependências fluindo para funções abstratas, não dados, e tudo nesse diagrama expondo uma interface pública e uma funcionalidade própria. Aqui todas as dependências podem estar muito soltas. Os objetos podem nem depender diretamente um do outro e interagir entre si por meio de interfaces puras. Ainda é muito difícil argumentar sobre esse sistema, especialmente se algo der errado, dado o complexo emaranhado de interações. Também haverá mais interações (mais acopladas, embora mais frouxas) do que o ECS, porque as entidades precisam conhecer os componentes que agregam, mesmo se souberem apenas da interface pública abstrata da outra.

Além disso, se houver alterações de design em qualquer coisa, você terá mais quebras em cascata do que o ECS, e normalmente haverá mais razões e tentações para alterações de design, pois tudo está tentando fornecer uma interface e abstração orientadas a objetos. Isso imediatamente vem com a idéia de que cada uma das pequenas coisas tentará impor restrições e limitações ao design, e essas restrições geralmente são o que justificam mudanças no design. A funcionalidade é muito mais restrita e precisa fazer muito mais suposições de design do que dados brutos.

Na prática, descobri que o tipo de sistema ECS "plano" acima é muito mais fácil de raciocinar do que os sistemas mais fracamente acoplados, com uma complexa teia de aranhas de dependências frouxas e, o mais importante para mim, encontro poucas razões. para que a versão do ECS precise alterar quaisquer componentes existentes, pois os componentes dos quais dependem não têm responsabilidade, exceto para fornecer os dados apropriados necessários para o funcionamento dos sistemas. Compare a dificuldade de projetar uma IMotioninterface pura e um objeto de movimento concreto implementando essa interface que fornece funcionalidade sofisticada ao tentar manter invariantes sobre dados privados versus um componente de movimento que só precisa fornecer dados brutos relevantes para resolver o problema e não se incomoda com funcionalidade.

A funcionalidade é muito mais difícil de acertar do que os dados, e é por isso que acho que é preferível direcionar o fluxo de dependências para os dados. Afinal, quantas bibliotecas de vetores / matrizes existem por aí? Quantos deles usam exatamente a mesma representação de dados e diferem apenas sutilmente na funcionalidade? Inúmeros, e ainda assim temos muitos, apesar de representações de dados idênticas, porque queremos diferenças sutis na funcionalidade. Quantas bibliotecas de imagens existem? Quantos deles representam pixels de uma maneira diferente e única? Quase nenhuma, e novamente mostrando que a funcionalidade é muito mais instável e propensa a alterações de design do que os dados em muitos cenários. É claro que, em algum momento, precisamos de funcionalidade, mas você pode projetar sistemas nos quais a maior parte das dependências flui para os dados, e não para abstrações ou funcionalidades em geral. Isso priorizaria a estabilidade acima do acoplamento.

As funções mais estáveis ​​que já escrevi (do tipo que uso e reutilizo desde o final dos anos 80 sem precisar alterá-las) eram aquelas que dependiam de dados brutos, como uma função de geometria que apenas aceitava uma variedade de flutuadores e números inteiros, não aqueles que dependem de um Meshobjeto ou IMeshinterface complexo , ou multiplicação de vetores / matrizes que apenas dependem float[]ou double[]não, dos que dependem FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.