Como gerenciar a complexidade acidental em projetos de software

73

Quando perguntaram a Murray Gell-Mann como Richard Feynman conseguiu resolver tantos problemas difíceis, Gell-Mann respondeu que Feynman tinha um algoritmo:

  1. Anote o problema.
  2. Pense bem.
  3. Anote a solução.

Gell-Mann estava tentando explicar que Feynman era um tipo diferente de solucionador de problemas e não havia informações a serem obtidas por meio do estudo de seus métodos. Eu me sinto do mesmo jeito sobre o gerenciamento de complexidade em projetos de software de médio / grande porte. As pessoas que são boas são apenas inerentemente boas nisso e de alguma forma conseguem colocar camadas e empilhar várias abstrações para tornar a coisa toda administrável sem introduzir qualquer coisa estranha.

Assim, o algoritmo de Feynman é a única maneira de gerenciar a complexidade acidental ou existem métodos reais que os engenheiros de software possam aplicar consistentemente para controlar a complexidade acidental?

    
por davidk01 17.02.2014 / 12:33
fonte

6 respostas

100

When you see a good move, look for a better one.
—Emanuel Lasker, 27-year world chess champion

Na minha experiência, o maior driver da complexidade acidental é o fato de os programadores adotarem o primeiro rascunho, simplesmente porque ele funciona. Isso é algo que podemos aprender com nossas aulas de redação em inglês. Eles constroem a tempo de passar por vários rascunhos em suas tarefas, incorporando o feedback dos professores. Aulas de programação, por algum motivo, não.

Existem livros cheios de formas concretas e objetivas de reconhecer, articular e corrigir códigos abaixo do ideal: Código Limpo , Trabalhando efetivamente com o código herdado e muitos outros. Muitos programadores estão familiarizados com essas técnicas, mas nem sempre têm tempo para aplicá-las. Eles são perfeitamente capazes de reduzir a complexidade acidental, eles simplesmente não têm o hábito de tentar .

Parte do problema é que muitas vezes não vemos a complexidade intermediária do código de outras pessoas, a menos que tenha passado por revisão por pares em um estágio inicial. O código limpo parece fácil de escrever, quando na verdade envolve vários rascunhos. Você escreve primeiro a melhor maneira que vem à sua cabeça, percebe complexidades desnecessárias que ela introduz, então "procure por um movimento melhor" e refatore para remover essas complexidades. Então você continua "procurando por um movimento melhor" até não conseguir encontrar um.

No entanto, você não coloca o código para revisão até depois de todo esse churn, então externamente parece que também pode ter sido um processo tipo Feynman. Você tem uma tendência a pensar que você não pode fazer tudo um pedaço desse jeito, então você não se incomoda tentando, mas a verdade é que o autor desse código maravilhosamente simples que você acabou de ler normalmente não pode escrever tudo em um pedaço assim também, ou se puderem, é só porque eles têm experiência em escrever código semelhante muitas vezes antes, e agora podem ver o padrão sem os estágios intermediários. De qualquer forma, você não pode evitar os rascunhos.

    
por 17.02.2014 / 14:41
fonte
44

"A habilidade de arquitetura de software não pode ser ensinada" é uma falácia generalizada.

É fácil entender por que muitas pessoas acreditam nisso (aqueles que são bons nisso querem acreditar que são místicos, e aqueles que não querem acreditar que não é culpa deles que não são .) No entanto, é errado; a habilidade é um pouco mais intensiva em prática do que outras habilidades de software (por exemplo, entender loops, lidar com ponteiros, etc.)

Acredito firmemente que a construção de grandes sistemas é suscetível à prática repetida e à aprendizagem da experiência da mesma forma que se tornar um grande músico ou orador público é: uma quantidade mínima de talento é uma pré-condição, mas não é um mínimo deprimente está fora do alcance da maioria dos praticantes.

Lidar com a complexidade é uma habilidade que você adquire em grande parte tentando e fracassando algumas vezes. É apenas que as muitas diretrizes gerais que a comunidade descobriu para programar em grande escala (usar camadas, lutar contra duplicações onde quer que ela apareça, aderir religiosamente a 0/1 / infinito ...) não são tão obviamente corretas e necessárias a um iniciante até que eles realmente programem algo que seja grande. Até que você tenha sido mordido por duplicação que causou problemas apenas alguns meses depois, você simplesmente não pode "obter" a importância de tais princípios.

    
por 17.02.2014 / 13:07
fonte
22

O pensamento pragmático de Andy Hunt aborda essa questão. Refere-se ao modelo de Dreyfus, segundo o qual existem 5 estágios de proficiência em várias habilidades. Os novatos (fase 1) precisam de instruções precisas para poder fazer algo corretamente. Especialistas (estágio 5), ao contrário, podem aplicar padrões gerais a um determinado problema. Citando o livro,

