Malka (Daniel Lemos) / Escrevendo código como um Sênior

Created Sat, 08 Mar 2025 15:00:51 -0300 Modified Thu, 15 May 2025 14:41:15 -0300
1505 Words

Um grupo de Desenvolvedores ao redor de um deles que está explicando algo - Gerado por IA

Olhando para trás e analisando o que aprendi sobre liderança técnica em desenvolvimento de software, um dos pontos que mais se destacou e que foi indicado por muitos dos meus menores é capacidade de produzir código de alta qualidade. Essa habilidade deveria se sobrepor ao mero orgulho pessoal, impactando diretamente na eficiência, na colaboração e na confiança dentro de equipes multidisciplinares (o que nem sempre acontece).

Imagine uma pessoa desenvolvedora enquadrada no nível de júnior enfrentando dificuldades para implementar uma funcionalidade complexa. Ao invés de longas explicações, essa pessoa recorre a um módulo similar, elegantemente escrito e bem documentado por uma pessoa que está no nível sênior. A clareza do código facilita o entendimento, a adaptação e a integração da nova funcionalidade, economizando tempo e recursos. Em um dos meus artigos anteriores, Código Limpo resumido em 9 Tópicos, falo especificamente sobre alguns pontos que podem auxiliar nessa busca para trazer mais clareza de código.

Código limpo e conciso serve como um guia prático, acelerando o aprendizado e reduzindo a necessidade de retrabalho. Isso evita o dispendioso cenário de reescrever código funcional apenas por sua complexidade ou falta de clareza.

Dica para o nível sênior: Priorize a legibilidade e a documentação do código. Utilize convenções de nomenclatura claras, escreva comentários concisos e organize o código em módulos coesos. Lembre-se, código bem documentado, com descrições claras e desacoplado para reutilização, facilita a compreensão por outros programadores, construindo confiança e respeito pelo seu trabalho.

Concluindo o esboço, Rabiscando o miolo, Lapidando o valor

Pessoas desenvolvedoras em nível sênior se distinguem pela capacidade de entregar soluções completas, não apenas funcionais. Em um ambiente de desenvolvimento ágil, onde a pressão por entregas rápidas é constante, essa habilidade se torna ainda mais crucial para a sustentabilidade e o sucesso do projeto. Mesmo que a gente entenda que a forma de aplicar o Agile atualmente não tem colaborado muito para o real sentido do Agile proposto lá atrás!

Ao invés de simplesmente implementar uma nova API e considerá-la “pronta”, aqueles em nível sênior se certificam de que todos os casos de uso foram testados, a documentação está atualizada e o código está otimizado para performance e segurança. Da mesma forma, em tarefas de correção de bugs, essa pessoa desenvolvedora se preocupa em entender a causa raiz do problema e não somente contornar a situação momentânea.

Muitas destas pessoas, sob diversas pressões do dia a dia, tendem a marcar tarefas como concluídas quando estão “quase prontas”, acumulando dívida técnica. É um esforço do nível sênior reconhecer que a transparência sobre o status real da tarefa, mesmo que isso impacte o cronograma, é fundamental para evitar problemas futuros.

Conclusão verdadeira significa testes exaustivos e nem sempre atingindo aqueles 100% de coverage que alguns acreditam ser o estado da arte, cobertura de casos extremos, documentação completa e preferencialmente com exemplos e pontos de atenção, além da aderência a todos os requisitos. Embora essa abordagem possa demandar mais tempo inicialmente, ela evita horas de retrabalho, depuração, refatoração por falta de compreensão e explicações no futuro.

Uma boa pessoa desenvolvedora, que já possui maturidade o suficiente, entende que o trabalho nem sempre é só código e pular tarefas nas colunas no Jira (ou ferramenta similar). Ela incentiva a cultura da conclusão ponta-a-ponta na equipe, mostrando através do exemplo que a qualidade e a sustentabilidade são prioridades, estimulando a escrita de testes unitários e de integração, reforçando a revisão constante para não ter código de teste abandonado e sem sentido. Seja a pessoa guardiã da qualidade, garantindo que o código entregue atenda aos mais altos padrões.

Para maior clareza, aqui está um exemplo simplificado de código; note que não se trata de uma implementação completa:

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
)

// Junior Approach - Incomplete Implementation
func fetchUserDataJunior(userID string) (map[string]interface{}, error) {
	resp, err := http.Get(fmt.Sprintf("/api/users/%s", userID))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var data map[string]interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, err
	}

	return data, nil
}

// Senior Approach - Complete Implementation
func fetchUserDataSenior(userID string) (map[string]interface{}, error) {
	if userID == "" {
		return nil, errors.New("User ID is required")
	}

	resp, err := http.Get(fmt.Sprintf("/api/users/%s", userID))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP error! status: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var data map[string]interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, err
	}

	// Validate expected data structure
	_, existsID := data["id"]
	_, existsName := data["name"]

	if !existsID || !existsName {
		return nil, fmt.Errorf("invalid user data received: %s", data)
	}

	return data, nil
}

