Table of Contents
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 (
#defineorenum) 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
| Smell | Sign | Fix |
|---|---|---|
| Long Function | 30+ lines, multiple responsibilities | Extract into smaller functions |
| Magic Numbers | Unexplained numeric literals | Use #define or const with meaningful names |
| Long Parameter List | 4+ parameters | Group into a config struct |
| Primitive Obsession | Bare int/char for everything | Use typedefs and structs |
| Large File | 500+ lines, multiple concerns | Split into focused modules |

Vivek Bhageria — Lead Firmware R&D Engineer, 12+ years. Ex-Bosch (automotive powertrain), MusicTribe (real-time audio), medical devices. M.Tech BITS Pilani. I write at NerdyElectronics — practical, register-level embedded systems for engineers who want to understand what’s actually happening under the hood.