It’s often difficult for experts to explain their actions to a fine level of detail; many of their responses are so well practiced that they become preconscious actions. Their vast experience is mined by nonverbal, preconscious areas of the brain, which makes it hard for us to observe and hard for them to articulate.

When experts do their thing, it appears almost magical to the rest of us—strange incantations, insight that seems to appear out of nowhere, and a seemingly uncanny ability to know the right answer when the rest of us aren’t even all that sure about the question. It’s not magic, of course, but the way that experts perceive the world, how they problem solve, the mental models they use, and so on, are all markedly different from nonexperts.

Esta regra geral de ver (e, como resultado, evitar) diferentes questões pode ser aplicada especificamente à questão da complexidade acidental. Ter um determinado conjunto de regras não é suficiente para evitar esse problema. Sempre haverá uma situação que não é coberta por essas regras. Precisamos ganhar experiência para poder prever problemas ou identificar soluções. A experiência é algo que não pode ser ensinado, só pode ser obtido através de tentativas constantes, fracasso ou sucesso e aprendendo com os erros.

Esta questão do local de trabalho é relevante e IMHO seria interessante ler neste contexto.

    
por 17.02.2014 / 13:10
fonte
4

Você não explicita, mas "complexidade acidental" é definida como uma complexidade que não é inerente ao problema, em comparação à complexidade "essencial". As técnicas requeridas para "Domesticação" dependerão de onde você começa. O que se segue refere-se principalmente a sistemas que já adquiriram complexidade desnecessária.

Eu tenho experiência em vários grandes projetos plurianuais onde o componente “acidental” superou significativamente o aspecto “essencial”, e também aqueles em que isso não aconteceu.

Na verdade, o algoritmo de Feynman se aplica até certo ponto, mas isso não significa que “pense realmente duro” significa apenas magia que não pode ser codificada.

Eu acho que existem duas abordagens que precisam ser tomadas. Leve os dois - eles não são alternativas. Uma é abordá-la aos poucos e a outra é fazer um grande retrabalho. Então, certamente, "anote o problema". Isso pode assumir a forma de uma auditoria do sistema - os módulos de código, seu estado (cheiro, nível de teste automatizado, quantos funcionários alegam entendê-lo), a arquitetura geral (existe um, mesmo que "tenha problemas" ), estado dos requisitos, etc., etc.

É a natureza da complexidade "acidental" que não há um problema que precise ser abordado. Então você precisa fazer uma triagem. Onde dói - em termos de capacidade de manter o sistema e progredir em seu desenvolvimento? Talvez algum código seja realmente malcheiroso, mas não é prioridade máxima e a correção pode ser feita para esperar. Por outro lado, pode haver algum código que retornará rapidamente o tempo gasto na refatoração.

Defina um plano para o que uma arquitetura melhor será e tente garantir que o novo trabalho esteja de acordo com esse plano - essa é a abordagem incremental.

Além disso, articule o custo dos problemas e use-o para criar um business case para justificar um refatorador. A principal coisa aqui é que um sistema bem arquitetado pode ser muito mais robusto e testável, resultando em um tempo muito menor (custo e cronograma) para implementar a mudança - isso tem um valor real.

Um grande retrabalho vem na categoria "pense bem difícil" - você precisa acertar. Este é o lugar onde ter um "Feynman" (bem, uma pequena fração de um estaria bem) dá uma enorme recompensa. Um grande retrabalho que não resulta em uma arquitetura melhor pode ser um desastre. Reescritas completas do sistema são notórias por isso.

Implícito em qualquer abordagem é saber distinguir “acidental” de “essencial” - o que significa dizer que você precisa ter um grande arquiteto (ou equipe de arquitetos) que realmente entenda o sistema e sua finalidade.

Dito tudo isso, o mais importante para mim é o teste automatizado . Se você tem o suficiente, seu sistema está sob controle. Se você não fizer isso. . .

    
por 18.02.2014 / 07:00
fonte
3

"Everything should be made as simple as possible, but no simpler."
— attributed to Albert Einstein

Deixe-me esboçar meu algoritmo pessoal para lidar com a complexidade acidental.

  1. Escreva uma história de usuário ou caso de uso. Revise com o proprietário do produto.
  2. Escreva um teste de integração que falhe porque o recurso não está lá. Analise com o QA ou com o engenheiro chefe, se houver algo assim na sua equipe.
  3. Escreva testes unitários para algumas classes que poderiam passar no teste de integração.
  4. Escreva a implementação mínima para as classes que passarem nos testes de unidade.
  5. Analise os testes de unidade e a implementação com um colega desenvolvedor. Vá para o passo 3.

Toda a mágica do design estaria na Etapa 3: como você configura essas classes? Isso se torna a mesma pergunta: como você imagina ter uma solução para o seu problema antes de ter uma solução para o seu problema?

