Refatoração de código legado em C++: guia passo a passo

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Refatoração de código legado em C++: como transformar um "zoológico" em um projeto sustentável

Trabalhar com código legado em C++ é uma realidade inevitável para muitos desenvolvedores. Muitas vezes, esse código carece de testes, é repleto de comportamento indefinido (UB), variáveis globais e gerenciamento manual de memória. No entanto, a refatoração de código legado em C++ não é apenas "pentear" arquivos, mas um processo sistemático para aumentar a confiabilidade e o desempenho. Neste artigo, vamos analisar as etapas principais que ajudarão você a modernizar com segurança um projeto antigo sem o risco de quebrar tudo.



1. Preparação: teste e isolamento

A primeira e mais importante etapa é criar uma "rede de segurança". Sem testes, qualquer refatoração se transforma em um jogo de roleta-russa.

  • Teste de caracterização: Escreva testes que capturem o comportamento atual do código. Mesmo que pareça errado, primeiro o fixe.
  • Análise estática: Use ferramentas como clang-tidy ou Cppcheck. Elas identificarão vazamentos potenciais, código não utilizado e violações de estilo.
  • Sanitizers: Execute o código com AddressSanitizer e UndefinedBehaviorSanitizer. Isso ajudará a encontrar UBs ocultos antes de iniciar as alterações.

Exemplo de adição de sanitizer no CMake:

# CMakeLists.txtset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")


2. Eliminação de ponteiros "nus" e gerenciamento manual de memória

C++ legado frequentemente sofre de vazamentos de memória e deleções duplas. O C++ moderno (C++11 e posteriores) oferece wrappers RAII.

  • Substituição de new/delete por smart pointers: std::unique_ptr para posse exclusiva, std::shared_ptr para posse compartilhada.
  • Uso de std::vector em vez de arrays brutos: Isso reduz o risco de estouro de limites.

Exemplo de refatoração:

Antes:

class LegacyBuffer {    char* data;    size_t size;public:    LegacyBuffer(size_t sz) : size(sz) {        data = new char[sz];    }    ~LegacyBuffer() { delete[] data; }    // Construtor de cópia e operador de atribuição esquecidos (violação da "regra dos três")};

Depois:

class ModernBuffer {    std::vector<char> data;public:    ModernBuffer(size_t sz) : data(sz) {}    // Destrutor, cópia e atribuição são gerados automaticamente pelo compilador};


3. Eliminação de macros e estados globais

Macros (#define) são uma causa comum de bugs difíceis de rastrear. Elas devem ser substituídas por constexpr, enum class ou templates. Variáveis globais complicam os testes e o paralelismo.

  • Substituição de #define BUFFER_SIZE 1024 por constexpr size_t BUFFER_SIZE = 1024;
  • Encapsulamento de globais: Mova o estado global para uma classe singleton ou, melhor ainda, passe as dependências através do construtor.

Exemplo com macro:

// Ruim: macro não tem escopo#define MAX_CONNECTIONS 100

// Bom: constante com tipo e escoponamespace Config { constexpr int maxConnections = 100;}

Uso de enum class em vez de #define para flags:

// Em vez de #define FLAG_READ 1enum class FileMode : uint8_t {    Read = 1,    Write = 2,    ReadWrite = 3};


4. Simplificação de funções complexas e remoção de código morto

Funções legadas frequentemente são "lençóis" de código com mais de 500 linhas e muitas condições aninhadas. Estratégia:

  • Extração de método: Quebre

Blogs

Book Recommendations