Se você continua atirando no próprio pé, conserte a arma
- Em equipes, muitas vezes há partes do sistema em que erros acontecem com frequência, mas pouca gente pensa em como reduzir esses erros
- Nesses casos, o importante é melhorar o sistema para diminuir a chance de erro
- Experiência:
- Ao usar CoreData no desenvolvimento iOS, atualizações de UI só podem ser feitas na thread principal
- O callback de assinatura acontecia tanto na thread principal quanto em threads de background, o que frequentemente causava problemas
- Os membros antigos da equipe sabiam disso e lidavam bem com a situação, mas isso aparecia com frequência nas revisões de colegas novos
- Quando algum erro acontecia, a solução vinha sendo olhar o relatório de crash e adicionar
DispatchQueue.main.async
- Para resolver isso, a camada de assinatura foi atualizada para chamar os assinantes na thread principal. Levou só 10 minutos.
- Isso eliminou toda uma classe de crashes e também uma pequena carga mental
- Qualquer pessoa teria percebido que era um problema óbvio se parasse alguns minutos para pensar
- Mas esse tipo de problema estranhamente dura muito tempo porque nunca existe um momento natural para corrigi-lo
- Ou seja, quando você passa muito tempo em uma equipe, esses problemas tendem a virar pano de fundo
- É preciso mudar a mentalidade
- De vez em quando, é bom lembrar a si mesmo que você pode tornar a vida da equipe — e a sua — mais fácil quando esse tipo de problema aparece
Equilibrar qualidade e velocidade
- Sempre existe um trade-off entre velocidade de implementação e confiança na correção
- Vale se perguntar o quanto é aceitável lançar bugs na situação atual
- Se a resposta a isso não afeta sua forma de trabalhar, então você está sendo rígido demais
- No meu primeiro emprego, trabalhando em projetos de processamento de dados, havia um bom sistema para reprocessar dados retroativamente
- O impacto de lançar bugs era muito pequeno e, nesse ambiente, dava para confiar mais nos guardrails e andar mais rápido
- 100% de cobertura de testes ou processos extensos de QA só atrasariam o desenvolvimento
- No segundo emprego, eu lidava com um produto usado por dezenas de milhões de pessoas, com dados financeiros de alto valor e informações de identificação pessoal, então bugs eram críticos
- Mesmo um bug pequeno exigia análise pós-incidente
- As funcionalidades eram lançadas muito devagar, mas acho que naquele ano não colocamos nenhum bug em produção
- Na maioria dos casos, você não está numa situação como a da segunda empresa
- Quando bugs não são catastróficos (como em 99% dos web apps), é melhor lançar rápido e corrigir rápido
- Isso permite avançar mais do que gastar tempo tentando lançar algo perfeito desde o início
O tempo gasto afiando o serrote quase sempre vale a pena
- É importante dominar bem suas ferramentas
- Você deve conseguir escrever código rápido, conhecer os principais atalhos e ter intimidade com o sistema operacional e com o shell
- Você vai renomear coisas, ir para definições de tipo, encontrar referências etc. o tempo todo
- Deve conhecer os principais atalhos do editor e digitar com rapidez e confiança
- Também é importante usar bem as ferramentas de desenvolvedor do navegador
- Escolher boas ferramentas e usá-las com fluência é uma grande vantagem
- Um dos maiores sinais positivos em engenheiros novos é justamente o interesse por escolher bem as ferramentas e usá-las com habilidade
Se você não consegue explicar com simplicidade por que algo é difícil, provavelmente é complexidade acidental — e vale a pena resolver esse problema
- Um dos gestores de quem mais gostei tinha o hábito de continuar pressionando sempre que eu dizia que algo era difícil de implementar
- Muitas vezes a resposta dele era algo como: "no fim das contas, não é só enviar X ao fazer Y?" ou "isso não é parecido com o Z que fizemos alguns meses atrás?"
- Eram objeções em um nível muito alto, e não no nível real das funções e classes que eu estava tentando explicar
- A visão comum é que esse tipo de simplificação por parte de um gestor é só irritante
- Mas, surpreendentemente, em uma porcentagem alta dos casos, eu percebia que boa parte da complexidade que eu estava descrevendo era complexidade acidental
- E, na prática, ao resolver isso primeiro, o problema realmente podia se tornar tão trivial quanto o gestor sugeria
- Esse tipo de abordagem também tende a facilitar mudanças futuras
Tente resolver bugs um nível mais fundo
- Em vez de corrigir bugs de forma superficial, é importante encontrar e corrigir a causa raiz
- Quando há um componente React no dashboard que trabalha com um objeto
User obtido do estado do usuário atualmente logado
- Surge no Sentry um relatório de bug dizendo que
user era null durante a renderização
- Você pode rapidamente adicionar
if (!user) return null
- Ou investigar um pouco mais e descobrir que a função de logout faz duas atualizações de estado separadas
- A primeira define o usuário como
null, e a segunda redireciona para a homepage
- Se você inverter a ordem das duas, nenhum componente terá esse bug de novo
- Porque, dentro do dashboard, o objeto do usuário nunca deveria ser
null
- Se você continuar fazendo o primeiro tipo de correção, tudo vira uma bagunça, mas
se continuar fazendo o segundo tipo, vai acabar com um sistema limpo e com uma compreensão profunda das invariantes
Não subestime o valor de cavar o histórico para investigar bugs
- Eu era bem bom em depurar problemas estranhos usando ferramentas comuns, como
println e o debugger
- Por isso, eu não costumava olhar muito para o git para entender o histórico de um bug, mas em alguns casos isso é extremamente importante
- Recentemente, parecia haver um vazamento contínuo de memória em um servidor, que acabava sendo encerrado por OOM e reiniciado
- Toda causa plausível já tinha sido descartada, e não dava para reproduzir localmente
- Parecia que eu estava jogando dardos de olhos vendados
- Ao olhar o histórico de commits, vi que o problema começou depois da adição de suporte a pagamentos da Play Store
- Eram só algumas requisições HTTP, então eu nunca teria procurado ali em um milhão de anos
- No fim, descobri que havia entrado em um loop infinito para obter access tokens depois que o primeiro expirava
- Cada requisição talvez adicionasse só cerca de 1 kB de memória, mas, ao tentar de novo a cada 10 ms em várias threads, isso acumulava rapidamente
- Normalmente isso teria causado stack overflow, mas como eu estava usando recursão assíncrona em Rust, não houve stack overflow
- Eu nunca teria pensado nisso sozinho, mas ao olhar para um trecho específico de código claramente ligado ao problema, a teoria surgiu de repente
- Não existe uma regra para saber quando fazer esse tipo de abordagem
- Isso depende de intuição; é um tipo diferente de "ué?" em relação a um bug report que dispara esse tipo de investigação
- Com o tempo, dá para desenvolver essa intuição, mas às vezes já basta saber que isso pode ser extremamente valioso
- Quando o problema se encaixar, tente usar
git bisect
- Quando você tem um commit que sabe que está errado e outro que sabe que está bom
Código ruim dá feedback, código perfeito não. Erre para o lado de escrever código ruim
- Escrever código horrível é muito fácil
- Mas também é muito fácil escrever código seguindo absolutamente todas as boas práticas
- Com testes unitários, de integração, fuzz, mutação e tudo mais, a startup fica sem dinheiro antes de terminar
- Grande parte da programação consiste em encontrar equilíbrio
- Se você errar para o lado de escrever código rápido...
- Às vezes vai sofrer com dívida técnica ruim
- Vai aprender que "precisa adicionar ótimos testes para processamento de dados"
- Porque muitas vezes corrigir isso depois é impossível
- Também vai aprender que "precisa pensar muito bem no design das tabelas"
- Porque mudar isso sem downtime pode ser muito difícil
- Se você errar para o lado de escrever código perfeito...
- Não vai receber nenhum feedback
- Tudo vai levar mais tempo, em todos os casos
- Você não vai saber onde está investindo bem o tempo e onde está desperdiçando
- Mecanismos de feedback são essenciais para aprender, mas assim você deixa de tê-los
- Esclarecendo o que significa código "ruim"
- Não significa algo como "usei um loop interno duas vezes porque não lembrava a sintaxe para criar um hashmap"
- Significa coisas como:
- Em vez de reescrever a coleta de dados para tornar impossível representar certos estados, adicionar algumas assertions de invariantes em pontos-chave
- Como o model do servidor é exatamente igual ao DTO que ele vai escrever, simplesmente serializá-lo; se necessário, o DTO pode ser criado depois em vez de escrever todo o boilerplate agora
- Pular a escrita de testes para componentes triviais, em que bugs não causariam grande impacto
Torne o debugging mais fácil
- Ao longo dos anos, aprendi muitos truques pequenos para tornar software mais fácil de depurar
- Se você não se esforçar para facilitar o debugging, vai acabar gastando uma quantidade enorme de tempo para depurar cada issue à medida que o software ficar mais complexo
- Você vai começar a ter medo de fazer mudanças, porque entender alguns bugs novos pode levar uma semana
- Preste atenção em quanto tempo do debugging vai para setup, reprodução e limpeza posterior
- Se isso passar de 50%, vale procurar uma forma de tornar esse processo mais fácil, mesmo que desta vez leve um pouco mais
- Mantidas as demais condições, corrigir bugs deveria ficar mais fácil com o tempo
Ao trabalhar em equipe, faça perguntas sempre
- Existe um espectro entre "tentar descobrir tudo sozinho" e "incomodar colegas com perguntas triviais"
- Acho que a maioria das pessoas no início da carreira pende demais para o primeiro lado
- Sempre haverá por perto alguém que está há mais tempo no codebase, conhece muito melhor a tecnologia X, entende melhor o produto ou simplesmente é mais experiente como engenheiro
- Nos primeiros seis meses em um lugar, é comum desperdiçar mais de uma hora tentando descobrir algo que poderia ser respondido em alguns minutos
- Faça perguntas. A única situação em que perguntar realmente incomoda alguém é quando está óbvio que você mesmo poderia encontrar a resposta em poucos minutos
O ciclo de deploy é extremamente importante. Pense com cuidado em como deployar rápido e com frequência
- Startups têm runway limitado, e projetos têm prazos
- Quando você sai do emprego para trabalhar por conta própria, o dinheiro guardado vai durar só alguns meses
- Idealmente, a velocidade do projeto aumenta de forma composta com o tempo, e você acaba lançando funcionalidades mais rápido do que imaginava ser possível
- Para deployar rápido, muitas coisas são necessárias
- Um sistema que não seja frágil a bugs
- Tempo de resposta rápido entre equipes
- Vontade de cortar 10% de uma nova funcionalidade (a parte que consumiria 50% do tempo de engenharia) e insight para saber identificar essas partes
- Padrões consistentes e reutilizáveis para compor novas telas/funcionalidades/endpoints
- Deploy rápido e fácil
- Processos que não atrasem a velocidade (testes instáveis, CI lento, linters chatos, revisão de PR demorada, JIRA tratado quase como religião etc.)
- E mais um milhão de coisas
- Deployar devagar deveria gerar uma análise pós-incidente tão séria quanto derrubar a produção
- Nossa indústria não funciona assim, mas isso não significa que você não possa, pessoalmente, tratar deploy rápido como sua estrela-guia
6 comentários
"Atirar no próprio pé" = significa algo como cavar a própria cova?
Se um problema surgir por causa de um código defeituoso (uma arma quebrada) e você acabar se prejudicando por isso (dando um tiro no próprio pé), a ideia é que você conserte a arma.
Foi um choque, parece exatamente como se tivessem tirado o que estava na minha cabeça e colocado ali, nossa..
Li com muito gosto!!
Li com atenção.
Não sou desenvolvedor, mas há muitas partes com as quais me identifico.