Trabalho como desenvolvedor de software há muitos anos. Foi minha experiência que os projetos se tornam mais complexos e insustentáveis à medida que mais desenvolvedores se envolvem no desenvolvimento do produto.
Parece que o software em um certo estágio de desenvolvimento tem a tendência de ficar "mais hackier" e "hackier", especialmente quando nenhum dos membros da equipe que definiu a arquitetura trabalha mais na empresa.
Acho frustrante que um desenvolvedor que tenha que mudar alguma coisa tenha dificuldade em obter uma visão geral da arquitetura. Portanto, há uma tendência para corrigir problemas ou fazer alterações de maneira que funcione na arquitetura original. O resultado é um código que se torna cada vez mais complexo e ainda mais difícil de entender.
Existe algum conselho útil sobre como manter o código fonte realmente sustentável ao longo dos anos?
Respostas:
A única solução real para evitar a podridão do código é codificar bem!
Como codificar bem é outra questão. É difícil o suficiente, mesmo se você é um excelente programador trabalhando sozinho. Em uma equipe heterogênea, torna-se muito mais difícil ainda. Em (sub) projetos terceirizados ... apenas ore.
As boas práticas usuais podem ajudar:
fonte
Os testes de unidade são seus amigos . Implementá-los força baixo acoplamento. Isso também significa que as partes "hacky" do programa podem ser facilmente identificadas e refatoradas. Isso também significa que quaisquer alterações podem ser testadas rapidamente para garantir que não quebrem a funcionalidade existente. Isso deve incentivar seus desenvolvedores a modificar os métodos existentes, em vez de duplicar o código por medo de quebrar as coisas.
Os testes de unidade também funcionam como uma documentação extra para o seu código, descrevendo o que cada parte deve fazer. Com testes de unidade extensivos, seus programadores não precisam conhecer toda a arquitetura do seu programa para fazer alterações e usar classes / métodos existentes.
Como um bom efeito colateral, esperamos que os testes de unidade reduzam a contagem de erros.
fonte
Todo mundo aqui é rápido em mencionar o apodrecimento do código , e eu entendo e concordo completamente com isso, mas ainda falta o quadro geral e o maior problema em questão aqui. O apodrecimento do código não acontece apenas. Além disso, são mencionados testes de unidade que são bons, mas na verdade não solucionam o problema. Pode-se ter uma boa cobertura de teste de unidade e um código relativamente livre de erros, no entanto, ainda possui código e design apodrecidos.
Você mencionou que o desenvolvedor que trabalha em um projeto tem dificuldade em implementar um recurso e perde a visão geral da arquitetura geral e, portanto, implementa um hack no sistema. Onde está a liderança técnica para aplicar e influenciar o design? Onde estão as revisões de código nesse processo?
Na verdade, você não está sofrendo de podridão de código, mas está sofrendo de podridão em equipe . O fato é que não deve importar se os criadores originais do software não estão mais na equipe. Se o líder técnico da equipe existente entender completamente e verdadeiramente o design subjacente e for bom no papel de líder técnico, isso não será um problema.
fonte
Existem várias coisas que podemos fazer:
Dê a uma pessoa a responsabilidade geral pela arquitetura. Ao escolher essa pessoa, garanta que ela tenha a visão e a habilidade para desenvolver e manter uma arquitetura, e que ela tenha a influência e autoridade para ajudar outros desenvolvedores a seguir a arquitetura. Essa pessoa deve ser um desenvolvedor experiente, de confiança da gerência e respeitado por seus colegas.
Crie uma cultura em que todos os desenvolvedores se apropriem da arquitetura. Todos os desenvolvedores precisam estar envolvidos no processo de desenvolvimento e manutenção da integridade da arquitetura.
Desenvolva um ambiente em que as decisões arquitetônicas sejam facilmente comunicadas. Incentive as pessoas a falar sobre design e arquitetura - não apenas no contexto do projeto atual, mas em geral também.
As melhores práticas de codificação facilitam a visualização da arquitetura - leve tempo para refatorar, comentar o código, desenvolver testes de unidade etc. Coisas como convenções de nomenclatura e práticas de codificação limpas podem ajudar muito na comunicação da arquitetura, como uma equipe que você precisa reserve um tempo para desenvolver e seguir seus próprios padrões.
Garanta que toda a documentação necessária seja clara, concisa, atualizada e acessível. Torne públicos os diagramas de arquitetura de alto e baixo nível (fixá-los na parede pode ajudar) e publicamente de manutenção.
Finalmente (como perfeccionista natural), preciso reconhecer que a integridade da arquitetura é uma aspiração digna, mas que pode haver coisas mais importantes - como formar uma equipe que possa trabalhar bem em conjunto e realmente enviar um produto em funcionamento.
fonte
A maneira como resolvo esse problema é cortá-lo na raiz:
Minha explicação será usar termos do Microsoft / .NET , mas será aplicável a qualquer plataforma / caixa de ferramentas:
fonte
Limpe o código apodrecido refatorando, enquanto escreve testes de unidade. Pague (isso) a dívida de projeto em todo o código que você tocar, sempre que:
Acelere bastante seu ciclo de desenvolvimento de teste primeiro:
Refatorar o código para usar baixo acoplamento (de unidades altamente coesas internamente):
O crescimento orgânico é bom; um grande projeto inicial é ruim.
Tenha um líder que tenha conhecimento sobre o design atual. Caso contrário, leia o código do projeto até ter conhecimento.
Leia livros de refatoração.
fonte
Resposta simples: você não pode .
É por isso que você deve procurar escrever software pequeno e simples . Não é fácil.
Isso só é possível se você pensar o suficiente sobre o seu problema aparentemente complexo para defini-lo da maneira mais simples e concisa possível.
A solução para problemas realmente grandes e complexos ainda pode ser resolvida com base em módulos pequenos e simples.
Em outras palavras, como outros apontaram, a simplicidade e o acoplamento flexível são os principais ingredientes.
Se isso não for possível ou possível, você provavelmente está pesquisando (problemas complexos sem soluções simples conhecidas ou nenhuma solução conhecida). Não espere que a pesquisa produza diretamente produtos de manutenção, não é para isso que serve a pesquisa.
fonte
Eu trabalho em uma base de código para um produto que está em desenvolvimento contínuo desde 1999, então, como você pode imaginar, já é bastante complexo. A maior fonte de hackers em nossa base de código é das inúmeras vezes em que tivemos que portá-la do ASP Classic para o ASP.NET , do ADO ao ADO.NET, de postbacks ao Ajax , alternando bibliotecas de interface do usuário, padrões de codificação etc.
Em suma, fizemos um trabalho razoável para manter a base de código sustentável. As principais coisas que fizemos e que contribuíram para isso são:
1) Refatoração constante - Se você precisar tocar em um pedaço de código hacky ou difícil de entender, espera-se que dedique um tempo extra para limpá-lo e terá a margem de manobra prevista para fazê-lo. Os testes de unidade tornam isso muito menos assustador, porque você pode testar contra regressões mais facilmente.
2) Mantenha um ambiente de desenvolvimento limpo - esteja atento sobre a exclusão de código que não é mais usado e não deixe cópias de backup / cópias de trabalho / código experimental no diretório do projeto.
3) Padrões de codificação consistentes para a vida útil do projeto - Vamos ser sinceros, nossos pontos de vista sobre os padrões de codificação evoluem com o tempo. Sugiro manter o padrão de codificação iniciado por toda a vida útil de um projeto, a menos que você tenha tempo para voltar e adaptar todo o código para estar em conformidade com o novo padrão. É ótimo que você tenha superado a notação húngara agora, mas aplique essa lição a novos projetos e não apenas mude o meio do caminho nesse novo projeto.
fonte
Desde que você marcou a pergunta com gerenciamento de projetos, tentei adicionar alguns pontos que não são de código :)
Planeje a rotatividade - suponha que toda a equipe de desenvolvimento tenha desaparecido quando chegar à fase de manutenção - nenhum desenvolvedor que se preze quer ficar preso mantendo o sistema para sempre. Comece a preparar os materiais de entrega assim que tiver tempo.
Consistência / uniformidade não pode ser estressada o suficiente. Isso desencorajará uma cultura de 'vá sozinho' e incentivará os novos desenvolvedores a perguntar, se eles estiverem em dúvida.
Mantenha o mainstream - tecnologias usadas, padrões e padrões de design - porque um novo desenvolvedor da equipe (em qualquer nível) terá mais chances de começar a funcionar rapidamente.
Documentação - especialmente arquitetura - por que as decisões foram tomadas e os padrões de codificação. Além disso, mantenha referências / notas / roteiros para documentar o domínio comercial - você ficaria surpreso com o quão difícil é para os negócios corporativos explicar o que eles fazem com um desenvolvedor sem experiência no domínio.
Estabeleça as regras claramente - não apenas para sua equipe de desenvolvimento atual, mas pense nos futuros desenvolvedores de manutenção. Se isso significa colocar um hiperlink para a documentação padrão relevante de design e codificação em todas as páginas, que assim seja.
Verifique se a arquitetura e, especialmente, as camadas de código são claramente demarcadas e separadas - isso potencialmente permitirá a substituição de camadas de código à medida que novas tecnologias surgirem, por exemplo, substituir uma interface do usuário de formulários da Web por uma interface de usuário do jQuery HTML5 etc. compre um ano mais ou menos de longevidade.
fonte
Uma propriedade do código altamente sustentável é a pureza da função .
Pureza significa que as funções devem retornar o mesmo resultado para os mesmos argumentos. Ou seja, eles não devem depender dos efeitos colaterais de outras funções. Além disso, é útil se eles próprios não tiverem efeitos colaterais.
É mais fácil testemunhar essa propriedade do que as propriedades de acoplamento / coesão. Você não precisa se esforçar para alcançá-lo, e eu pessoalmente o considero mais valioso.
Quando sua função é pura, seu tipo é uma documentação muito boa por si só. Além disso, escrever e ler documentação em termos de argumentos / valor de retorno é muito mais fácil do que mencionar algum estado global (possivelmente acessado por outros threads O_O).
Como um exemplo do uso extensivo de pureza para ajudar na manutenção, você pode ver o GHC . Trata-se de um grande projeto com cerca de 20 anos em que grandes refatorações estão sendo feitas e novos recursos importantes ainda estão sendo introduzidos.
Por fim, não gosto muito do ponto "Mantenha as coisas simples". Você não pode manter seu programa simples quando estiver modelando coisas complexas. Tente criar um compilador simples e seu código gerado provavelmente acabará lento. Claro, você pode (e deve) simplificar as funções individuais, mas o programa inteiro não será simples como resultado.
fonte
Além das outras respostas, eu recomendaria camadas. Não são muitos, mas o suficiente para separar diferentes tipos de código.
Usamos um modelo de API interno para a maioria dos aplicativos. Há uma API interna que se conecta ao banco de dados. Em seguida, uma camada de interface do usuário . Pessoas diferentes podem trabalhar em cada nível sem interromper ou quebrar outras partes dos aplicativos.
Outra abordagem é fazer com que todos leiam comp.risks e The Daily WTF para que aprendam as consequências de um design ruim e de uma programação ruim, e eles terão medo de ver seu próprio código publicado no The Daily WTF .
fonte
Como muitas dessas respostas parecem se concentrar em equipes grandes, mesmo desde o início, vou colocar minha opinião como parte de uma equipe de desenvolvimento de dois homens (três se você incluir o designer) para uma startup.
Obviamente, projetos e soluções simples são os melhores, mas quando você tem o cara que literalmente paga seu salário pelo pescoço, não tem tempo para pensar na solução mais elegante, simples e sustentável. Com isso em mente, meu primeiro grande ponto é:
Documentação Não comentários, o código deve ser principalmente auto-documentado, mas coisas como documentos de design, hierarquias e dependências de classes, paradigmas arquitetônicos etc. Qualquer coisa que ajude um programador novo ou mesmo existente a entender a base de código. Além disso, documentar aquelas pseudo-bibliotecas ímpares que aparecem eventualmente, como "adicionar esta classe a um elemento para essa funcionalidade" pode ajudar, pois também impede que as pessoas reescrevam a funcionalidade.
No entanto, mesmo se você tiver um limite de tempo severo, acho que outra coisa boa a ter em mente é:
Evite hacks e correções rápidas. A menos que a solução rápida seja a solução real, é sempre melhor descobrir o problema subjacente para alguma coisa e, em seguida, corrigir isso. A menos que você tenha literalmente um cenário "faça isso funcionar nos próximos 2 minutos ou esteja demitido", fazer a correção agora é uma idéia melhor, porque você não corrigirá o código mais tarde, apenas passe para a próxima tarefa que você tem.
E minha dica favorita pessoal é mais uma citação, embora não me lembre da fonte:
"Codifique como se a pessoa que vem atrás de você é um psicopata homicida que sabe onde você mora"
fonte
/** Gets the available times of a clinic practitioner on a specific date. **/
or/** Represents a clinic practitioner. **/
.Um princípio que não foi mencionado, mas que considero importante, é o princípio aberto / fechado .
Você não deve modificar o código que foi desenvolvido e testado: qualquer parte desse código está selada. Em vez disso, estenda as classes existentes por meio de subclasses ou use-as para escrever wrappers, classes decoradoras ou usar qualquer padrão que achar adequado. Mas não altere o código de trabalho .
Apenas meus 2 centavos.
fonte
Seja um olheiro . Sempre deixe o código mais limpo do que o encontrado.
Corrija as janelas quebradas . Todos esses comentários "mudam na versão 2.0" quando você está na versão 3.0.
Quando houver grandes invasões, projete uma solução melhor como equipe e faça-o. Se você não pode consertar o hack como uma equipe, não entende o sistema suficientemente bem. "Peça ajuda a um adulto." As pessoas mais velhas podem ter visto isso antes. Tente desenhar ou extrair um diagrama do sistema. Tente desenhar ou extrair os casos de uso particularmente invasivos como diagramas de interação. Isso não corrige, mas pelo menos você pode vê-lo.
Que suposições não são mais verdadeiras que impulsionaram o design em uma direção específica? Pode haver uma pequena refatoração escondida atrás de um pouco dessa bagunça.
Se você explicar como o sistema funciona (mesmo apenas um caso de uso) e tiver que se desculpar repetidamente de um subsistema, esse é o problema. O que o comportamento tornaria o resto do sistema mais simples (não importa o quão difícil pareça implementar em comparação com o que existe). O subsistema clássico a ser reescrito é aquele que polui todos os outros subsistemas com sua semântica e implementação operacionais. "Ah, você precisa alterar os valores antes de alimentá-los no subsistema froo e descompactá-los novamente à medida que obtém a saída do froo. Talvez todos os valores devam ser alterados quando lidos pelo usuário e armazenamento, e o resto do sistema está errado? Isso fica mais emocionante quando existem duas ou mais especificações diferentes.
Passe uma semana como uma equipe removendo avisos para que problemas reais sejam visíveis.
Reformate todo o código para o padrão de codificação.
Verifique se o seu sistema de controle de versão está vinculado ao seu rastreador de erros. Isso significa que mudanças futuras são agradáveis e responsáveis, e você pode descobrir POR QUE.
Faça alguma arqueologia. Encontre os documentos de design originais e revise-os. Eles podem estar naquele PC antigo no canto do escritório, no espaço abandonado ou no arquivo que ninguém abre.
Republicar os documentos de design em um wiki. Isso ajuda a institucionalizar o conhecimento.
Escreva procedimentos do tipo lista de verificação para lançamentos e compilações. Isso impede as pessoas de pensarem, para que possam se concentrar na solução de problemas. Automatize compilações sempre que possível.
Experimente a integração contínua . Quanto mais cedo você obtiver uma compilação com falha, menos tempo o projeto poderá gastar fora dos trilhos.
Se o líder da sua equipe não faz essas coisas, isso é ruim para a empresa.
Tente garantir que todo o novo código obtenha testes de unidade adequados com cobertura medida. Portanto, o problema não pode ficar muito pior.
Tente testar alguns dos bits antigos que não foram testados. Isso ajuda a reduzir o medo da mudança.
Automatize seu teste de integração e regressão, se puder. Pelo menos tenha uma lista de verificação. Os pilotos são inteligentes, recebem lotes pagos e usam listas de verificação. Eles também estragam muito raramente.
fonte
Leia e, em seguida, releia o Code Complete de Steve McConnell. É como uma bíblia de boa escrita de software, desde o design inicial do projeto até uma única linha de código e tudo mais. O que mais gosto sobre isso é o backup de décadas de dados sólidos; não é apenas o próximo melhor estilo de codificação.
fonte
Eu vim para chamar isso de "Efeito Winchester Mystery House". Como a casa, começou bastante simples, mas, ao longo dos anos, muitos trabalhadores diferentes adicionaram tantos recursos estranhos sem um plano geral que ninguém mais o entende mais. Por que essa escada não leva a lugar nenhum e por que essa porta se abre apenas de um jeito? Quem sabe?
A maneira de limitar esse efeito é começar com um bom design feito de forma flexível o suficiente para lidar com a expansão. Várias sugestões já foram oferecidas sobre isso.
Porém, muitas vezes você aceita um trabalho onde o dano já foi causado e é tarde demais para um bom design sem executar uma reformulação e reescrita cara e potencialmente arriscada. Nessas situações, é melhor tentar encontrar maneiras de limitar o caos enquanto o adota em algum grau. Pode incomodar as sensibilidades de seu projeto de que tudo tem que passar por uma classe enorme e feia de 'gerenciadores' ou a camada de acesso a dados está fortemente acoplada à interface do usuário, mas aprenda a lidar com isso. Codifique defensivamente dentro dessa estrutura e tente esperar o inesperado quando aparecerem os 'fantasmas' dos programadores.
fonte
A refatoração de código e o teste de unidade estão perfeitamente corretos. Mas, como esse projeto de longa duração está sendo invadido por hackers, isso significa que a gerência não está se esforçando para limpar a podridão. É necessário que a equipe introduza hacks, porque alguém não está alocando recursos suficientes para treinar pessoas e analisar o problema / solicitação.
Manter um projeto de longo prazo é tanto uma responsabilidade do gerente de projeto quanto um desenvolvedor individual.
As pessoas não introduzem hacks porque gostam; eles são forçados pelas circunstâncias.
fonte
Eu só quero colocar uma questão não técnica e uma abordagem (talvez) pragmática.
Se o seu gerente não se importa com a qualidade técnica (código gerenciável, arquitetura simples, infraestrutura confiável e assim por diante), fica difícil melhorar o projeto. Nesse caso, é necessário educar o gerente e convencê-lo a "investir" os esforços na manutenção e no endividamento técnico .
Se você sonha com a qualidade do código encontrada nesses livros, também precisa de um chefe preocupado com isso.
Ou se você quiser domesticar um "projeto Frankenstein", estas são as minhas dicas:
Na minha experiência, a programação é entrópica e não emergente (pelo menos no paradigma estruturado em imperativos populares). Quando as pessoas escrevem código para "simplesmente trabalhar", a tendência é perder a organização. Agora, a organização do código requer tempo, às vezes muito mais do que fazê-lo funcionar.
Além da implementação de recursos e correções de bugs, reserve um tempo para a limpeza do código.
fonte
Fiquei surpreso ao descobrir que nenhuma das numerosas respostas destacava o óbvio: faça o software consistir em inúmeras bibliotecas pequenas e independentes. Com muitas bibliotecas pequenas, você pode construir um software grande e complexo. Se os requisitos forem alterados, você não precisará descartar toda a base de códigos ou investigar como modificar uma grande base de códigos de buzinas para fazer algo diferente do que está fazendo atualmente. Você apenas decide quais dessas bibliotecas ainda são relevantes após a alteração dos requisitos e como combiná-las para obter a nova funcionalidade.
Use qualquer técnica de programação nessas bibliotecas que facilite o uso da biblioteca. Observe que, por exemplo, qualquer ponteiro de função que suporte a linguagem não orientada a objetos está suportando realmente programação orientada a objetos (OOP). Então, por exemplo, em C, você pode fazer OOP.
Você pode até considerar compartilhar essas bibliotecas pequenas e independentes entre muitos projetos (os submódulos git são seus amigos).
Escusado será dizer que cada pequena biblioteca independente deve ser testada em unidade. Se uma biblioteca específica não pode ser testada por unidade, você está fazendo algo errado.
Se você usa C ou C ++ e não gosta da idéia de ter muitos arquivos .so pequenos, é possível vincular todas as bibliotecas em um arquivo .so maior ou, alternativamente, pode vincular estática. O mesmo vale para Java, basta alterar .so para .jar.
fonte
Simples: reduza os custos de manutenção da maior parte do seu código para zero até que você tenha um número sustentável de peças móveis. O código que nunca precisa ser alterado não incorre em custos de manutenção. Eu recomendo que o objetivo de tornar o código realmente tenha custo zero de manutenção, não tentando reduzir o custo em muitas iterações de refatoração pequenas e exigentes. Faça com que o custo seja zero imediatamente.
Ok, é verdade que é muito, muito mais difícil do que parece. Mas não é difícil começar. Você pode pegar uma parte da base de código, testá-la, construir uma interface agradável sobre ela se o design da interface estiver uma bagunça e começar a aumentar as partes da base de código que são confiáveis, estáveis (como na falta de motivos para mudar), enquanto simultaneamente encolhendo as partes que não são confiáveis e instáveis. As bases de código que parecem um pesadelo para manter geralmente não distinguem as partes móveis que precisam ser alteradas das partes que não o fazem, pois tudo é considerado não confiável e propenso a mudanças.
Na verdade, recomendo separar a organização da sua base de código em partes "estáveis" e "instáveis", com as partes estáveis sendo uma enorme PITA para reconstruir e mudar (o que é uma coisa boa, pois elas não precisam alterados e reconstruídos se realmente pertencerem à seção "estável").
Não é do tamanho de uma base de código que dificulta a manutenção. É o tamanho da base de código que precisa ser mantida. Eu dependo de milhões de linhas de código sempre que, digamos, uso a API do sistema operacional. Mas isso não contribui para os custos de manutenção do meu produto, pois não preciso manter o código fonte do sistema operacional. Eu apenas uso o código e funciona. O código que eu apenas uso e nunca preciso manter não incorre em custos de manutenção do meu lado.
fonte