Quão rigorosamente você segue a regra "No Dependency Cycle" (NDepend)

10

Um pouco de histórico: como líder de equipe, uso o NDepend uma vez por semana para verificar a qualidade do nosso código. Especialmente a cobertura de teste, linhas de código e métricas de complexidade ciclomática são inestimáveis ​​para mim. Mas quando se trata de ciclos de nivelamento e dependência, estou um pouco ... bem preocupado. Patrick Smacchia tem um bom post no blog que descreve o objetivo da nivelação.

Para ser claro: em "ciclo de dependência" eu entendo uma referência circular entre dois namespaces.

Atualmente, estou trabalhando em uma estrutura de GUI baseada em Windows CE para instrumentos incorporados - pense na plataforma gráfica do Android, mas em instrumentos de ponta muito baixa. A estrutura é um conjunto único com cerca de 50.000 linhas de código (testes excluídos). A estrutura é dividida nos seguintes namespaces:

  • Subsistema de Navegação Principal e Menu
  • Subsistema de tela (Apresentadores / Exibições / ...)
  • Controles / camada de widget

Hoje, passei o meio dia tentando trazer o código para níveis adequados [graças ao Resharper sem problemas em geral], mas em todos os casos existem alguns ciclos de dependência.

Então, minha pergunta: quão estritamente você segue a regra "No Dependency Cycle"? A nivelação é realmente tão importante?

ollifant
fonte
2
Se "sem ciclo de dependência" significa sem referências circulares, então - sem desculpas. Aqueles são pura maldade.
Arnis Lapsa 21/03
@ollifant: defina exatamente o que você quer dizer quando escreve "No Dependency Cycle". Entre camadas ou entre classes dentro de uma camada?
Doc Brown

Respostas:

9

Recentemente, escrevi 2 livros brancos, publicados no Simple-Talk, sobre o tópico de arquitetura de código .NET (o primeiro livro é sobre assemblies .NET, o segundo sobre namespaces e nivelamento):

Particionando sua base de código por meio de assemblies .NET e projetos do Visual Studio

Definindo componentes .NET com namespaces

A nivelação é realmente tão importante?

Sim, ele é!

Citação do 2º livro branco:

Se o gráfico de dependências entre componentes contiver um ciclo, os componentes envolvidos no ciclo não poderão ser desenvolvidos e testados independentemente. Por esse motivo, o ciclo de componentes representa um supercomponente, com maior entropia do que a soma das entropias de seus componentes contidos.

(...)

Enquanto a restrição de componentes acíclicos for continuamente respeitada, a base de código permanecerá altamente aprendida e passível de manutenção.

  • Na arquitetura tradicional de construção, a força da gravidade exerce pressão sobre artefatos de baixo nível. Isso os torna mais estáveis: 'estáveis' no sentido em que são difíceis de se mover.
  • Na arquitetura de software, o cumprimento da idéia de componente acíclico pressiona os componentes de baixo nível. Isso os torna mais estáveis, no sentido de que é doloroso refatorá-los. Abstrações empíricas são menos frequentemente sujeitas a refatoração do que implementações. por esse motivo, é uma boa idéia que os componentes de baixo nível contenham principalmente abstrações (interfaces e enumerações) para evitar refatoração dolorosa.
Patrick Smacchia - desenvolvedor NDepend
fonte
6

Eu nunca permito dependências circulares entre classes ou namespaces. Em C #, Java e C ++, você sempre pode interromper uma dependência de classe circular introduzindo uma interface.

A codificação do teste primeiro dificulta a introdução de dependências circulares.

Kevin Cline
fonte
4

É sempre uma troca: você precisa entender o custo das dependências para entender por que as dependências devem ser evitadas. Da mesma forma, se você entender o custo de uma dependência, poderá decidir melhor se vale a pena no seu caso específico.

Por exemplo, em videogames para console, geralmente dependemos de dependências onde precisamos de relacionamentos estreitos de informações, principalmente por motivos de desempenho. Tudo bem, pois não precisamos ser tão flexíveis quanto uma ferramenta de edição, por exemplo.

Se você entende as restrições de que seu software precisa ser executado (seja hardware, SO ou apenas design (como "a interface do usuário mais simples possível")), será fácil selecionar o entendimento de quais dependências não devem ser feitas e qual é a dependência. Está bem.

Mas se você não tiver uma boa e clara razão, evite qualquer dependência que puder. O código dependente é o inferno da sessão de depuração.

Klaim
fonte
2

Sempre que esse tópico é discutido, os participantes geralmente perdem o foco da distinção entre ciclos de tempo de compilação e tempo de execução. O primeiro é o que John Lakos chamou de "design físico", enquanto o último é basicamente irrelevante para a discussão (não se preocupe com os ciclos de tempo de execução, como os criados por retornos de chamada).

Dito isto, John Lakos foi muito rigoroso quanto à eliminação de todos os ciclos (em tempo de construção). Bob Martin, no entanto, adotou a atitude de que apenas ciclos entre binários (por exemplo, DLLs, executáveis) eram significativos e deveriam ser evitados; ele acreditava que ciclos entre classes dentro de um binário não são terrivelmente importantes.

Pessoalmente, mantenho a opinião de Bob Martin sobre isso. No entanto, ainda presto alguma atenção aos ciclos entre as classes, porque a ausência de tais ciclos facilita o código para outras pessoas lerem e aprenderem.

No entanto, é importante ressaltar que qualquer código criado com o Visual Studio não é capaz de ter dependências circulares entre binários (código nativo ou gerenciado). Assim, o problema mais grave dos ciclos foi resolvido para você. :)

Brent Arias
fonte
0

Quase totalmente, porque os ciclos de dependência do tipo build são inconvenientes no Haskell e impossíveis no Agda e no Coq, e esses são os idiomas que normalmente uso.

Robin Green
fonte