DEV Community

Luis Enrique Vargas Azcona
Luis Enrique Vargas Azcona

Posted on

Evita Demasiados Parámetros en Funciones

Cuando una función recibe demasiados parámetros, el código se vuelve más difícil de leer, mantener y usar correctamente.

Problemas típicos:

  1. Legibilidad baja: cuesta entender qué representa cada argumento.
  2. Mantenibilidad pobre: agregar o quitar parámetros rompe llamadas existentes.
  3. Errores por orden: es fácil intercambiar argumentos del mismo tipo.
  4. Pruebas más complejas: preparar casos de test se vuelve más tedioso.

Ejemplo problemático

void CreateUser(
    const std::string& first_name,
    const std::string& last_name,
    const std::string& email,
    const std::string& phone_number,
    const std::string& address,
    const std::string& city,
    const std::string& state,
    const std::string& zip_code,
    const std::string& country,
    std::chrono::year_month_day date_of_birth,
    bool is_admin,
    bool is_verified)
{
    // ...
}

CreateUser(
    "John", "Doe", "john@example.com",
    "123-456-7890", "123 Main St", "Anytown", "CA", "12345", "USA",
    std::chrono::year{1990}/1/1,
    false,
    true
);
Enter fullscreen mode Exit fullscreen mode

En una llamada así, false, true no comunica intención y el orden de strings es propenso a errores.


Regla práctica

Como guía simple: intenta no superar 3 parámetros por función.

Si necesitas más, normalmente conviene:

  • agrupar en una estructura,
  • dividir responsabilidades,
  • o cambiar flags booleanos por enums.

Soluciones

1) Usar una estructura de parámetros

Agrupa datos relacionados en un solo tipo.

struct UserCreateParams
{
    std::string first_name;
    std::string last_name;
    std::string email;
    std::optional<std::string> phone_number;
    std::optional<std::string> address;
    std::optional<std::string> city;
    std::optional<std::string> state;
    std::optional<std::string> zip_code;
    std::optional<std::string> country;
    std::optional<std::chrono::year_month_day> date_of_birth;
    bool is_admin = false;
    bool is_verified = false;
};

void CreateUser(const UserCreateParams& params)
{
    // ...
}

CreateUser(UserCreateParams{
    .first_name = "John",
    .last_name = "Doe",
    .email = "john@example.com",
    .phone_number = "123-456-7890",
    .city = "Anytown",
    .state = "CA",
    .is_verified = true
});
Enter fullscreen mode Exit fullscreen mode

Ventaja clave: con designated initializers (.campo = valor) la llamada es autodescriptiva.

Requiere compilador con soporte de C++20.


2) Dividir funciones por responsabilidad o nivel de abstracción

Si una función hace varias cosas, sepárala.

User CreateBasicUser(const std::string& first_name,
                     const std::string& last_name,
                     const std::string& email)
{
    // ...
}

void AddUserContactInfo(User& user, const UserContactInfo& contact)
{
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Esto mejora cohesión y hace más simples los tests.


3) Evitar banderas booleanas y usar enums

Los booleanos en firmas suelen ocultar intención.

enum class EWidgetLayout : uint8_t
{
    Compact,
    Full
};

enum class EWidgetTheme : uint8_t
{
    Light,
    Dark
};

struct WidgetRenderParams
{
    std::string content;
    EWidgetLayout layout = EWidgetLayout::Full;
    EWidgetTheme theme = EWidgetTheme::Light;
};

void RenderWidget(const WidgetRenderParams& params)
{
    // ...
}

RenderWidget(WidgetRenderParams{
    .content = "Hello World",
    .layout = EWidgetLayout::Compact,
    .theme = EWidgetTheme::Dark
});
Enter fullscreen mode Exit fullscreen mode

Con enums, el código expresa intención y evita true/false ambiguos.


4) Combinar defaults + designated initializers

Puedes definir valores por defecto y sobrescribir solo lo necesario:

struct ExportParams
{
    std::string output_path;
    bool compress = true;
    int quality = 90;
    bool include_metadata = true;
};

void ExportReport(const ExportParams& params)
{
    // ...
}

ExportReport(ExportParams{
    .output_path = "report.json",
    .quality = 75
});
Enter fullscreen mode Exit fullscreen mode

Esto reduce ruido y mantiene llamadas cortas y claras.


5) Cuándo usar Builder en C++

Si el objeto tiene muchas validaciones, pasos condicionales o configuración incremental, un Builder puede ser mejor que una lista grande de parámetros.

Aun así, para muchos casos cotidianos, struct + designated initializers es suficiente y más directo.


Herramientas útiles en Unreal Engine

Si trabajas con UE, este enfoque encaja muy bien con patrones del engine:

  1. USTRUCT(BlueprintType) para definir objetos de parámetros reutilizables en C++ y Blueprint.
  2. UENUM(BlueprintType) para reemplazar flags booleanos por estados explícitos.
  3. TOptional<T> para campos opcionales sin usar valores “mágicos”.
  4. meta = (AutoCreateRefTerm = "Params") en UFUNCTION este metadato permite que los parámetros marcados se inicialicen automáticamente con valores por defecto cuando no se especifican en Blueprint. Esto mejora la ergonomía, ya que evita que el usuario tenga que crear manualmente un struct vacío cada vez que llama a la función.
  5. FInstancedStruct (StructUtils) Permite manejar structs de diferentes tipos sin necesidad de usar herencia o plantillas. Es ideal para sistemas donde los datos son dinámicos, como eventos, configuraciones o sistemas de mensajes.

Nota práctica sobre designated initializers en UE

Los designated initializers funcionan muy bien con tipos agregados estándar en C++20. En tipos de UE que no sean agregados (por ejemplo, ciertos USTRUCT con constructores personalizados), puede ser mejor usar:

  • constructor explícito,
  • funciones fábrica estáticas,
  • o helpers de creación.

Checklist rápido

Antes de cerrar una API, pregúntate:

  • ¿Esta función hace una sola cosa?
  • ¿Más de 3 parámetros podrían agruparse en una struct?
  • ¿Hay bools que deberían ser enum?
  • ¿La llamada se entiende sin leer la implementación?

Si la respuesta es “no”, probablemente conviene rediseñar la firma.


Conclusión

Reducir parámetros no es solo estética: mejora claridad, reduce bugs y facilita evolución del código.

En C++, la combinación struct de parámetros + designated initializers + enums da APIs más legibles y mantenibles, especialmente en código de gameplay y herramientas donde las firmas cambian con frecuencia.

Top comments (0)