Стиль кодування
Кожна компанія має власний стиль кодування.
Мій стиль кодування походить зі стандартів, що застосовуються в автомобільній галузі, — удосконалених для embedded-розробки, — бо, на мій погляд, він найчитабельніший і найпідтримуваніший.
Нижче наведено обґрунтування кожного правила та те, як воно сприяє ясності й довгостроковій підтримуваності.
🔡 Змінні
Змінні мають іменуватися в стилі lower camelCase.
Ім'я змінної має містити щонайменше три символи.
❌ Неправильно
int x, y, z;
for(int i = 0; i < 10; i++) { ... }
✅ Правильно
int loopIndex;
float temperatureCelsius;
uint8_t sensorCount;
📘 Чому: Кожна змінна повинна мати змістовне ім'я.
Короткі, незрозумілі імена руйнують читабельність і ускладнюють підтримку коду.
⚠️ Зовнішні змінні
Існування змінних extern має бути суворо заборонене.
Якщо таку змінну знайдено, її автора слід публічно лінчувати (образно кажучи 😉).
💡 Причина:
Зовнішні змінні майже неможливо відстежити — важко зрозуміти, хто їх записує чи читає.
Завжди інкапсулюйте дані за getters і setters.
🧱 Типи даних
Імена типів дотримуються шаблону lowerCamelCase, але мають закінчуватися на _t,
за стандартним іменуванням у <stdint.h> (наприклад, uint8_t, uint16_t тощо).
✅ Приклад
typedef uint16_t temperature_t;
typedef float voltage_t;
typedef uint8_t humidity_t;
🧩 Причина:
Ви одразу знатимете, чи ідентифікатор позначає тип, чи змінну.
🚫 Уникайте універсальних вбудованих типів
Не використовуйте безпосередньо у проєкті звичайні uint8_t, uint16_t тощо.
Натомість визначайте власні семантично змістовні typedef.
✅ Це запобігає змішуванню несумісних змінних:
ви не зможете випадково присвоїтиtemperature_tзміннійhumidity_t.
temperature_t temp = 25;
humidity_t humidity = 40;
temp = humidity; // ❌ Compilation error
Це забезпечує безпеку типів і скорочує час налагодження.
🧭 Імена функцій
Імена функцій мають використовувати стиль UpperCamelCase.
Кожна функція повинна містити префікс модуля, щоб було зрозуміло, кому вона належить.
✅ Приклад
rcc_InitSystemClock();
rcc_GetClockFrequency();
gpio_SetPinState(GPIOA, PIN_5, true);
📘 Чому:
Одразу зрозуміло, чи ідентифікатор є змінною, функцією або типом.
Префікс модуля дає змогу легко знайти функцію в ієрархії проєкту.
⚙️ Директиви препроцесора
Директиви препроцесора та макроси мають використовувати UPPER_CASE_WITH_UNDERSCORES.
✅ Приклад
#define MAX_SENSOR_COUNT 8
#define ENABLE_DEBUG_MODE 1
📘 Чому:
Іменування верхнім регістром одразу показує, що це символ часу компіляції, а не змінна часу виконання.
📄 Імена файлів
Імена файлів використовують стиль UpperCamelCase.
Кожне ім'я файлу має починатися з назви модуля, після якої йдуть підкреслення та опис функціональності.
✅ Приклад
Rcc_Config.c
Gpio_Handler.c
Modbus_Transport.c
🧩 Повна структура організації файлів описана на окремій сторінці вікі:
Організація файлів
🗂️ Переліки (Enumerations)
Типи переліків дотримуються UpperCamelCase для типу та UPPER_CASE для елементів переліку.
✅ Приклад
typedef enum
{
STATE_IDLE,
STATE_RUNNING,
STATE_ERROR
} systemState_t;
📘 Чому:
Значення верхнім регістром відрізняють константи переліку від змінних, а суфікс_tчітко позначає тип.
🧮 Константи
Константи, визначені в коді (не макроси), мають використовувати імена в стилі UpperCamelCase.
✅ Приклад
const uint32_t SystemTimeoutMs = 5000;
const float PiValue = 3.14159f;
💡 Віддавайте перевагу
constперед#defineскрізь, де це можливо, — це забезпечує перевірку типів і контроль області видимості.
🧰 Структури та об'єднання
Імена структур використовують UpperCamelCase,
а їхні поля — lowerCamelCase.
✅ Приклад
typedef struct
{
uint8_t deviceId;
uint16_t firmwareVersion;
float batteryVoltage;
} DeviceInfo_t;
📘 Чому:
Послідовний регістр чітко розрізняє тип структури та її поля.
🔣 Макроси та inline-функції
- Макроси (
#define) → UPPER_CASE_WITH_UNDERSCORES - Допоміжні inline-функції → UpperCamelCase (так само, як звичайні функції)
✅ Приклад
#define ENABLE_INTERRUPTS() __enable_irq()
#define DISABLE_INTERRUPTS() __disable_irq()
static inline void DelayMs(uint32_t ms) { ... }
💬 Коментарі та документація
Використовуйте коментарі у стилі Doxygen для всіх публічних функцій, типів і макросів.
✅ Приклад
/**
* @brief Initializes system clocks.
* @param None
* @retval None
*/
void Rcc_InitSystemClock(void);
💡 Тримайте коментарі короткими, точними й написаними англійською.
Описуйте, чому щось робиться, а не що робиться — це має бути зрозуміло з самого коду.
🧾 Правила форматування
| Правило | Опис |
|---|---|
| Відступи | 4 пробіли, ніколи табуляції |
| Дужки | Стиль K&R ({ в тому самому рядку) |
| Максимальна довжина рядка | 120 символів |
| Пробіли | Завжди пробіл після ком і навколо операторів |
| Порожні рядки | Використовуйте для візуального розділення логічних секцій |
| Include guards | Формат #ifndef MODULE_FILENAME_H |
| Порядок include | <system> → "project" → "module" |
✅ Приклад
#include <stdint.h>
#include "Rcc_Config.h"
#include "Gpio_Handler.h"
void Gpio_Init(void)
{
gpioState_t state = GPIO_LOW;
if (state == GPIO_LOW)
{
gpio_SetPinState(GPIOA, PIN_5, true);
}
}
🧩 Простори імен і префікси
Кожен модуль повинен мати унікальний префікс (наприклад, rcc_, gpio_, adc_).
Це гарантує, що імена функцій і типів не конфліктуватимуть у межах системи.
📘 Практичне правило:
- Префікс = назва модуля
- CamelCase = функція
- _t = тип
- lowerCamelCase = змінна
Приклад узгодженості:
rcc_Init()
rcc_Config_t
rccState_t
rcc_GetClock()
✅ Підсумок
| Елемент | Стиль | Приклад |
|---|---|---|
| Змінна | lowerCamelCase | loopIndex, sensorCount |
| Тип | lowerCamelCase + _t | temperature_t, systemState_t |
| Функція | UpperCamelCase | Rcc_InitSystemClock() |
| Макрос / Define | UPPER_CASE_WITH_UNDERSCORES | MAX_BUFFER_SIZE |
| Константа | UpperCamelCase | PiValue |
| Ім'я структури | UpperCamelCase | DeviceInfo_t |
| Поле структури | lowerCamelCase | deviceId |
| Значення enum | UPPER_CASE | STATE_IDLE |
| Ім'я файлу | UpperCamelCase + підкреслення | Gpio_Handler.c |