Lidando com interseções de recursos

11

Testemunhei recentemente problemas cada vez mais semelhantes aos explicados neste artigo sobre interseções de recursos. Outro termo para isso seria linhas de produtos, embora eu tenha a tendência de atribuí-las a produtos realmente diferentes, enquanto geralmente encontro esses problemas na forma de possíveis configurações de produtos.

A idéia básica desse tipo de problema é simples: você adiciona um recurso a um produto, mas de alguma forma as coisas ficam complicadas devido a uma combinação de outros recursos existentes. Eventualmente, o controle de qualidade encontra um problema com uma rara combinação de recursos que ninguém pensava antes e o que deveria ter sido uma simples correção de bug pode até exigir mudanças importantes no design.

As dimensões desse problema de interseção de recursos são de uma complexidade alucinante. Digamos que a versão atual do software tenha Nrecursos e você adicione um novo recurso. Vamos também simplificar as coisas dizendo que cada um dos recursos pode ser ativado ou desativado apenas, então você já tem 2^(N+1)possíveis combinações de recursos a considerar. Devido à falta de melhores termos de redação / pesquisa, refiro-me à existência dessas combinações como problema de interseção de recursos . (Pontos de bônus por uma resposta, incluindo referência (s) para um termo mais estabelecido.)

Agora, a questão com a qual luto é como lidar com esse problema de complexidade em cada nível do processo de desenvolvimento. Por razões óbvias de custo, é impraticável até o ponto de ser utópico, querer abordar cada combinação individualmente. Afinal, tentamos ficar longe dos algoritmos de complexidade exponencial por um bom motivo, mas transformar o próprio processo de desenvolvimento em um monstro de tamanho exponencial está fadado a levar ao fracasso total.

Então, como você obtém o melhor resultado de maneira sistemática, que não explode nenhum orçamento e é completo de uma maneira decente, útil e profissionalmente aceitável.

  • Especificação: Quando você especifica um novo recurso - como garantir que ele funcione bem com todas as outras crianças?

    Percebo que é possível examinar sistematicamente cada recurso existente em combinação com o novo recurso - mas isso estaria isolado dos outros recursos. Dada a natureza complexa de alguns recursos, essa visão isolada já está quase tão envolvida que precisa de uma abordagem estruturada por si só, sem falar no 2^(N-1)fator causado pelos outros recursos que alguém ignorou de bom grado.

  • Implementação: quando você implementa um recurso - como garantir que seu código interaja / intercepte adequadamente em todos os casos.

    Mais uma vez, estou me perguntando sobre a pura complexidade. Conheço várias técnicas para reduzir o potencial de erro de dois recursos que se cruzam, mas nenhum que fosse dimensionado de maneira razoável. Suponho, porém, que uma boa estratégia durante a especificação deve manter o problema distante durante a implementação.

  • Verificação: ao testar um recurso - como você lida com o fato de que você só pode testar uma fração do espaço de interseção desse recurso?

    Já é bastante difícil saber que testar um único recurso isoladamente não garante nada próximo ao código sem erros, mas quando você reduz isso a uma fração 2^-N, parece que centenas de testes nem sequer cobrem uma única gota de água em todos os oceanos combinados. . Pior ainda, os erros mais problemáticos são aqueles que se originam da interseção de recursos, que não se espera que levem a problemas - mas como você os testa se não espera uma interseção tão forte?

Embora eu queira ouvir como outras pessoas lidam com esse problema, estou principalmente interessado na literatura ou nos artigos que analisam o tópico em maior profundidade. Portanto, se você seguir pessoalmente uma certa estratégia, seria bom incluir fontes correspondentes em sua resposta.

Frank
fonte
6
Uma arquitetura de aplicativo projetada com sensibilidade pode acomodar novos recursos sem virar o mundo de cabeça para baixo; a experiência é o grande nivelador aqui. Dito isto, essa arquitetura nem sempre é fácil de acertar na primeira tentativa e, às vezes, é necessário fazer ajustes difíceis. O problema do teste não é necessariamente o atoleiro que você pensa ser, se você souber encapsular adequadamente os recursos e a funcionalidade e cobri-los com testes de unidade adequados.
Robert Harvey

Respostas:

6

Já sabíamos matematicamente que a verificação de um programa é impossível em tempo finito no caso mais geral, devido ao problema de parada. Portanto, esse tipo de problema não é novo.

Na prática, um bom design pode fornecer dissociação, de modo que o número de recursos que se cruzam seja muito menor que 2 ^ N, embora certamente pareça estar acima de N, mesmo em sistemas bem projetados.

No que diz respeito às fontes, parece-me que quase todos os livros ou blogs sobre design de software estão tentando efetivamente reduzir esse 2 ^ N o máximo possível, embora eu não conheça nenhum que lance o problema nos mesmos termos que você. Faz.

Para um exemplo de como o design pode ajudar nisso, no artigo mencionado, algumas das interseções de recursos ocorreram porque a replicação e a indexação foram acionadas pela eTag. Se eles tivessem disponível outro canal de comunicação para sinalizar a necessidade de cada um deles separadamente, possivelmente eles poderiam ter controlado a ordem dos eventos mais facilmente e ter menos problemas.

Ou talvez não. Não sei nada sobre o RavenDB. A arquitetura não pode evitar problemas de interseção de recursos se os recursos realmente estiverem inexplicavelmente entrelaçados, e nunca poderemos saber com antecedência que não desejaremos um recurso que realmente tenha o pior caso de interseção 2 ^ N. Mas a arquitetura pode pelo menos limitar as interseções devido a problemas de implementação.

Mesmo que eu esteja errado sobre o RavenDB e as eTags (e apenas o estou usando como argumento - eles são pessoas inteligentes e provavelmente acertaram), deve ficar claro como a arquitetura pode ajudar. A maioria dos padrões sobre os quais as pessoas falam são projetados explicitamente com o objetivo de reduzir o número de alterações de código exigidas por recursos novos ou variáveis. Isso remonta - por exemplo, "Padrões de design, elementos de software orientado a objetos reutilizáveis", a introdução afirma "Cada padrão de design permite que algum aspecto da arquitetura varie independentemente de outros aspectos, tornando assim um sistema mais robusto a um tipo específico de mudança".

O que quero dizer é que é possível entender o Big O das interseções de recursos na prática, observando o que acontece na prática. Ao pesquisar esta resposta, descobri que a maioria das análises de pontos de função / esforço de desenvolvimento (ou seja, produtividade) encontrou um crescimento inferior ao linear do esforço do projeto por ponto de função ou muito ligeiramente acima do crescimento linear. O que achei um pouco surpreendente. Este teve um exemplo bastante legível.

Isso (e estudos semelhantes, alguns dos quais usam pontos de função em vez de linhas de código) não prova que a interseção de recursos não ocorra e cause problemas, mas parece uma evidência razoável de que não é devastador na prática.

psr
fonte
0

Esta não será a melhor resposta, de forma alguma, mas estive pensando em algumas coisas que se cruzam com os pontos da sua pergunta, então pensei em mencioná-las:

Suporte Estrutural

Pelo pouco que vi, quando os recursos são de bugs e / ou não se encaixam bem com outros, isso se deve em grande parte ao baixo suporte fornecido pela estrutura / estrutura principal do programa para gerenciá-los / coordená-los. Passar mais tempo aprimorando e completando o núcleo, acho, deve facilitar a adição de novos recursos.

Uma coisa que eu encontrei para ser comum nas aplicações onde eu trabalho é que a estrutura de um programa foi configurado para lidar com um de um tipo de objeto ou processo, mas muitas das extensões que fizemos ou quer fazer tem a lidar com muitos tipos. Se isso fosse levado em consideração mais no início do design do aplicativo, teria ajudado a adicionar esses recursos posteriormente.

Isso se torna bastante crítico ao adicionar suporte para vários Xs, que envolvem código encadeado / assíncrono / orientado a eventos, porque essas coisas podem ficar ruins rapidamente - eu tive o prazer de depurar vários problemas relacionados a isso.

Provavelmente, é difícil justificar esse tipo de esforço antecipadamente, especialmente para protótipos ou projetos pontuais - mesmo que alguns desses protótipos ou pontuais sejam usados ​​novamente ou como (a base do) sistema final, significando que as despesas teriam valido a pena a longo prazo.

Projeto

Ao projetar o núcleo de um programa, começar com uma abordagem de cima para baixo pode ajudar a transformar as coisas em partes gerenciáveis ​​e permite que você compreenda o domínio do problema; depois disso, acho que uma abordagem de baixo para cima deve ser usada - isso ajudará a tornar as coisas menores, mais flexíveis e melhores para adicionar posteriormente. (Conforme mencionado no link, fazer as coisas dessa maneira gera implementações menores dos recursos, o que significa menos conflitos / bugs.)

Se você se concentrar nos componentes básicos do sistema e garantir que todos eles interajam bem, qualquer coisa criada usando-os provavelmente também se comportará bem e deverá se integrar melhor ao restante do sistema.

Quando um novo recurso é adicionado, acho que uma rota semelhante poderia ser tomada ao projetá-lo, como foi feito no restante do framework: decompondo-o e depois de baixo para cima. Se você puder reutilizar qualquer um dos blocos originais da estrutura na implementação do recurso, isso definitivamente seria útil; quando terminar, você pode adicionar novos blocos que você obtém do recurso àqueles que já estão na estrutura principal, testando-os com o conjunto original de blocos - para que sejam compatíveis com o resto do sistema e utilizáveis ​​no futuro recursos também.

Simplificar!

Ultimamente, tenho adotado uma postura minimalista no design, começando com a simplificação do problema e depois a solução. Se for possível reservar um tempo, simplificando a iteração do design em um projeto, eu pude ver isso sendo muito útil ao adicionar coisas mais tarde.

Enfim, esse é o meu 2c.

Paulo
fonte