Refactorización de código legacy en C++: estrategias y ejemplos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Refactorización de código legacy en C++: guía paso a paso



Trabajar con código heredado (legacy) en C++ es una de las tareas más difíciles, pero inevitables en la carrera de un desarrollador. El código legacy a menudo carece de pruebas unitarias, contiene números mágicos, fugas de memoria y dependencias enredadas. Sin embargo, la refactorización de dicho código sin riesgo de romper el sistema es posible si se siguen estrategias probadas. En este artículo, analizaremos los enfoques clave para la refactorización de código legacy en C++ con ejemplos prácticos.



1. Preparación: creación de una red de seguridad



Antes de cambiar el código, es necesario crear una "red de seguridad" de pruebas. Para C++ esto es especialmente importante, ya que los errores pueden provocar un comportamiento indefinido o la caída del programa.



Paso 1.1: Pruebas de caracterización

Escriba pruebas que capturen el comportamiento actual del código, incluso si es incorrecto. Utilice frameworks como Google Test o Catch2.



// Ejemplo de prueba de caracterización para una función legacy#include <gtest/gtest.h>

// Función legacy que vamos a refactorizarint calculatePrice(int quantity, float basePrice) { if (quantity > 100) { return static_cast<int>(basePrice * quantity * 0.9); } else { return static_cast<int>(basePrice * quantity); }}

TEST(PriceCalculatorTest, LegacyBehavior) { EXPECT_EQ(calculatePrice(50, 10.0f), 500); EXPECT_EQ(calculatePrice(150, 10.0f), 1350); // 150*10*0.9}


Paso 1.2: Integración en CI/CD

Agregue las pruebas al sistema de integración continua. Esto garantiza que los cambios no rompan la lógica existente.



2. Estrategia: Refactorización "inteligente" sin dolor



No intente reescribir todo de una vez. Utilice un enfoque incremental.



2.1. Extracción de interfaces (Extract Interface)

Si el código está estrechamente acoplado a estados globales o clases concretas, comience extrayendo abstracciones.



Problema: La clase trabaja directamente con el sistema de archivos.

// Código legacyclass DataProcessor {public:    void process(const std::string& filename) {        std::ifstream file(filename);        // lógica compleja de procesamiento    }};


Solución: Introducimos una interfaz e inyección de dependencias.

// Nueva interfazclass IFileReader {public:    virtual std::string read(const std::string& path) = 0;    virtual ~IFileReader() = default;};

// Refactorizaciónclass DataProcessor {public: explicit DataProcessor(std::unique_ptr<IFileReader> reader) : reader_(std::move(reader)) {} void process(const std::string& filename) { auto data = reader_->read(filename); // lógica de procesamiento }

private: std::unique_ptr<IFileReader> reader_;};


2.2. Reemplazo de números mágicos por constantes

El código legacy a menudo contiene números cuyo significado no está claro.



// Antes de la refactorizaciónif (buffer.size() > 4096) {    resizeBuffer(8192);}

// Después de la refactorizaciónconstexpr size_t kMaxBufferSize = 4096;constexpr size_t kResizeTarget = 8192;

if (buffer.size() > kMaxBufferSize) { resizeBuffer(kResizeTarget);}


3. Lucha contra fugas de memoria y construcciones inseguras



El código legacy en C++ a menudo utiliza punteros crudos y gestión manual de memoria. Reemplácelos con punteros inteligentes y envoltorios RAII.



3.1. Reemplazo de new/delete por std::unique_ptr

// Código antiguo (riesgo de fuga en caso de excepción)void processData() {    Data* data = new Data();    // ... trabajo con data ...    delete data;}

// Código nuevo (seguro)void processData() { auto data = std::make_unique<Data>(); // ... trabajo con data ... // delete no es necesario}


3.2. Uso de std::vector en lugar de arrays crudos

//                

Blogs

Book Recommendations