Начну с важного: особой защиты от DoS атак здесь нет, любой злоумышленник со скриптом на python заблокирует сервер на веки, если просто не будет посылать читать данные из сокета. В прочем это учебный проект, чья цель попрактиковаться в работе с сетью на линукс без высокоуровневых абстракций и напрямую читать / писать в сокет, как-будто это условный файл (оч нравится эта фишка linux, что работа с сетью, stdin/out/err (консолью) и всем остальным унифицирована до структуры сокета с методами read, write, close и ещё каким-то). А windows никому и не нужен, так как на нём сервера не крутятся. У него нет такой крутой унификации(((. С ним практиковаться не хочу. Знаю только, что там нет epoll, потому Python async-сервера запущенные на windows могут ломаться из-за того, что event-loop использует костыли для имитации epoll. Anyway, я и так и так работаю через wsl, так что...
Тип: любой struct, enum, union.
Функция типа: любой метод, или функция, предназначенная для работы с этим типом, будь то конструктор, геттер, или мутирующий метод.
protected - Обращение доступно из функций типа и из методов типов, использующих этот тип внутри композиции (например, HTTPResponse response внутри HTTPJsonResponse).
private - Обращение доступно только из методов или функций привязанных к этому конкретному типу. Обращение через композицию не доступно.
Предисловие
В C запрещено давать префикс _ чему либо, кроме полей структуры.
_[A-Z]зарезервировано для новых ключевых слов C.__зарезервировано для libc и прочего внутреннего.- Формально можно использовать
_[a-z], но некоторые реализации всё равно его используют для внутренних нужд.
На это конечно можно забить, но в теории что-то может сломаться из-за конфликта при обновлениях.
Поля структуры:
protected: префикс_, напр._fieldprivate: префикс__, напр.__field
Функции, привязанные к типу (доступные в .h):
protected: префиксpt_, напр.pt_get_Vec_buffer_sizeprivate: префиксpv_, напр.pv_get_Vec_buffer_size
Функции, привязанные к типу (доступны только в .c):
protected: помечать какstatic, напр.static ... get_Vec_buffer_size()private: помечать какstatic, напр.static ... get_Vec_buffer_size()
Функции (только в .c):\
private: помечать какstatic, напр.static ... do_something().
Типы (уровень модуля):
Все типы, используемые в публичных функциях и переменных, также должны быть публичными.
private: префиксm_, напр.m_ReadRequestState
Глобальные переменные (только в .c):\
private: помечать какstaticc префиксомm_, напр.static m_CONFIG_FIELD_NAMES. static здесь означает "не помечай доступным для импорта из .obj файла"
Макросы в .h:\
private: префиксm_, напр.m_DEF_WIDE_PTR
Макросы в .c:
Без спецификаторов уровня доступа.
Макросы, определяемые внутри тела функций, должны начинаться с префикса l_ (локальная) и обязательно должны быть удалены в самом низу тела функции.
Пример:
int foo(int x) {
int y = x * x * x;
// такие макросы, обращающиеся к локальным переменным, лучше объявлять после объявления этих переменных, пусть оно и будет работать, если объявить перед т.к это текстовая подстановка
#define l_BAR(factor) (x + (y * factor))
...
if (l_BAR(4) > 11)
return 42;
...
return x * 2;
#undef l_SOMETHING
}Макросы для передачи их в качестве параметра в другой макрос, например:
#define CONF_FIELDS \
X(u16, FOO) \
X(u16, BAR) \
X(char*, BAZ)где X - макрос-параметр вида X(T, name).
Пример использования:
struct Conf {
#define X(T, name) T name;
CONF_FIELDS
#undef X
}Или другой пример:
#define SERR(code_) \
(struct X_FUNC_NAME##_Result) { \
.code = func_name##_##code_ \
}где X_FUNC_NAME - просто "some_func".
Пример использования:
struct foo_Result foo(void data[], size_t data_len, size_t i) {
#define X_FUNC_NAME foo
if (i >= data_len)
return SERR(OutOfBounds); // instead ERR(foo, OutOfBounds);
return SOK(data[i]);
#undef X_FUNC_NAME
}Все глобальные переменные только const.
-
Если размер может быть рассчитан на этапе компиляции (напр. внутри используется
snprintfформатирование с подстановкой элементов известной длины):
Принимать ссылку на буфер и размер буфера в байтах или ёмкости (размер в элементах), например,char *out, size_t out_len. Возвращать необходимый размер буфера результатом выполнения функции. Создать макрос, вычисляющий макс. необходимый размер буфера во время компиляции для избежания лишних аллокаций, чтобы определить буфер этого фиксированного размера на стеке.
Например, этоto_stringдляHTTPVersion, чей формат и макс. размер подставленных значений заранее известен:"HTTP/255.255". -
Если размер невозможно, или крайне сложно посчитать заранее, возвращать ссылку на аллоцированную через
allocпамять с указанием в комментарии, что эту память необходимо освободить черезfree.
- Модификатор
*указывается рядом с именем переменной, а не с типом. Пример:int *pX. Связано с тем, что*принадлежит к имени, а не типу. Записьint* px, py;объявит указательpxи переменную типаintpy, а не 2 указателя наint. - Фигурная скобка остаётся на той же строке, что и оператор / объявление. Пример:
if (expr) {, а неif (expr). - В аргументах функции, указатель на массив указателей записывать как
*X[], а не**X. Пример:char *argv[]. Для явности, компилятор всё равно преобразует кchar **argv.
CamelCase: любые типы, напр. HTTPRequest.
SCREAMING_CASE: макросы, константы. Примеры: DEF_RESULT, MAX,
m_CONF_FIELD_NAMES.
snake_case: функции, локальные переменные. Примеры:, ``
Функции делятся на группы. Каждая группа выделяется в .h & .c файле с помощью комментария, например /* Special functions */ или /* Read-only methods */.
- Специальные
- Конструкторы:
- Возвращают новый экземпляр типа на основе переданных аргументов не содержащих другой экземпляр этого типа.
- Формат:
T new_<T>[_<subname>](...) - Примеры:
struct Vec new_Vec(size_t item_size)struct Vec new_Vec_with_buffer(size_t item_size, void *buffer, size_t buffer_capacity)struct Config new_Config_from_command_line(int argc, char *argv[])
- Парсеры:
- Похожи на конструкторы, но возвращают
bool(true- успешно,false- не валидная строка) и новый экземпляр структуры в out параметре (указатель). Допустимо 2 формата для строк с\0на конце и без. (реализовывать оба не обязательно). Обязательно должны быть объявлены с атрибутом[[nodiscard]](нельзя игнорировать возвращаемое значение). - Форматы:
bool parse_<T>[_<subname>](char str[], T *out)bool parse_n_<T>[_<subname>](char str[], size_t len, T *out)
- Примеры:
[[nodiscard]] bool parse_HTTPVersion(char str[], struct HTTPVersion *out)[[nodiscard]] bool parse_n_HTTPVersion(char str[], size_t len, struct HTTPVersion *out)[[nodiscard]] bool parse_long(char str[], long *out)[[nodiscard]] bool parse_n_long(char str[], size_t len, long *out)
- Похожи на конструкторы, но возвращают
- Деструкторы:
- Разрушают объект, освобождая ресурсы (аллоцированную память, открытые файлы и т.д.). После уничтожения объект должен быть не доступен для взаимодействия.
- Формат:
void dispose_<T>(T *this) - Пример:
void dispose_Vec(struct Vec *this)
- Конструкторы:
- Методы:
- Read-only:
- Методы, никак не мутирующие состояние объекта. Например, получение объекта по индексу.
- Формат:
<T>_<name> - Примеры:
- Мутирующие методы:
- .
- Формат:
- Примеры:
- Read-only:
- Геттеры / Сеттеры:
- Управляемо возвращают / устанавливают значение поля структуры, либо вычисляемого поля. Основное применение - получение значения приватного поля, чтобы не делать поле публичным с пометкой "не мутируйте поле, это только для чтения!"
- Важно:
- Не создавайте приватное поле структуры и 2 метода get/set для него. Просто сделайте поле публичным. Создавайте эти методы только если вам нужно сделать что-то помимо простого чтения/установки (напр. обновить связанные поля). Актуально и для других языков.
- Для бизнес-операций (в этом проекте таких нет, но если были бы), вместо условного
set_User_nameлучше создавайте явныеUser_renameметоды. Не выполняйте бизнес-логику в геттерах/сеттерах, они нужны только для поддержания целостности данных И вычисляемых значений. Актуально и для других языков.
- Форматы:
get_<T>_<name>(const T *this)set_<T>_<name>(T *this, value)
- Примеры:
size_t get_Vec_count(const struct Vec *this)size_t get_Vec_buffer_size(const struct Vec *this)- вычисляемое значение. Удобнее, чемVec_calc_buffer_size.void set_Window_is_visible(struct SomeType *this, bool is_visible), но тут как-будто лучше простоvoid Window_show(Window *this)в паре сvoid Window_hide(Window *this)
- В C обычно принято именовать функции полностью в
snake_case, а для указания на тип добавлять префикс с типом вsnake_case:vec_create,vec_push,vec_free,http_request_create,http_request_parse. - Св-ва могут быть спокойно заменены на обычные read-only и мутирующие методы. Также вычисляемые значения в языках без свойств обычно именуют без каких-либо доп. слов, например метод
level()дляGameSessionв тетрисе для вычисления текущего уровня по набранным очкам, безcalculate,getи т.д. - "Специальные" функции тоже могут быть заменены на обычные. Например
new_Vec->Vec_new,dispose_Vec->Vec_dispose
Все функции возвращающие Result или только ResCode должны быть помечены атрибутом [[nodiscard]] (нельзя игнорировать возвращаемое значение).