
Desde o início de 2018, ocupo a posição de líder/capítulo/desenvolvedor principal na equipe — chame como quiser, mas a questão é que estou completamente responsável por um dos módulos e por todos os desenvolvedores que trabalham nele. Essa posição me proporciona uma nova perspectiva sobre o processo de desenvolvimento, pois estou envolvido em um número maior de projetos e participo ativamente na tomada de decisões. Recentemente, devido a essas duas circunstâncias, percebi o quanto a profundidade da compreensão influencia o código e o aplicativo.
A ideia que quero expressar é que a qualidade do código (e do produto final) está intimamente ligada ao nível de consciência das pessoas que projetam e escrevem o código sobre o que realmente estão fazendo.
Você pode estar pensando agora: "Obrigado, capitão. Claro, seria bom entender o que se está escrevendo. Caso contrário, poderíamos muito bem contratar um grupo de macacos para que eles batucassem aleatoriamente nas teclas, e assim ficarmos satisfeitos". E você está absolutamente certo. Portanto, aceito como um fato: você entende que ter uma visão geral do que está fazendo é essencial. Isso pode ser chamado de nível zero de compreensão, e não entraremos em detalhes sobre isso. Vamos explorar com mais detalhes o que exatamente precisa ser entendido e como isso afeta as decisões que você toma todos os dias. Se eu soubesse essas coisas antecipadamente, teria poupado muito tempo desperdiçado e código duvidoso.
Embora você não veja uma única linha de código abaixo, ainda assim considero que tudo o que foi dito aqui é muito relevante para a escrita de código de qualidade e expressivo.
O primeiro nível de compreensão: Por que não está funcionando?
Os desenvolvedores geralmente alcançam esse nível nos estágios iniciais de suas carreiras, às vezes até sem qualquer ajuda dos outros — pelo menos, essa é a minha observação. Imagine que você recebeu um relatório de bug: alguma função no aplicativo não está funcionando, é necessário consertá-la. Como você agiria?
O procedimento padrão é o seguinte:
- Encontrar o trecho de código que está causando o problema (como isso é feito é um tema separado, que exploro no meu livro sobre código legado)
- Fazer alterações nesse trecho
- Certificar-se de que o bug foi corrigido e que não surgiram erros regressivos
Agora vamos nos concentrar no segundo ponto — fazer alterações no código. Existem duas abordagens para esse processo. A primeira: entender o que exatamente está acontecendo no código atual, identificar o erro e corrigi-lo. A segunda: avançar no escuro — adicionar, por exemplo, +1 em uma instrução condicional ou laço, ver se essa função começa a funcionar no cenário desejado, tentar mais alguma coisa e assim por diante indefinidamente.
A abordagem correta é a primeira. Como explica em seu livro Code Complete, Steve McConnell (aliás, recomendo muito), sempre que fazemos alguma alteração no código, devemos ser capazes de prever com segurança como isso afetará o aplicativo. Cito isso de memória, mas se a correção do bug não funcionar como você esperava, isso deve acender um alerta; você deve questionar todo o seu plano de ação.
Resumindo, para realizar uma boa correção de bug que não prejudique a qualidade do código, é necessário entender tanto a estrutura do código quanto a fonte do problema específico.
O segundo nível de compreensão: Por que isso funciona?
Esse nível é compreendido de maneira muito menos intuitiva do que o anterior. Eu, ainda sendo um desenvolvedor iniciante, aprendi isso através do meu chefe, e depois, várias vezes, expliquei o conceito para iniciantes.
Desta vez, vamos imaginar que você recebeu dois relatórios de bug ao mesmo tempo: o primeiro diz respeito ao cenário A, o segundo ao cenário B. Em ambos os cenários, algo está errado. Portanto, você começa pelo primeiro bug. Com base nos princípios que estabelecemos para o primeiro nível de compreensão, você se aprofunda no código relacionado ao problema, descobre por que ele faz o aplicativo se comportar dessa maneira no cenário A e faz ajustes razoáveis que produzem exatamente o resultado esperado. Tudo corre bem.
Depois, você passa para o cenário B. Você repete o cenário na tentativa de provocar o erro, mas — surpresa! — agora tudo funciona como deveria. Para confirmar sua suposição, você desfaz as alterações feitas durante o trabalho no erro A, e o bug B retorna. Sua correção resolveu ambos os problemas. Que sorte!
Você não esperava por isso. Você encontrou uma maneira de corrigir o erro no cenário A e não tem ideia de por que isso funcionou para o cenário B. Nesse ponto, é muito tentador concluir que ambas as tarefas foram realizadas com sucesso. Isso faz sentido: o objetivo era corrigir os erros, certo? Mas o trabalho ainda não terminou: você ainda precisa entender por que suas ações corrigiram o erro no cenário B. Por quê? Porque pode ser que ele esteja funcionando com princípios incorretos, e então você precisará buscar uma saída diferente. Aqui estão alguns exemplos dessas situações:
- Uma vez que a solução não foi ajustada especificamente para o erro B, levando em conta todos os fatores, você pode ter quebrado a função C sem saber.
- Não está fora de questão que haja um terceiro bug escondido em algum lugar, relacionado à mesma função, e sua correção de bug pode estar amarrando o funcionamento correto do sistema no cenário B a ele. Agora tudo parece bem, mas um dia esse terceiro bug será notado e corrigido. Então, o erro voltará a surgir no cenário B, e espero que seja apenas lá.
Tudo isso traz caos ao código e em algum momento certamente recairá sobre você — provavelmente no momento mais inoportuno. Você terá que reunir força de vontade para se obrigar a gastar tempo entendendo por que tudo parece estar funcionando, mas vale a pena.
Terceiro nível de entendimento: Por que isso funciona?
Minha recente epifania está relacionada exatamente a esse nível, e provavelmente é ele que me daria mais vantagens se eu tivesse chegado a esse pensamento mais cedo.
Para tornar mais claro, vamos usar um exemplo: seu módulo precisa ser compatível com a função X. Você não está particularmente familiarizado com a função X, mas lhe disseram que, para ser compatível com ela, você deve usar o framework F. Outros módulos que se integram com X funcionam exatamente com ele.
Seu código nunca teve contato com o framework F desde o primeiro dia de sua existência, portanto, implementá-lo não será tão simples. Isso terá consequências sérias para alguns componentes do módulo. No entanto, você se dedica inteiramente ao desenvolvimento: passa semanas escrevendo código, testando, lançando versões beta, recebendo feedback, corrigindo erros regressivos, descobrindo complicações imprevistas, não cumprindo os prazos inicialmente estabelecidos, escrevendo mais código, testando, recebendo feedback, corrigindo erros regressivos — tudo isso para implementar o framework F.
E, em algum momento, você de repente percebe — ou talvez ouça de alguém — que talvez o framework F não lhe garanta compatibilidade com a função X. Pode ser que todo esse tempo e esforço tenham sido aplicados em algo completamente errado.
Algo semelhante aconteceu uma vez durante o trabalho em um projeto pelo qual eu era responsável. Por que isso aconteceu? Porque eu não entendia bem o que era a função X e como ela se relacionava com o framework F. O que eu deveria ter feito? Pedir para a pessoa que define a tarefa de desenvolvimento explicar claramente como o plano de ação proposto levaria ao resultado desejado, em vez de simplesmente repetir o que foi feito para outros módulos ou acreditar que era necessário para o funcionamento da função X.
A experiência desse projeto me ensinou a recusar começar o processo de desenvolvimento até que tenhamos uma compreensão clara do porquê de sermos solicitados a realizar determinadas ações. Recusar diretamente. Quando você recebe uma tarefa, o primeiro impulso é imediatamente se envolver com ela, para não perder tempo à toa. Mas a política de "congelar o projeto até que compreendamos todos os detalhes" pode economizar um tempo exorbitante perdido.
Mesmo que tentem pressioná-lo, forçando-o a começar o trabalho, mesmo que você não entenda a razão — resista. Primeiro, entenda qual é o objetivo da tarefa proposta e decida se este é o caminho certo para atingi-lo. Eu tive que aprender tudo isso na prática — espero que meu exemplo facilite a vida de quem está lendo isso.
Quarto nível de compreensão: ???
Na programação, sempre há algo a aprender e, a meu ver, eu só toquei nas superfícies do entendimento. Que outros níveis de compreensão você descobriu ao longo dos anos trabalhando com código? Que decisões você tomou que impactaram positivamente a qualidade do código e do aplicativo? Quais decisões se mostraram equivocadas e que lições valiosas você aprendeu com elas? Compartilhe suas experiências nos comentários.
Fonte: habr.com
