Para lhe dar um pouco de conhecimento: eu trabalho para uma empresa com aproximadamente doze desenvolvedores do Ruby on Rails (+/- estagiários). Trabalho remoto é comum. Nosso produto é composto de duas partes: um núcleo bastante gordo e fino para grandes projetos de clientes construídos sobre ele. Projetos de clientes geralmente expandem o núcleo. A substituição dos principais recursos não ocorre. Devo acrescentar que o núcleo tem algumas partes ruins que precisam urgentemente de refatorações. Existem especificações, mas principalmente para os projetos dos clientes. A pior parte do núcleo não foi testada (não como deveria ser ...).
Os desenvolvedores são divididos em duas equipes, trabalhando com um ou dois pedidos para cada sprint. Normalmente, um projeto de cliente está estritamente associado a uma das equipes e OPs.
Agora, nosso problema: com bastante frequência, quebramos as coisas um do outro. Alguém da equipe A expande ou refatora o recurso principal Y, causando erros inesperados em um dos projetos de clientes da equipe B. Principalmente, as mudanças não são anunciadas pelas equipes, portanto os bugs atingem quase sempre inesperados. A equipe B, incluindo o OP, considerou o recurso Y estável e não o testou antes da liberação, sem saber das alterações.
Como se livrar desses problemas? Que tipo de 'técnica de anúncio' você pode me recomendar?
Respostas:
Eu recomendaria ler Working Effective with Legacy Code, de Michael C. Feathers . Explica que você realmente precisa de testes automatizados, como pode adicioná-los facilmente, se ainda não os tiver, e que "código cheira" a refatorar de que maneira.
Além disso, outro problema central na sua situação parece ser a falta de comunicação entre as duas equipes. Qual o tamanho dessas equipes? Eles estão trabalhando em diferentes atrasos?
Quase sempre é uma prática ruim dividir equipes de acordo com sua arquitetura. Por exemplo, uma equipe principal e uma equipe não central. Em vez disso, eu criaria equipes no domínio funcional, mas com vários componentes.
fonte
Este é o problema. A refatoração eficiente depende muito do conjunto de testes automatizados. Se você não os tiver, os problemas que você está descrevendo começam a aparecer. Isso é especialmente importante se você usar uma linguagem dinâmica como Ruby, onde não há compilador para detectar erros básicos relacionados à passagem de parâmetros para métodos.
fonte
As respostas anteriores que apontam para melhores testes de unidade são boas, mas acho que pode haver questões mais fundamentais a serem abordadas. Você precisa de interfaces claras para acessar o código principal a partir do código dos projetos do cliente. Dessa forma, se você refatorar o código principal sem alterar o comportamento conforme observado nas interfaces , o código da outra equipe não será quebrado. Isso tornará muito mais fácil saber o que pode ser refatorado "com segurança" e o que precisa de uma reformulação, possivelmente quebrando a interface.
fonte
Outras respostas destacaram pontos importantes (mais testes de unidade, equipes de recursos, interfaces limpas aos componentes principais), mas há um ponto que acho que falta, que é o controle de versão.
Se você congelar o comportamento do seu núcleo executando uma liberação 1 e a colocar em um sistema de gerenciamento de artefatos privado 2 , qualquer projeto do cliente poderá declarar sua dependência da versão principal X e não será interrompido na próxima liberação X + 1 .
A "política de anúncio" se reduz a um arquivo ALTERAÇÕES a cada versão ou a uma reunião de equipe para anunciar todos os recursos de cada nova versão principal.
Além disso, acho que você precisa definir melhor o que é "núcleo" e qual subconjunto é "chave". Você parece (corretamente) evitar fazer muitas alterações nos "componentes principais", mas permite alterações frequentes no "núcleo". Para confiar em algo, você precisa mantê-lo estável; se algo não estiver estável, não o chame de núcleo. Talvez eu possa sugerir chamá-lo de componentes "auxiliares"?
EDIT : Se você seguir as convenções no sistema de versão semântica , qualquer alteração incompatível na API do núcleo deverá ser marcada por uma alteração de versão principal . Ou seja, quando você altera o comportamento do núcleo existente anteriormente ou remove algo, não apenas adiciona algo novo. Com essa convenção, os desenvolvedores sabem que atualizar da versão '1.1' para '1.2' é seguro, mas passar de '1.X' para '2.0' é arriscado e deve ser cuidadosamente revisado.
1: Eu acho que isso é chamado de gema, no mundo do Ruby
2: O equivalente ao Nexus em Java ou PyPI em Python
fonte
Como outras pessoas disseram, um bom conjunto de testes de unidade não resolve o seu problema: você terá problemas ao mesclar as alterações, mesmo que cada conjunto de testes da equipe seja aprovado.
Mesmo para TDD. Não vejo como isso pode resolver isso.
Sua solução não é técnica. Você precisa definir claramente os limites do "núcleo" e atribuir um papel de "cão de guarda" a alguém, seja ele o desenvolvedor ou arquiteto principal. Quaisquer alterações no núcleo devem passar por esse cão de guarda. Ele é responsável por garantir que todos os resultados de todas as equipes sejam mesclados sem muitos danos colaterais.
fonte
Como uma correção a longo prazo, você também precisa de uma comunicação melhor e mais oportuna entre as equipes. Cada uma das equipes que alguma vez utilizará, por exemplo, o recurso principal Y, precisa estar envolvida na criação dos casos de teste planejados para o recurso. Esse planejamento, por si só, destacará os diferentes casos de uso inerentes ao recurso Y entre as duas equipes. Depois que o recurso deve funcionar, e os casos de teste são implementados e acordados, é necessária uma alteração adicional no seu esquema de implementação. A equipe que está liberando o recurso é necessária para executar o testcase, não a equipe que está prestes a usá-lo. A tarefa, se houver, que deve causar colisões, é a adição de um novo caso de teste de qualquer uma das equipes. Quando um membro da equipe pensa em um novo aspecto do recurso que não foi testado, eles devem ter a liberdade de adicionar uma caixa de teste que eles verificaram passando em sua própria caixa de proteção. Dessa maneira, as únicas colisões que ocorrerão serão no nível de intenção e devem ser definidas antes que o recurso refatorado seja liberado na natureza.
fonte
Embora todo sistema precise de conjuntos de testes eficazes (o que significa, entre outras coisas, automação) e, embora esses testes, se usados com eficácia, detectem esses conflitos mais cedo do que são agora, isso não soluciona os problemas subjacentes.
A questão revela pelo menos dois problemas subjacentes: a prática de modificar o "núcleo" para satisfazer os requisitos de clientes individuais e a falha das equipes em se comunicar e coordenar sua intenção de fazer alterações. Nenhuma dessas causas é raiz e você precisará entender por que isso está sendo feito antes que você possa corrigi-lo.
Uma das primeiras coisas a serem determinadas é se os desenvolvedores e os gerentes percebem que há um problema aqui. Se pelo menos alguns o fazem, então você precisa descobrir por que eles acham que não podem fazer nada a respeito ou optam por não fazer. Para aqueles que não o fazem, tente aumentar a capacidade deles de prever como suas ações atuais podem criar problemas futuros ou substituí-los por pessoas que possam. Até que você tenha uma força de trabalho ciente de como as coisas estão dando errado, é improvável que você consiga resolver o problema (e talvez nem mesmo assim, pelo menos a curto prazo).
Pode ser difícil analisar o problema em termos abstratos, pelo menos inicialmente, portanto, concentre-se em um incidente específico que resultou em um problema e tente determinar como isso aconteceu. Como é provável que as pessoas envolvidas sejam defensivas, você precisará estar atento a justificativas egoístas e post-hoc para descobrir o que realmente está acontecendo.
Hesito em mencionar uma possibilidade, porque é muito improvável: os requisitos dos clientes são tão díspares que não há uma comunalidade suficiente para justificar o código principal compartilhado. Nesse caso, você realmente tem vários produtos separados e deve gerenciá-los como tal, e não criar um acoplamento artificial entre eles.
fonte
Todos sabemos que os testes de unidade são o caminho a percorrer. Mas também sabemos que é realista ajustá-los de maneira realista a um núcleo.
Uma técnica específica que pode ser útil para você ao estender a funcionalidade é tentar temporariamente e localmente verificar se a funcionalidade existente não foi alterada. Isso pode ser feito assim:
Pseudo-código original:
Código de teste temporário no local:
Execute esta versão através dos testes em nível de sistema existentes. Se estiver tudo bem, você sabe que não quebrou as coisas e pode remover o código antigo. Observe que, ao verificar a correspondência de resultados antigos e novos, você também pode adicionar código para analisar diferenças para capturar casos que você sabe que devem ser diferentes devido a uma alteração pretendida, como uma correção de bug.
fonte
"Principalmente, as mudanças não são anunciadas pelas equipes, então os bugs atingem quase sempre inesperados"
Problema de comunicação alguém? Que tal (além do que todo mundo já apontou, que você deve fazer testes rigorosos), para garantir uma comunicação adequada? Que as pessoas tenham consciência de que a interface para a qual estão escrevendo mudará no próximo lançamento e quais serão essas mudanças?
E conceda a eles acesso a pelo menos uma interface fictícia (com implementação vazia) o mais rápido possível durante o desenvolvimento para que eles possam começar a escrever seu próprio código.
Sem tudo isso, os testes de unidade não farão muito, exceto apontar durante os estágios finais que há algo fora de controle entre partes do sistema. Você quer saber disso, mas quer saber cedo, muito cedo, e fazer com que as equipes conversem entre si, coordenem esforços e tenham acesso frequente ao trabalho que a outra equipe está realizando (cometer regularmente, não uma tarefa maciça confirmar após várias semanas ou meses, 1-2 dias antes da entrega).
Seu bug NÃO está no código, certamente não no código da outra equipe que não sabia que você estava brincando com a interface contra a qual eles estão escrevendo. Seu bug está no seu processo de desenvolvimento, na falta de comunicação e colaboração entre as pessoas. Só porque você está sentado em salas diferentes não significa que você deve se isolar dos outros caras.
fonte
Principalmente, você tem um problema de comunicação (provavelmente também vinculado a um problema de formação de equipe ), então acho que uma solução para o seu caso deve ser focada em ... bem, comunicação, em vez de técnicas de desenvolvimento.
Eu tenho como certo que não é possível congelar ou bifurcar o módulo principal ao iniciar um projeto do cliente (caso contrário, basta integrar nos agendamentos da sua empresa alguns projetos não relacionados ao cliente que visam atualizar o módulo principal).
Portanto, ficamos com a questão de tentar melhorar a comunicação entre as equipes. Isso pode ser tratado de duas maneiras:
Você pode encontrar mais informações sobre o IC como um processo de comunicação aqui .
Finalmente, você ainda tem um problema com a falta de trabalho em equipe no nível da empresa. Eu não sou um grande fã de eventos de formação de equipes, mas isso parece ser um caso em que eles seriam úteis. Você tem reuniões regulares para todo o desenvolvedor? Você pode convidar pessoas de outras equipes para as retrospectivas do seu projeto? Ou talvez tomar alguma cerveja na sexta à noite?
fonte