Table of Contents
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
| Attribute | Key Question | Goal |
|---|---|---|
| Readability | Can someone else understand this? | Clear names, simple logic |
| Maintainability | Can I change this easily? | Named constants, small functions |
| Testability | Can I test this without hardware? | Inject dependencies, pure functions |
| Reliability | Does it handle edge cases? | Null checks, error handling |
| Modularity | Is each module independent? | One module, one job |
| Reusability | Can I use this elsewhere? | Generic interfaces, no hardcoding |
| Extensibility | Can I add features without editing? | Function pointers, registration |
| Portability | Does it work on other platforms? | HAL layers, no platform specifics |
| Robustness | Does it survive bad inputs? | Defensive coding, bounds checks |
| Scalability | Will it work with more data? | Configurable limits, clean data structures |
| Cohesion | Do these functions belong together? | High cohesion within modules |
| Coupling | Are 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)

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.







