TransWikia.com

Git fazer comits de código instavel é má prática?

Stack Overflow em Português Asked by Andre on October 3, 2020

Eu estou comçando agora a trabalhar com Git, e eu não sei o que fazer nos seguintes cenarios: Tenho uma alteração muito grande para fazer ou tenho causa de uma classe que possui muitos relacionamentos. Eu preciso deixar o projeto numa situação estavel antes de fazer commits locais? Ou o código testado e estavel só é nescessario para o pull request? Pergunto porque gostaria de manter as versoes do meu proprio trabalho para mim, mesmo que não estavel ainda, mas não sei se isso pode atrapalhar a pessoa que vai validar o merge request.

2 Answers

Há controvérsias sobre isso. Não existe uma forma "certa", existe a certa para você.

Muitos dizem que sim, mas antes de mandar para o repositório remoto tem que fazer uma rebase para simplificar isto. O rebase dá uma "limpada" no histórico.

Tem quem diga que nem precisa se preocupar com o rebase. Então tem que começar fazer e ver se atende sua necessidades e a do time também, que é até mais importante.

De fato se ficar preocupado demais com o commits pode não fazer quando precisa e o Git ficar sem o máximo de sua utilidade. Um dos motivos que o controle de versão foi inventado e muito melhorado com o controle descentralizado é você poder experimentar, ir melhorando e tendo um histórico, e poder voltar ao estado anterior, tudo feito com confiança. Isso incentiva fazer mudanças mais contidas, cuidar de um problema de cada vez, dar mais granularidade ao trabalho.

Correct answer by Maniero on October 3, 2020

Não é sobre boas práticas, mas Kent Beck sugeriu o esquema experimental test && commit || revert.

Não cheguei a experimentar, mas a ideia serve como uma espécie de quick dojo para aumentar expertise e assertividade do código.

Preliminares: explicando o nome

Quem está acostumado com a shell do mundo Unix já deve ter entendido o nome.

A ideia do && e do || é da execução condicional do shell. Esses são operadores de execução condicional de comandos, fazendo curto-circuito booleano.

Então, o que quer dizer test && commit || revert? Simplesmente isto: faça o teste, se for bem sucedido, execute o commit; senão reverta as mudanças.

Comparação com TDD clássico

O TDD clássico prevê que as mudanças em produção são ocasionadas por um erro. Por exemplo, um erro de compilação porque agora precisa existir um novo método Rebimboca para a classe Parafuseta. Em cima desse erro de compilação, se altera o código de produção para que o teste passe.

Uma vantagem do TDD é que todo código novo de produção já vai vir coberto e os casos conhecidos/de fácil testabilidade programática em que ele se aplica já estarão todos cobertos. Então, é um código que garante segurança para uma parte do universo de entradas.

Sim, essa vantagem é criticável, tem outros fatores que entram em cena. Um desses fatores é o dispêndio de recursos em coisas que não possuem valor de comercialização. Não estou no mérito do TDD, o foco aqui é a comparação.

Então, o código TDD meio que faz a seguinte evolução (a cor é se deu sucesso ou falha nos testes, entre parênteses é a ação tomada que fez a evolução chegar naquela cor):

  • verde (código prévio, que pode ser inclusive nenhum código)
  • vermelho (escrita do caso de testes)
  • verde (correção do código de produção que corrija o teste)
  • verde (limpeza de código para remover eventual lixo/sujeira da refatoração)

Em um ambiente clássico de commits atômicos e indivisíveis (essa definição de atomicidade de commits não é tão sólida assim, mas fiquemos com uma ideia informal), a escrita do teste seguido da sua correção seria um átomo, talvez a limpeza entrasse junto já que antes dessa mudança não deveria ter essa sujeira específica.

Uma outra abordagem de atomização de commits seria fazer um commit para o código de teste (um delta de código que existe por si só), então outro para a correção e limpeza.

Na primeira abordagem, você é indicado a fazer um commit grande, depois de um tanto de tempo de codificação. E quanto mais tempo se passa até se fazer o commit, mais difícil será fazer esse commit.

Já na segunda abordagem, você pode levantar um código "instável" para o repositório, já que essa é a intenção do TDD (denunciar instabilidades de executável para que se possa consertá-la).

Já no test && commit || revert, você codificará uma refatoração ou um novo caso de teste junto da nova funcionalidade. Se deu certo, precisa commitar tudo que foi feito, sem exceção. Se houver uma falha, então toda a mudança será descartada. Qual o efeito disso, na prática?

Bem, começa que o repositório sempre estará "verde". Nunca se faz um commit com código instável (afinal, realizar o commit está atrelado ao sucesso do teste). E, também, o programador será instruído por si mesmo a realizar os testes mais precocemente. Quanto mais cedo se testa, menos mudanças foram feitas e, portanto, menor fica o escopo de introdução de instabilidades.

Com isso, também temos que o código terá diversos "save points", onde fica fácil dar um game over e ressurgir próximo do ponto problemático.

Commits frequentes, repositório sempre verde, parece ser tudo flores e belezas, não é?

Eu particularmente acho que o TDD tem uma grande vantagem: a garantia de que o código antigo dava erro para aquele cenário específico e que o novo não dá mais.

Criticismo inicial de Kent Beck

Parafraseando a segunda seção do artigo:

How could you make progress if the tests always have to work? Don’t you make mistakes sometimes? What if you write a bunch of code and it just gets wiped out? Won’t you get frustrated?

Em tradução livre:

Como progredir se os testes sempre precisam funcionar? Você não comete erros às vezes? E se você escreve uma penca de código e puff some tudo? Não seria frustrante?

O interessante é que ele mesmo responde, depois de experimentar esse fluxo:

  1. é possível sim
  2. você fará besteira sim, mas tudo será limpo antes de dar continuidade (evita a "sunk cost fallacy")
  3. se você não quer perder uma penca de código entre dois sinais verdes, então não escreva uma penca de código entre dois sinais verdes
  4. sim, frustra muito, mas a solução achada em seguida é normalmente melhor, mais assertiva e mais incremental

Sobre a relevância à sua pergunta

Tudo depende do fluxo de desenvolvimento adotado. Como o Maniero mesmo falou: há controvérsias.

O test && commit || revert é uma sugestão de fluxo que preza por automatização de testes (se não vira XGH) e pequenas mudanças incrementais, commits devidamente atomizados.

A ideia de mudanças pequenas e incrementais não é nova. Inclusive, se você estivesse trabalhando em equipe e seu código passasse por revisão, quanto menos contexto de mudança for realizado, melhor será para o revisor. Vai corrigir um bug? Então evite tocar naquela classe toda remendada que não tem absolutamente nada a ver com esse bug, deixe para um outro pull request. Apesar de revisão de pull request ser feita considerando todo o delta do pull request, já saber atomizar a nível de commits facilitará a "atomizar" o pull request.

Como esse fluxo de escrita de código exige do desenvolvedor um mínimo sobre testes automatizados, talvez não seja exatamente o melhor dos mundos para se aprender a programar. Como é um fluxo de alta frustrabilidade, pode não ser o melhor para se aprender sobre ferramentas de controle de versão.

Mas, no final, se você conseguir adotar com méritos o test && commit || revert, saiba que sei código sempre estará chuchu, e que você conseguirá facilmente realizar pequenas e incrementeis mudanças (o que é bom para a saúde do código e à psiquê do revisor).

Answered by Jefferson Quesado on October 3, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP