Convenções de Código


Assim como escrevi sobre estilização em SQL, sigo na busca por melhores práticas. Afinal, prática é justamente aquilo que repetimos. E quanto melhor fazemos, mais evoluímos. Da palavra prática vem a praticidade. E quanto mais prático algo é, mais vantagem traz. A praticidade nos faz ganhar tempo e simplificar a vida.

Quase sempre, aquilo que você ainda não fez, alguém já fez antes. Essa pessoa provavelmente enfrentou dificuldades e encontrou uma solução. A prova está nos fóruns da internet e no próprio Stack Overflow, que vivem cheios de exemplos disso.

Me convença

Toda vez que preciso escrever código, que nada mais é do que uma representação de coisas do mundo real, organizadas em instruções e comandos, me pergunto: “Essa é a melhor forma de fazer?”.

E ai começa uma batalha mental, com diversos questionamentos:

  • Está seguro e atualizado?
  • Está escrito corretamente?
  • Está fácil de entender?
  • Será que tem uma forma mais simples de fazer?

Pra resolver esse tipo de problema, encontrei um único caminho — padrões e convenções.

Um trecho de código não tão bom...
// pages/api/buscartime.js
// Exemplo propositalmente RUIM, usando Next.js (antigo)
// e Postgres (driver pg básico)

import { Client } from "pg";

// ❌ tudo hardcoded, nada de variáveis de ambiente
const client = new Client({
  user: "postgres",
  host: "localhost",
  database: "futebol_db",
  password: "123456",
  port: 5432,
});

// ❌ conexão manual, sem pool, e sem tratar reconexão
client.connect(err => {
  if (err) {
    console.log("Erro ao conectar no banco: ", err);
  } else {
    console.log("Conectado no Postgres!");
  }
});

// ❌ rota Next.js antiga, lógica toda misturada
export default (req, res) => {
  if (req.method !== "GET") {
    res.status(405).send("Somente GET é permitido");
    return;
  }

  const { nomeTime } = req.query; // sem validação

  if (!nomeTime) {
    res.status(400).send("Você precisa informar o nome do time");
    return;
  }

  // ❌ query concatenada → risco de SQL injection
  const query = `SELECT * FROM times WHERE nome = '${nomeTime}'`;

  client.query(query, (err, result) => {
    if (err) {
      console.log("Erro na query: ", err);
      res.status(500).json({ erro: "Erro ao buscar time" });
    } else {
      if (result.rows.length > 0) {
        let resposta = {
          status: "ok",
          quantidade: result.rows.length,
          times: [],
        };

        // ❌ loop desnecessário, poderia mandar direto result.rows
        for (let i = 0; i < result.rows.length; i++) {
          resposta.times.push({
            id: result.rows[i].id,
            nome: result.rows[i].nome,
            cidade: result.rows[i].cidade,
            estadio: result.rows[i].estadio,
            fundacao: result.rows[i].fundacao,
            tecnico: result.rows[i].tecnico,
          });
        }

        // ❌ mistura log e resposta
        console.log("Resposta enviada: ", resposta);
        res.status(200).json(resposta);
      } else {
        res.status(404).send("Nenhum time encontrado: " + nomeTime);
      }
    }
  });
};

Nesse exemplo, o código pode até funcionar, porém ele está sensível desde a segurança até qualquer mudança em infraestrutura.

Padrões e Convenções

Esses 2 termos são muito próximos, vamos as definições:

Padrão (Standard) → Regra oficial documentada, servindo como uma norma.

Padrão é algo formalizado, geralmente definido por um órgão, entidade ou pela própria comunidade técnica. Tem caráter de norma ou regra oficial. Costuma ser documentado e aceito amplamente para garantir compatibilidade entre sistemas, ferramentas e equipes.

Exemplos:

  • SQL ANSI é um padrão (definido pela ANSI/ISO).
  • HTTP/1.1, HTTP/2 são padrões definidos pelo IETF.
  • Em código, o ECMAScript é o padrão do JavaScript.

Convenção (Convention) → Acordo com time interno ou comunidade, servindo como boas práticas.

Convenção é algo que surge de um acordo não formal dentro de um time, empresa ou comunidade. Não é obrigatório por norma externa, mas é seguido para manter consistência, legibilidade e entendimento comum. Pode variar entre grupos diferentes.

Exemplos:

  • Nomear classes em C# com PascalCase é convenção (não é exigido pelo compilador).
  • Usar snake_case para colunas no banco de dados.
  • Definir que commits sigam o Conventional Commits (é convenção, não padrão oficial).