Notavelmente, apenas imaginar que você tem a solução parece ser uma das principais recomendações de pessoas que escrevem sobre resolução de problemas (chamada "wishful thinking" por Abelson e Sussman em Estrutura e Interpretação de Programas de Computador e "trabalhando para trás" em Polya's

Por outro lado, nem todo mundo tem o mesmo " gosto por soluções imaginadas ": há soluções que só você acha elegante, e há outras mais compreensíveis por um público mais amplo. É por isso que você precisa revisar seu código com colegas desenvolvedores: não tanto para ajustar o desempenho, mas para concordar com soluções entendidas. Normalmente, isso leva a um novo design e, depois de algumas iterações, a um código muito melhor.

Se você preferir escrever implementações mínimas para passar em seus testes e escrever testes que são entendidos por muitas pessoas, você deve terminar com uma base de código onde apenas complexidade irredutível permanece.

    
por 18.02.2014 / 17:06
fonte
2

Complexidade acidental

A pergunta original (parafraseada) foi:

How do architects manage accidental complexity in software projects?

A complexidade acidental surge quando aqueles com orientação sobre um projeto optam por anexar tecnologias que são únicas, e que a estratégia geral dos arquitetos originais do projeto não tinha a intenção de trazer para o projeto. Por essa razão, é importante registrar o raciocínio por trás da escolha na estratégia.

A complexidade acidental pode ser evitada pela liderança que adere à sua estratégia original até que um afastamento deliberado dessa estratégia se torne aparentemente necessário.

Evitando Complexidade Desnecessária

Com base no corpo da pergunta, eu reformularia assim:

How do architects manage complexity in software projects?

Esta reformulação é mais a propósito do corpo da questão, onde o algoritmo de Feynman foi introduzido, fornecendo um contexto que propõe que, para os melhores arquitetos, quando confrontados com um problema, tenham uma gestalt a partir da qual eles habilmente construam um solução, e que o resto de nós não pode esperar aprender isso. Ter uma gestalt de compreensão depende da inteligência do assunto e de sua disposição em aprender as características das opções arquitetônicas que poderiam estar dentro de seu escopo.

O processo de planejamento do projeto usaria o aprendizado da organização para fazer uma lista dos requisitos do projeto e, em seguida, tentar construir uma lista de todas as opções possíveis e, em seguida, reconciliar as opções com os requisitos. A gestalt do especialista permite que ele faça isso rapidamente, e talvez com pouco trabalho evidente, fazendo com que pareça fácil para ele.

Eu submeto a você que isto vem a ele por causa de sua preparação. Ter a gestalt do especialista requer familiaridade com todas as suas opções, e a visão de fornecer uma solução direta que permita as necessidades futuras previstas que o projeto deve prever, bem como a flexibilidade de se adaptar às necessidades de mudança de o projeto. A preparação de Feynman foi que ele tinha uma profunda compreensão de várias abordagens em matemática e física teórica e aplicada. Ele era naturalmente curioso e inteligente o bastante para entender as coisas que descobriu sobre o mundo natural ao seu redor.

O arquiteto especialista em tecnologia terá uma curiosidade semelhante, com base em uma profunda compreensão dos fundamentos, bem como uma ampla exposição a uma grande diversidade de tecnologias. Ele (ou ela) terá a sabedoria para utilizar as estratégias que obtiveram êxito em vários domínios (como Princípios da Programação Unix e aqueles que se aplicam a domínios específicos (como padrões de design e transformações de estilo ). Ele pode não ser intimamente conhecedor de todos os recursos, mas saberá onde encontrar o recurso.

Construindo a solução

Esse nível de conhecimento, compreensão e sabedoria pode ser extraído da experiência e da educação, mas requer inteligência e atividade mental para montar uma solução estratégica de gestalt que trabalhe em conjunto de uma maneira que evite a complexidade acidental e desnecessária. Requer que o especialista junte esses fundamentos; estes foram os trabalhadores do conhecimento que Drucker previu quando cunhou o termo pela primeira vez.

Voltar às perguntas finais específicas:

Métodos específicos para domar a complexidade acidental podem ser encontrados nos seguintes tipos de fontes.

Seguindo os Princípios da Programação Unix, você terá que criar programas modulares simples que funcionem bem e sejam robustos com interfaces comuns. Seguir Padrões de Projeto ajudará você a construir algoritmos complexos que não são mais complexos do que o necessário. Os Guias de estilo a seguir garantirão que seu código seja legível, sustentável e ideal para o idioma em que seu código é escrito. Especialistas terão internalizado muitos dos princípios encontrados nesses recursos, e serão capazes de colocá-los juntos de forma coesa e sem falhas.

    
por 18.02.2014 / 02:54
fonte