Skip to content
Home » Software Design » DRY Principle (Don’t Repeat Yourself) in C

DRY Principle (Don’t Repeat Yourself) in C

DRY Principle featured image with blue-purple background, Clean Code badge, D icon, Dont Repeat Yourself in C subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 7 of 31View Full Path →

KEY TAKEAWAYS

  • DRY: every piece of logic should exist in exactly one place
  • Use functions to eliminate repeated logic
  • Use #define or const to eliminate repeated constants
  • DRY reduces bugs: change one place, not ten
  • But do not merge code that only looks similar — it must represent the same knowledge

What Is the DRY Principle?

DRY stands for Don’t Repeat Yourself. It means: every piece of knowledge or logic should have a single, unambiguous representation in the system.When the same logic exists in multiple places, changing it requires finding and updating every copy. Miss one, and you have a bug. DRY eliminates this risk by putting shared logic in one place.

Example 1: Duplicated Validation Code

Bad: Same validation logic copy-pasted in three places

int set_motor_speed(int speed) {
    /* Validation - copy 1 */
    if (speed  100) speed = 100;
    motor_reg = speed;
    return speed;
}

int set_fan_speed(int speed) {
    /* Validation - copy 2 (identical logic) */
    if (speed  100) speed = 100;
    fan_reg = speed;
    return speed;
}

int set_pump_speed(int speed) {
    /* Validation - copy 3 (identical logic) */
    if (speed  100) speed = 100;
    pump_reg = speed;
    return speed;
}
Problem: If the valid range changes to 0-255, you must update three places. Miss one and one actuator will behave differently.

Good: Extract shared logic into one function

int clamp(int value, int min, int max) {
    if (value  max) return max;
    return value;
}

int set_motor_speed(int speed) {
    speed = clamp(speed, 0, 100);
    motor_reg = speed;
    return speed;
}

int set_fan_speed(int speed) {
    speed = clamp(speed, 0, 100);
    fan_reg = speed;
    return speed;
}

int set_pump_speed(int speed) {
    speed = clamp(speed, 0, 100);
    pump_reg = speed;
    return speed;
}
Now the clamping logic exists in one place. Change the range once, it applies everywhere.

Example 2: Repeated Struct Initialization

Bad: Copy-pasting initialization code

typedef struct {
    char name[32];
    int  id;
    int  status;
    int  error_count;
    int  last_reading;
} sensor_t;

void init_sensors(void) {
    /* Copy-paste for each sensor */
    strcpy(sensors[0].name, "Temperature");
    sensors[0].id = 1;
    sensors[0].status = 0;
    sensors[0].error_count = 0;
    sensors[0].last_reading = 0;

    strcpy(sensors[1].name, "Humidity");
    sensors[1].id = 2;
    sensors[1].status = 0;
    sensors[1].error_count = 0;
    sensors[1].last_reading = 0;

    strcpy(sensors[2].name, "Pressure");
    sensors[2].id = 3;
    sensors[2].status = 0;
    sensors[2].error_count = 0;
    sensors[2].last_reading = 0;
}

Good: Use a helper function

void sensor_init(sensor_t *s, const char *name, int id) {
    strncpy(s->name, name, sizeof(s->name) - 1);
    s->name[sizeof(s->name) - 1] = '';
    s->id = id;
    s->status = 0;
    s->error_count = 0;
    s->last_reading = 0;
}

void init_sensors(void) {
    sensor_init(&sensors[0], "Temperature", 1);
    sensor_init(&sensors[1], "Humidity",    2);
    sensor_init(&sensors[2], "Pressure",    3);
}
Adding a new field (like calibration_offset) requires changing only sensor_init, not every initialization site.

DRY Also Applies To Constants

/* Bad: Same magic number in multiple places */
char buffer1[256];
char buffer2[256];
if (len > 256) return -1;

/* Good: Define once, use everywhere */
#define BUFFER_SIZE 256
char buffer1[BUFFER_SIZE];
char buffer2[BUFFER_SIZE];
if (len > BUFFER_SIZE) return -1;

When NOT to Over-Apply DRY

  • If two pieces of code look similar but serve different purposes, they may evolve independently — forcing them into one function creates a confusing abstraction
  • Do not create a shared function for two lines of code that happen to look alike but have different meanings
  • DRY is about knowledge duplication, not code that looks similar

Key Takeaways

  • DRY: every piece of logic should exist in exactly one place
  • Use functions to eliminate repeated logic
  • Use #define or const to eliminate repeated constants
  • DRY reduces bugs: change one place, not ten
  • But do not merge code that only looks similar — it must represent the same knowledge

Example 3: Repeated Struct Initialization

In many C projects, you see the same struct initialization logic scattered across multiple functions. This is a subtle form of duplication that becomes painful when the struct changes.

Bad — Copy-Pasted Initialization

void create_user_report(void) {
    Report r;
    r.type = REPORT_USER;
    r.timestamp = time(NULL);
    r.version = 2;
    r.flags = FLAG_ACTIVE | FLAG_VERBOSE;
    r.data = NULL;
    r.size = 0;
    // ... generate user report
}

void create_system_report(void) {
    Report r;
    r.type = REPORT_SYSTEM;
    r.timestamp = time(NULL);
    r.version = 2;
    r.flags = FLAG_ACTIVE | FLAG_VERBOSE;
    r.data = NULL;
    r.size = 0;
    // ... generate system report
}

void create_error_report(void) {
    Report r;
    r.type = REPORT_ERROR;
    r.timestamp = time(NULL);
    r.version = 2;
    r.flags = FLAG_ACTIVE | FLAG_VERBOSE;
    r.data = NULL;
    r.size = 0;
    // ... generate error report
}
If version changes to 3 or a new required field is added, you must update every function. Miss one, and you have a bug.

Good — Factory Function

Report report_create(ReportType type) {
    Report r;
    r.type = type;
    r.timestamp = time(NULL);
    r.version = 2;
    r.flags = FLAG_ACTIVE | FLAG_VERBOSE;
    r.data = NULL;
    r.size = 0;
    return r;
}

void create_user_report(void) {
    Report r = report_create(REPORT_USER);
    // ... generate user report
}

void create_system_report(void) {
    Report r = report_create(REPORT_SYSTEM);
    // ... generate system report
}
One place to change when the struct evolves. Every caller gets the update automatically.

When NOT to Apply DRY

DRY is powerful, but over-applying it can hurt more than help:
  • Coincidental similarity — Two blocks of code look the same today but serve different purposes. Merging them creates a false dependency. If they change for different reasons, keep them separate.
  • Premature abstraction — Don’t extract a shared function the first time you see duplication. Wait until you see the pattern three times (the “Rule of Three”). The third occurrence confirms it’s a real pattern.
  • Cross-module coupling — Sometimes two modules have similar code, but extracting a shared utility creates an unwanted dependency between them. A little duplication is better than tight coupling.

Leave a Reply

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