@wscld
Published on

Código Feio vs. Código Bonito

Authors
  • avatar
    Name
    Wesley Caldas
    Twitter

Código Feio e Funcional vs. Código Bonito e com Performance Ruim: Um Dilema do Desenvolvimento

Recentemente andei fazendo a análise de um código que estava bem legível, porém a performace dele era bem ruim na hora de tratar uma grande quantidade de dados. Nisso notei que um dos grandes dilemas que nós enfrentam é o equilíbrio entre legibilidade e performance. Nem sempre o código mais eficiente é o mais bonito, e nem sempre o código mais bonito é o mais eficiente. Vou dar um exemplo para ilustrar esse cenário:

Nos exemplos abaixo, o primeiro código é feio, mas eficiente. Já o segundo é mais elegante e legível, porém com performance inferior.

Exemplo 1: Código Feio, Mas Funcional e Performático

static preencherValoresFaltantes(
  registros: Registro[],
  datasConsolidadas: Date[],
  tipoRegistro: TipoRegistro,
) {
  const registrosLookup = registros.reduce((mapa, registro) => {
    if (registro.tipoRegistro === tipoRegistro) {
      const dataRegistro = moment(registro.dataRegistro).format("YYYY-MM-DD");
      mapa[dataRegistro] = registro;
    }
    return mapa;
  }, {});

  let ultimoRegistro = { ...registros[0] };
  return datasConsolidadas.map((dia, i) => {
    const diaFormatado = moment(dia).format("YYYY-MM-DD");
    const registroEncontrado = registrosLookup[diaFormatado];

    if (!registroEncontrado) {
      return ultimoRegistro;
    }
    ultimoRegistro = registroEncontrado;
    return registroEncontrado;
  });
}

Esse exemplo de código faz duas coisas principais:

  1. Cria um dicionário de cotações (lookup), onde a chave é a data da cotação formatada, e o valor é o objeto de cotação correspondente.
  2. Percorre uma lista de datas consolidadas para verificar se há uma cotação correspondente. Caso contrário, ele usa a última cotação válida.

Características do Código:

  • Performance: O código é altamente eficiente. Ao criar o dicionário lookup, ele permite que as buscas por cotações sejam feitas em tempo constante O(1). Ou seja, para cada data consolidada, o código apenas consulta o dicionário em vez de fazer uma busca completa no array de cotações.
  • Complexidade: Apesar de ser eficiente, o código não é exatamente bonito. O uso de reduce e o acesso direto aos indices da lista, especialmente para quem não está acostumado a essas práticas. Isso compromete a clareza do código.

Exemplo 2: Código Bonito, Mas com Performance Ruim

static preencherRegistrosFaltantes(
  registros: Registro[],
  datasConsolidadas: Date[],
  TipoRegistro: TipoRegistro,
) {
  let ultimoRegistro = { ...registros[0] };

  return datasConsolidadas.map((data) => {
    const dataFormatada = moment(data).format("YYYY-MM-DD");
    const item = registros.find((registro) => registro.tipoRegistro === TipoRegistro && moment(registro.dataRegistro).format("YYYY-MM-DD") === dataFormatada);

    if (!item) {
      return ultimoRegistro;
    }

    ultimoRegistro = item;
    return item;
  });
}

Neste segundo exemplo, o código faz basicamente a mesma coisa, mas de maneira mais "elegante" e legível:

  • O lookup foi removido e, em vez disso, o código faz uma busca direta com o método find dentro do array de cotações.

Características do Código:

  • Legibilidade: Esse código é muito mais legível e fácil de entender. Ele utiliza o método find de forma clara, o que melhora sua compreensão, especialmente para desenvolvedores que estão acostumados a trabalhar com arrays em JavaScript/TypeScript.
  • Performance: Apesar de mais legível, o uso de find resulta em uma complexidade de O(n²), pois para cada data consolidada, o código precisa percorrer todo o array de cotações. Isso pode ser ineficiente em grandes volumes de dados.

Comparação Direta

AspectoCódigo Feio e FuncionalCódigo Bonito e com Performance Ruim
LegibilidadeModerada, especialmente para iniciantesAlta, fácil de entender e manter
ComplexidadeO(n) para buscas nas datas consolidadasO(n²) devido ao uso de find
EscalabilidadeMelhor performance para grandes conjuntos de dadosNão escala bem com grandes volumes
Uso de EstruturasUso eficiente de lookup para buscas rápidasNão utiliza estruturas de busca otimizadas

Quando Escolher Código Feio e Performático?

  • Projetos que lidam com grandes volumes de dados: Quando a performance é um fator crítico, como em sistemas de tempo real, grandes bancos de dados ou cálculos intensivos, o código feio, mas eficiente, pode ser necessário.
  • Soluções temporárias ou legados: Em sistemas legados, ou quando se precisa de uma solução rápida e funcional, pode ser aceitável sacrificar a legibilidade em prol da performance.

Quando Escolher Código Bonito e Menos Performático?

  • Sistemas pequenos ou moderados: Se o volume de dados é relativamente pequeno e a performance não é um fator crítico, o código mais bonito e legível pode ser a melhor escolha, já que ele é mais fácil de manter.
  • Equipes grandes: Em ambientes colaborativos, onde muitos desenvolvedores trabalham no mesmo código, um código legível e bem estruturado pode evitar problemas de manutenção e melhorar a produtividade.

O dilema entre código feio e performático vs código bonito e menos eficiente é uma escolha que deve ser feita com base no contexto. Nem sempre o código mais eficiente é a melhor escolha, principalmente quando se lida com equipes grandes ou projetos de manutenção contínua. Por outro lado, sistemas de alta performance e grande escala muitas vezes exigem soluções mais complexas e otimizadas, mesmo que isso signifique sacrificar a clareza.

A chave é sempre encontrar o equilíbrio certo para o seu caso de uso. Se a performance é fundamental, vale a pena investir em uma solução mais eficiente, mesmo que seja mais difícil de entender. Se a manutenção e a legibilidade são prioridades, optar por um código mais simples, mesmo que menos performático, pode ser a melhor abordagem.

No fim das contas, o bom código não é necessariamente aquele que é apenas eficiente ou bonito, mas sim aquele que resolve o problema da melhor forma possível dentro do contexto em que está inserido.