Quando perguntaram a Murray Gell-Mann como Richard Feynman conseguiu resolver tantos problemas difíceis, Gell-Mann respondeu que Feynman tinha um algoritmo:
- Anote o problema.
- Pense bem.
- 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 com o estudo de seus métodos. Eu meio que sinto o mesmo sobre o gerenciamento de complexidade em projetos de software de médio / grande porte. As pessoas que são boas são inerentemente boas nisso e, de alguma forma, conseguem estratificar e empilhar várias abstrações para tornar a coisa toda gerenciável, sem a introdução de qualquer insólito estranho.
Então, o algoritmo de Feynman é a única maneira de gerenciar a complexidade acidental ou existem métodos reais que os engenheiros de software podem aplicar de maneira consistente à complexidade acidental?
fonte
Respostas:
Na minha experiência, o maior fator de complexidade acidental são os programadores que aderem ao primeiro rascunho, apenas porque ele funciona. Isso é algo que podemos aprender com nossas aulas de composição em inglês. Eles criam a tempo de passar por vários rascunhos em suas tarefas, incorporando o feedback dos professores. As aulas de programação, por algum motivo, não.
Existem livros cheios de maneiras concretas e objetivas de reconhecer, articular e corrigir códigos abaixo do ideal: Código Limpo , Trabalhando Efetivamente com o Código Legado , 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, mas não criaram 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 pela revisão por pares em um estágio inicial. O código limpo parece fácil de escrever, quando na verdade geralmente envolve vários rascunhos. Você escreve da melhor maneira que lhe vem à cabeça, percebe as complexidades desnecessárias que introduz, depois "procura uma mudança melhor" e refatora para remover essas complexidades. Então você continua "procurando uma jogada melhor" até não conseguir encontrar uma.
No entanto, você não coloca o código para revisão até depois de toda essa agitação, então externamente parece que pode ter sido um processo semelhante ao de Feynman. Você tem a tendência de pensar que não pode fazer tudo isso de uma só vez, para não se incomodar em tentar, mas a verdade é que o autor desse código lindamente simples que você acabou de ler geralmente não pode escrever tudo em um único pedaço assim, ou se puderem, é apenas 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.
fonte
"A habilidade em 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 misticamente especiais e aqueles que não querem acreditar que não é culpa deles que não são). é, no entanto, errado; a habilidade é apenas um pouco mais 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 a práticas repetidas e a aprender com a experiência da mesma maneira que se torna 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 enorme que está fora do comum. alcance da maioria dos praticantes.
Lidar com a complexidade é uma habilidade que você adquire em grande parte tentando e falhando algumas vezes. É que as muitas diretrizes gerais que a comunidade descobriu para a programação em geral (use camadas, lute contra a duplicação sempre que quiser, adote religiosamente 0/1 / infinito ...) não são tão obviamente corretas e necessárias para um iniciantes até que eles realmente programam algo que é grande. Até que você realmente tenha sido picado pela duplicação que causou problemas apenas alguns meses depois, você simplesmente não pode "entender" a importância de tais princípios.
fonte
O pensamento pragmático de Andy Hunt aborda esta questão. Refere-se ao modelo Dreyfus, segundo o qual existem 5 estágios de proficiência em várias habilidades. Os novatos (estágio 1) precisam de instruções precisas para poder fazer algo corretamente. Especialistas (estágio 5), pelo contrário, podem aplicar padrões gerais a um determinado problema. Citando o livro,
Esta regra geral de ver (e, como resultado, evitar) problemas diferentes pode ser aplicada especificamente ao problema de 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 adquirido por constantes tentativas, falhas ou sucessos e aprendendo com os erros.
Esta pergunta do Workplace é relevante e o IMHO seria interessante de ler neste contexto.
fonte
Você não explica, mas "complexidade acidental" é definida como complexidade que não é inerente ao problema, em comparação com a complexidade "essencial". As técnicas necessárias para "domar" dependerão de onde você começa. O que se segue se refere principalmente a sistemas que já adquiriram complexidade desnecessária.
Tenho experiência em vários grandes projetos plurianuais, nos quais o componente “acidental” supera significativamente o aspecto “essencial”, e também aqueles onde não.
Na verdade, o algoritmo de Feynman se aplica, em certa medida, mas isso não significa que "pensar bem" significa apenas mágica que não pode ser codificada.
Acho que há duas abordagens que precisam ser adotadas. Pegue os dois - eles não são alternativas. Um é abordá-lo aos poucos e o outro é 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, quantas equipes pretendem entendê-lo), a arquitetura geral (existe um, mesmo que "tenha problemas" ), estado dos requisitos, etc. etc.
É da natureza da complexidade "acidental" que não há um problema que apenas precise ser resolvido. Então você precisa fazer a triagem. Onde dói - em termos de capacidade de manter o sistema e progredir em seu desenvolvimento? Talvez algum código seja realmente fedido, mas não seja a principal prioridade e a correção pode ser feita para aguardar. Por outro lado, pode haver algum código que retornará rapidamente o tempo gasto na refatoração.
Defina um plano para o que será uma arquitetura melhor e tente garantir que o novo trabalho esteja em conformidade com esse plano - essa é a abordagem incremental.
Além disso, articule o custo dos problemas e use-o para criar um caso de negócios para justificar um refator. O principal 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 valor real.
Um grande retrabalho vem na categoria "pense muito bem" - você precisa acertar. É aqui que ter um "Feynman" (bem, uma pequena fração de um seria bom) compensa enormemente. 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” - ou seja, você precisa ter um ótimo arquiteto (ou equipe de arquitetos) que realmente entenda o sistema e sua finalidade.
Dito tudo isso, a principal coisa para mim é o teste automatizado . Se você tiver o suficiente, seu sistema está sob controle. Se não . .
fonte
Deixe-me esboçar meu algoritmo pessoal para lidar com complexidade acidental.
Toda a mágica do design estaria na Etapa 3: como você configura essas classes? Isso passa a ser a mesma pergunta que: como você imagina que tem 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 das pessoas que escrevem sobre a solução de problemas (chamada "wishful thinking" de Abelson e Sussman em Estrutura e interpretação de programas de computador e "trabalhando para trás" em Como Resolvê-lo )
Por outro lado, nem todos têm o mesmo " gosto por soluções imaginadas ": existem soluções que somente você considera elegantes e 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 em soluções entendidas. Geralmente, isso leva a um novo design e, após algumas iterações, a um código muito melhor.
Se você se dedica a escrever implementações mínimas para passar nos testes e escrever testes que são entendidos por muitas pessoas, você deve terminar com uma base de código onde apenas a complexidade irredutível permanece.
fonte
Complexidade acidental
A pergunta original (parafraseada) era:
A complexidade acidental surge quando aqueles que dirigem um projeto optam por acrescentar tecnologias únicas e que a estratégia geral dos arquitetos originais do projeto não pretendia trazer ao projeto. Por esse motivo, é importante registrar o raciocínio por trás da escolha da estratégia.
A complexidade acidental pode ser evitada pela liderança que mantém sua estratégia original até o momento em que uma saída deliberada dessa estratégia se torna aparentemente necessária.
Evitando complexidade desnecessária
Com base no corpo da pergunta, eu a reformularia assim:
Essa reformulação é mais apropriada ao 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 constroem uma solução, e que o resto de nós não pode esperar aprender isso. Ter uma gestalt de entendimento depende da inteligência do sujeito e de sua vontade de aprender os recursos 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, tentaria construir uma lista de todas as opções possíveis e depois 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 parecer fácil para ele.
Eu submeto a você que se trata dele por causa de sua preparação. Ter a gestalt do especialista requer familiaridade com todas as suas opções, e a previsão para fornecer uma solução direta que permita as necessidades futuras previstas que são determinadas pelo projeto, bem como a flexibilidade de se adaptar às necessidades variáveis de o projeto. A preparação de Feynman foi que ele tinha uma compreensão profunda de várias abordagens, tanto na matemática teórica quanto na aplicada e na física. Ele era curioso por natureza e inteligente o suficiente para entender as coisas que descobriu sobre o mundo natural ao seu redor.
O arquiteto especialista em tecnologia terá uma curiosidade semelhante, aproveitando uma profunda compreensão dos fundamentos, bem como uma ampla exposição a uma grande diversidade de tecnologias. Ele (ou ela) terá a sabedoria de recorrer às estratégias que foram bem-sucedidas em domínios (como Princípios de Programação Unix ) e aquelas que se aplicam a domínios específicos (como padrões de design e guias de estilo ). Ele pode não ter um conhecimento profundo de todos os recursos, mas saberá onde encontrá-lo.
Construindo a solução
Esse nível de conhecimento, entendimento 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 gestalt que trabalhe em conjunto de maneira a evitar complexidade acidental e desnecessária. Requer que o especialista junte esses fundamentos; esses eram os trabalhadores do conhecimento que Drucker previu quando cunhou o termo pela primeira vez.
De volta à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ê cria programas modulares simples que funcionam bem e são robustos com interfaces comuns. Os seguintes padrões de design o ajudarão a construir algoritmos complexos que não são mais complexos que o necessário. Os Guias de estilo a seguir garantirão que seu código seja legível, manutenível e ideal para o idioma em que seu código foi escrito. Os especialistas internalizaram muitos dos princípios encontrados nesses recursos e serão capazes de reuni-los de maneira coesa e integrada.
fonte
Esta pode ter sido uma pergunta difícil há alguns anos, mas hoje não é mais difícil eliminar a complexidade acidental da IMO.
O que Kent Beck disse sobre ele mesmo, em algum momento: "Não sou um grande programador; sou apenas um bom programador com bons hábitos".
Vale destacar duas coisas: OMI: ele se considera um programador , não um arquiteto, e seu foco é nos hábitos, não no conhecimento.
A maneira de Feynman de resolver problemas difíceis é a única maneira de fazê-lo. A descrição não é necessariamente muito fácil de entender, por isso vou dissecá-la. A cabeça de Feynman não estava apenas cheia de conhecimento, mas também cheia de habilidade para aplicar esse conhecimento. Quando você tem o conhecimento e as habilidades para usá-lo, resolver um problema difícil não é difícil nem fácil. É o único resultado possível.
Existe uma maneira completamente não mágica de escrever código limpo, que não contém complexidade acidental e é semelhante ao que Feynman fez: adquirir todo o conhecimento necessário, treinar para se acostumar a colocá-lo para trabalhar, em vez de apenas escondê-lo em algum canto do seu cérebro, depois escreva um código limpo.
Agora, muitos programadores nem sequer estão cientes de todo o conhecimento necessário para escrever um código limpo. Programadores mais jovens tendem a descartar o conhecimento sobre algoritmos e estruturas de dados, e a maioria dos programadores mais velhos costuma esquecê-lo. Ou grande notação O e análise de complexidade. Programadores mais antigos tendem a descartar padrões ou odores de código - ou nem sabem que eles existem. A maioria dos programadores de qualquer geração, mesmo que conheça padrões, nunca se lembra exatamente de quando usar as peças dos drivers. Poucos programadores de qualquer geração avaliam constantemente seu código em relação aos princípios do SOLID. Muitos programadores misturam todos os níveis possíveis de abstração em todo o lugar. Por enquanto, não conheço um colega programador que avalie constantemente seu código em relação às fintas descritas por Fowler em seu livro de refatoração. Embora alguns projetos usem alguma ferramenta de métricas, a métrica mais usada é a complexidade, de um tipo ou de outro, enquanto duas outras métricas - acoplamento e coesão - são em grande parte ignoradas, mesmo que sejam muito importantes para o código limpo. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. a métrica mais usada é a complexidade, de um tipo ou de outro, enquanto duas outras métricas - acoplamento e coesão - são em grande parte ignoradas, mesmo que sejam muito importantes para o código limpo. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. a métrica mais usada é a complexidade, de um tipo ou de outro, enquanto duas outras métricas - acoplamento e coesão - são em grande parte ignoradas, mesmo que sejam muito importantes para o código limpo. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. enquanto duas outras métricas - acoplamento e coesão - são em grande parte ignoradas, mesmo que sejam muito importantes para o código limpo. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. enquanto duas outras métricas - acoplamento e coesão - são em grande parte ignoradas, mesmo que sejam muito importantes para o código limpo. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. Outro aspecto que quase todo mundo ignora é a carga cognitiva. Poucos programadores tratam os testes de unidade como documentação, e menos ainda estão cientes de que a dificuldade de escrever ou nomear testes de unidade é outro fedor de código, que geralmente indica fatoração ruim. Uma minoria minúscula está ciente do mantra do design orientado a domínio para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, uma vez que discrepâncias tendem a criar problemas no caminho. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. s para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, pois as discrepâncias tendem a criar problemas no futuro. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora. s para manter o modelo de código e o modelo de domínio de negócios o mais próximo possível do outro, pois as discrepâncias tendem a criar problemas no futuro. Tudo isso precisa ser considerado, o tempo todo, se você deseja que seu código seja limpo. E muito mais que não consigo me lembrar agora.
Você quer escrever um código limpo? Não há mágica necessária. Basta aprender tudo o que é necessário, depois usá-lo para avaliar a limpeza do seu código e refatorar até que você esteja feliz. E continue aprendendo - o software ainda é um campo jovem, e novas idéias e conhecimentos são adquiridos em ritmo acelerado.
fonte