func main() {
	// example of junior implementation.
	juniorData, juniorErr := fetchUserDataJunior("123")
	if juniorErr != nil {
		log.Println("Junior Error:", juniorErr)
	} else {
		log.Println("Junior Data:", juniorData)
	}

	// example of senior implementation
	seniorData, seniorErr := fetchUserDataSenior("123")

	if seniorErr != nil {
		log.Println("Senior Error:", seniorErr)
		// error handling and recovery if necessary
	}
	log.Println("Senior Data:", seniorData)

	// testing for error case
	seniorData, seniorErr = fetchUserDataSenior("")
	if seniorErr != nil {
		log.Println("Senior Error (empty ID):", seniorErr)
	}
}

Dicas e Pontos de Atenção que o tempo me ensinou

1. Conheça e use padrões, mas não fique preso a eles.

Padrões de codificação e arquitetura de software são cruciais para a manutenção e escalabilidade de um projeto. No entanto, é importante lembrar que esses padrões devem ser aplicados com flexibilidade e bom senso, sempre visando a entrega de valor como objetivo principal.

Seguir cegamente qualquer padrão ou metodologia, sem entender suas limitações e aplicações, pode levar a um desenvolvimento ineficiente e até mesmo prejudicial. A experiência e o contexto do projeto devem sempre ser levados em consideração na hora de escolher e aplicar qualquer padrão.

Ferramentas de automação, como linters e formatters, podem auxiliar na padronização do código e na aplicação de boas práticas, tornando o processo mais eficiente e permitindo que a equipe foque na entrega de valor.

Resumidamente, o uso inteligente de padrões, aliado à automação e a uma cultura de colaboração, pode levar a um desenvolvimento de software mais eficiente e focado na entrega de valor. Do que adianta criar uma arquitetura gigante fatiada em diversas camadas para implementar uma API com 5 rotas que praticamente busca dados de um banco e de outros serviços, une os dados em um contrato e retorna para um frontend?

2. Refatorar ou Migrar?

Comece simples, expanda e desacople na medida que for se tornando necessário, o planejamento é crucial para esse tipo de estratégia (Tech Spec, Product Spec, etc). Mas em determinados momentos não existem muitos caminhos a não ser migrar para outra arquitetura, refatorar o que já existe ou quem sabe quebrar tudo em serviços menores. Dica de Ouro: muitas vezes as pessoas escolhem caminhos que mais lhe convém e não que mais geram valor, então analise o que melhor se encaixa e traga retorno!

Seniores adotam uma abordagem sofisticada para refatoração, integrando-a ao fluxo de trabalho regular em vez de isolá-la em tarefas separadas. Essa estratégia sutil, mas eficaz, garante a evolução contínua da base de código sem gerar atrito com a gestão. Por exemplo, em vez de criar tickets específicos para refatoração, o desenvolvedor identifica oportunidades de melhoria durante o desenvolvimento de novas funcionalidades ou a correção de bugs, muitas vezes já incorporando na priorização da tarefa um prazo que flexibilize esse caminho. É importante conhecer os desafios do sistema e da tarefa em questão.

Pequenas refatorações são incorporadas às tarefas regulares, minimizando o impacto no cronograma e evitando a sensação de “trabalho extra”. Desacoplar um trecho de código que não permite a expansão de novas funcionalidade enquanto se desenvolve, remoção de um trecho de código duplicado extraindo a lógica comum para uma função reutilizável, ao corrigir um bug percebe-se que para realmente resolver o problema será preciso aumentar a cobertura de testes daquela parte do sistema.

Discussões abertas devem ser feitas quanto às necessidades de refatoração com a equipe, buscando consenso e alinhamento. O planejamento da refatoração é feito em incrementos, distribuídos ao longo de vários ciclos de entrega de valor, permitindo uma evolução gradual e controlada da base de código. O tempo necessário para refatoração já deve ser considerado nas estimativas, conforme já mencionado acima.

Como benefício dessas práticas podemos citar a prevenção da dívida técnica, reduzindo o acúmulo de pontos de melhoria e TODOs, garantindo que o código permaneça limpo, organizado e fácil de manter; facilita a implementação de novas funcionalidades, permitindo que o código existente seja adaptado e expandido de forma eficiente; promove uma cultura de qualidade contínua, onde a melhoria do código é vista como parte integrante do processo e consequentemente acaba incentivando a colaboração e o aprendizado entre os membros da equipe.

A estratégia de refatoração contínua guiada pelo nível sênior é um exemplo de liderança técnica garantindo a sustentabilidade dos projetos, promovendo a qualidade e fortalecendo a colaboração dentro da equipe.