Seguir padrões e convenções só trazem vantagens, além de gerar confiança no desenvolvimento e aumentar o nível de DX - Developer eXperience, melhorando o dia a dia do programador ao criar e manter o código.

Menos é mais

Aqui um ponto importante: nada está escrito em pedra. Nem todo o código está em constante mudança, mas quando muda, precisa ser pra melhor e não pra pior.

Colocar 20 camadas de abstrações, 2 bancos de cache, 5 frameworks e 3 nuvens só pra inflar o ego não garante o resultado. Talvez assegure que a aplicação fique refém de um ou mais desenvolvedores narcisistas que querem se perpetuar ali.

Uma boa prática para afastar esse cenário é o uso do pensamento crítico, incluindo no processo de desenvolvimento a entrega baseada em métricas e benchmark. Um cenário com antes e depois documentado e comprovado garante longevidade a operação.

Busque o equilíbrio para todas as situações ⚖️.

Aquele trecho de código com convenções...
// /pages/api/football-teams/[teamName].js
// Exemplo MELHORADO com padrões e convenções.

import { Pool } from "pg";

// ✅ Padrão: uso de variáveis de ambiente (env) em vez de hardcode.
// ✅ Convenção: nomes em MAIÚSCULAS para variáveis de ambiente.
const pool = new Pool({
  user: process.env.DB_USER,
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  password: process.env.DB_PASSWORD,
  port: process.env.DB_PORT || 5432,
});

// ✅ Padrão: funções assíncronas (async/await) no lugar de callbacks.
// ✅ Convenção: early return para erros.
export default async function handler(req, res) {
  if (req.method !== "GET") {
    return res.status(405).json({ error: "Método não permitido" });
  }

  // `teamName` vem da rota: /api/football-teams/SaoPauloFutebolClube.
  const { teamName } = req.query;

  if (!teamName) {
    return res.status(400).json({ error: "Informe o nome do time" });
  }

  try {
    // ✅ Padrão: uso de parâmetros ($1, $2, ...) → evita SQL Injection.
    // ✅ Convenção: escrita de queries em letras maiúsculas para comandos SQL.
    const query = `
      SELECT
        id,
        team_name,
        city,
        stadium,
        coach
      FROM
        football_teams
      WHERE
        team_name = $1
      ;
    `;

    // O retorno de rows será sempre um array.
    const { rows } = await pool.query(query, [teamName]);

    if (rows.length === 0) {
      return res.status(404).json({ error: `Nenhum time encontrado: ${teamName}` });
    }

    // ✅ Convenção: nomes claros e consistentes no JSON, retornando apenas 1 time.
    const time = rows[0]; // pega o primeiro (único) registro retornado
    return res.status(200).json({
      status: "ok",
      time: {
        id: time.id, // ID do time: 6331
        teamName: time.team_name, // Nome do time: São Paulo Futebol Clube
        city: time.city, // Cidade sede: São Paulo
        stadium: time.stadium, // Estádio principal: Morumbi
        coach: time.coach, // Técnico atual: Hernán Crespo
      },
    });
  } catch (error) {
    // ✅ Mantém o log do erro para debugging
    console.error("Erro ao buscar time:", error);

    // ✅ Retorna erro genérico para o cliente
    return res.status(500).json({ error: "Erro interno no servidor" });
  }
}

// Exemplo de chamada ao método.
const foundTeam = fetch("/api/football-teams/SaoPauloFutebolClube")
  .then(res => res.json())
  .then(data => console.log(data));

// Exemplo do JSON retornado.
{
  "status": "ok",
  "time": {
    "id": 6331,
    "teamName": "São Paulo Futebol Clube",
    "city": "São Paulo",
    "stadium": "Morumbi",
    "coach": "Hernán Crespo"
  }
}

Opa, o código ganhou clareza, podendo ser aprimorado com alguns níveis de abstração, sem exagero.

Evite repetições, abstraia

Comunicação com banco de dados, chamada e retorno de API, tratamento de erros… essas operações ocorrem a todo instante e tem um comportamento parecido e repetitivo.

Repetição de comportamento em uma aplicação é um candidato a ter sua complexidade abstraída e organizada. Assim fica fácil reaproveitar o código, deixando o foco para trabalhar apenas em novas regras de negócio. Separando as responsabilidades, da rota no navegador até o banco de dados, temos o seguinte fluxo:

