From 64b9f8953311fd4f21e221b5e3affc05b69f0a23 Mon Sep 17 00:00:00 2001 From: vdromanov Date: Wed, 24 Sep 2025 16:39:45 +0300 Subject: [PATCH 1/6] remove workflow --- README.md | 1 - workflow.md | 311 ---------------------------------------------------- 2 files changed, 312 deletions(-) delete mode 100644 workflow.md diff --git a/README.md b/README.md index 71f094d..a1d309b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Codestyle ### Общие - * [Workflow](./workflow.md) - как пользоваться Git и трекером задач * [Tools](./tools.md) - полезные утилиты на все случаи жизни ### Разработка под Linux diff --git a/workflow.md b/workflow.md deleted file mode 100644 index ddb7d75..0000000 --- a/workflow.md +++ /dev/null @@ -1,311 +0,0 @@ -Процесс разработки проектов Wiren Board с использованием Git, Github, Youtrack -============================ - -Общее -------------- - -Процесс построен так: описание задачи в битриксе, работа над кодом в нашем гите на гитхабе. -Работать нужно в ветке в гите, всё коммитить, в конце прибираться и сделать pull-request. -Дальше на этот PR смотрит ответственный человек из числа коллег и либо принимает как есть, либо как-то комментирует и просит изменений. - - -_Верный сценарий - одна фича/исправление - один PR._ - - -Документирование -------------------------- -Новую функциональность после принятия PR (но до релиза) нужно описать в нашей публичной вики и хорошо протестировать. - -Если разработка касается прошивок, то нужно помнить, что железки чаще всего используют с нашим линуксовым контроллером, так что потом с ним тоже надо протестировать. - - -Стайлгайд ------------- -Код пишем одинаково. Что-то по [embedded_C](embedded_c.en.md)/[C++](C%2B%2B.ru.md)/[Python](python.ru.md) описано в .md рядом, что-то выяснится после ревью пулл-реквестов. Если чего-то нет в стайлгайде, то лучше посмотреть как написано рядом. - - -Создание веток в Git --------------------- - -В этом разделе описана работа с ветками _до создания PR_. Работа с ветками, для которых уже создан PR, отличается, смотри следующий раздел. - -_Следует писать сообщения коммитам на английском в повелительном наклонении. Образец: "add <что_было_добавлено>" - ок. "add**ed** <что_было_добавлено>" или "<что_было_добавлено> is added" - не ок. Почему так - можно почитать [здесь](https://cbea.ms/git-commit/)._ - -В гайдлайне работа с git рассматривается на примере **консольного** клиента. _Используя его, при возникновении проблем нагуглить решение / получить консультацию от коллег - сильно проще!_ - - 1. Получаем последнее состояние ветки `main` (или `master`). - 2. Создаём ветку. Допустимые имена веток: - - - `feature/SOFT-XXXX-my-description` или `bugfix/SOFT-XXXX-my-description` (`XXXX` - номер тикета в youtrack); - - `feature/my-description` или `bugfix/my-description`, если тикета нет (например, быстро что-то исправили); - - `tmp/username/my-description` для временных веток. - - 3. Делаем что-то в ветке. До того как был создан PR, с веткой можно делать что угодно: перетаскивать (rebase), сливать коммиты и так далее. - Соответственно, можно делать `git push -f`. Нельзя писать в чужие ветки без согласования с автором. И нет смысла ожидать, что чужая ветка - будет изменяться предсказуемо, если это не согласовано. - - Если в ветке потребовалось переименовать/переместить файлы и одновременно отредактировать их, - перемещение нужно сделать отдельным коммитом перед редактированием. Если этого не сделать, Github - может интерпретировать это изменение как удаление старого файла и создание нового с нуля, а так можно - будет хотя бы посмотреть изменение в рамках одного коммита. - - 4. Перед подготовкой PR нужно сделать следующее: - - - убедиться, что история линейна (ветка - последовательность из одного или нескольких коммитов без мержей с другими ветками, в т.ч. `main` (или `master`)); - - в ветке адекватное количество коммитов (часто достаточно одного, желательно - до 5); - - каждый коммит имеет смысловую нагрузку и соответствует какому-то атомарному изменению; - - одно и то же место в коде не должно изменяться разными коммитами; - - если потребовалось переименование/перемещение файлов, оно должно быть выполнено отдельным коммитом - до редактирования файла; - - каждый коммит имеет адекватное описание. Например, `fix some bugs` - плохое описание. В хорошем указано, что изменилось и зачем это нужно, - идеально было бы сослаться на тикет (по номеру) или обсуждение в техподдержке; - - очень желательно, чтобы в каждом коммите ветки проект собирался и получалось что-то адекватное; - - желательно выдерживать хороший баланс между объёмом коммита и его описанием, не стоит делать коммит с `названием добавил режим работы` - в котором изменения в 10 файлах охватывают и режим работы, и регистры, и сохранение их во флеш, лучше разбить работу каждого модуля с новой функцией - в отдельный коммиты, такие коммиты приятнее смотреть когда приходится окунуться в историю проекта на пару лет. - - если проект собирается в deb-пакет или в файл прошивки с версией, то нужно добавить запись в changelog. Это можно сделать в одном коммите с - изменениями, если их не очень много, или отдельным коммитом в конце. Правила обновления номера версии - semver (https://semver.org/lang/ru/). - Пиши запись в changelog опрятно и грамотно: потом эта запись появится в релизном changelog-е и будет видна всем пользователям. - - если проект - прошивка то последним перед мерджем должен быть коммит с сообщением вида change version to: x.x.x с записью в Changelog и изменениями в регистре. - на данном коммите проект точно должен собираться в соответствующую версию. - - если в проекте настроена проверка покрытия тестами и в PR добавились тесты, увеличивающие покрытие, необходимо увеличить порог в Jenkinsfile. - - В приведении ветки в порядок помогут эти команды: `git rebase -i` и `git add -p`, можно почитать об этом в мане или в интернете. - - Если строгое деление на коммиты не обязательно и их можно объединить в один перед слиянием, можно не объединять их заранее, - а просто выполнить слияние кнопкой "Squash and merge". Адекватные описания коммитов и обновление версии пакета всё ещё обязательны. - - 5. Создаём PR. После создания PR ветка перестаёт быть только твоей собственностью, и мы начинаем действовать по инструкции из следующего раздела. - -Нельзя смешивать в одном PR изменения в функционале и форматировании кода. Их стоит оформлять в виде нескольких отдельных последовательных PR. - -Создание нового репозитория на GitHub -------------------------------------- - - ### 1. Получение прав - - Для того, чтобы вы имели возможность создавать PR в открытых репозиториях и - создавать новые репозитории от имени компании wirenboard - нужно: - - Создать аккаунт GitHub зарегистрировав его на почту - компании `*@wirenboard.com` - - Запросить у непосредственного руководителя, чтобы вас добавили - в организацию `@wirenboard` и в группу `developers` - - Проверить что вам выдали права можно следующим образом: - - У вас на личной странице `Your profile`, под вашей аватаркой - появится - иконка организации wirenboard. - - На главной странице [wirenboard](https://github.com/wirenboard) появится - зеленая кнопка "New" - позволяющая вам создать новый репозиторий от имени - компании. - - - - ### 2. Создание репозитория - - Создать новый репозиторий можно несколькими путями, но самый простой вариант: - - Перейти на главную страницу WB на GitHub: https://github.com/wirenboard - - Нажить на зеленую кнопку "New". - - Название репозитория должно начинаться на `wb-*`, слова отделяются - с помошью тире, например `wb-embedded-controller`. - Конкретное название нужно обсудить с владельцем продукта. - - Добавить `Description` (например из файла дебиан пакета `debian/control`) - - По умолчанию большая часть репозиториев должна иметь visibility: `Public`. - Конкретный тип нужно обсудить с владельцем продукта. - - На данном этапе не добавляем `README` , `.gitignore`, `license`. - - Нажимаем `Create repository` - - После создания репозитория GitHub покажет вам страницу `Quick setup` - на которой нам пригадится ссылка `creating a new file`. - - ### 3. Создание первого коммита - - Новый репозиторий должен быть пустым - сразу заливать код в main является - плохой практикой, но создать PR в пустой репозиторий (без файлов) нельзя. - PR должен иметь diff относительно чего-то, поэтому нужно сначала сделать - первый коммит, который должен быть максимально пустым и уже относительно - него будет сделан первый PR с вашими измеренеиями. - - Проше всего сделать первый коммит сразу через WEBUI - нажав на странице - репозитория ссылку `creating a new file`: - - Добавить `README.md` (Можно пустой) - - Сделать коммит с сообщением "Initial commit" - - Залить первый коммит в main ветку на прямую - - ### 4. Общие настройки репозитория - - Если репозиторий будет содержать правила для wb-rules - заходим в созданный - репозиторий, справа от слова `About` находим шестеренку, нажимаем на нее: - - В `Topics` добавляем `wb-rules` - - Выключить `Packages` и `Environments` с главной страницы (не используем) - - Выбираем сверху вкладку `Settings` для данного репозитория, далее: - - Выключить `Wikis`, `Issues`, `Projects` (не используем) - - Перейти слева в раздел `Code and automation` -> `Access`: - - Выключить `Actions` поставив `Disable actions` (кроме репозиториев, которые планируется собирать в GitHub Actions в docker-образы и хранить на ghcr.io; например, веб-сервисы, которые деплоятся через infra в docker-контейнерах) - - ### 5. Настройки доступа - - Для полноценного использования коллегами нового репозитория в разделе - `Settings` репозитория -> `Access` -> `Collaborators and teams` нужно - добавить в коллабораторы людей. - - В каждом репозитории добавить: - - Евгения [`@evgeny-boger`](https://github.com/evgeny-boger) - с `Admin` доступом напрямую (не полагаясь на права организации). - - Если репозиторий `Public`, то добавить всех девелоперов: - - Команду `@wirenboard/developers` c `Maintain` доступом для создания PR - в вашем репозитории. Мотивация в том, что разница с `Write` не большая, - но с Maintain можно поправить описание репы, топики и тд по мелочи. - - Опционально, в зависимости от задачи: - - Так же для ревью отделом документации можно (не обязательно) добавить - команду `@wirenboard/docs` c `Triage` доступом. - - Для более специфичных задач смотреть актуальный список групп и инструкции. - - ### 6. Защита веток - - Зайти в раздел `Settings` репозитория -> `Code and automation` -> `Branches` - и добавить два отдельных `Branch protection rules`: - - В первый раз - нужно нажать на `Add classic branch protection rule`, - а при последующих будет кнопка `Add rule` - - Ввести `Branch name pattern` первому правилу `main`, второму `release/*` - - Включить галочку: - `Require a pull request before merging` -> (`Require approvals: 1`) - - По согласованию с командой - можно включить галку разрешающую делать - только слияния с одним коммитом (с использованием squash). Для этого - отметьте `Require linear history`. - - Нажать внизу на зеленую кнопку `Create`. - - ### 7. Создание первого PR - - Теперь можно сделать PR - который покажет коллегам что вы добавляете в main. - Данный коммит уже должен содержать минимум, три составляющих: - - Файл лицензии `LICENSE`, за основу можно взять следующий файл - из репозитория `wirenboard/wb-mqtt-serial`: - [LICENSE](https://github.com/wirenboard/wb-mqtt-serial/blob/master/LICENSE) - - Дополненный `README.md` - - Ваш первый реализованный функционал который должен пройти ревью - - Далее нужно опубликовать PR, дождаться проверки со стороны коллег - и самостоятельнопровести слияние. Подробнее об этом смотрите ниже - в - секции "Работа с PR". - -Работа с PR ------------ - -Работа с веткой в PR отличается от обычной работы с веткой, потому что теперь в игру вступают инструменты Github, -которые капризно относятся к изменениям истории. - -**Особенности работы с веткой в PR**: - - - **нельзя** менять историю! То есть, запрет на любые действия, кроме добавления коммитов. - Github может потерять обсуждения, привязанные к коду. Все изменения в рамках PR создаём новыми коммитами, - не забывая о правилах оформления коммитов из описания выше. - -**Жизненный цикл PR**: Открытие PR => ревью и правки => слияние (Merge) => удаление ветки. - - 1. **Открытие PR:** - - Нужно добавить ревьювера/ов и описание. - Цель описания - помочь ревьюверам (даже плохо знакомым с проектом) быстро погрузиться в контекст. - - В описании - концептуально рассказать, что происходит; добавить скриншоты (или gif/видео) было/стало (если изменения в интерфейсе). - PR без описаний или с плохими описаниями (из которых ревьювер не может войти в контекст) могут быть отправлены ревьювером на - доработку без заглядывания в код! Пожалуйста, цените время своих коллег. - - - Обязательно надо добавить мейнтейнера пакета (указан в `debian/control`). - - Если указанный мейнтейнер у нас уже не работает, надо спросить в чате, кто сейчас отвечает за пакет. - - Если добавляешь ещё кого-то в ревьюверы (например, обсуждал это с кем-то заранее), то в описании PR надо призвать их - через `@mention` и объяснить, зачем кто-то ещё был добавлен. Например, `@webconn взгляни на изменения в Jenkinsfile`; - - Github не позволяет добавлять нескольких ревьюверов в приватные репозитории; если это нужно, то одного добавляем явно, - а остальных призываем в описании, через `@mention`. Добавить своё ревью при этом они смогут без проблем обычными средствами гитхаба. - - 2. **Ревью и правки**: - - В это время другие люди смотрят изменения, идёт обсуждение спорных моментов, предлагаются правки. - - - Правки вносятся отдельными коммитами сверху. Можно разделять правки, относящиеся к разным исходным коммитам (чтобы потом привести историю в порядок, если надо). - - Если в процессе исправлений нужно что-то проверить в сборке на CI (например, починить упавшую сборку или тесты) - стоит использовать для этого временную ветку (вида ``tmp//``), чтобы не присылать ревьюверам лишних уведомлений. После использования не забыть эту ветку удалить. - - _Хороший тон_ - отвечать что-то вроде "готово" под комментариями с замечаниями. - - Не трогать кнопку "Resolve Conversation". Она - для ревьювера. - - Когда все правки внесены _(не забыть проверить, что проект собирается, запускается и функциональность вокруг вносимых правок работает должным образом)_ - сделать push ветки, из которой открыт PR, и нажать на "re-request review" (сверху в интерфейсе гитхаба; рядом с ревьюверами). **_Напомнить ревьюверам в лс/dev-чат, что пора смотреть_**. - - 3. **Слияние (merge)**. - - _Желательный сценарий - одна задача -> одна небольшая фича -> один PR -> один коммит в мастер в итоге._ - - Если вдруг нужно сохранить коммиты (нежелательно) - см 3.1* - - - Слияние выполняет автор PR после того, как был получен approve от всех отмеченных ревьюверов. - - Последний коммит ветки обязательно должен пройти проверку `continuous-integration/jenkins/pr-merge` - (если настроен CI для репозитория). Если Jenkins ругается - не стесняемся читать логи (внизу страницы "Show all checks" -> "Details" у любого красного крестика -> "Console Output"). Не получилось разобраться быстро - дёргаем ответственных за Jenkins. - - Вливаем по умолчанию с помощью `Squash and merge` (сольёт все коммиты в один и даст отредактировать описание), написав осмысленное сообщение, отражающее суть проделанного. - Если ``master/main`` уже убежал - нужно сделать ``git pull origin master`` и порешать merge-конфликты (не забываем про ``git add`` и ``git commit`` после). - - Если хочется сохранить несколько коммитов в ветке, идём по 3.1*. - - Сразу удаляй ненужные ветки - не заставляй Jenkins страдать! (на гитхабе после вливания ветки появляется кнопка "Delete Branch" внизу) - -3.1* **Слияние с сохранением коммитов (нежелательно)** - -Если вдруг надо сохранить несколько коммитов в ветке, то надо проверить, продвинулась ли ветка `main` (или `master`) -за время существования PR, и были ли в PR добавлены коммиты, которые нужно слить с другими. - -Если не продвинулась и нет лишних коммитов, то просто выполняем слияние кнопкой `Merge pull request`. - -Иначе действуем по следующей инструкции: - - 1. Закрываем существующий PR без слияния. - 2. Создаём из исходной ветки новую с именем `<имя ветки>-rebaseX`, где X - номер итерации (начиная с 1) на случай, если понадобится - несколько раз подряд переносить ветку. Старую ветку теперь обновлять нельзя. - 3. Вносим изменения в историю: - - - получаем последнее состояние `main` (или `master`): `git checkout main && git pull` - - переходим в нашу исходную ветку: `git checkout feature/XXXXX-my-feature`; - - создаём в этом месте rebase-ветку: `git checkout -B feature/XXXXX-my-feature-rebaseX`; - - переносим ветку на вершину `main` (или `master`): `$ git rebase main`; - - если нужно, сливаем коммиты с правками с исходными коммитами: `$ git rebase -i main`. - - 4. Создаём новый PR с новой веткой. Ссылаемся на новый PR в комментариях к старому, оставляем ссылку на старый в новом. - В общем, наводим связность. - 5. _<сознательность\_моде>_ Если в процессе rebase не было сделано никаких **новых** изменений (например, для решения - конфликтов с новым `main` (или `master`), то вливать PR можно без ревью. Правила вливания как в первом сценарии. - Чтобы посмотреть изменения между новой и старой ветками, можно воспользоваться `git diff <имя-старой-ветки>` - (находясь в этот момент на новой). - 6. Если были внесены изменения, с которыми стоит ознакомиться команде, то нужно добавлять ревьюверов и весь процесс повторяется сначала. - -Стабильные версии и релизы --------------------------- - -После введения системы регулярных релизов (https://wirenboard.com/wiki/Software_Releases) то, что находится в `main` (или `master`), не является стабильным. - -Код с вершины `main` (или `master`) автоматически загружается в `unstable` (и, следовательно, в `testing`, если он не заморожен). - -Версия кода становится стабильной после включения её в релизный список и выпуска релиза. В момент выпуска релиза коммит с выпущенной -версией помечается веткой с именем `release/wb-YYYY` (`YYYY` - номер релиза). - -Исправления, вносимые в репозиторий после выпуска релиза, должны составлять линейную историю в ветке `release/wb-YYYY`. -Этого можно достичь несколькими способами: - - 1. Если исправление вышло сразу после публикации релиза, и ветка `main` (или `master`) находится в той же позиции, что `release/wb-YYYY`, - то после вливания ветки с исправлением в `main` (или `master`) и добавления новой версии в релизный список, ветка `release/wb-YYYY` - переносится на новый коммит в `main` (или `master`) (типа fast-forward merge). - -В этом случае номер версии изменяется естественным образом согласно semver. - - - 2. Если после выпуска релиза в `master` оказались изменения, которые не должны попасть в релиз, в месте `release/wb-YYYY` создаётся - ветка с именем `feature/XXXXX-description/release/wb-YYYY`, в которую вносятся нужные исправления. После этого создаётся PR - из новой ветки в `release/wb-YYYY`. - -Во втором случае номер версии формируется как версия уже выпущенного пакета и суффикс `-wbZ`, где `Z` - номер патча в этот релиз. - -Например, если изначально в релиз вошла версия `2.3.4`, но в процессе потребовалось добавить патч в выпущенную версию, -номер новой версии в релизе будет `2.3.4-wb1`. Если в уже пропатченную версию потребовались изменения, то номер новой -версии будет `2.3.4-wb2` и так далее. _В правила нумерации версий в релизах в отдельных случаях могут быть внесены изменения_. - - -Связь с youtrack ----------------- - -Настроена связь git и youtrack: ветки с названием X/SOFT-N-Y (например: feature/SOFT-1234-bitrix-integration), автоматически привязываются к задаче с номером N в youtrack. From 423f3ba7358007cd878fd2a31ae0ca2d17f75592 Mon Sep 17 00:00:00 2001 From: vdromanov Date: Wed, 24 Sep 2025 16:46:52 +0300 Subject: [PATCH 2/6] migrate all codestyle guides to one file (style.md) --- C++.en.md | 134 ------ C++.ru.md | 132 ------ embedded_c.en.md | 397 ----------------- go.en.md | 16 - js.ru.md | 80 ---- python.ru.md | 431 ------------------- style.md | 1069 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1069 insertions(+), 1190 deletions(-) delete mode 100644 C++.en.md delete mode 100644 C++.ru.md delete mode 100644 embedded_c.en.md delete mode 100644 go.en.md delete mode 100644 js.ru.md delete mode 100644 python.ru.md create mode 100644 style.md diff --git a/C++.en.md b/C++.en.md deleted file mode 100644 index 7dd477c..0000000 --- a/C++.en.md +++ /dev/null @@ -1,134 +0,0 @@ -Wiren Board C++ Style Guide -======================== - -The following sheet is based on Yandex code style for C++. If any guideline is missing from the following document, -please follow Yandex official documentation: https://github.com/catboost/catboost/blob/master/CPP_STYLE_GUIDE.md . But if you -see any collision between documents, please follow the guide below. - -## Marking - -Indent is 4 spaces wide. You can configure your IDE to insert 4 spaces on pressing Tab. - -Aritmetic operations are always separated by spaces: -```C++ -int sum = a + b; -``` -At sequence of variables comma should be placed right after variable name and the comma should be followed by space before -next variable name. It is true for all kind of punctuation mark (comma, semmicolon, question mark, etc): - -```C++ -int sum = GetSum (a, b); -if (! variable) { -.... -``` - -## Parentheses - -At function declaration curly bracket should be placed in new line after function declaration. -```C++ -int foo () -{ - std::cout << "foo" << std:endl; - return 0; -} -``` - -At conditinal expressions please use compact style, where curly bracket is in the same line with condition. -```C++ -if (foo && bar) { - std::cout << "foobar" << std:endl; -} -``` - -At multi-line conditions cury bracket is placed in new line. -```C++ -if (foo && - bar) -{ - std::cout << "foobar" << std:endl; -} -``` - -Basically single-line loops or conditional expressions are not allowed. Plese follow the description above and place curly bracket -in the same line with condition and place program body in the next line. -```C++ -//uncorrect -if (foo) {std::cout << "foo" << std:endl;} -``` - -```C++ -//correct -if (foo) { - std::cout << "foo" << std:endl; -} -``` - -Loops without body should be enclosed with curly brackets. -```C++ -while (ReadNoise ()) {} -``` - - - - -## Naming -### General -In C++ the general naming rule is **JavaCamelCase**. -*snake_case* is allowd only for local variables and they should be never used in one file with CamelCase variables. - -Funtion naming should describe purpose of the function: **const std::string& GetMethodName()**. - -The goal of naming is making code more readable and easy to understand function purpose, return value, effect without -checking the actual implementation of code. It is true at varible names as well. -Please avoid funtion names like: *DoWork()* or variables like: *counter* etc, except if it does not limit readability, like: -```C++ -int buffer = zipNumbers[i]; -zipNumbers[i] = zipNumbers[j]; -zipNumbers[j] = buffer; -``` - -Short names, like *i ,j* are basically allowed only as loop variables. -At shortened names, variable name should be started with capital letter: *TMqttClient* - -### Classes - - * Class names should start with capital *T*: *TModbusClient*. - * Base classes should include *Base* at the end of class name: *TModbusClientBase*. - -Intarface class name should start with letter "*I*" for example: *class IException* - -### Methods - -Class method naming should follow CamelCase style and start each word with capital letter -regardless it is the first word or not: *GetMethodName*. - -### Class fields -Class variable names should start with capital letter. - -```C++ -class TModbusClient -{ -public: - -std::string GetMethodName () { return MethodName; }; - -private: - -std::string MethodName; -} -``` - -### Local variables - -Local variable names should start with small letter and followed by capital letters (camelCase): *magicVariableName*. As a second -option snake_case could be also used, but please be careful and never mix it with camelCase. - -## C macros - -It's generally very bad idea to use C preprocessor. C++ language features are to be used instead. - -There are very rare circumstances when using C macros is justified, for instance, by significatnly improving source code readability. In these rare cases it's allowed to use C preprocessor, but please be ready to defend this design decision during code review. - -## Examples - -https://github.com/contactless/wb-homa-drivers/tree/master/wb-mqtt-serial diff --git a/C++.ru.md b/C++.ru.md deleted file mode 100644 index bfa20ac..0000000 --- a/C++.ru.md +++ /dev/null @@ -1,132 +0,0 @@ -Wiren Board C++ Style Guide -======================== - -За основу взят яндексовский Style Guide для C++. - -## Разметка - -Для отступов используются только пробелы. Один уровень отступа - это 4 пробела. - -Знаки арифметических операций выделяются пробелами: - -```C++ -int sum = a + b; -``` - -Запятые выделяются пробелами с одной стороны: - -```C++ -int sum = GetSum(a, b); -``` - - -## Скобочки - -В определениях функций и методов открывающая определение фигурная скобка переносится на следующую строчку. - -```C++ -int foo() -{ - std::cout << "foo" << std:endl; - return 0; -} -``` - -Используется компактный стиль расстановки фигурных скобок внутри определений функций и методов. - -```C++ -if (foo && bar) { - std::cout << "foobar" << std:endl; -} -``` - -В случае многострочных условий if и циклов фигурная скобка переносится на отдельную строку. - -```C++ -if (foo && - bar) -{ - std::cout << "foobar" << std:endl; -} -``` - -Блоки кода в циклах и условных выражениях, состоящие из одной строки, заключаются в фигурные скобки при переносе на другую строчку: - -```C++ -if (foo) return 0; -``` - -```C++ -if (foo) { - std::cout << "foo" << std:endl; -} -``` - -Пустое тело в циклах должно быть заключено в фигурные скобки - -```C++ -while (ReadNoise()) {} -``` - - - - -## Именование -### Общее -Основным способом создания имён всего является **JavaCamelCase**. -*snake_case* допускается в названиях локальных переменных, при этом они не должны использоваться в одном файле с CamelCase локальными переменными. - - - -Функции и методы называются словосочетанием, описывающим то, что делаем функция, и начинаются с глагола: **const std::string& GetMethodName()**. - -Цель к которой нужно стремиться: по названию должно быть понятно, что делает функция, класс или что хранит в себе переменная без необходимости смотреть реализацию. -Стоит избегать названий функций и методов типа *DoWork()*, переменных типа *counter* и т.д, кроме случаев, когда это не мешает пониманию. - -Допускаются общепринятые короткие названия для переменных типа *i,j* для счётчиков в циклах. - -В аббревиатурах остаётся первая заглавная буква: *TMqttClient*. - - -### Классы - -Классы именуются с заглавной *T*, например *TModbusClient*. - -Базовые классы должны иметь название, заканчивающееся на *Base*: *TModbusClientBase*. - -Названия классов-интерфейсов начинаются с *I*: *class IException* - -### Методы - -Методы называются в CamelCase с большой буквы: *GetMethodName*. - -### Поля классов -Поля данных (переменные) классов называются с большой буквы: - -```C++ -class TModbusClient -{ -public: - -std::string GetMethodName() { return MethodName; }; - -private: - -std::string MethodName; -} -``` - -### Локальные переменные - -Называются с маленькой буквы в camelCase. Допускается наименование с маленькой буквы в snake_case. - -## Макросы - -Препроцессор C использовать не стоит. Вместо него предпочтительно использовать конструкции C++. - -В очень редких случаях использование макросов действительно даёт большой выигрыш в читаемости кода. -Если вы столкнулись именно с таким случаем, то пользоваться препроцессором можно. Но стоит быть готовыми отстаивать свою правоту на review. - -## Примеры - -https://github.com/contactless/wb-homa-drivers/tree/master/wb-mqtt-serial diff --git a/embedded_c.en.md b/embedded_c.en.md deleted file mode 100644 index b8b5864..0000000 --- a/embedded_c.en.md +++ /dev/null @@ -1,397 +0,0 @@ -Wiren Board Embedded C Style Guide -======================== - -The following sheet describes few recomodations in order to make an easy to read -and well organised program code focusing on C programming language convenstions what can be -different from C++ conventions. - -## Marking and Parenthesis -These are similar those what we have got used in C++. Read more in: https://github.com/contactless/codestyle. - -## Naming -In embedded C we don't follow CamelCase style however we use snake_case. At snake_case style function and variable names can -include **only lower case characters**, like *snake_case* (even though officially upper case characters are also allowed) and words must be separated by underscore character (_). - -## Library naming -1. When you add a new module to your project you should put it in a new library. -2. Please avoid libraries like: *peripherals.h*. -3. Library names should describe the module behind the code. Use *mcp230xx.h* isntead of *gpio_chip.h* -4. Try to separate module funtionalities, in order to support your code reusability. Better to implement basic IC - related functions in one library, and write task specific functions in an other library. Like: *mcp230xx.h* includes - driver to chip and *wbio-dio.h" library is using functions of *mcp230xx.h". - -## Function names -Since in C is no option to use namespace or classes, somehow you should mark your function's library and avoid this kind of -program code in *main.c*: -```C -int pin_value = read_value_digit();//function for module1 -set_config(CONFIG_1); //function for module2 -``` -Better to mark library name as a prefix for each function even if it is a static function and used only in the given -library: -```C -int pin_value = mcp230xx_read_gpio(); -ads1015_set_config(ADS_CONFIG_1); -``` -It is also true for *defines* where you should mark where is it defined: -```C -#define MCP23008_IODIR 0x00 -#define MCP23008_IPOL 0x01 -#define MCP23008_GPINTEN 0x02 -#define MCP23008_DEFVAL 0x03 -#define MCP23008_INTCON 0x04 -``` - -## Declarations -1. Functions what is not supposed be called from outside of library should be defined as *static*. -2. Static function prototypes should be in *.c* file, because it is related to implementation not to usage. -3. *#defines* used only in *.c* also should be placed in *.c* file. -4. In header file should be *defines* and functions what are necessary to use library. -5. Avoid using hardcoded constants because first, they are not describing their functionality, second harder to modify all -if once it must be. Avoid this: -```C -uint16_t gpio_config = mcp230xx_read_config(); -if (gpio_config & 0x04) { -... -``` -Better to use *define* and replace magic constant. -```C -#define MCP230xx_REG_IOCON_BANK (1 << 2) -... -uint16_t gpio_config = mcp230xx_read_config(); -if (gpio_config & MCP230xx_REG_IOCON_BANK) { -... -``` - -## C Macros - -Using of C preproccessor is generally discouraged because it makes source code hard to read and mantain. - -Whenever possible, please use C language features instead. - -Common use cases for C macros include: - -* `#define`-ing constants, for instance, array sizes, configuration parameters, register addresses or so on. -* switching on and off particular chunks of firmware via `#if` and friends. Prefereable done at module level. -* compile-time magic otherwise impossible to implement. Say, altering of control flow in coroutines or implementing assert. Try to limit it to few low-level libraries. - -If expressions are used in macro, they must be wrapped in `do {} while (0)`. -Function-like macros should follow function-like calling semantics. Consider `START_TIMER();` instead of `START_TIMER;`. - -All macros must be named in `CAPITAL` letters. - -Please consider the following alternatives to C preprocessor: -* `if`s with condition evaluating to `true` at compile time insted of `#if`s -* `const` variables instead of `#define`s -* `static inline` functions instead of macros containing expressions. - -## Code Tips - -### Clearing Interrupt Flags - -It is important to NOT use RMW (read-modify-write) operation when clearing interrupt flag in the status register of peripherals. If status register was modified by peripheral during 'modify' operation, the new interrupt flag will be cleared together with the specified flag. As a result, new interrupt will be lost. - -Worst exapmle: - -``` -if (TIM1-SR & TIM_SR_CC1IF) { - TIM1-SR &= ~TIM_SR_CC1IF; -} -if (TIM1-SR & TIM_SR_CC2IF) { - TIM1-SR &= ~TIM_SR_CC2IF; -} -``` - -If CC1 and CC2 events are near in time, one of them may be lost. - -Good example: - -``` -if (TIM1-SR & TIM_SR_CC1IF) { - TIM1-SR = ~TIM_SR_CC1IF; // rc_w0 -} -if (TIM1-SR & TIM_SR_CC2IF) { - TIM1-SR = ~TIM_SR_CC2IF; // rc_w0 -} -``` - -This works because the bits in SR register are rc_w0 type and writing '1' does nothing. - -## Кодировка -Все файлы должны быть созданы в кодировке UTF-8. - -## Конец строки -Конец файла всегда заканчивается переводом строки. - -## Выравнивание -Пробелы - -1. Отступы 4 пробела между блоками - не использовать символ табуляции (TAB). -2. Знаки бинарных операций отделяются от переменных пробелами, а знаки унарных операций - нет. -```C -a += b / c; -c++; -c &= ~d; -``` -3. Пробелы между ``` if while do for``` и открывающей скобкой выражения. -4. Аргументы функций перечисляются без пробелов вокруг скобок. Когда аргументов несколько - запятая сразу после аргумента, после запятой - пробел: -```C -task_schedule(adc_start_periodic_conversion_task_id, ADC_FILTRATION_PERIOD_MS); -``` -5. Значения макросов, которые именуют константы, должны быть расположены от 40 до 60 знакоместа кратно 4 символам. -Это нужно для того чтобы оставить место для макросов с длинными именами, которые могут быть потенциально добавлены позже в процессе разработки. - -Отступы и пробелы можно проверять, например, используя команду git diff. С помощью команды git diff master проверяется то что не изменено форматирование уже написанного ранее кода относительно мастер-ветки. - -Не использовать отступы для выравнивания - -Примеры плохого кода: -```C -static uint16_t task_next_free_id[TASK_TYPE_NUMBER] = {GET_TASK_ID(TASK_IMMEDIATE, 0), - GET_TASK_ID(TASK_BACKGROUND, 0)}; -``` - -```C - hlw8012_channels_state[channel].energy_factor = fix16_to_int(fix16_mul(fix16_div(fix16_from_int(hlw8012_channel_defs[channel].energy_unit_time),hlw8012_channels_state[channel].value_factor),F16(HLW8012_PULSE_COUNTING_FACTOR))); -``` - -```C - hlw8012_channels_state[channel].energy_factor = fix16_to_int(fix16_mul(fix16_div(fix16_from_int(hlw8012_channel_defs[channel].energy_unit_time), - hlw8012_channels_state[channel].value_factor),F16(HLW8012_PULSE_COUNTING_FACTOR))); -``` - -Примеры хорошего кода: - -```C -static uint16_t task_next_free_id[TASK_TYPE_NUMBER] = { - GET_TASK_ID(TASK_IMMEDIATE, 0), - GET_TASK_ID(TASK_BACKGROUND, 0) -}; -``` - -```C - hlw8012_channels_state[channel].energy_factor = fix16_to_int( - fix16_mul( - fix16_div( - fix16_from_int( - hlw8012_channel_defs[channel].energy_unit_time - ), - hlw8012_channels_state[channel].value_factor - ), - F16(HLW8012_PULSE_COUNTING_FACTOR) - ) - ); -``` - -Пример расстановки пробелов при обьявлении массива: -```C -static const uint8_t array_example[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; -``` - -## Комментарии -Комментарии после строки (короткий) или до строки (длинный): -```C -код // короткий комментарий -``` -или -```C -// длинный комментарий -код -``` -```//``` выравнивается кратно 4-м знакоместам (в редакторе vscode клавишей TAB в режиме 4 пробела вместо табуляции). -Текст от знака ```//``` отделяется пробелом. - -## Расстановка скобок -Даже если тело условного блока состоит из одной строки, оно заключается в фигурные скобки. - -Перенос скобки на следующую строку начала определения осуществляется в функциях и многострочных условиях (if, else if), в остальном нет. -Расстановка скобок: -```C -void function(void) -{ - if (a == b) { - // single str action use brackets - } - - if (c == d) { - // do if equal - } else { - // do if not equal - } - - if ((a == b) || - (c == d)) - { - // multi str condition use statement body open bracket with newline - } else if ((a != b) && - (c != e)) - { - // multi str condition use statement body open bracket with newline - } else if (c == e) { - // single str condition use statement body open bracket with one line - } -} - -void function(void) -{ - return; -} - -``` -Скобки ставятся всегда, даже когда тело блока 1 строчка и даже когда его нет: -```C -while () {}; - -if () { - return 0; -} else { - return 1; -} -``` - -Операции всегда выделяются скобками, даже если порядок действий очевиден: -```C -(a || b && !c) -> (a || (b && (!c))) -``` - -## Объявления -В заголовочном (.h) файле должен быть только интерфейс к модулю т.е. обьявления функций, структур и макросы которые вызываются и используются в других модулях. Также в заголовочном файле могут помещаться ```static inline``` функции, если они например используются как заглушки. - -При переопределении или определении новых типов данных в конце названий добавляется ```_t```. -Пример: - -```C -typedef struct { - uint16_t address; - uint16_t data; -} hold_reg_t; -``` - -имена структур не должны иметь в названии ```_t```. - -```C -struct hold_reg { - uint16_t address; - uint16_t data; -}; -``` - -## Ветвления - -Запрещается использование тренарных операций, так как они усложняют понимание кода. ```int a = (cond) ? var1 : var 2``` - -Если условие содержит логическое И, рекомендуется сделать 2 вложенных if. Такой подход нагляднее демонстрирует условия выполнения, а также заставляет задуматься о порядке проверки условий, и упрощении кода. - -``` -if (cond1) { - if (cond2) { - ... - } -} -``` - -## Однотипные сущности - -Часто бывает что устройство имеет несколько входов/выходов/шин/светодиодов/кнопок итд, абсолютно всегда все что потенциально может быть больше чем в одном экземпляре должно обрабатываться через индексы и циклы. - -## Исправление ошибок в master - -Ранее допущенные ошибки форматирования исправляются в отдельной ветке с PR согласно кодстайлу. - -## Неинициализированные переменные - -Неинициализированных переменных быть не должно. Любая переменная, структура, указатель или массив должны быть инициализированы при объявлении. Даже если где то позже переменная будет принимать значение, например при чтении настроек из файловой системы. Нули уменьшат спектр чудес при ошибке или гарантированно будут вызывать hard fault, ускоряя отладку. - -## Логические выражения - -Запрещается использовать логические операции где либо кроме операторов условия. - -`return (enabled_ch_mask & (1 << channel)) != 0;` - -или - -`x = (enabled_ch_mask & (1 << channel)) != 0;` - -Проверка логического условия в выражении присваивания усложняет понимание. Вместо этого предлагается написать обычный читаемый if. - -``` -if (enabled_ch_mask & (1 << channel)) { - return 1; -} -return 0; -``` - -или - -``` -x = 0; -if (enabled_ch_mask & (1 << channel)) { - x = 1; -} -``` - -## Выбор типа переменной - -Типы с явной разрядностью (uint8_t, uint16_t, uint32_t) нужно использовать только в случаях, где это важно для работы (протоколы, структуры данных) или необходимо для читаемости. -* данные -* регистры переферии -* результаты вычисления CRC и подобное -* статически выделенные переменные, для экономии ОЗУ - -В остальных случаях использовать unsigned для беззнакового или int для знакового типа, а именно: -* счетчик цикла for -* индексы -* аргументы функций - индекс, длина, все что не данные -* все что на стеке и не критично к размеру, флаги, промежуточные вычисления - -Это связано с тем что компилятор вставляет дополнительные инструкции UXTH UXTB SXTH SXTB для приведения типов (зануления старших разрядов). Чтобы uint8 преобразовать к машинному слову 32 бита, нужно отбросить старшие разряды с мусором). Это увеличивает размер кода и время его выполнения. Посмотрите дизасемблером любопытства ради. Используем их осмысленно только при необходимости. - -## Порядок полей в структурах - -Нужно помнить что компилятор выравнивает поля в структурах по размеру слова архитектуры - 32 бита. Для экономии памяти нужно думать как структура будет хранится в памяти. - -Поля расположены плохо, размер структуры 12 байт: -```C -struct hold_reg { - uint8_t data; - uint32_t address; - uint8_t crc; -}; -``` - -Поля расположены хорошо, размер структуры с теми же самыми данными 8 байт: -```C -struct hold_reg { - uint32_t address; - uint8_t data; - uint8_t crc; -}; -``` - -# Имена вариантов enum - -Нужно выбирать осмысленное имя типа enum, и все варианты должны начинаться с префикса данного имени - -Хорошее именование, по вариантам понятно о каком enum идет речь: -```C -enum w1_transaction { - W1_TRANSACTION_SEND, - W1_TRANSACTION_RECEIVE -} -``` - -Плохое именование, невозможно понять какому enum принадлежит вариант пока не посмотрим определение: -```C -enum w1_transaction { - W1_PROTOCOL_SEND, - W1_PROTOCOL_RECEIVE -} -``` - -Плохое именование, слишком длинные имена, наверное можно подумать и выбрать более короткий вариант без потери смысла: -```C -enum w1_protocol_transaction { - W1_PROTOCOL_TRANSACTION_SEND, - W1_PROTOCOL_TRANSACTION_RECEIVE -} -``` diff --git a/go.en.md b/go.en.md deleted file mode 100644 index e77addf..0000000 --- a/go.en.md +++ /dev/null @@ -1,16 +0,0 @@ -## Style guide - -Format your code with `go fmt`. - -## Static analysis - -Install [staticcheck](https://staticcheck.dev/): -```sh -apt install go-staticcheck -``` - -Running: -```sh -go mod vendor -staticcheck -go 1.13 ./... -``` diff --git a/js.ru.md b/js.ru.md deleted file mode 100644 index db10a40..0000000 --- a/js.ru.md +++ /dev/null @@ -1,80 +0,0 @@ -Wiren Board JavaScript Style Guide -============================== - -За основу взят стиль [Airbnb](https://airbnb.io/javascript/). - -Для форматирования кода используется [Prettier](https://prettier.io/). - -Для проверки кода используется [ESLint](https://eslint.org/). - - -Установка `ESLint` и `Prettier` -------------- - -```console -$ npm install --save-dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks @babel/eslint-parser @babel/preset-react -``` - -Файлы с настройками находятся в репозитории в директории `js`. -Их надо скопировать в каталог проекта под именами -``` -.eslintrc -.prettierrc -``` - -Для wb-rules правил используйте `js/eslintrc-es5`, так как правила пишутся на ECMAScript 5. - -В раздел `scripts` файла `package.json` можно добавить строку - -```js - "lint": "eslint \"**/*.{js,jsx}\"" -``` - -### Запуск eslint - -```console -$ npx eslint FILE.js -``` - -### Запуск prettier - -```console -$ npx prettier -``` - - -Настройка IDE -------------- - -### VSCode - - * устанавливаем расширение ESlint: `Ctrl-Shift-X` (открывает Marketplace), в строке поиска вводим `eslint`, - устанавливаем первое расширение из списка (от Microsoft); - * устанавливаем расширение Prettier: `Ctrl-Shift-X` (открывает Marketplace), в строке поиска вводим `prettier`; - * открываем редактор настроек VSCode: `Ctrl-Shift-P`, в поиске вводим `settings json`, - выбираем `Preferences: Open Settings (JSON)`; - * в открывшемся редакторе вводим (или добавляем опции в существующий объект); -```json -{ - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[javascriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - } -} -``` - - * если файлы настройки `prettier` расположены не в каталоге проекта, то надо указать параметр `prettier.configPath`; - * если файлы настройки `eslint` расположены не в каталоге проекта, то надо добавить параметр: - -```json -{ - "eslint.options": { - "overrideConfigFile": "PATH/.eslintrc" - } -} -``` - diff --git a/python.ru.md b/python.ru.md deleted file mode 100644 index 03adae0..0000000 --- a/python.ru.md +++ /dev/null @@ -1,431 +0,0 @@ -Wiren Board Python Style Guide -============================== - -За основу взят стиль, описанный в PEP8 (https://peps.python.org/pep-0008/). - -Для форматирования кода используется `black` (https://github.com/psf/black). - -Отличия от PEP8 ---------------- - - * Максимальная длина строки - 110 символов вместо 78. Это позволяет не сильно дробить строки при - написании кода и в то же время просматривать изменения в две колонки в интерфейсе Github - (проверил на мониторе с разрешением 1920x1080). - * Используем двойные кавычки для строк (`"string"`), не используем одинарные (`'string'`). - -Общее ------ - -Описанные здесь правила взяты напрямую из PEP8 и перечислены здесь для удобства. - -### Отступы - -Отступы делаются пробелами, по 4 пробела на уровень. - -### Именование - -Короткий свод правил: - - * Имена классов пишем в `JavaCamelCase` с большой буквы. Аббревиатуры (например, `MQTT`, `HTTP`) - пишутся большими буквами. Это же касается названий типов (например, для `namedtuple`). - * Имена функций, методов, аргументов, переменных пишем в `snake_case`. - * Имена полей и методов класса, задуманных как приватные, должны начинаться с нижнего подчёркивания (`_`). - * Имена констант пишем в `SNAKE_CASE` заглавными буквами. - -При написании кода на python3 может понадобиться создавать классы-интерфейсы (с `metaclass=abc.ABCMeta`). -Имена таких классов начинаем с заглавной буквы `I` (например, `IMyInterface`). - -Пример: - -```python3 -from collections import namedtuple - - -MY_CONST = 42 - -MyNamedTuple = namedtuple("MyNamedTuple", "arg_1 arg_2") - - -class MyClass: - """ - docstring for this class - """ - - CLASS_PI = 3.14159 - - def __init__(self, my_arg): - self._my_arg = my_arg # Объявили приватное поле - - def print_my_arg(self): - print(self._my_arg) -``` - -### Аннотации типов - -При написании кода на python3 рекомендуется по возможности использовать аннотации типов -(https://docs.python.org/3.5/library/typing.html). При написании аннотаций стоит помнить, что на контроллерах -доступна версия Python 3.5, то есть, не все поддерживаемые аннотации типов из Python 3 будут доступны. - -Аннотации позволяют использовать автодополнение в редакторах кода, а также позволяют лучше понимать -происходящее в коде при чтении. - -Можно не увлекаться созданием сложных описаний типов (вроде `Iterable[Mapping[str, Union[int, str]]]`), -но простые случаи описывать стоит. - - -```python3 -from collections import namedtuple - - -MyNamedTuple = namedtuple("MyNamedTuple", "arg_1 arg_2") - - -def format_tuple(tuple: MyNamedTuple, delimiter: str = ", ") -> str: - return str(tuple.arg_1) + delimiter + str(tuple.arg_2) -``` - -### Длинные списки аргументов / элементов коллекций - -Black форматирует длинные перечисления аргументов функций или элементов коллекций примерно так: - - * пока помещается в строку, положить в одну строку; - * если не поместилось, перенести начало списка на следующую строку; - * если и так не поместилось, писать каждый элемент списка на новой строке. - -Это может породить неоднородности при форматировании там, где раньше было хорошо. Например: - -```python3 -# до форматирования - -self.parser.add_argument('-m', '--model', dest='device_model', type=str, - help='Модель устройства', required=True, choices=some_long_function_name_call()) - -self.parser.add_argument('-r', '--hw-rev', dest='hw_rev', type=str, - help='Версия платы', required=False, default=None) - -self.parser.add_argument('-p', '--batch', type=validate_batch, - help='Номер партии', required=True) - -# после форматирования - -self.parser.add_argument( - "-m", - "--model", - dest="device_model", - type=str, - help="Модель устройства", - required=True, - choices=some_long_function_name_call(), -) - -self.parser.add_argument( - "-r", "--hw-rev", dest="hw_rev", type=str, help="Версия платы", required=False, default=None -) - -self.parser.add_argument("-p", "--batch", type=validate_batch, help="Номер партии", required=True) - -``` - -Для того чтобы бороться с такими ситуациями, рекомендуется добавлять после последнего аргумента запятую. -В таком случае black всегда будет разбивать аргументы по строкам: - -```python3 -# до форматирования - -self.parser.add_argument('-m', '--model', dest='device_model', type=str, - help='Модель устройства', required=True, choices=some_long_function_name_call(),) - -self.parser.add_argument('-r', '--hw-rev', dest='hw_rev', type=str, - help='Версия платы', required=False, default=None,) - -self.parser.add_argument('-p', '--batch', type=validate_batch, - help='Номер партии', required=True,) - -# после форматирования - -self.parser.add_argument( - "-m", - "--model", - dest="device_model", - type=str, - help="Модель устройства", - required=True, - choices=some_long_function_name_call(), -) - -self.parser.add_argument( - "-r", - "--hw-rev", - dest="hw_rev", - type=str, - help="Версия платы", - required=False, - default=None, -) - -self.parser.add_argument( - "-p", - "--batch", - type=validate_batch, - help="Номер партии", - required=True, -) -``` - -То же самое рекомендуется делать с объявлениями списков и словарей. - -Приятным дополнением такого форматирования будет чуть более лаконичный diff при добавлении или перестановке -элементов в списке. - -Проверка кода -------------- - -Для автоматической проверки кода на CI используется `pylint`. - -Для проверки форматирования (и непосредственно форматирования) используются `black` и `isort`. -`black` автоматически форматирует файл согласно своим внутренним правилам. `isort` группирует и -сортирует список подключаемых модулей. - -Файлы с настройками находятся в репозитории codestyle в директории `python`. - -Что именно происходит на CI - можно посмотреть [здесь](https://github.com/wirenboard/jenkins-pipeline-lib/blob/master/vars/wb.groovy) (поискав по ```runPythonChecks```; все скрипты берутся из codestyle). - -> :warning: На момент подготовки версии 1.0 ошибки `pylint` не будут приводить к падению сборки по умолчанию (при снятой галочке "angry pylint"). -> Это связано с множеством ложных срабатываний, в частности, на тестах с использованием `pytest`. -> -> Тем не менее, проверки будут проводиться и будут собираться их логи для анализа в будущем. - - -### Установка codestyle-тулзов (один раз для проекта) -На CI и в локальной системе разработчика тулзы должны быть одинаковыми, поэтому black, isort и pylint устанавливаем путём создания virtualenv внутри каждого проекта. Если нужно, тулзы и конфиги можно доустановить и в существуюший venv проекта. - -> :information_source: Isort имеет очень своеобразную захардкоженную внутри логику поиска конфига и определения корня проекта, из-за чего и получались разногласия с запуском локально и на CI. -> -> Похоже, единственно рабочее решение - иметь pyproject.toml в корне проекта. - -Если в проекте нет `pyproject.toml`, `requirements.txt` - выкачать их [отсюда](https://github.com/wirenboard/codestyle/tree/master/python) и положить в корень проекта. - -> :warning: Только для Linux! (в Windows venv через vscode создаётся не в проекте, а системный) -Открыть в vscode проект -> ```Ctrl+Shift+P``` -> ```Python: Create Environment``` -> ```Venv``` - -```Ctrl + Shift + P``` -> ```Python: Create Terminal``` -> откроется терминал с уже активным venv; выполнить: ```pip3 install -r requirements.txt``` - -### Если без vscode -#### Linux (и Jenkins) -Выполнить в корне проекта -```console -$ python3 -m venv .venv -$ source .venv/bin/activate -$ pip3 install -r requirements.txt -$ deactivate -``` -_Можно доустановить requirements.txt из codestyle и в свой venv._ - -#### Windows -* открыть в командной строке корневую директорию проекта (запустив от имени администратора!) -* выполнить -```console -$ py -m venv .venv -$ .\.venv\Scripts\activate -$ py -m pip install --upgrade pip -$ py -m pip install -r requirements.txt -$ deactivate -``` -* настроить VSCode далее по инструкции; с конфигом для windows - -### Запуск руками (в директории проекта; [pyproject.toml](https://raw.githubusercontent.com/wirenboard/codestyle/master/python/pyproject.toml) присутствует) - -**Активировать venv** (пример по умолчанию; venv может быть и другим) -```console -$ source .venv/bin/activate -``` - -#### pylint -```console -$ python3 -m pylint $(find . -name '*.py') -``` - -#### black + isort (dry-run) -```console -$ python3 -m black --config pyproject.toml --check --diff $(find . -name '*.py') -$ python3 -m isort --settings-file pyproject.toml --check --diff $(find . -name '*.py') -``` - -#### black + isort (автоформатирование) -```console -$ python3 -m black --config pyproject.toml $(find . -name '*.py') -$ python3 -m isort --settings-file pyproject.toml $(find . -name '*.py') -``` - -> :information_source: При изменении форматирования в репозитории может сильно испортиться вывод `git blame`. -> -> Начиная с версии 2.23, `git` умеет игнорировать изменения из таких коммитов. -> Для этого при смене форматирования кода надо будет добавлять в репозиторий файл `.git-blame-ignore-revs`. -> -> Подробнее об этом можно почитать здесь: https://black.readthedocs.io/en/stable/guides/introducing_black_to_your_project.html - -**по завершению - выйти из venv** -```console -$ deactivate -``` - -Автоформатирование в IDE -------------- - -### VSCode - -Один раз настраиваем VSCode: - - * устанавливаем расширение Python: `Ctrl-Shift-X` (открывает Marketplace), в строке поиска вводим `python`, - устанавливаем первое расширение из списка (от Microsoft); -* аналогично устанавливаем расширения black, isort, pylint (все от Microsoft) - * открываем редактор настроек VSCode: `Ctrl-Shift-P`, в поиске вводим `settings json`, - выбираем `Preferences: Open Settings (JSON)`; - * в открывшемся редакторе вводим (или добавляем опции в существующий объект); - * если файлы настроек расположены не в `~/.config/wb/`, то заменяем `${env:HOME}/.config/wb/` на корректный путь: - -#### Linux -```json -{ - "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python3", - "python.terminal.activateEnvironment": true, - "black-formatter.args": [ - "--config=${workspaceFolder}/pyproject.toml" - ], - "black-formatter.importStrategy": "fromEnvironment", - "isort.check": true, - "isort.args": [ - "--settings-path=${workspaceFolder}" - ], - "[python]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "ms-python.black-formatter", - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - }, - "pylint.lintOnChange": true, - "isort.importStrategy": "fromEnvironment", - "pylint.importStrategy": "fromEnvironment", - "pylint.severity": { - "refactor": "Warning" - }, -} -``` - -#### Windows -```json -{ - "python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe", - "python.terminal.activateEnvironment": true, - "black-formatter.args": [ - "--config=${workspaceFolder}\\pyproject.toml" - ], - "black-formatter.importStrategy": "fromEnvironment", - "isort.check": true, - "isort.args": [ - "--settings-path=${workspaceFolder}" - ], - "[python]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "ms-python.black-formatter", - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - }, - "pylint.lintOnChange": true, - "isort.importStrategy": "fromEnvironment", - "pylint.importStrategy": "fromEnvironment", - "pylint.severity": { - "refactor": "Warning" - }, -} -``` - -Code coverage -------------- - -Для оценки покрытия тестов используется плагин pytest `coverage`. Оценка показывает процент выполнившихся строк кода и веток условий относительно общего объема файлов, использованных в процессе тестирования. - -### Запуск - -В зависимости от того, как вы запускаете тесты, нужно использовать разные сценарии. -**Минимально допустимое значение покрытия для прохождения тестов указывается в аргументе `--cov-fail-under`** - -#### Внутри devcontainer - -1. Скачать из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положить в корень проекта. -2. Запустить -```console -$ pytest --cov --cov-config=.coveragerc --cov-report=term --cov-branch --cov-fail-under= -``` - -#### Внутри venv - -1. Настроить venv [по инструкции для codestyle-проверок](#установка-codestyle-тулзов-один-раз-для-проекта). В venv должен установиться пакет pytest-cov. -2. Скачать из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положить в корень проекта. -3. Запустить -```console -$ pytest --cov --cov-config=.coveragerc --cov-report=term --cov-branch --cov-fail-under= -``` - -#### Сборка wbdev - -Работает только для `wbdev ndeb` и `wbdev cdeb` -1. Скачать из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положить в корень проекта. -2. Запустить -```console -$ export WBDEV_PYBUILD_TEST_ARGS="--cov --cov-config=../../../.coveragerc --cov-report=term --cov-branch --cov-fail-under=" -$ wbdev ndeb -``` - -### Отчеты - -Для сборки внутри devcontainer и внутри venv есть возможность сгенерировать html отчет (для варианта wbdev сложные аргументы запуска, поэтому не приводим его). Для включения генерации нужно добавить опцию запуска -```console ---cov-report=html -``` -В папке проекта создастся папка htmlcov. Чтобы посмотреть отчет, откройте файл index.html в браузере. - -### Настройка VSCode - -#### Для запуска тестов из VSCode: - -1. Скачайте из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положите в корень проекта. -2. Установите плагин для Python и настройте запуск тестов через pytest -3. Откройте настройки workspace: нажмите комбинацию `Ctrl-Shift-P`, в поиске введите `settings json`, - выбираем `Preferences: Open Workspace Settings (JSON)` -4. К массиву опций запуска добавляем: -``` -"--cov","--cov-config",".coveragerc","--cov-report","term","--cov-branch","--cov-fail-under","" -``` -Должны получиться примерно такие настройки: -```json -{ - "python.testing.pytestArgs": [ - "tests","--cov","--cov-config",".coveragerc","--cov-report","term","--cov-branch","--cov-fail-under","" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true -} -``` - -#### Для просмотра отчета в исходных файлах: -1. Установите плагин [Coverage Gutters](https://github.com/ryanluker/vscode-coverage-gutters) -2. К опциям запуска добавьте `"--cov-report","xml"` -3. Проведите тестирование -3. Откройте файл, покрытие которого хотите посмотреть. В статус-баре VSCode найдите надпись `Watch` и нажмите ее для включения подсветки. - - -Ещё гайдлайны -------------- - - * [Реализация сервисов на Python](guidelines/service-python.md) - * [Шаблон setup.py](python/setup.py) - -Changelog ---------- - -### v1.0 (2022/04/19) - - * Initial version diff --git a/style.md b/style.md new file mode 100644 index 0000000..88ffe6f --- /dev/null +++ b/style.md @@ -0,0 +1,1069 @@ +Wiren Board Python Style Guide +============================== + +За основу взят стиль, описанный в PEP8 (https://peps.python.org/pep-0008/). + +Для форматирования кода используется `black` (https://github.com/psf/black). + +Отличия от PEP8 +--------------- + + * Максимальная длина строки - 110 символов вместо 78. Это позволяет не сильно дробить строки при + написании кода и в то же время просматривать изменения в две колонки в интерфейсе Github + (проверил на мониторе с разрешением 1920x1080). + * Используем двойные кавычки для строк (`"string"`), не используем одинарные (`'string'`). + +Общее +----- + +Описанные здесь правила взяты напрямую из PEP8 и перечислены здесь для удобства. + +### Отступы + +Отступы делаются пробелами, по 4 пробела на уровень. + +### Именование + +Короткий свод правил: + + * Имена классов пишем в `JavaCamelCase` с большой буквы. Аббревиатуры (например, `MQTT`, `HTTP`) + пишутся большими буквами. Это же касается названий типов (например, для `namedtuple`). + * Имена функций, методов, аргументов, переменных пишем в `snake_case`. + * Имена полей и методов класса, задуманных как приватные, должны начинаться с нижнего подчёркивания (`_`). + * Имена констант пишем в `SNAKE_CASE` заглавными буквами. + +При написании кода на python3 может понадобиться создавать классы-интерфейсы (с `metaclass=abc.ABCMeta`). +Имена таких классов начинаем с заглавной буквы `I` (например, `IMyInterface`). + +Пример: + +```python3 +from collections import namedtuple + + +MY_CONST = 42 + +MyNamedTuple = namedtuple("MyNamedTuple", "arg_1 arg_2") + + +class MyClass: + """ + docstring for this class + """ + + CLASS_PI = 3.14159 + + def __init__(self, my_arg): + self._my_arg = my_arg # Объявили приватное поле + + def print_my_arg(self): + print(self._my_arg) +``` + +### Аннотации типов + +При написании кода на python3 рекомендуется по возможности использовать аннотации типов +(https://docs.python.org/3.5/library/typing.html). При написании аннотаций стоит помнить, что на контроллерах +доступна версия Python 3.5, то есть, не все поддерживаемые аннотации типов из Python 3 будут доступны. + +Аннотации позволяют использовать автодополнение в редакторах кода, а также позволяют лучше понимать +происходящее в коде при чтении. + +Можно не увлекаться созданием сложных описаний типов (вроде `Iterable[Mapping[str, Union[int, str]]]`), +но простые случаи описывать стоит. + + +```python3 +from collections import namedtuple + + +MyNamedTuple = namedtuple("MyNamedTuple", "arg_1 arg_2") + + +def format_tuple(tuple: MyNamedTuple, delimiter: str = ", ") -> str: + return str(tuple.arg_1) + delimiter + str(tuple.arg_2) +``` + +### Длинные списки аргументов / элементов коллекций + +Black форматирует длинные перечисления аргументов функций или элементов коллекций примерно так: + + * пока помещается в строку, положить в одну строку; + * если не поместилось, перенести начало списка на следующую строку; + * если и так не поместилось, писать каждый элемент списка на новой строке. + +Это может породить неоднородности при форматировании там, где раньше было хорошо. Например: + +```python3 +# до форматирования + +self.parser.add_argument('-m', '--model', dest='device_model', type=str, + help='Модель устройства', required=True, choices=some_long_function_name_call()) + +self.parser.add_argument('-r', '--hw-rev', dest='hw_rev', type=str, + help='Версия платы', required=False, default=None) + +self.parser.add_argument('-p', '--batch', type=validate_batch, + help='Номер партии', required=True) + +# после форматирования + +self.parser.add_argument( + "-m", + "--model", + dest="device_model", + type=str, + help="Модель устройства", + required=True, + choices=some_long_function_name_call(), +) + +self.parser.add_argument( + "-r", "--hw-rev", dest="hw_rev", type=str, help="Версия платы", required=False, default=None +) + +self.parser.add_argument("-p", "--batch", type=validate_batch, help="Номер партии", required=True) + +``` + +Для того чтобы бороться с такими ситуациями, рекомендуется добавлять после последнего аргумента запятую. +В таком случае black всегда будет разбивать аргументы по строкам: + +```python3 +# до форматирования + +self.parser.add_argument('-m', '--model', dest='device_model', type=str, + help='Модель устройства', required=True, choices=some_long_function_name_call(),) + +self.parser.add_argument('-r', '--hw-rev', dest='hw_rev', type=str, + help='Версия платы', required=False, default=None,) + +self.parser.add_argument('-p', '--batch', type=validate_batch, + help='Номер партии', required=True,) + +# после форматирования + +self.parser.add_argument( + "-m", + "--model", + dest="device_model", + type=str, + help="Модель устройства", + required=True, + choices=some_long_function_name_call(), +) + +self.parser.add_argument( + "-r", + "--hw-rev", + dest="hw_rev", + type=str, + help="Версия платы", + required=False, + default=None, +) + +self.parser.add_argument( + "-p", + "--batch", + type=validate_batch, + help="Номер партии", + required=True, +) +``` + +То же самое рекомендуется делать с объявлениями списков и словарей. + +Приятным дополнением такого форматирования будет чуть более лаконичный diff при добавлении или перестановке +элементов в списке. + +Проверка кода +------------- + +Для автоматической проверки кода на CI используется `pylint`. + +Для проверки форматирования (и непосредственно форматирования) используются `black` и `isort`. +`black` автоматически форматирует файл согласно своим внутренним правилам. `isort` группирует и +сортирует список подключаемых модулей. + +Файлы с настройками находятся в репозитории codestyle в директории `python`. + +Что именно происходит на CI - можно посмотреть [здесь](https://github.com/wirenboard/jenkins-pipeline-lib/blob/master/vars/wb.groovy) (поискав по ```runPythonChecks```; все скрипты берутся из codestyle). + +> :warning: На момент подготовки версии 1.0 ошибки `pylint` не будут приводить к падению сборки по умолчанию (при снятой галочке "angry pylint"). +> Это связано с множеством ложных срабатываний, в частности, на тестах с использованием `pytest`. +> +> Тем не менее, проверки будут проводиться и будут собираться их логи для анализа в будущем. + + +### Установка codestyle-тулзов (один раз для проекта) +На CI и в локальной системе разработчика тулзы должны быть одинаковыми, поэтому black, isort и pylint устанавливаем путём создания virtualenv внутри каждого проекта. Если нужно, тулзы и конфиги можно доустановить и в существуюший venv проекта. + +> :information_source: Isort имеет очень своеобразную захардкоженную внутри логику поиска конфига и определения корня проекта, из-за чего и получались разногласия с запуском локально и на CI. +> +> Похоже, единственно рабочее решение - иметь pyproject.toml в корне проекта. + +Если в проекте нет `pyproject.toml`, `requirements.txt` - выкачать их [отсюда](https://github.com/wirenboard/codestyle/tree/master/python) и положить в корень проекта. + +> :warning: Только для Linux! (в Windows venv через vscode создаётся не в проекте, а системный) +Открыть в vscode проект -> ```Ctrl+Shift+P``` -> ```Python: Create Environment``` -> ```Venv``` + +```Ctrl + Shift + P``` -> ```Python: Create Terminal``` -> откроется терминал с уже активным venv; выполнить: ```pip3 install -r requirements.txt``` + +### Если без vscode +#### Linux (и Jenkins) +Выполнить в корне проекта +```console +$ python3 -m venv .venv +$ source .venv/bin/activate +$ pip3 install -r requirements.txt +$ deactivate +``` +_Можно доустановить requirements.txt из codestyle и в свой venv._ + +#### Windows +* открыть в командной строке корневую директорию проекта (запустив от имени администратора!) +* выполнить +```console +$ py -m venv .venv +$ .\.venv\Scripts\activate +$ py -m pip install --upgrade pip +$ py -m pip install -r requirements.txt +$ deactivate +``` +* настроить VSCode далее по инструкции; с конфигом для windows + +### Запуск руками (в директории проекта; [pyproject.toml](https://raw.githubusercontent.com/wirenboard/codestyle/master/python/pyproject.toml) присутствует) + +**Активировать venv** (пример по умолчанию; venv может быть и другим) +```console +$ source .venv/bin/activate +``` + +#### pylint +```console +$ python3 -m pylint $(find . -name '*.py') +``` + +#### black + isort (dry-run) +```console +$ python3 -m black --config pyproject.toml --check --diff $(find . -name '*.py') +$ python3 -m isort --settings-file pyproject.toml --check --diff $(find . -name '*.py') +``` + +#### black + isort (автоформатирование) +```console +$ python3 -m black --config pyproject.toml $(find . -name '*.py') +$ python3 -m isort --settings-file pyproject.toml $(find . -name '*.py') +``` + +> :information_source: При изменении форматирования в репозитории может сильно испортиться вывод `git blame`. +> +> Начиная с версии 2.23, `git` умеет игнорировать изменения из таких коммитов. +> Для этого при смене форматирования кода надо будет добавлять в репозиторий файл `.git-blame-ignore-revs`. +> +> Подробнее об этом можно почитать здесь: https://black.readthedocs.io/en/stable/guides/introducing_black_to_your_project.html + +**по завершению - выйти из venv** +```console +$ deactivate +``` + +Автоформатирование в IDE +------------- + +### VSCode + +Один раз настраиваем VSCode: + + * устанавливаем расширение Python: `Ctrl-Shift-X` (открывает Marketplace), в строке поиска вводим `python`, + устанавливаем первое расширение из списка (от Microsoft); +* аналогично устанавливаем расширения black, isort, pylint (все от Microsoft) + * открываем редактор настроек VSCode: `Ctrl-Shift-P`, в поиске вводим `settings json`, + выбираем `Preferences: Open Settings (JSON)`; + * в открывшемся редакторе вводим (или добавляем опции в существующий объект); + * если файлы настроек расположены не в `~/.config/wb/`, то заменяем `${env:HOME}/.config/wb/` на корректный путь: + +#### Linux +```json +{ + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python3", + "python.terminal.activateEnvironment": true, + "black-formatter.args": [ + "--config=${workspaceFolder}/pyproject.toml" + ], + "black-formatter.importStrategy": "fromEnvironment", + "isort.check": true, + "isort.args": [ + "--settings-path=${workspaceFolder}" + ], + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + "pylint.lintOnChange": true, + "isort.importStrategy": "fromEnvironment", + "pylint.importStrategy": "fromEnvironment", + "pylint.severity": { + "refactor": "Warning" + }, +} +``` + +#### Windows +```json +{ + "python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe", + "python.terminal.activateEnvironment": true, + "black-formatter.args": [ + "--config=${workspaceFolder}\\pyproject.toml" + ], + "black-formatter.importStrategy": "fromEnvironment", + "isort.check": true, + "isort.args": [ + "--settings-path=${workspaceFolder}" + ], + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + "pylint.lintOnChange": true, + "isort.importStrategy": "fromEnvironment", + "pylint.importStrategy": "fromEnvironment", + "pylint.severity": { + "refactor": "Warning" + }, +} +``` + +Code coverage +------------- + +Для оценки покрытия тестов используется плагин pytest `coverage`. Оценка показывает процент выполнившихся строк кода и веток условий относительно общего объема файлов, использованных в процессе тестирования. + +### Запуск + +В зависимости от того, как вы запускаете тесты, нужно использовать разные сценарии. +**Минимально допустимое значение покрытия для прохождения тестов указывается в аргументе `--cov-fail-under`** + +#### Внутри devcontainer + +1. Скачать из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положить в корень проекта. +2. Запустить +```console +$ pytest --cov --cov-config=.coveragerc --cov-report=term --cov-branch --cov-fail-under= +``` + +#### Внутри venv + +1. Настроить venv [по инструкции для codestyle-проверок](#установка-codestyle-тулзов-один-раз-для-проекта). В venv должен установиться пакет pytest-cov. +2. Скачать из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положить в корень проекта. +3. Запустить +```console +$ pytest --cov --cov-config=.coveragerc --cov-report=term --cov-branch --cov-fail-under= +``` + +#### Сборка wbdev + +Работает только для `wbdev ndeb` и `wbdev cdeb` +1. Скачать из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положить в корень проекта. +2. Запустить +```console +$ export WBDEV_PYBUILD_TEST_ARGS="--cov --cov-config=../../../.coveragerc --cov-report=term --cov-branch --cov-fail-under=" +$ wbdev ndeb +``` + +### Отчеты + +Для сборки внутри devcontainer и внутри venv есть возможность сгенерировать html отчет (для варианта wbdev сложные аргументы запуска, поэтому не приводим его). Для включения генерации нужно добавить опцию запуска +```console +--cov-report=html +``` +В папке проекта создастся папка htmlcov. Чтобы посмотреть отчет, откройте файл index.html в браузере. + +### Настройка VSCode + +#### Для запуска тестов из VSCode: + +1. Скачайте из [codestyle-репозитория](https://github.com/wirenboard/codestyle/tree/master/python) файл `.coveragerc` и положите в корень проекта. +2. Установите плагин для Python и настройте запуск тестов через pytest +3. Откройте настройки workspace: нажмите комбинацию `Ctrl-Shift-P`, в поиске введите `settings json`, + выбираем `Preferences: Open Workspace Settings (JSON)` +4. К массиву опций запуска добавляем: +``` +"--cov","--cov-config",".coveragerc","--cov-report","term","--cov-branch","--cov-fail-under","" +``` +Должны получиться примерно такие настройки: +```json +{ + "python.testing.pytestArgs": [ + "tests","--cov","--cov-config",".coveragerc","--cov-report","term","--cov-branch","--cov-fail-under","" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} +``` + +#### Для просмотра отчета в исходных файлах: +1. Установите плагин [Coverage Gutters](https://github.com/ryanluker/vscode-coverage-gutters) +2. К опциям запуска добавьте `"--cov-report","xml"` +3. Проведите тестирование +3. Откройте файл, покрытие которого хотите посмотреть. В статус-баре VSCode найдите надпись `Watch` и нажмите ее для включения подсветки. + + +Ещё гайдлайны +------------- + + * [Реализация сервисов на Python](guidelines/service-python.md) + * [Шаблон setup.py](python/setup.py) + +Changelog +--------- + +### v1.0 (2022/04/19) + + * Initial version + + + +Wiren Board Embedded C Style Guide +======================== + +The following sheet describes few recomodations in order to make an easy to read +and well organised program code focusing on C programming language convenstions what can be +different from C++ conventions. + +## Marking and Parenthesis +These are similar those what we have got used in C++. Read more in: https://github.com/contactless/codestyle. + +## Naming +In embedded C we don't follow CamelCase style however we use snake_case. At snake_case style function and variable names can +include **only lower case characters**, like *snake_case* (even though officially upper case characters are also allowed) and words must be separated by underscore character (_). + +## Library naming +1. When you add a new module to your project you should put it in a new library. +2. Please avoid libraries like: *peripherals.h*. +3. Library names should describe the module behind the code. Use *mcp230xx.h* isntead of *gpio_chip.h* +4. Try to separate module funtionalities, in order to support your code reusability. Better to implement basic IC + related functions in one library, and write task specific functions in an other library. Like: *mcp230xx.h* includes + driver to chip and *wbio-dio.h" library is using functions of *mcp230xx.h". + +## Function names +Since in C is no option to use namespace or classes, somehow you should mark your function's library and avoid this kind of +program code in *main.c*: +```C +int pin_value = read_value_digit();//function for module1 +set_config(CONFIG_1); //function for module2 +``` +Better to mark library name as a prefix for each function even if it is a static function and used only in the given +library: +```C +int pin_value = mcp230xx_read_gpio(); +ads1015_set_config(ADS_CONFIG_1); +``` +It is also true for *defines* where you should mark where is it defined: +```C +#define MCP23008_IODIR 0x00 +#define MCP23008_IPOL 0x01 +#define MCP23008_GPINTEN 0x02 +#define MCP23008_DEFVAL 0x03 +#define MCP23008_INTCON 0x04 +``` + +## Declarations +1. Functions what is not supposed be called from outside of library should be defined as *static*. +2. Static function prototypes should be in *.c* file, because it is related to implementation not to usage. +3. *#defines* used only in *.c* also should be placed in *.c* file. +4. In header file should be *defines* and functions what are necessary to use library. +5. Avoid using hardcoded constants because first, they are not describing their functionality, second harder to modify all +if once it must be. Avoid this: +```C +uint16_t gpio_config = mcp230xx_read_config(); +if (gpio_config & 0x04) { +... +``` +Better to use *define* and replace magic constant. +```C +#define MCP230xx_REG_IOCON_BANK (1 << 2) +... +uint16_t gpio_config = mcp230xx_read_config(); +if (gpio_config & MCP230xx_REG_IOCON_BANK) { +... +``` + +## C Macros + +Using of C preproccessor is generally discouraged because it makes source code hard to read and mantain. + +Whenever possible, please use C language features instead. + +Common use cases for C macros include: + +* `#define`-ing constants, for instance, array sizes, configuration parameters, register addresses or so on. +* switching on and off particular chunks of firmware via `#if` and friends. Prefereable done at module level. +* compile-time magic otherwise impossible to implement. Say, altering of control flow in coroutines or implementing assert. Try to limit it to few low-level libraries. + +If expressions are used in macro, they must be wrapped in `do {} while (0)`. +Function-like macros should follow function-like calling semantics. Consider `START_TIMER();` instead of `START_TIMER;`. + +All macros must be named in `CAPITAL` letters. + +Please consider the following alternatives to C preprocessor: +* `if`s with condition evaluating to `true` at compile time insted of `#if`s +* `const` variables instead of `#define`s +* `static inline` functions instead of macros containing expressions. + +## Code Tips + +### Clearing Interrupt Flags + +It is important to NOT use RMW (read-modify-write) operation when clearing interrupt flag in the status register of peripherals. If status register was modified by peripheral during 'modify' operation, the new interrupt flag will be cleared together with the specified flag. As a result, new interrupt will be lost. + +Worst exapmle: + +``` +if (TIM1-SR & TIM_SR_CC1IF) { + TIM1-SR &= ~TIM_SR_CC1IF; +} +if (TIM1-SR & TIM_SR_CC2IF) { + TIM1-SR &= ~TIM_SR_CC2IF; +} +``` + +If CC1 and CC2 events are near in time, one of them may be lost. + +Good example: + +``` +if (TIM1-SR & TIM_SR_CC1IF) { + TIM1-SR = ~TIM_SR_CC1IF; // rc_w0 +} +if (TIM1-SR & TIM_SR_CC2IF) { + TIM1-SR = ~TIM_SR_CC2IF; // rc_w0 +} +``` + +This works because the bits in SR register are rc_w0 type and writing '1' does nothing. + +## Кодировка +Все файлы должны быть созданы в кодировке UTF-8. + +## Конец строки +Конец файла всегда заканчивается переводом строки. + +## Выравнивание +Пробелы + +1. Отступы 4 пробела между блоками - не использовать символ табуляции (TAB). +2. Знаки бинарных операций отделяются от переменных пробелами, а знаки унарных операций - нет. +```C +a += b / c; +c++; +c &= ~d; +``` +3. Пробелы между ``` if while do for``` и открывающей скобкой выражения. +4. Аргументы функций перечисляются без пробелов вокруг скобок. Когда аргументов несколько - запятая сразу после аргумента, после запятой - пробел: +```C +task_schedule(adc_start_periodic_conversion_task_id, ADC_FILTRATION_PERIOD_MS); +``` +5. Значения макросов, которые именуют константы, должны быть расположены от 40 до 60 знакоместа кратно 4 символам. +Это нужно для того чтобы оставить место для макросов с длинными именами, которые могут быть потенциально добавлены позже в процессе разработки. + +Отступы и пробелы можно проверять, например, используя команду git diff. С помощью команды git diff master проверяется то что не изменено форматирование уже написанного ранее кода относительно мастер-ветки. + +Не использовать отступы для выравнивания + +Примеры плохого кода: +```C +static uint16_t task_next_free_id[TASK_TYPE_NUMBER] = {GET_TASK_ID(TASK_IMMEDIATE, 0), + GET_TASK_ID(TASK_BACKGROUND, 0)}; +``` + +```C + hlw8012_channels_state[channel].energy_factor = fix16_to_int(fix16_mul(fix16_div(fix16_from_int(hlw8012_channel_defs[channel].energy_unit_time),hlw8012_channels_state[channel].value_factor),F16(HLW8012_PULSE_COUNTING_FACTOR))); +``` + +```C + hlw8012_channels_state[channel].energy_factor = fix16_to_int(fix16_mul(fix16_div(fix16_from_int(hlw8012_channel_defs[channel].energy_unit_time), + hlw8012_channels_state[channel].value_factor),F16(HLW8012_PULSE_COUNTING_FACTOR))); +``` + +Примеры хорошего кода: + +```C +static uint16_t task_next_free_id[TASK_TYPE_NUMBER] = { + GET_TASK_ID(TASK_IMMEDIATE, 0), + GET_TASK_ID(TASK_BACKGROUND, 0) +}; +``` + +```C + hlw8012_channels_state[channel].energy_factor = fix16_to_int( + fix16_mul( + fix16_div( + fix16_from_int( + hlw8012_channel_defs[channel].energy_unit_time + ), + hlw8012_channels_state[channel].value_factor + ), + F16(HLW8012_PULSE_COUNTING_FACTOR) + ) + ); +``` + +Пример расстановки пробелов при обьявлении массива: +```C +static const uint8_t array_example[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; +``` + +## Комментарии +Комментарии после строки (короткий) или до строки (длинный): +```C +код // короткий комментарий +``` +или +```C +// длинный комментарий +код +``` +```//``` выравнивается кратно 4-м знакоместам (в редакторе vscode клавишей TAB в режиме 4 пробела вместо табуляции). +Текст от знака ```//``` отделяется пробелом. + +## Расстановка скобок +Даже если тело условного блока состоит из одной строки, оно заключается в фигурные скобки. + +Перенос скобки на следующую строку начала определения осуществляется в функциях и многострочных условиях (if, else if), в остальном нет. +Расстановка скобок: +```C +void function(void) +{ + if (a == b) { + // single str action use brackets + } + + if (c == d) { + // do if equal + } else { + // do if not equal + } + + if ((a == b) || + (c == d)) + { + // multi str condition use statement body open bracket with newline + } else if ((a != b) && + (c != e)) + { + // multi str condition use statement body open bracket with newline + } else if (c == e) { + // single str condition use statement body open bracket with one line + } +} + +void function(void) +{ + return; +} + +``` +Скобки ставятся всегда, даже когда тело блока 1 строчка и даже когда его нет: +```C +while () {}; + +if () { + return 0; +} else { + return 1; +} +``` + +Операции всегда выделяются скобками, даже если порядок действий очевиден: +```C +(a || b && !c) -> (a || (b && (!c))) +``` + +## Объявления +В заголовочном (.h) файле должен быть только интерфейс к модулю т.е. обьявления функций, структур и макросы которые вызываются и используются в других модулях. Также в заголовочном файле могут помещаться ```static inline``` функции, если они например используются как заглушки. + +При переопределении или определении новых типов данных в конце названий добавляется ```_t```. +Пример: + +```C +typedef struct { + uint16_t address; + uint16_t data; +} hold_reg_t; +``` + +имена структур не должны иметь в названии ```_t```. + +```C +struct hold_reg { + uint16_t address; + uint16_t data; +}; +``` + +## Ветвления + +Запрещается использование тренарных операций, так как они усложняют понимание кода. ```int a = (cond) ? var1 : var 2``` + +Если условие содержит логическое И, рекомендуется сделать 2 вложенных if. Такой подход нагляднее демонстрирует условия выполнения, а также заставляет задуматься о порядке проверки условий, и упрощении кода. + +``` +if (cond1) { + if (cond2) { + ... + } +} +``` + +## Однотипные сущности + +Часто бывает что устройство имеет несколько входов/выходов/шин/светодиодов/кнопок итд, абсолютно всегда все что потенциально может быть больше чем в одном экземпляре должно обрабатываться через индексы и циклы. + +## Исправление ошибок в master + +Ранее допущенные ошибки форматирования исправляются в отдельной ветке с PR согласно кодстайлу. + +## Неинициализированные переменные + +Неинициализированных переменных быть не должно. Любая переменная, структура, указатель или массив должны быть инициализированы при объявлении. Даже если где то позже переменная будет принимать значение, например при чтении настроек из файловой системы. Нули уменьшат спектр чудес при ошибке или гарантированно будут вызывать hard fault, ускоряя отладку. + +## Логические выражения + +Запрещается использовать логические операции где либо кроме операторов условия. + +`return (enabled_ch_mask & (1 << channel)) != 0;` + +или + +`x = (enabled_ch_mask & (1 << channel)) != 0;` + +Проверка логического условия в выражении присваивания усложняет понимание. Вместо этого предлагается написать обычный читаемый if. + +``` +if (enabled_ch_mask & (1 << channel)) { + return 1; +} +return 0; +``` + +или + +``` +x = 0; +if (enabled_ch_mask & (1 << channel)) { + x = 1; +} +``` + +## Выбор типа переменной + +Типы с явной разрядностью (uint8_t, uint16_t, uint32_t) нужно использовать только в случаях, где это важно для работы (протоколы, структуры данных) или необходимо для читаемости. +* данные +* регистры переферии +* результаты вычисления CRC и подобное +* статически выделенные переменные, для экономии ОЗУ + +В остальных случаях использовать unsigned для беззнакового или int для знакового типа, а именно: +* счетчик цикла for +* индексы +* аргументы функций - индекс, длина, все что не данные +* все что на стеке и не критично к размеру, флаги, промежуточные вычисления + +Это связано с тем что компилятор вставляет дополнительные инструкции UXTH UXTB SXTH SXTB для приведения типов (зануления старших разрядов). Чтобы uint8 преобразовать к машинному слову 32 бита, нужно отбросить старшие разряды с мусором). Это увеличивает размер кода и время его выполнения. Посмотрите дизасемблером любопытства ради. Используем их осмысленно только при необходимости. + +## Порядок полей в структурах + +Нужно помнить что компилятор выравнивает поля в структурах по размеру слова архитектуры - 32 бита. Для экономии памяти нужно думать как структура будет хранится в памяти. + +Поля расположены плохо, размер структуры 12 байт: +```C +struct hold_reg { + uint8_t data; + uint32_t address; + uint8_t crc; +}; +``` + +Поля расположены хорошо, размер структуры с теми же самыми данными 8 байт: +```C +struct hold_reg { + uint32_t address; + uint8_t data; + uint8_t crc; +}; +``` + +# Имена вариантов enum + +Нужно выбирать осмысленное имя типа enum, и все варианты должны начинаться с префикса данного имени + +Хорошее именование, по вариантам понятно о каком enum идет речь: +```C +enum w1_transaction { + W1_TRANSACTION_SEND, + W1_TRANSACTION_RECEIVE +} +``` + +Плохое именование, невозможно понять какому enum принадлежит вариант пока не посмотрим определение: +```C +enum w1_transaction { + W1_PROTOCOL_SEND, + W1_PROTOCOL_RECEIVE +} +``` + +Плохое именование, слишком длинные имена, наверное можно подумать и выбрать более короткий вариант без потери смысла: +```C +enum w1_protocol_transaction { + W1_PROTOCOL_TRANSACTION_SEND, + W1_PROTOCOL_TRANSACTION_RECEIVE +} +``` + + + + +Wiren Board C++ Style Guide +======================== + +За основу взят яндексовский Style Guide для C++. + +## Разметка + +Для отступов используются только пробелы. Один уровень отступа - это 4 пробела. + +Знаки арифметических операций выделяются пробелами: + +```C++ +int sum = a + b; +``` + +Запятые выделяются пробелами с одной стороны: + +```C++ +int sum = GetSum(a, b); +``` + + +## Скобочки + +В определениях функций и методов открывающая определение фигурная скобка переносится на следующую строчку. + +```C++ +int foo() +{ + std::cout << "foo" << std:endl; + return 0; +} +``` + +Используется компактный стиль расстановки фигурных скобок внутри определений функций и методов. + +```C++ +if (foo && bar) { + std::cout << "foobar" << std:endl; +} +``` + +В случае многострочных условий if и циклов фигурная скобка переносится на отдельную строку. + +```C++ +if (foo && + bar) +{ + std::cout << "foobar" << std:endl; +} +``` + +Блоки кода в циклах и условных выражениях, состоящие из одной строки, заключаются в фигурные скобки при переносе на другую строчку: + +```C++ +if (foo) return 0; +``` + +```C++ +if (foo) { + std::cout << "foo" << std:endl; +} +``` + +Пустое тело в циклах должно быть заключено в фигурные скобки + +```C++ +while (ReadNoise()) {} +``` + + + + +## Именование +### Общее +Основным способом создания имён всего является **JavaCamelCase**. +*snake_case* допускается в названиях локальных переменных, при этом они не должны использоваться в одном файле с CamelCase локальными переменными. + + + +Функции и методы называются словосочетанием, описывающим то, что делаем функция, и начинаются с глагола: **const std::string& GetMethodName()**. + +Цель к которой нужно стремиться: по названию должно быть понятно, что делает функция, класс или что хранит в себе переменная без необходимости смотреть реализацию. +Стоит избегать названий функций и методов типа *DoWork()*, переменных типа *counter* и т.д, кроме случаев, когда это не мешает пониманию. + +Допускаются общепринятые короткие названия для переменных типа *i,j* для счётчиков в циклах. + +В аббревиатурах остаётся первая заглавная буква: *TMqttClient*. + + +### Классы + +Классы именуются с заглавной *T*, например *TModbusClient*. + +Базовые классы должны иметь название, заканчивающееся на *Base*: *TModbusClientBase*. + +Названия классов-интерфейсов начинаются с *I*: *class IException* + +### Методы + +Методы называются в CamelCase с большой буквы: *GetMethodName*. + +### Поля классов +Поля данных (переменные) классов называются с большой буквы: + +```C++ +class TModbusClient +{ +public: + +std::string GetMethodName() { return MethodName; }; + +private: + +std::string MethodName; +} +``` + +### Локальные переменные + +Называются с маленькой буквы в camelCase. Допускается наименование с маленькой буквы в snake_case. + +## Макросы + +Препроцессор C использовать не стоит. Вместо него предпочтительно использовать конструкции C++. + +В очень редких случаях использование макросов действительно даёт большой выигрыш в читаемости кода. +Если вы столкнулись именно с таким случаем, то пользоваться препроцессором можно. Но стоит быть готовыми отстаивать свою правоту на review. + +## Примеры + +https://github.com/contactless/wb-homa-drivers/tree/master/wb-mqtt-serialo + + + +Wiren Board JavaScript Style Guide +============================== + +За основу взят стиль [Airbnb](https://airbnb.io/javascript/). + +Для форматирования кода используется [Prettier](https://prettier.io/). + +Для проверки кода используется [ESLint](https://eslint.org/). + + +Установка `ESLint` и `Prettier` +------------- + +```console +$ npm install --save-dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks @babel/eslint-parser @babel/preset-react +``` + +Файлы с настройками находятся в репозитории в директории `js`. +Их надо скопировать в каталог проекта под именами +``` +.eslintrc +.prettierrc +``` + +Для wb-rules правил используйте `js/eslintrc-es5`, так как правила пишутся на ECMAScript 5. + +В раздел `scripts` файла `package.json` можно добавить строку + +```js + "lint": "eslint \"**/*.{js,jsx}\"" +``` + +### Запуск eslint + +```console +$ npx eslint FILE.js +``` + +### Запуск prettier + +```console +$ npx prettier +``` + + +Настройка IDE +------------- + +### VSCode + + * устанавливаем расширение ESlint: `Ctrl-Shift-X` (открывает Marketplace), в строке поиска вводим `eslint`, + устанавливаем первое расширение из списка (от Microsoft); + * устанавливаем расширение Prettier: `Ctrl-Shift-X` (открывает Marketplace), в строке поиска вводим `prettier`; + * открываем редактор настроек VSCode: `Ctrl-Shift-P`, в поиске вводим `settings json`, + выбираем `Preferences: Open Settings (JSON)`; + * в открывшемся редакторе вводим (или добавляем опции в существующий объект); +```json +{ + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + } +} +``` + + * если файлы настройки `prettier` расположены не в каталоге проекта, то надо указать параметр `prettier.configPath`; + * если файлы настройки `eslint` расположены не в каталоге проекта, то надо добавить параметр: + +```json +{ + "eslint.options": { + "overrideConfigFile": "PATH/.eslintrc" + } +} +``` + + + +Wiren Board GO style guide +========================== + +Format your code with `go fmt`. + +## Static analysis + +Install [staticcheck](https://staticcheck.dev/): +```sh +apt install go-staticcheck +``` + +Running: +```sh +go mod vendor +staticcheck -go 1.13 ./... +``` \ No newline at end of file From b32aeff12f63475bf036041cdf5a4b39c52a7385 Mon Sep 17 00:00:00 2001 From: vdromanov Date: Wed, 24 Sep 2025 17:07:24 +0300 Subject: [PATCH 3/6] move lintian.md to its own folder --- lintian.md => lintian/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lintian.md => lintian/README.md (100%) diff --git a/lintian.md b/lintian/README.md similarity index 100% rename from lintian.md rename to lintian/README.md From 94a04061b8dbcacfd63b4b934fb899b680000d84 Mon Sep 17 00:00:00 2001 From: vdromanov Date: Wed, 24 Sep 2025 17:27:08 +0300 Subject: [PATCH 4/6] add cross-files anchors --- README.md | 10 +++++----- style.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a1d309b..b777808 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Wiren Board codestyle Codestyle --------- - * [C++](./C++.ru.md) ([en](./C++.en.md)) - * [Python](./python.ru.md) - * [Embedded C (en)](./embedded_c.en.md) - * [Javascript](https://github.com/airbnb/javascript) - * [Go (en)](./go.en.md) + * [C++](https://github.com/wirenboard/codestyle/blob/master/style.md#cplusplus) + * [Python](https://github.com/wirenboard/codestyle/blob/master/style.md#python) + * [Embedded C](https://github.com/wirenboard/codestyle/blob/master/style.md#embedded-c) + * [Javascript](https://github.com/wirenboard/codestyle/blob/master/style.md#js) + * [Go](https://github.com/wirenboard/codestyle/blob/master/style.md#go) Гайдлайны --------- diff --git a/style.md b/style.md index 88ffe6f..a766d21 100644 --- a/style.md +++ b/style.md @@ -1,4 +1,4 @@ -Wiren Board Python Style Guide +[Wiren Board Python Style Guide](#python) ============================== За основу взят стиль, описанный в PEP8 (https://peps.python.org/pep-0008/). @@ -432,7 +432,7 @@ Changelog -Wiren Board Embedded C Style Guide +[Wiren Board Embedded C Style Guide](#embedded-c) ======================== The following sheet describes few recomodations in order to make an easy to read @@ -833,7 +833,7 @@ enum w1_protocol_transaction { -Wiren Board C++ Style Guide +[Wiren Board C++ Style Guide](#cplusplus) ======================== За основу взят яндексовский Style Guide для C++. @@ -968,7 +968,7 @@ https://github.com/contactless/wb-homa-drivers/tree/master/wb-mqtt-serialo -Wiren Board JavaScript Style Guide +[Wiren Board JavaScript Style Guide](#js) ============================== За основу взят стиль [Airbnb](https://airbnb.io/javascript/). @@ -1050,7 +1050,7 @@ $ npx prettier -Wiren Board GO style guide +[Wiren Board GO style guide](#go) ========================== Format your code with `go fmt`. From a03edb3150b5ed0a4aeab1114ec78131e8361999 Mon Sep 17 00:00:00 2001 From: vdromanov Date: Wed, 24 Sep 2025 17:27:57 +0300 Subject: [PATCH 5/6] move tools.md to guidelines --- README.md | 2 +- tools.md => guidelines/tools.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tools.md => guidelines/tools.md (100%) diff --git a/README.md b/README.md index b777808..8e7479c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Codestyle ### Общие - * [Tools](./tools.md) - полезные утилиты на все случаи жизни + * [Tools](./guidelines/tools.md) - полезные утилиты на все случаи жизни ### Разработка под Linux * [WB services](./guidelines/services.md) - настройка WB сервисов diff --git a/tools.md b/guidelines/tools.md similarity index 100% rename from tools.md rename to guidelines/tools.md From 2f0827a364a858b5a97a9f848e975fef8e10b7f2 Mon Sep 17 00:00:00 2001 From: vdromanov Date: Wed, 24 Sep 2025 17:33:09 +0300 Subject: [PATCH 6/6] translate go styleguide --- style.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/style.md b/style.md index a766d21..82edd79 100644 --- a/style.md +++ b/style.md @@ -1053,16 +1053,16 @@ $ npx prettier [Wiren Board GO style guide](#go) ========================== -Format your code with `go fmt`. +Форматировать код стандартными тулзами `go fmt`. -## Static analysis +## Статический анализ -Install [staticcheck](https://staticcheck.dev/): +Установка [staticcheck](https://staticcheck.dev/): ```sh apt install go-staticcheck ``` -Running: +Запуск: ```sh go mod vendor staticcheck -go 1.13 ./...