четверг, 21 августа 2025 г.

[prog.c++] Я бы розгами прививал C++никам привычку использовать using для псевдонимов типов

На первом курсе ВУЗа нас учили языку Pascal. Пожалуй, лучшее, что удалось взять из Pascal -- это привычку давать имена типам. Полагаю, возникла она потому, что преподаватели хорошенько нам разъясняли почему это правильно, полезно и удобно.

Эта привычка помогала и после перехода на Си, а затем и на C++.

Почему-то мне казалось, что в те старые времена и в Си, и в C++ была культура использования typedef-ов для создания псевдонимов типов. Вероятно из-за того, что платформ было много, платформы были разные. А псевдонимы, сделанные через typedef, помогали справляться с их различием. Достаточно вспомнить приключения при переходе от 16 бит к 32 битам и обратно.

Но сейчас складывается впечатление, что многие C++ разработчики про typedef и using даже и не знают. Вот реально приятно удивляешься, когда открываешь чужой проект -- а там систематическое применение using-ов (или typedef-ов). Обычно же все гораздо печальнее и приходится иметь дело с чем-то вроде:

void do_something(
  std::vector<std::pair<int, std::shared_ptr<my_obj>>> & first,
  std::map<std::string, std::tuple<int, int, std::string>> & second);

А еще хуже, на мой взгляд, когда один и тот же фундаментальный тип (вроде int-а или unsigned long-а) используется для совершенно разных целей:

struct some_item {
  int _x;
  int _y;
  int _width;
  int _height;
  int _usage_percentage;
  int _reusing_mode;
  int _logging_level;
  ...
};

Когда со временем меняешь int на что-нибудь другое, то обязательно выясняется что где-то как-то параметры перепутали -- выдали _width за _height или _usage_percentage вместо _reusing_mode.

Конечно, от многих подобных проблем в C++ можно было бы избавиться, если бы в C++ из коробки была поддержка strong typedef. Но даже в ее отсутствии обычные псевдонимы типов, сделанные через using, сильно повышают читабельность кода и упрощают его сопровождение (или портирование на другую платформу).

Вроде бы уже не раз говорил в разных местах о достоинствах using-ов:

  • сокращение присанины, приведение кода к более лаконичному и, как правило, более понятному виду;
  • сокрытие деталей реализации, что помогает затем менять эти самые детали всего лишь в одном месте, но сказываться замена будет автоматически повсюду.

Как по мне, так это более чем очевидно. Поэтому и не буду в очередной раз распространяться.

Но если мне это очевидно, то почему это не очевидно другим C++разработчикам?

Возможно потому, что им не привили правильные привычки во время обучения.

Отсюда проистекает мысль, что если учить людей C++у, то нужно вкладывать им в голову не только идею RAII, как одну из самых основополагающих, но и идею повсеместного применения using-а. А всякий, кто не внемлет мудрости старших, должен быть порот розгами, дабы полезная наука усвоена была, пусть и через другое место.

PS. Очень надеюсь, что рано или поздно, но C++ обзаведется штатным, доступным прямо в стандартной библиотеке, механизмом strong typedef. И если в вашем коде using используется должным образом, то переход к применение strong typedef-а окажется более простым и менее болезненным.

понедельник, 18 августа 2025 г.

[prog] Мои подходы по именованию веток в git-е

Git славен тем, что работа с ветками в нем возведена чуть ли не в абсолют. Насколько я могу судить, в более-менее вменяемо построенном процессе разработки использование веток -- это обязательная часть внесения изменений в проект: отпочковался от master, внес изменения, взял из master-а свежие правки из коллег, чтобы разрулить конфликты, затем влил свои правки в master.

Это ведет к тому, что при активной работе (особенно активной командной работе) одновременно живет несколько веток. А плюс к этому может быть еще сколько-то, скажем так, позабытых веток. Т.е. веток в которых работа велась, но которые не были по тем или иным причинам доведены до вливания в master. Например, пилили фичу, которую в итоге признали ненужной или преждевременной. Или начали какой-то эксперимент, получили неутешительные результаты и решили не продолжать.

Да, я понимаю, что наличие таких "позабытых и позаброшенных" веток не есть хорошо, но такова се ля ви. Во всех виденных мной проектах они были. Где-то "мусор" время от времени чистят. Но какие-то ветки просто так удалять и не хочется -- вот те же самые результаты неудачных экспериментов. В них могут оставаться какие-то наработки, которые могут оказаться тем или иным образом востребованы со временем. Хотя бы для того, чтобы подсмотреть как уже делали и что именно не получилось.