Convenções MVC API

Aquele mesmo exemplo, abstraído em camadas...

Organizando e separando a lógica usando o padrão MVC adaptado para APIs:

1️⃣ Handler da API (Rota + Controller), quem definitivamente executa as ações.
pages/api/football-teams/[teamName].js → regras de rota e resposta JSON.

// pages/api/football-teams/[teamName].js
// next-connect, router e outros trechos ocultados para didática.
import { createRouter } from "next-connect";
import controller from "infra/controller.js";
import footballTeam from "models/footballTeam.js";

async function getHandler(request, response) {
  const { teamName } = request.query;

  // Chamada limpa para o model
  const teamFound = await footballTeam.findOneByName(teamName);

  return response.status(200).json({
    status: "ok",
    team: teamFound,
  });

  const footballTeams = {
    getHandler,
  };

  export default footballTeams;
}

2️⃣ Representando os times de futebol em um Modelo chamado footballTeam.
models/footballTeam.js → define propriedades e métodos de acesso.

// models/footballTeam.js
import database from "infra/database.js";
import { NotFoundError } from "infra/errors.js";

async function findOneByName(teamName) {
  const teamFound = await runSelectQuery(teamName);
  return teamFound;

  async function runSelectQuery(teamName) {
    const results = await database.query({
      text: `
        SELECT
          id,
          team_name,
          city,
          stadium,
          coach
        FROM
          football_teams
        WHERE
          team_name = $1
        LIMIT
          1
        ;`,
      values: [teamName],
    });

    if (results.rowCount === 0) {
      throw new NotFoundError({
        message: `Nenhum time encontrado: ${teamName}`,
        action: "Verifique se o nome do time foi digitado corretamente.",
      });
    }

    return results.rows[0];
  }
}

const footballTeam = {
  findOneByName,
};

export default footballTeam;

3️⃣ Infraestrutura do banco de dados.
infra/database.js → centraliza pool e conexão com Postgres.

// infra/database.js
// Importa todo o pacote "pg"
import pg from "pg";
// Desestrutura a classe Pool do pacote
const { Pool } = pg;

// Pool singleton para toda a aplicação
const pool = new Pool({
  user: process.env.DB_USER,
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  password: process.env.DB_PASSWORD,
  port: process.env.DB_PORT || 5432,
  max: process.env.DB_MAX_CONNECTIONS || 20, // Máximo de conexões
  idleTimeoutMillis: process.env.DB_MAX_IDLE_TIMEOUT_MILLIS || 30000, // 30s
  connectionTimeoutMillis: process.env.DB_MAX_CONNECTION_TIMEOUT_MILLIS || 2000, // 2s
});

// Testa a conexão inicial
pool.connect((err, client, release) => {
  if (err) {
    console.error("Erro ao conectar no banco de dados:", err.stack);
  } else {
    console.log("Conectado ao Postgres com sucesso!");
    release();
  }
});

// Método genérico para executar queries
async function query({ text, values }) {
  const client = await pool.connect();
  try {
    const result = await client.query(text, values);
    return result;
  } catch (err) {
    console.error("Erro na query:", err);
    throw err;
  } finally {
    client.release();
  }
}

// Exporta apenas o método query, mantendo pool encapsulado
const database = {
  query,
};

export default database;

Detalhando a estrutura do último trecho de código apresentado, separamos as camadas em:

  • Handler/Router → cuidam das rotas e da resposta HTTP, mantendo o fluxo limpo ([teamName].js com next-connect).
  • Controllers → implementam a lógica de negócio e abstraem validações, chamadas aos models (footballTeams.js, users.js).
  • Models → encapsulam a lógica de acesso ao banco (footballTeam.js, user.js).
  • Infraestrutura → banco de dados, pool e erros centralizados (database.js, controller.js, errors.js).

Resumindo, o que fizemos até aqui foi uma boa organização, deixando o código flexível para mudanças. Há oportunidade de melhoria? Sim, mas para o contexto do post está bem encaminhado. Futuramente quero escrever sobre linting de código, onde adicionamos um analisador de sintaxe, validando a escrita do software.


Minha dica para o seu coração: ao trabalhar com qualquer linguagem de programação e banco de dados, use os padrões e convenções sempre que possível. Adapte quando necessário.

Quer se aprofundar em padrões, convenções, estilos e abstrações? Fica meu convite pra conferir esse projeto aqui.

Happy coding!

Referências

➡️ Mais artigos…