Skip to content
Home » Software Design » Software Quality Attributes Every Developer Should Know

Software Quality Attributes Every Developer Should Know

Software Quality Attributes featured image with purple background, Foundation badge, Q icon, Every Developer Should Know subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 1 of 31View Full Path →

KEY TAKEAWAYS

  • Quality attributes are not optional extras — they determine whether your code survives its first maintenance cycle
  • Most attributes are interrelated: improving modularity usually improves testability, maintainability, and reusability
  • Aim for high cohesion (related things together) and low coupling (modules independent of each other)
  • You do not need to optimize all attributes at once — focus on the ones that matter most for your project

What Are Software Quality Attributes?

Software quality attributes (also called “-ilities”) are the characteristics that define how good your code is beyond just working correctly. A program can produce the right output yet still be a nightmare to maintain, test, or extend. Understanding these attributes helps you write code that lasts.Below are the most important quality attributes every developer should know, with simple C examples showing good and bad practices.

Readability

Definition: How easily another developer (or your future self) can read and understand the code.
/* Bad: What does this do? */
int f(int a, int b) {
    return a > b ? a : b > 0 ? b : 0;
}

/* Good: Clear naming, obvious logic */
int get_clamped_max(int value_a, int value_b) {
    int max_val = (value_a > value_b) ? value_a : value_b;
    if (max_val < 0) {
        max_val = 0;
    }
    return max_val;
}

Maintainability

Definition: How easy it is to modify the code to fix bugs, add features, or adapt to new requirements.
/* Bad: Magic numbers scattered everywhere */
void configure_timer(void) {
    TIMER_REG = 0x1F4;   /* What is 0x1F4? */
    CTRL_REG  = 0x03;    /* What does 0x03 mean? */
}

/* Good: Named constants, easy to change */
#define TIMER_PERIOD_MS    500
#define TIMER_PRESCALER    0x1F4
#define CTRL_ENABLE_IRQ    0x01
#define CTRL_AUTO_RELOAD   0x02

void configure_timer(void) {
    TIMER_REG = TIMER_PRESCALER;
    CTRL_REG  = CTRL_ENABLE_IRQ | CTRL_AUTO_RELOAD;
}

Testability

Definition: How easy it is to write automated tests that verify the code behaves correctly.
/* Bad: Directly reads from hardware - impossible to test on PC */
int read_temperature(void) {
    int raw = ADC_READ_REG;
    return (raw * 330) / 1024;
}

/* Good: Inject the raw value - now you can test with any input */
int convert_raw_to_celsius(int raw_adc_value) {
    return (raw_adc_value * 330) / 1024;
}

/* Test: */
assert(convert_raw_to_celsius(512) == 165);
assert(convert_raw_to_celsius(0) == 0);

Reliability

Definition: The ability of software to perform its required functions under stated conditions without failure.
/* Bad: No error checking - crashes on NULL */
void print_name(char *name) {
    printf("Hello, %s\n", name);
}

/* Good: Handles edge cases gracefully */
void print_name(const char *name) {
    if (name == NULL) {
        printf("Hello, Guestn");
        return;
    }
    printf("Hello, %s\n", name);
}

Modularity

Definition: The degree to which code is divided into separate, independent modules, each with a clear responsibility.
/* Bad: Everything in one file/function */
void run_system(void) {
    /* Read sensor */
    int raw = ADC_REG;
    int temp = (raw * 330) / 1024;
    /* Check threshold */
    if (temp > 80) {
        GPIO_SET(FAN_PIN);
    }
    /* Send to UART */
    uart_send_int(temp);
}

/* Good: Separate modules */
/* sensor.c */
int sensor_read_temperature(void);

/* fan_control.c */
void fan_update(int temperature);

/* comm.c */
void comm_send_temperature(int temperature);

/* main.c */
void run_system(void) {
    int temp = sensor_read_temperature();
    fan_update(temp);
    comm_send_temperature(temp);
}

Reusability

Definition: The ability to use existing code in new contexts without modification.
/* Bad: Hardcoded for one specific use */
void sort_sensor_readings(int readings[], int count) {
    /* Bubble sort, only works with int sensor data */
    for (int i = 0; i < count - 1; i++)
        for (int j = 0; j  readings[j+1]) {
                int tmp = readings[j];
                readings[j] = readings[j+1];
                readings[j+1] = tmp;
            }
}

/* Good: Generic, reusable with any data type */
void sort_array(void *arr, int count, int elem_size,
                int (*compare)(const void *, const void *)) {
    qsort(arr, count, elem_size, compare);
}

Extensibility

