Skip to content
Home » Software Design » Code Smells in C: Bloaters (Long Functions, Magic Numbers, and More)

Code Smells in C: Bloaters (Long Functions, Magic Numbers, and More)

Code Smells Bloaters featured image with dark purple background, Code Smells badge, exclamation icon, Long Functions and Magic Numbers subtitle in the Software Design in C series
Software Design Principles in C
Part 22 of 31View Full Path →

KEY TAKEAWAYS

  • Long functions, magic numbers, and oversized files are “bloater” code smells that indicate design problems
  • Functions longer than 20-30 lines likely do too much and should be split into focused helper functions
  • Replace magic numbers with named constants (#define or enum) for readability and maintainability
  • Bloaters accumulate gradually — regular refactoring prevents code from becoming unmanageable

What Are Code Smells?

A code smell is a surface-level indication that something may be wrong with your code. It does not mean there is definitely a bug — but it is a warning sign that the code may be hard to maintain, extend, or debug.Bloaters are code smells where something has grown too large: functions too long, too many parameters, numbers without meaning, or files that do too much.

Smell 1: Long Function

What it is: A function that does too many things and is hard to read at a glance. Generally, if a function exceeds 30-40 lines, it is a candidate for splitting.

Smelly Code

void process_packet(uint8_t *data, int len) {
    /* Validate header (10 lines) */
    if (len < 4) return;
    if (data[0] != 0xAA) return;
    if (data[1] != 0x55) return;
    uint8_t checksum = 0;
    for (int i = 0; i < len - 1; i++) checksum ^= data[i];
    if (checksum != data[len - 1]) return;

    /* Parse fields (10 lines) */
    uint8_t cmd = data[2];
    uint8_t payload_len = data[3];
    uint8_t *payload = &data[4];

    /* Handle command (20 lines) */
    if (cmd == 0x01) {
        int temp = payload[0] < 800) {
            GPIO_SET(ALARM_PIN);
            uart_send("ALARMrn");
        }
        log_temperature(temp);
    } else if (cmd == 0x02) {
        /* ... another 15 lines ... */
    }
    /* ... continues for 80+ lines ... */
}

Fix: Extract meaningful functions

int validate_packet(const uint8_t *data, int len) {
    if (len < 4 || data[0] != 0xAA || data[1] != 0x55) return 0;
    uint8_t checksum = 0;
    for (int i = 0; i < len - 1; i++) checksum ^= data[i];
    return (checksum == data[len - 1]);
}

void handle_temperature_cmd(const uint8_t *payload) {
    int temp = payload[0] < 800) {
        GPIO_SET(ALARM_PIN);
        uart_send("ALARMrn");
    }
    log_temperature(temp);
}

void process_packet(uint8_t *data, int len) {
    if (!validate_packet(data, len)) return;
    uint8_t cmd = data[2];
    uint8_t *payload = &data[4];

    switch (cmd) {
        case 0x01: handle_temperature_cmd(payload); break;
        case 0x02: handle_config_cmd(payload);      break;
    }
}

Smell 2: Magic Numbers

What it is: Unexplained numeric literals scattered through the code. No one knows what they mean without digging into the datasheet.

Smelly Code

if (adc_value > 3276) {
    set_pwm(204);
    delay(50);
} else if (adc_value < 819) {
    set_pwm(25);
    delay(200);
}

Fix: Named constants

#define ADC_80_PERCENT    3276   /* 80% of 4096 (12-bit ADC) */
#define ADC_20_PERCENT     819   /* 20% of 4096 */
#define PWM_80_PERCENT     204   /* 80% of 255 (8-bit PWM) */
#define PWM_10_PERCENT      25   /* 10% of 255 */
#define FAST_UPDATE_MS      50
#define SLOW_UPDATE_MS     200

if (adc_value > ADC_80_PERCENT) {
    set_pwm(PWM_80_PERCENT);
    delay(FAST_UPDATE_MS);
} else if (adc_value < ADC_20_PERCENT) {
    set_pwm(PWM_10_PERCENT);
    delay(SLOW_UPDATE_MS);
}

Smell 3: Long Parameter List

What it is: A function that takes so many parameters it is hard to call correctly.

Smelly Code

void configure_uart(int baud, int data_bits, int stop_bits,
                    int parity, int flow_control, int tx_pin,
                    int rx_pin, int buffer_size, int timeout_ms) {
    /* ... */
}

/* Caller: which parameter is which? */
configure_uart(9600, 8, 1, 0, 0, 5, 6, 128, 1000);

Fix: Group into a struct

typedef struct {
    int baud;
    int data_bits;
    int stop_bits;
    int parity;
    int flow_control;
    int tx_pin;
    int rx_pin;
    int buffer_size;
    int timeout_ms;
} uart_config_t;

void configure_uart(const uart_config_t *config);

/* Caller: clear and self-documenting */
uart_config_t cfg = {
    .baud         = 9600,
    .data_bits    = 8,
    .stop_bits    = 1,
    .parity       = 0,
    .flow_control = 0,
    .tx_pin       = 5,
    .rx_pin       = 6,
    .buffer_size  = 128,
    .timeout_ms   = 1000,
};
configure_uart(&cfg);

Smell 4: Primitive Obsession

What it is: Using basic types (int, char) for everything instead of creating meaningful types.

Smelly Code

/* What does each int mean? */
void schedule_task(int priority, int period, int deadline, int offset) {
    /* ... */
}
schedule_task(3, 100, 50, 10);  /* Which is which? */

/* Is this temperature in Celsius or Fahrenheit? Millidegrees? */
int temperature = 2500;

Fix: Use typedefs and structs to add meaning

typedef struct {
    int priority;     /* 0 = highest */
    int period_ms;
    int deadline_ms;
    int offset_ms;
} task_schedule_t;

void schedule_task(const task_schedule_t *sched);

task_schedule_t my_task = {
    .priority    = 3,
    .period_ms   = 100,
    .deadline_ms = 50,
    .offset_ms   = 10,
};
schedule_task(&my_task);

/* Clear unit in the type name */
typedef int temperature_celsius_t;
temperature_celsius_t temp = 25;

Smell 5: Large File / God Module

What it is: A single source file that has grown to 500+ lines and handles multiple unrelated responsibilities.Fix: Split into focused files. A good C source file is typically 100-300 lines. If a file exceeds 400 lines, look for responsibilities that can be extracted into their own module.

Summary

SmellSignFix
Long Function30+ lines, multiple responsibilitiesExtract into smaller functions
Magic NumbersUnexplained numeric literalsUse #define or const with meaningful names
Long Parameter List4+ parametersGroup into a config struct
Primitive ObsessionBare int/char for everythingUse typedefs and structs
Large File500+ lines, multiple concernsSplit into focused modules

Leave a Reply

Your email address will not be published. Required fields are marked *