Так вот, ближе к теме разговора: когда в Git-репозитории одновременно существует несколько десятков веток, надо бы как-то в этом многообразии разбираться. А значит нужна какая-то система именования веток, чтобы сделав git branch можно было понять где, что и как.

С большим удивлением для себя регулярно обнаруживаю, что часто в именованиях веток нет вообще никакой системы. Обычно встречаются ветки с именами "bug-fix" или "memory-usage". Иногда к именам веток добавляются префиксы типа "bug/" (тогда получаются имена вида "bug/login-crach" или "bug/memory-leak") или "feature/" (тогда получаются имена вида "feature/open-auth-login" или "feature/sparse-table"), или "experimental/" (тогда получаются имена вида "experimental/simd-json"). Хотя, как по мне, эти префиксы не особо-то и полезны.

Но самое плохое, что лично мне такие имена ни о чем не говорят 🙁

Ситуация получше когда для всех задач проекта есть тикеты в условной JIRA и номер тикета. Особенно когда к номеру тикета добавляется еще что-то поясняющее, вроде "issue-1416-attempt-2". Но и это не идеал.

Наблюдая за подобным решил описать два своих подхода к именованию веток, которые я использую в несколько разных условиях. Ни в коем случае не как образец для подражания, а просто в рамках обмена опытом.


Первая схема используется при работе над нашими открытыми проектами, вроде SObjectizer и RESTinio.

Там имя ветки строится из трех компонентов:

  • имя мажорной версии продукта. Например, сейчас развивается версия 5.8 для SObjectizer. Поэтому первая составляющая будет иметь вид "5.8-dev", т.е. основная dev-ветка для версии 5.8;
  • имя конкретной версии продукта. Например, сейчас в работе версия 5.8.5, которая пока до релиза не дошла. Поэтому вторая составляющая будет иметь вид "5.8.5";
  • краткое именование того, над чем идет работа. Например, "reduce-state-size". Возможно, с указанием того, какая это итерация, например, "reduce-state-size-v2".

В результате получаются ветки с именами "5.8-dev-5.8.5-reduce-state-size-v2".

Чтобы не быть голословным, вот имена нескольких веток, которые можно сейчас увидеть в репозитории SObjectizer-а:

5.8-dev
5.8-dev-5.8.5
5.8-dev-5.8.5-cmake-files-update
5.8-dev-5.8.5-issue-96
5.8-dev-5.8.5-state-time-limit-v2
5.8-dev-5.8.5-issue-93
...

Можно обратить внимание, что имена образуют иерархичность и указывают что и куда затем должно вливаться. Так, изменения из "5.8-dev-5.8.5-cmake-files-update" будут влиты в "5.8-dev-5.8.5", а затем изменения из "5.8-dev-5.8.5" будут влиты в "5.8-dev".

Как-то у меня не прижился прямой слэш в качестве разделителя, поэтому мне удобнее работать с именами вида "5.8-dev-5.8.5-cmake-files-update", а не с "5.8-dev/5.8.5/cmake-files-update". Вкусовщина, конечно же, но у меня вот так.


Вторая схема используется мной когда я работаю консультантом на внешнем проекте и имею доступ к репозиторию заказчика. В этом случае имена создаваемых мной веток также состоят из трех составляющих:

  • никнейм основного автора кода в ветке, в моем случае это "eao197";
  • дата начала работы над веткой в формате YYYYMMDD;
  • краткое именование того, над чем идет работа. Например, "reduce-state-size". Возможно, с указанием того, какая это итерация, например, "reduce-state-size-v2".

Что в результате дает имена веток вида: "eao197-20250814-versioned-data-v2-tmp". Если для задачи есть номер тикета, то он включается в третью часть. Типа "eao197-20250818-issue-77889-attempt-1".

Такое именование лично для меня оказывается полезным тем, что:

  • сразу видно кто отвечает за ветку. В частности, мне легко понято что было сделано (и/или забыто) лично мной;
  • степень "свежести" изменений. Когда прямо в имени ветки видишь, что она относится к декабрю 2023-го года, то это сразу наводит на мысль, что код в ней довольно древний. Можно даже не делать git log 😉

К счастью, пока мне за такую схему по рукам не били. Видимо, она устраивает не только меня. По крайней мере хочется в это верить.


Интересно, а какие схемы именования веток в git-е вы считаете удобными и, может быть, даже практикуете?