Definition: How easy it is to add new functionality without modifying existing code.
/* Bad: Adding a new sensor type means editing this function */
int read_sensor(int type) {
    if (type == 1) return read_temperature();
    if (type == 2) return read_humidity();
    /* Must edit here to add type 3 */
    return -1;
}

/* Good: Register new sensors without touching existing code */
typedef int (*sensor_read_fn)(void);

sensor_read_fn sensor_table[MAX_SENSORS];

void register_sensor(int id, sensor_read_fn fn) {
    sensor_table[id] = fn;
}

int read_sensor(int id) {
    if (sensor_table[id] != NULL)
        return sensor_table[id]();
    return -1;
}

Portability

Definition: How easily software can be transferred from one environment (hardware, OS, compiler) to another.
/* Bad: Platform-specific code everywhere */
void delay(int ms) {
    for (volatile int i = 0; i < ms * 8000; i++);  /* Only works at 8MHz */
}

/* Good: Abstract platform details behind a header */
/* hal_delay.h */
void hal_delay_ms(int ms);

/* hal_delay_avr.c */
void hal_delay_ms(int ms) { _delay_ms(ms); }

/* hal_delay_stm32.c */
void hal_delay_ms(int ms) { HAL_Delay(ms); }

Robustness

Definition: The ability of software to handle unexpected inputs, errors, or environmental conditions without crashing.
/* Bad: Division by zero, buffer overflow possible */
float calculate_average(int values[], int count) {
    int sum = 0;
    for (int i = 0; i < count; i++) sum += values[i];
    return (float)sum / count;
}

/* Good: Defensive coding */
float calculate_average(const int values[], int count) {
    if (values == NULL || count <= 0) return 0.0f;
    long sum = 0;  /* Use long to prevent overflow */
    for (int i = 0; i < count; i++) sum += values[i];
    return (float)sum / (float)count;
}

Scalability

Definition: The ability of software to handle growing amounts of work or data without a complete redesign.
/* Bad: Fixed size, cannot grow */
#define MAX_DEVICES 3
int device_ids[MAX_DEVICES];

/* Good: Configurable at compile time, easy to scale */
#ifndef MAX_DEVICES
#define MAX_DEVICES 32
#endif

typedef struct {
    int id;
    char name[16];
} device_t;

device_t device_list[MAX_DEVICES];
int device_count = 0;

Cohesion

Definition: The degree to which elements within a module belong together. High cohesion is good — every function in a module serves the same purpose.
/* Bad: Low cohesion - unrelated functions in one file */
/* utils.c */
int read_temperature(void);
void send_email(const char *to, const char *body);
void sort_array(int arr[], int n);
int calculate_crc(uint8_t *data, int len);

/* Good: High cohesion - all functions serve one purpose */
/* crc.c */
uint16_t crc16_ccitt(const uint8_t *data, int len);
uint32_t crc32(const uint8_t *data, int len);
uint8_t  crc8_maxim(const uint8_t *data, int len);

Coupling

Definition: The degree of dependency between modules. Low coupling is good — changing one module should not force changes in another.
/* Bad: Tight coupling - display knows about sensor internals */
void display_update(void) {
    extern int sensor_raw_value;       /* Reaches into sensor module */
    extern int sensor_calibration;     /* Knows internal details */
    int temp = (sensor_raw_value - sensor_calibration) / 10;
    lcd_print(temp);
}

/* Good: Loose coupling - display only calls a public function */
void display_update(void) {
    int temp = sensor_get_temperature();  /* Clean interface */
    lcd_print(temp);
}

Summary Table

AttributeKey QuestionGoal
ReadabilityCan someone else understand this?Clear names, simple logic
MaintainabilityCan I change this easily?Named constants, small functions
TestabilityCan I test this without hardware?Inject dependencies, pure functions
ReliabilityDoes it handle edge cases?Null checks, error handling
ModularityIs each module independent?One module, one job
ReusabilityCan I use this elsewhere?Generic interfaces, no hardcoding
ExtensibilityCan I add features without editing?Function pointers, registration
PortabilityDoes it work on other platforms?HAL layers, no platform specifics
RobustnessDoes it survive bad inputs?Defensive coding, bounds checks
ScalabilityWill it work with more data?Configurable limits, clean data structures
CohesionDo these functions belong together?High cohesion within modules
CouplingAre modules too dependent?Low coupling between modules

Key Takeaways

  • Quality attributes are not optional extras — they determine whether your code survives its first maintenance cycle
  • Most attributes are interrelated: improving modularity usually improves testability, maintainability, and reusability
  • Aim for high cohesion (related things together) and low coupling (modules independent of each other)
  • You do not need to optimize all attributes at once — focus on the ones that matter most for your project

📖 Related: Code Smells in C: Change Preventers (Duplicate Code, Tight Coupling, and More)

Leave a Reply

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