четверг, 24 октября 2024 г.

[prog.c++] Впечатления от Google-овского sparsetable

Давеча довелось применить sparsetable из Google-овского sparsehash для представления разреженного вектора значений. Разреженный вектор в моем случае -- это вектор с обычной индексацией от 0 до N, где части элементов нет физически. Обращения к элементам идут именно по целочисленным индексам (к тому же индексы в моём случае были представлены std::uint32_t).

В принципе, здесь можно было бы воспользоваться каким-то ассоциативным контейнером на базе деревьев или хэш-таблиц, но все эксперименты показывали, что когда в разреженном векторе хранятся небольшие значения (например, указатели), то любые ассоциативные контейнеры потребляют значительно больше памяти чем обычный std::vector вектор, в котором незанятые ячейки заполнены специальными "мусорными" значениями.

Тогда как sparsetable показал себя здесь очень и очень хорошо.

Причем речь идет именно о классе sparsetable, который хоть и публичный в библиотеке sparsehash, но все-таки выглядит как вспомогательный внутренний класс, использующийся в реализации sparse_hash_map и dense_hash_map. Т.е. это именно что специальная структура данных для представления разреженного вектора, а не какой-то из вариантов хэш-таблицы.

При том, что класс sparsetable отлично зарекомендовал себя именно как специализированная структура данных, не могу не отметить несколько неприятных особенностей реализации sparsetable, обусловленных как старым C++ стандартом, в рамках которого написан sparsetable (а это C++03), так и подходом к разработке софта в Google.

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

Итак, поехали.

Поскольку sparsetable написан для C++03, то он не поддерживает move semantic. Вообще. Причем как для самого sparsetable -- там нет ни конструктора, ни оператора перемещения, хотя swap для sparsetable есть. Но, самое плохое, sparsetable не поддерживает movable-only типы, вроде std::unique_ptr. Т.е. из коробки не получится хранить в sparsetable экземпляры std::unique_ptr 🙁

Еще sparsetable не поддерживает strong exception safety. Там прямо в комментариях в коде написано, что следовало бы реализовать обработку ситуации, когда конструктор копирования для помещаемого в контейнер объекта бросает исключение. Если такое произойдет, то контейнер останется в хз каком состоянии.

И с exception safety там есть еще один прикольный момент: дело в том, что sparsetable выполняет переаллокацию памяти даже при удалении элемента. Т.е. да, вы вызываете erase, внутри erase sparsetable решает заменить блок из которого элемент изъят блоком меньшего размера, для чего обращается к аллокатору... А аллокатор, в принципе, может бросить исключение. Т.е. из erase может вылететь bad_alloc 🙁 Что неприятно, т.к. время от времени я пишу код, в котором для выполнения отката ранее внесенных изменений при исключении делаются erase для возврата модифицированных контейнеров к исходному виду. Представьте себе что в блоке catch или даже в деструкторе объекта вы вызываете erase для sparsetable, а он бросает bad_alloc 🥴

Ну и работа с памятью там имеет одну важную особенность. По умолчанию sparsetable использует собственный аллокатор под названием libc_allocator_with_realloc. Этот аллокатор, как следует из его названия, полагается на Си-шный realloc при переаллокации блоков данных. Но вот если std::realloc возвращает NULL, то sparsetable просто... прерывает работу программы через вызов std::exit. Понятно, что это особенности Google-овского софта, под нужды которого sparsetable и писался, но блин, представьте себе, что у вас сервер, который держит в ОП информацию о десятках тысяч текущий сессий. И вдруг весь этот сервер одномоментно падает из-за того, что при обработке запроса в рамках одной из сессий не удалось переаллоцировать блок в одном sparsetable. Как-то печально, как по мне 🙁

Плюс к тому исходный sparsetable содержит методы для какой-то странной сериализации/десериализации. Полагаю, что в Google от sparsetable это требовалось. Но зачем это всем остальным -- хз.

И совсем отдельная история -- это отношение sparsetable к несуществующим элементам. Так, константная версия operator[] возвращает константную ссылку. В том числе и на несуществующие элементы! Если значения по индексу i нет, то operator[] вернет константную ссылку на статический объект типа T. Что, помимо прочего, означает, что тип T должен быть default-constructible.

Из-за того, что sparsetable выдает несуществующие элементы за существующие, но с общим дефолтным значением, проистекает и то, что у sparsetable есть два типа итераторов. Обычный итератор, который возвращается посредством begin/end, и который итерируется, в том числе, и по несуществующим элементам. И специальный nonempty-итератор + специальные методы nonempty_begin/nonempty_end, которые следует использовать, если хочется пробежаться только по существующим элементам.

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


В общем, под нужды заказчика sparsetable из sparsehash был вытащен, избавлен от части ненужного хлама, слегка доработан напильником для устранения проблем с компиляцией под C++20 и использования std::allocator. Но до глубокой модернизации sparsetable руки так и не дошли. Однако желание сделать это осталось. Буду надеятся, что это получится сделать.


Если кто-то из читателей никогда не слышал про sparsetable, то впечатление можно составить вот из этого описания: Implementation of sparse_hash_map, dense_hash_map, and sparsetable. Там первая часть как раз sparsetable и посвящена. Описание лаконичное, но толковое.

пятница, 18 октября 2024 г.

[prog.flame] Адекватен ли выбор языка программирования для такой задачи (Rust для Radicle)?

Часто говорят, что язык программирования нужно подбирать под задачу. Мол, не нужно брать C++ для того, что легко делается на Python. И не нужно брать Python там, где потребуется что-то более быстрое и менее ресурсоемкое.

Недавно узнал о Radicle -- это что-то вроде GitLab и Gitea, т.е. инструмент для совместной работы над программным кодом.

Radicle написан на Rust-е.

Не на Python-е. Не на Ruby. Не на Node.js. Не на Java или C#. Не на Go. А на Rust-е.

Как по мне, так это почти тоже самое, что и GitLab, написанный на C++. С поправкой на то, что, во-первых, Rust -- это можно и молодежно. И, во-вторых, для программирования Web-приложений все-таки Rust побезопаснее C++ будет. Но, в общем-то, тоже самое, вид в профиль.

Посему лично я в недоумении и сам бы для проекта, подобного Radicle, точно бы не стал брать C++ или Rust. Как по мне, так здесь достаточно языка со сборкой мусора: Java, Kotlin, C#, возможно даже Go (простите, динамика в лице Python/Ruby/JavaScript идет лесом, т.к. не из тех мазохистов, которые делают что-то большое на динамически-типизированных языках).

А вот брать нативный язык без GC с претензией на околосистемность... Зачем?

Собственно, непонимание и привело к появлению этого поста. Надеюсь, в комментариях мне напихают в панамку и объяснят насколько я дебил и отстал от современных трендов. Ибо я реально не понимаю, почему в 2020-х нужно отказываться от всего, что накопилось в мире безопасных языков с GC и трахать себе мозг Rust-ом в задаче, где не нужна ни супер-пупер производительность, ни супер-пупер экономия ресурсов, ни тотальный контроль за происходящим.


Если не лень, то напишите в комментариях, плиз, какой бы вы язык программирования предпочли для реализации проекта по типу Radicle/GitLab/Gitea. Я бы лично выбирал бы между C#, Kotlin и Go (хотя не на одном из них не программировал), но точно бы не C++ и не Rust.

вторник, 15 октября 2024 г.

[prog.c++] Никогда не задумывался о таком поведении С++ в случае адресов вложенных объектов, а ведь оно логично

Давайте посмотрим на структуру данных B:

struct A {};

struct B : public A {
    A _first;
};

Какой у нее размер? С учетом того, что даже у пустой структуры размер будет, как минимум, 1 байт. И что в C++ есть empty base class optimization.

То, что B наследуется от пустого A благодаря empty base class optimization не увеличивает размер B. Т.е. размер B будет определяться размером поля B::_first, а это один байт. Следовательно, sizeof(B) должен быть равен единице.

На самом деле нет.

Дело в адресах для объекта B и для его поля B::_first:

B b;
A * p1 = &b; // Это легально т.к. B есть A благодаря наследованию.
A * p2 = &b._first;

assert(p1 != p2);

Объект типа B является объектом типа A из-за наследования. Соответственно, адрес объекта типа B будет и адресом объекта типа A.

Внутри типа B есть еще один объект типа A. И у него так же есть свой адрес.

При этом объект B и его поле B::_first -- это два разных объекта типа A.

А в C++ у двух разных объектов одного типа не может быть одинаковых адресов.

Поэтому в показанном примере компилятор не может применить для типа B оптимизацию пустой базы, что и добавляет специальное выравнивание для поля B::_first, чтобы эти адреса оказались разными. Тем самым увеличивая размер B.

Убедиться в этом можно на wandbox - цынк

Информация об этой особенности C++ найдена здесь.

PS. Какой практический смысл у этой информации? Вероятно, никакого. Но это лишняя иллюстрация того, сколько же разных нюансов приходится учитывать при развитии C++.

пятница, 11 октября 2024 г.

[life.work] 30 лет профессионального программизма

Как же все-таки летит время. Вроде бы недавно публиковал аналогичный пост, но про 25 лет, а тут хоба! И уже 30.

Ну а так-то да, где-то в октябре 1994-го мне сделали трудовую книжку и я стал считаться профессиональным программистом. Писать программки начал пораньше, года с 1990-го, но именно за зарплату только с 1994-го.

Многое из того, что можно было бы сказать по этому поводу, уже было написано пять лет назад. Попробую добавить еще (не)много.

среда, 9 октября 2024 г.

[prog.data.structures] В склерозник: ссылки на тему prefix/radix tree

Довелось недавно столкнуться с темой prefix/radix деревьев. По ходу изучения нашел несколько ссылок, которые для меня оказались полезными, поэтому зафиксирую их в склерозник, чтобы было проще найти в будущем.

Даю только ссылки, без пространных пояснений, уж извините меня за халтуру, но так проще.

Adaptive Radix Tree. Собственно, основная PDF-ка, с которой и следует начинать.

HAT-trie, a cache-conscious trie. Обзор сразу нескольких подходов к реализации префиксных деревьев и хеш-таблицы с переходом к рассказу об их комбинации HAT-trie. Плюс реализация этой идеи на C++.

Crit-bit trees и qp tries and crit-bit tries. Еще несколько вариаций на тему префиксных деревьев.

Akamai Radix Tree. Реализация на C++ radix tree от Akamai. Очень интересно если хочется посмотреть, как это дело можно реализовать. Хотя оформлена библиотека (в плане описания, документации, примеров и комментариев в коде), как по мне, бестолково. Поэтому разбираться не просто + реализация на многоэтажных шаблонах, так что требуется хорошее знание C++.

Compressing dictionaries with a DAWG и Directed Acyclic Word Graphs. Пара статей вокруг идей о том, как можно сократить объем данных в префиксном дереве.

Ну и вот эта ссылка регулярно всплывала, хотя напрямую к теме, вроде бы, не относится: Implementation of sparse_hash_map, dense_hash_map, and sparsetable.

От себя добавлю, что для сценария, в котором мне потребовалось что-то вроде prefix/radix дерево, "классический" вариант префиксного дерева вел к слишком большому расходу памяти. И, как я понял, это характерно для подобного рода деревьев, поэтому как раз и есть статьи о том, как уменьшить объем данных.

пятница, 4 октября 2024 г.

[prog.c++] Использование одного аргумента шаблона (aka Traits) вместо нескольких

В догонку ко вчерашнему посту про недостающую функциональность в std::vector.

Понятное дело, что std::vector уже не сможет получить каких-то дополнительных шаблонных аргументов, потому что это поломает кучу существующего кода.

Кстати говоря, есть у меня ощущение, что в любом C++ном проекте, который писали обычные люди, вроде меня, а не монстры, вроде Девида Вандервуда или Барри Ревзина, куча кода поломается, если в std::vector начнут использовать собственные аллокаторы. Поломается потому, что код написан в стиле:

void do_something(const std::vector<int> & data) {...}

И даже шаблонный код написан вот так:

template<typename T>
void do_something(const std::vector<T> & data) {...}

а не вот так (хотя бы вот так):

template<typename T, template Allocator>
void do_something(const std::vector<T, Allocator> & data) {...}

Впрочем, это уже совсем другая история...

Но если пофантазировать?

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

Это подход на базе Traits. Уже не помню, откуда про него узнал, не удивлюсь, если из книг Александреску. Но подход уже старый и мы, например, применяем его в RESTinio.

Суть в том, что шаблон параметризуется отдельным типом Traits, внутри которого уже сосредоточена все остальная нужная шаблону информация.

Например, для std::vector это могло бы выглядеть так:

template<typename T>
struct default_vector_traits {
  using allocator = std::allocator<T>;
};

template<typename T, typename Traits = default_vector_traits<T> >
class vector {...};

И если бы со временем нам бы потребовалось добавить в шаблон std::vector еще один параметр (тот же нужный мне growth_policy), то это можно было бы сделать не меняя списка параметров для std::vector:

struct default_vector_growth_policy {
  std::size_t operator()(std::size_t current_capacity) const {
    // Код примерный, прошу помидорами не бросаться ;)
    return (current_capacity > 1 ? static_cast<std::size_t>(capacity * 1.5) : 2);
  }
};

template<typename T>
struct default_vector_traits {
  using allocator = std::allocator<T>;
  growth_policy = default_vector_growth_policy;
};

template<typename T, typename Traits = default_vector_traits<T> >
class vector {...};

И если бы мне захотелось использовать с векторами собственную политику роста емкости, то мне бы потребовалось всего лишь:

struct my_growth_policy {
  std::size_t operator()(std::size_t current_capacity) const {...}
};

template<typename T>
struct my_vector_traits : public std::default_vector_traits<T> {
  using growth_policy = my_growth_policy;
};

using my_int_vector = std::vector<int, my_vector_traits<T>>;

Понятное дело, что шаблоны классов контейнеров из стандартной библиотеки на Traits уже не перевести. Но если вы пишите свои библиотеки шаблонных классов (особенно собственных типов контейнеров, неприведихоспади!), то имеет смысл подумать о применении подхода с Traits.

четверг, 3 октября 2024 г.

[prog.c++] В std::vector не хватает вот какой штуки...

Интересно, много ли C++ программистов задумывается о том, а как растет std::vector, когда мы в него добавляем элементы через push_back и не имеем возможности сделать предварительно reserve?

А ведь рост размера std::vector может существенным образом повлиять на расход памяти.

Предположим, что реализация std::vector в вашей стандартной библиотеке использует коэффициент 1.5 для увеличения размера вектора. Т.е. вы делаете push_back в полный вектор и опа! Ваш вектор увеличился в полтора раза. Была емкость на 1000 элементов, а стала на 1500. А использоваться оттуда будет 1001.

А если реализация стандартной библиотеки использует коэффициент 2, то дела еще хуже.

Сильно негативно это начинает проявляться на векторах размером в миллион-два-три и т.д. Если у вас в программе таких векторов не один и не два, то вы с удивлением для себя сможете обнаружить, что в этих векторах где-то 1/5 или 1/4, а то и 1/3 объема не занято. Что в сумме может дать десятки мегабайт впустую потраченной памяти.

Чтобы этого не происходило приходится вручную контролировать размер и емкость вектора и, опять же, вручную дергать reserve перед push_back-ами.

А то, что делается вручную, легко забыть или же сделать неправильно 😣

Поэтому мне лично не хватает возможности задать для std::vector какую-то собственную политику роста. Что-то типа:

struct my_growth_policy {
  [[nodiscard]] std::size_t operator()(std::size_t current_capacity) {
    return ... // здесь какие-то вычисления.
  }
};

using my_vector = std::vector<my_data, std::allocator<my_data>, my_growth_policy>;

Тогда не пришлось бы вручную контролировать емкость перед каждым push_back-ом.

Но, боюсь, в стандартном std::vector такого не будет никогда.

среда, 2 октября 2024 г.

[life.cinema] О фильмах про "Чужого"

Можно сказать, что пост в догонку к недавнему кинообзору, в котором был упомянут очередной фильм из вселенной "Чужих".

Мне очень нравятся первые три фильма, каждый из них шедеврален. Хотя первый "Чужой" на мой взгляд, самый слабый из них. Но тут обязательно нужно делать поправку на время, в которое он был снят. Полагаю, в конце 1970-х сложно было сделать лучше. А вот "Чужих" и "Чужой-3" пересматриваю регулярно. Причем чем старше становлюсь, тем больше мне нравится именно третья часть.

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

Четвертый фильм был бы говном, если бы не несколько но. Во-первых, он очень красиво и атмосферно снят. Во-вторых, там есть несколько шикарных моментов. Например, как чужие реагируют на "обучение" и как они находят способ выбраться наружу. Самый же гениальный из этих моментов -- это как Рипли заходит в лабораторию, в которой ее клонировали, и видит результаты предшествующих неудавшихся попыток. Так что есть в нем что-то хорошее. Но это хорошее точно не относится к финалу.

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

Недавний "Ромул" вроде как возвращается к истокам. Но мне он кажется вторичным, поскольку подобное мы уже видели в четвертом фильме. Даже антропоморфный мутант в конце, с которым пришлось бороться в последние мгновения перед возможной катастрофой.

Не знаю, будут ли еще продолжения, полагаю, что будут. Вопрос только когда.

И, предположу, что там обязательно главным борцом с очередным чужим окажется девушка или молодая женщина. Ну такой сильный женский персонаж, который все превозмогёт и всех победит. Как же может быть иначе? ;) Что делает потенциально продолжение немного предсказуемым.

Еще, как бы это странно не звучало, мне зашел "Чужой против Хищника". Конечно же кино специфическое и по своему уровню ему далеко как до первых трех "Чужих", так и до первого "Хищника". Но зато качественно сделанное. Как мне показалось с любовью и уважением к двум исходным франшизам. Жаль что вторая часть этого противостояния оказалась отстоем, хотя этого и можно было ожидать.

вторник, 1 октября 2024 г.

[life.cinema] Очередной кинообзор (2024/09)

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

Фильмы

Чужой: Ромул (Alien: Romulus, 2024). Ну хоть отказались от прямого продолжения маразматической линии "Прометея" и "Завета", хотя и есть отсылки к этим двум говношедеврам. В целом мне показался похожим на четвертую часть. И не понравился он мне тем же, чем и четвертая часть -- очередной попыткой скрестить чужого и человека и получить неведомого мутанта. В целом же любители вселенной "Чужого" вполне могут посмотреть, это явно лучше "Прометея" и "Завета".

Восхитительно! (Délicieux, 2021). Этот фильм можно посмотреть просто ради красивой картинки. Временами работа оператора выше всяких похвал, хочется поставить воспроизведение на паузу и рассматривать кадр как картину. Сама же история простенькая и, как мне кажется, откровенно сказочная.

Ускорение (Slingshot, 2024). Хорошо сделанное кино. Но не могу сказать, что мне понравилось, т.к. с некоторых пор не люблю открытые финалы и недосказанность.

Одинокие волки (Wolfs, 2024). Противоречивые впечатления. С одной стороны фильм из категории тех, что мне, в принципе, нравятся. Но не понравился, т.к., во-первых, герои не вызывают сопереживания. И вообще нет ощущения реальности происходящего. Ну типа есть два крутых героя, круче которых, сразу понятно, нет никого. Поэтому ничего с ними произойти не может. И остается только ждать когда случится какой-то очередной прикол. Во-вторых, как-то создатели не озадачились внятным объяснением происходящего, так что я в итоге не понял кто кого куда и зачем отправлял. В-третьих, некоторые моменты в фильме выглядели надуманными и нелепыми. В общем, кино с такими двумя актерами не оправдывает ожиданий.

Ловушка (Trap, 2024). Отличная игра Джоша Хартнетта в фильме, где все происходящее выглядит каким-то откровенным маразмом.

Мистер Блейк к вашим услугам (Complètement cramé, 2023). Не понял что это было. Возможно, я еще слишком молодой, а кино рассчитано на 65-75 летних зрителей. Но выглядит это все как ужасно рафинированная сказочка-сказочка, в которой нет ни одного нормального персонажа, не говоря уже о хоть каком-либо отношении к реальности.

Ворон (The Crow, 2024). Затянуто и скучно.

Оставь мир позади (Leave the World Behind, 2023). Редкая муть. Посмотреть можно разве что если вам почему-то захотелось выкинуть два часа из своей жизни.

Воровка (2023). Халтурная халтура. Смело можно проходить мимо.

Сериалы

Охотничьи псы (Sayanggaedeul, первый сезон, 2023). Я бы сказал, что это сериал для подростков, настолько глуповаты там главные герои. Но для подросткового кино оно излишне жесткое местами. Так что даже не знаю как классифицировать: для взрослых слишком наивное, для детей слишком кровавое.

Точка ноль (первый сезон, 2024). Сплошное разочарование. Ожидал напряженного фильма про борьбу с неизвестным вирусом, но как раз ни напряжения, ни борьбы, ни ожидания глобального коллапса из-за вырвавшейся на свободу болезни в сериале не обнаружилось. Зато рассказали про сына-алкоголика зав.лаборатории, про бывшего любовника главной героини, про отголоски каких-то разборок из 90-х годов и пр. муть. В общем, категорически не рекомендую.

Не смог досмотреть

Властелины воздуха (Masters of the Air, первый сезон, 2024). Снято, конечно же, красиво. Вероятно сцены воздушных боев сделаны мастерски с максимальным сохранением исторических деталей и стремлением к достоверности. Но вот не торкает и все. Скучно и ничего с этим не смог поделать. С трудом дождался завершения второй серии и решил не продолжать.

План Б (Plan B, первый сезон, 2024). Когда во второй серии окончательно прояснилось вокруг какой идеи крутится все происходящее на экране, то стало неинтересно. А когда в третьей серии начались какие-то странные действия со стороны персонажей и приплюсовалась современная толерантная повесточка, то вообще желание продолжать просмотр пропало окончательно.

Криминальное прошлое (Criminal Record, первый сезон, 2024). Сперва раздражал откровенные непрофессионализм главной героини, а потом еще и возникло ощущение, что конфликты между некоторыми героями создают искусственно и на ровном месте, без должной мотивации. Плюс традиционное для сериалов неспешное развитие событий. В общем, в какой-то момент возникло желание прекратить просмотр и забыть про это "кино".

понедельник, 23 сентября 2024 г.

[prog.c++] Десять лет библиотеке timertt

Десять лет назад в свет вышла небольшая библиотека timer thread template, она же timertt. Делалась она чтобы иметь возможность создавать в приложении большое количество одноразовых и/или периодических таймеров. Когда я говорю про "большое", то речь идет о сотнях тысяч, как минимум.

Сделать собственные средства для поддержки таймеров заставила жизнь. Долгое время в SObjectizer-е применялись таймеры из замечательной библиотеки ACE. И не только таймеры. Много лет ACE для нас служила базовым слоем, мы брали оттуда и мутексы, и сокеты, и даже хэш-таблицы, поскольку ничего из этого в C++98 не было.

Однако, в SObjectizer-5 мы заложились сразу на C++11, в котором многое нашлось в стандартной библиотеке. Кроме того, после выхода SObjectizer-а в "свободное плавание" нам пришлось отказаться от развития ряда построенных над SObjectizer-ом библиотек, так что нам больше не нужны были сокеты и инструменты для ручной загрузки-выгрузки динамических библиотек.

В итоге, к середине 2014-го года единственное, что нас привязывало к ACE, -- это таймеры. Которые, как по мне, в ACE были сделаны очень здорово. И мы оказались в ситуации, когда небольшой SObjectizer, архив которого "весили" ~600Kb, требовал внешней зависимости размером порядка 6Mb в архиве. И нужна нам ACE была только для таймеров 😣

В общем, решили от ACE отказаться полностью для чего и пришлось написать свой timertt, т.к. ничего готового на просторах тогдашнего Интернета не нашлось. А для этого потребовалось погрузиться в тему различных механизмов таймеров и реализовать пару-тройку оных самостоятельно. На C++ных шаблонах, понятное дело, чтобы было хардкорнее...

Если мне не отшибает склероз, на все про все ушло порядка месяца. В конце августа 2014-го работа началась, 4-го сентября 2014-го был сделан первый коммит, 18-го сентября 2014-го был зафиксирован первый стабильный тег.

timertt изначально создавалась под SObjectizer, хотя к самому SObjectizer-у она и не привязана, тут полностью обратная ситуация, можно сказать, что без timertt не было бы того SObjectizer-5, каким мы его знаем сегодня. Но т.к. timertt писалась под SObjectizer, то отдельно мы ее мало пиарили. Было несколько анонсов то здесь, то там, но не более того. Самый эпичный анонс случился на LOR-е, сейчас перечитываешь и остатки волос непроизвольно шевелятся... 😂

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

В завершение хочется пожелать и самому себе, и библиотеке timertt, и читателям блога вернуться к продолжению этой истории в этом же блоге в сентябре 2034-го 😁


Что-то круглые даты как-то кучно пошли. И это еще не последняя, в начале октября ожидается еще одна ;)

суббота, 21 сентября 2024 г.

[prog.memories] Vim, Ruby, Mxx_ru -- двадцать лет в пути...

Когда-то давно, в сентябре 2009-го года здесь появилась первая заметка про мое знакомство с ViM, Ruby и рождение Mxx_ru: ViM, Ruby, Mxx_ru – пять лет в пути! Часть первая: Mxx_ru (вот вторая часть). Спустя пять лет вышло продолжение истории: Vim, Ruby, Mxx_ru -- десять лет в пути... Затем прошло еще пять лет и была написана заметка Vim, Ruby, Mxx_ru -- пятнадцать лет в пути... И вот, спустя еще пять лет, можно опубликовать очередную часть этой истории 🤓


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

При этом не могу сказать, что я чему-то еще научился за прошедшие пять лет (при этом пять лет назад знал и умел совсем немного). Продолжаю довольствоваться малым и использую ViM в минималистичном, можно сказать, базовом варианте. Под катом мой .vimrc, желающие могут посмотреть если это вдруг кому-то интересно.

В таком минимализме для меня, в общем-то, и кроется весь смысл ViM-а: я оказываюсь практически в привычном окружении на любой новой системе с минимальными телодвижениями. Это как раз то, что мне и нужно.

Про ViM могу рассказать показательную историю. В конце прошлого года подключился к проекту, в котором пока основная часть разработки ведется под Windows (с эпизодическими профилактическими сборками под Linux). Основная среда -- VisualStudio, даже сам проект был оформлен в виде .sln-файла. И, что самое важное, разработка ведется на сервере заказчика, доступ к которому дается через RDC.

Так вот, какое-то время я честно пробовал вести разработку в VisualStudio... Но сама VisualStudio оказалась тем еще тормозом, плюс у нее куча раздражающих меня настроек по умолчанию, когда она сама начинает что-то выравнивать, что-то переносить, где-то расставлять парные кавычки или скобки, которые мне не нужны, и последствия этого приходится удалять вручную... Плюс к этому добавляется не очень быстрый отклик при работе в самом RDC, т.к. я нахожусь чуть ли не в тысяче километров от сервера. В общем, набор кода в VisualStudio стал для меня тем еще испытанием и оказался крайне медленным и нервирующим процессом.

Пару месяцев терпел, но в конце-концов не выдержал. Установил ViM и начал писать код в привычном для себя окружении, а компилировался в командной строке вызывая devenv с передачей ему .sln-файла и нужных параметров. Можно сказать, вернулся в родной мир.

Так что ViM-ом продолжаю с удовольствием пользоваться. А посему могу смело сказать: 20 years with Vim and still learning :) And happy Vimmming!


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

Пару лет назад в одном из проектов довелось столкнуться с Python-ом. Мне не нужно было на Python-е программировать самому, требовалось встроить его в C++ приложение, но все равно пришлось почитать что это такое и погрузится в некоторые его особенности. Порадовался тому, что в свое время выбрал Ruby, а не Python. Как по мне, так Ruby делали как инструмент для программистов, тогда как Python -- для <censored> не умеющих программировать, мягко говоря. И, судя по тому, какое распространение Python получил за прошедшие годы, умеющих программировать больше не стало 😏


Mxx_ru продолжаю использовать при разработке SObjectizer и json_dto. И кайфую от этого. Но это прекрасное время уже скоро закончится... 🥹

В прошлом году в RESTinio-0.7 мы уже полностью перешли с Mxx_ru на CMake.

Полагаю, что в 2025-ом или в 2026-ом, при переводе SObjectizer-а на C++20 (а может быть и сразу на C++23) мы также откажемся от Mxx_ru в пользу CMake. А там и очередь json_dto подойдет.

И дело здесь не в том, что CMake стал лучше. Нет, как был CMake говном говна, так и остается. Но так уж вышло, что CMake сейчас стандарт де-факто и что если ты хочешь, чтобы твоей разработкой пользовались, то поддержка CMake обязательна. Ну а тянуть в проекте две системы сборки -- это сомнительное удовольствие.

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

Возможностей сделать Mxx_ru-2.0 с поддержкой C++ных модулей у меня нет и вряд ли найдутся, сам я не становлюсь моложе, запаса сил все меньше. Поэтому Mxx_ru доживает свои последние годы. Се ля ви.


Наверное, как-то грустно получилось. Но, с другой стороны, отрадно, что сама по себе история продлилась целых двадцать лет. Так что все хорошо, нет повода расстраиваться. Даже интересно, смоглу ли написать что-то на эту тему спустя еще пять лет. Будем посмотреть 😉

понедельник, 16 сентября 2024 г.

[prog.c++] Еще несколько ссылок вдогонку к теме std::launder/std::start_lifetime_as

В качестве продолжения темы std::launder/std::start_lifetime_as зафиксирую в склерознике еще несколько полезных ссылок.

Первая -- это статья "Reinterpet_cast, UB and a Pointer Type Casting in C++17" в которой еще раз разбирается тема безопасного преобразования указателей и приводится пример кода, который типа делает это правильно и без UB для C++17.

Вторая -- это доклад Джонатана Мюллера на C++Now 2024: A Deep Dive Into C++ Object Lifetimes. Сам я не осилил слушать полтора часа на английском, поэтому для меня очень полезными оказались слайды к этому докладу: PDF. И вот эти вот слайды я настоятельно рекомендую всем, кто интересуется данной темой. Хотя бы просто для того, чтобы убедится насколько глубока кроличья нора.

понедельник, 9 сентября 2024 г.

[prog.c++] Сюрприз с размером производного класса в разных компиляторах

С размером вот этой простой структуры на x64 при выравнивании по умолчанию все понятно:

struct base {
    void * m_ptr;
    short m_sh;
    char m_ch;
};

Вполне ожидаемо имеем 16 байт. Из которых реально используются 11, а остальное -- это выравнивание на границу 8 байт (т.к. указатель на 64-х битовых платформах занимает 8 байт).

А вот что с размером вот этой не менее простой структуры?

struct derived : public base {
    char m_mask;
};

А вот тут сюрприз!

Результат зависит от компилятора: для VC++ получается 24 байта (и в этом есть некоторая логика), тогда как для GCC и clang -- 16 байт (и в этом также есть логика).

Но лично мне представляется, что логика GCC/clang как-то логичнее. Тогда как поведение VC++ стало неприятным открытием. Можно даже сказать как серпом...

Посмотреть самому можно на godbolt: https://godbolt.org/z/sbo8jPcxE

пятница, 6 сентября 2024 г.

[prog.c++] В склерозник: красивый кусок кода для подсчета смещения полей объекта и их выравнивания

В LinkedIn встретил ссылку на реализацию тупла без использования рекурсии: тыц. Реализация активно эксплуатирует C++ный fold expression и std::integer_sequence. Вот прям отличная демонстрация того, как эти фичи могут (и должны?) применяться.

Еще очень удачно совпало, что ссылка эта попала мне на глаза вскоре после того, как я проделал в чем-то похожую работу. Правда, у меня ситуация была чуть сложнее, ведь в тупле все N полей присутствуют всегда, поэтому размер всех туплов одного типа одинаков и фиксирован. Тогда как в моем случае объекты могут состоять из разного набора полей, поэтому нужно размер каждого объекта определять индивидуально, да и расположение полей в каждом объекте может быть уникальным. Так что в своей реализации без метапрограммирования на базе рекурсии я не смог обойтись. Возможно, еще и потому, что у меня мало опыта с fold expression и std::integer_sequence.

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

Этот кусочек кода отвечает за подсчет расположения полей внутри объекта с учетом их правильного выравнивания. В результате своей работы функция calculate_positions возвращает std::array размером (N+1), где элементы 0..(N-1) содержат смещение i-го поля, а элемент N -- общий размер объекта.

Оригинал кода можно увидеть здесь, а вот чуть-чуть модифицированная мной версия:

template <class T>
struct PositionWrapper {};

template <std::size_t _last, std::size_t... _is>
struct Positions
{
  static consteval auto to_array() {
    return std::array<std::size_tsizeof...(_is) + 1>{_is..., _last};      
  }
};

template <class T, std::size_t _last, std::size_t... _is>
consteval auto operator+(
  const Positions<_last, _is...>& /*_sizes*/,
  const PositionWrapper<T>& /*_w*/)
{
  if constexpr (_last % alignof(T) == 0) {
    constexpr auto last_new = _last + sizeof(T);
    return Positions<last_new, _is..., _last>{};
  } else {
    constexpr auto last_corrected = (_last / alignof(T) + 1) * alignof(T);
    constexpr auto last_new = last_corrected + sizeof(T);
    return Positions<last_new, _is..., last_corrected>{};
  }
}

template <class... Types>
consteval auto calculate_positions() {
  return (Positions<0>{} + ... + PositionWrapper<Types>{}).to_array();
}

понедельник, 2 сентября 2024 г.

[prog.c++] Конструирование объекта в котором физически могут отсутствовать некоторые поля

На прошлой неделе делал интересную штуку: С++ный объект, в котором могут физически отсутствовать некоторые части.

Пришлось заняться этим потому что в текущем проекте было большое количество объектов вида:

struct data {
  mandatory_field_one m_one;
  mandatory_field_two m_two;
  mandatory_field_three m_three;

  std::vector<first_opt_attribute_type> m_first_type_attrs;
  std::vector<second_opt_attribute_type> m_second_type_attrs;
  std::vector<third_opt_attribute_type> m_third_type_attrs;
};

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

Объектов типа `data` было много, в некоторых случаях десятки миллионов. И на таком количестве хранение пустых std::vector внутри миллионов объектов типа `data` оказывается очень расточительным (ведь каждый пустой std::vector -- это, как минимум, 24 байта -- size, capacity + указатель на блок с данными).

Объекты же `data` создавались в динамической памяти и ссылки на них хранились как std::unique_ptr.

Причем модель данных не позволяла легко вынести значения опциональных атрибутов из `data` куда-то еще. Например, можно было бы попробовать завести какое-то общее хранилище атрибутов, а в самом `data` тогда хранилась бы ссылка (или индекс) в этом хранилище. Тогда если у объекта атрибутов нет, то в хранилище для объекта ничего нет, а в самом объекте лежит нулевая ссылка. Ну т.е. попробовать бы можно было, но без особых шансов на успех, но зато с большим геморроем 🙁

В общем, хотелось бы, чтобы поля m_first_type_attrs, m_second_type_attrs и m_third_type_attrs таки оставались внутри `data`, но ничего бы не потребляли, если были пустыми.

Здесь бы очень ко двору пришлись бы массивы вроде чего-то такого:

struct data {
  mandatory_field_one m_one;
  mandatory_field_two m_two;
  mandatory_field_three m_three;

  std::size_t m_first_type_attrs_count;
  first_opt_attribute_type m_first_type_attrs[m_first_type_attrs_count];

  std::size_t m_second_type_attrs_count;
  second_opt_attribute_type m_second_type_attrs[m_second_type_attrs_count];

  std::size_t m_third_type_attrs_count;
  third_opt_attribute_type m_third_type_attrs[m_third_type_attrs_count];
};

Но в C++ таких массивов нет. Это во-первых. А во-вторых, даже такой способ хранения, будь он возможен, все равно был бы расточительным. Ведь если для объекта нет атрибутов, то поля *_attrs_count с нулевыми значениями в нем все равно есть. Три поля std::size_t -- это 24 байта, умножаем на десяток миллионов объектов `data` и теряем пару десятков мегабайт на ровном месте 🙁

Поэтому было решено прибегнуть к шаблонной магии C++ и сделать класс, который сам рассчитывает, сколько байт ему будет нужно для представления объекта с учетом того, что какие-то поля в нем могут отсутствовать, а какие-то могут иметь переменный размер.

Получилось что-то вроде такого:

// Описание части, которая в объекте присутствует всегда.
struct data_header {
  mandatory_field_one m_one;
  mandatory_field_two m_two;
  mandatory_field_three m_three;

  // Вспомогательные типы для идентификации опциональных полей.
  struct first_attr_tag {};
  struct second_attr_tag {};
  struct third_attr_tag {};

  // Типы опциональных полей.
  using first_attr_vec = compound::vec<first_attr_tag, first_opt_attribute_type>;
  using second_attr_vec = compound::vec<second_attr_tag, second_opt_attribute_type>;
  using third_attr_vec = compound::vec<third_attr_tag, third_opt_attribute_type>;
};

Создается такой объект специальной функцией-фабрикой. Ей на вход подаются начальные значения опциональных полей. Если для какого-то опционального поля значения не заданы, то этого опционального поля и нет.

std::vector<second_opt_attribute_type> attrs{...};

auto my_data = data::build(
  // Данные для инициализации фиксированной части должны быть всегда.
  data_header{...},
  // А вот опциональные данные нужны только те, которые в объекте присутствуют.
  data::second_attr_vec::move_from(attrs));

// Доступ к полям фиксированной части возможен напрямую.
std::cout << my_data->m_one << std::endl;
std::cout << my_data->m_two << std::endl;

// Доступ к опциональным полям нужно проверять.
if(my_data->has_field<data::second_attr_tag>()) {
  // Поле есть, можно с ним работать.
  for(const auto & a : m_data->get_field_ref<data::second_attr_tag>()) {
    ...
  }
}

Не буду вдаваться в подробности, т.к. все это делалось в закрытом проекте. Но скажу, что весь фокус тут в функции-фабрике build, которая вычисляет сколько же места потребуется (с учетом необходимых выравниваний) и создает обычный динамический массив std::byte. А уже дальше внутри этого массива посредством placement new размещается все то, что в объекте должно присутствовать.

Для меня реализация этой кухни оказалась на грани моих знаний C++ и возможностей как программиста, где-то, наверное, даже за гранью. Но как-то все это заработало 🤓

Порадовало то, что C++ позволил это сделать. Что еще раз убедило меня в том, что современный C++ в некоторых нишах все еще остается адекватным выбором (даже более чем адекватным, если у вас есть толковые C++ разработчики). C++ позволяет программисту при необходимости перемещаться между разными уровнями абстракции и, если нужно, выполнять всякие хитрые оптимизации. Правда, для этого желательно знать побольше, чем я, и руки иметь попрямее, чем у меня, тогда будет еще проще и дешевле.

Что напрягало, как это функциональный стиль C++ного метапрограммирования (работа велась в рамках C++20). Вся эта рекурсия по спискам типов... 😓 Если сталкиваешься с этим самым метапрограммированием раз в пару лет, то непросто на эти рекурсии перестроится.

Еще, конечно же, доставляла тема с std::launder. Но к ней мне придется вернуться еще раз, как минимум.

В целом же каких-то непреодолимых препятствий не встретилось, со всем удалось справиться. Так что, приходится признать, что C++ развивается в правильном направлении. Не смотря на то, что какие-то вещи в современном C++ мне сильно не нравятся.

ЗЫ. Кстати говоря, пригодился трюк вот из этой статьи: "Pulling a single item from a C++ parameter pack by its index".

воскресенье, 1 сентября 2024 г.

[life.cinema] Очередной кинообзор (2024/08)

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

Фильмы

Ненасытные люди (Greedy People, 2024). Как по мне, так это что-то вроде "Фарго", но на минималках. Совершенно не шедевр, но смотреть интересно, а на фоне остального современного шлака, как вообще выглядит нормально сделанным кино.

Зачинщики (The Instigators, 2024). Не очень понял что это было. Для комедии мне не хватило юмора, для серьезной криминальной драмы не хватило серьезности и драмы. Такое впечатление, что материала было на минисериал, но почему-то решили сделать один полнометражный фильм, в котором не удалось раскрыть и десятой доли всех промелькнувших в кадре персонажей.

Нефариус (Nefarious, 2023). Мне почти что зашло. На мой личный вкус первые 3/4 фильма просто шикарные, все держится на диалогах и актерской игре. А вот развязку, имхо, можно было сделать и покруче. Так что финал слегка разочаровал, но вот то, что ему предшествовало мне понравилось.

Материнский инстинкт (Mothers' Instinct, 2024). Качественно снято. Но! Во-первых, никто из героев не вызвал сопереживания. Во-вторых, за происходящим оказалось следить не интересно и, есть ощущение, что авторы как-то и не сильно пытались зрителя запутать. Общее впечатление: добротно сделано, но не цепляет.

Резервная копия (Bod obnovy, 2023). Неоднозначно. С одной стороны, хороший визуал, есть ощущение, что смотришь качественную фантастику, погружающую тебя в атмосферу недалекого будущего. Но, с другой стороны, неинтересный сюжет и отсутствие чего-то цепляющего в происходящем на экране. В результате добротный середнячок, который не претендует на что-то большее.

Смерч 2 (Twisters, 2024). Красочно сделанный динамичный аттракцион. Но все настолько шаблонно, а персонажи настолько картонные, что все последующие события прочитываются наперед уже после первых 15 минут фильма.

Отчаянные наследники (El favor, 2023). Средненькая комедия из категории "богатые тоже плачут", местами откровенно глупая, местами смешная.

Союз (The Union, 2024). Сложно оценивать это кино. Во-первых, сам фильм изначально откровенно слабый. Во-вторых, ему сделали настолько ужасный русский дубляж (абсолютное не попадание голосов дубляжа на персонажей), что это убило остатки того хорошего, что в фильме еще могло быть. В общем, в русском дубляже это просто унылое говно, которое смело можно не смотреть.

Сериалы

Мошенники (первый сезон, 2023). Отлично, посмотрел с большим удовольствием.

ГДР (первый сезон, 2023). Мне не зашел. Во-первых, как-то все подзатянуто. Во-вторых, показались лишними вставки с Горбачевым и его окружением. Вроде как сериал должен был быть шпионским детективом, а не пересказом баек о дурачке Горбачеве. В-третьих, чувствовалось, что бюджет не позволяет воссоздать более-менее большой кусочек Берлина 1989-го года. И это не шло в плюс сериалу. В общем, не рекомендую.

Фурия (Furia, первый сезон, 2021). Во-первых, очень затянуто. Во-вторых, слишком уж часто возникает вопрос "Что за фигню нам здесь показывают?" Так что первый сезон не зашел, желание посмотреть второй сезон не возникло.

Кино вне категории

Пара фильмов, которые я затрудняюсь адекватно оценить.

Бордерлендс (Bordarlands, 2024). Не понял на кого кино рассчитано: для взрослых оно слишком детское, для детей слишком взрослое. На меня произвело впечатление откровенного треша, но такого, про который можно сказать "настолько плохо, что даже хорошо".

Покажи мне Луну (Fly Me to the Moon, 2024). Не являюсь ценителем жанра комедийных мелодрам, так что понятия не имею, как этот фильм оценивать. Но если брать отдельно сюжет, отдельно операторскую работу и отдельно игру актеров, то в целом это один из наиболее добротных и вменяемых фильмов за последние несколько месяцев.

пятница, 30 августа 2024 г.

[job.flame] Откуда такой акцент на софт-скиллз в последние годы?

Позволю себе немного пофлеймить в теме, в которой не разбираюсь (с другой стороны, а разве можно как-то иначе? 😉)

Когда я начинал работать, ни о каких софт-скиллах и речи не было. Это, по моим ощущениям, вообще тема текущего десятилетия. А реальный акцент на этих самых софт-скиллах делается в последние 3-4 года.

И я не мог понять, почем вдруг стало так много разговоров про софт-скиллы. Неужели с хард-скиллами все стало настолько хорошо, что можно уже подумать и о душе софт-скиллах?

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

А давеча посмотрел случайно два интервью с нейробиологом Вячеславом Дубыниным:

ЛЮДИ ТУПЕЮТ! Причина - не алкоголь или никотин. Вячеслав Дубынин.

Вячеслав Дубынин про поиски себя, выгорание, аффирмации. Как работа влияет на мозг?

И у меня появилась другая версия. Практически медицинская 🙂

Суть в том, что выросло поколение, которое с детства было окружено гаджетами и виртуальным общением. А это значит, что представители этого поколения развивались в других условиях.

Мы с раннего детства проводили много времени в разного рода коллективах: будь то детский сад, школа, кружки, спортивные секции, пионерские и спортивные лагеря, не говоря уже про двор. Сплошной реал, никакой виртуальности. В котором навыки этого самого общения, как и навыки существования в рамках коллектива, прививались прямыми и незатейливыми способами. Уже к 4-5-м классам школы практически каждый на своей шкуре усваивал, что трепать языком нужно поменьше, за слова принято отвечать, да и получить в лыч за эти самые слова можно очень даже легко и непринужденно. А можно и не за слова, а просто потому, что оказался не в то время и не в том месте. Всякое бывало... В общем, прямые и незатейливые способы иногда оказывались настолько жесткими и суровыми, что не все вписывались в эти условия, но это уже тема другого разговора.

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

Что наводит меня на следующую мысль: а что если у молодого поколения, которое вместо игры в "войнюшку" всем двором, чатилось в соцсетях, просто оказались недостаточно развиты те самые нейросети для общения и социализации, которые естественным образом прокачивались у нас, старпёров, в нашем далеком уже детстве?

Т.е. тупо часть мозга, которая у поколения 1960-х, 1970-х и 1980-х была хорошо развита вот просто потому, что другого выхода-то и не было, у поколения 2000-х уже просто на недоразвитом (в биологическом смысле) уровне. И, грубо говоря, нынешние 20-летние не могут в человеческое общение на таком же уровне, на котором умели мы.

С этой точки зрения становится понятным акцент на софт-скиллах. Ведь это в наше время можно было не особо заморачиваться: ведь если индивид дожил нормальным человеком до совершеннолетия, да еще и образование получил, значит с высокой долей вероятности, с общением у него более-менее. Не, конечно, феномены вроде Григория Перельмана были всегда. Но в том-то и дело, что они были феноменами. Основной же массе как-то на базовом уровне было понятно, что такое "быть мудаком", откуда и проистекало стремление таковыми не быть.

А сейчас уже непонятно. Вроде как и человек взрослый, и образование есть, и поработал уже где-то. Но вот умеет ли он с людьми общаться именно как с людьми? А это уже большой вопрос. Ответ на который и пытаются найти посредством оценки софт-скиллов.


Ну ладно, эта медицинская версия слишком уж попахивает стариковским ворчанием по поводу никчемной молодежи. Так что вот еще одна.

Ранее (лет 25 назад) в найме роль HR была не столь существенна, как сейчас. А вот нонче, судя по постам в LinkedIn, практически только через HR.

Могут ли HR адекватно оценить хард-скиллы соискателей? Сильно сомневаюсь. Может и есть единицы таких продвинутых, но именно что единицы.

Тогда как софт-скиллы могут, да еще как. Отсюда и значимость этих самых софт-скиллов: ведь если процессами найма рулят HR, то растет и вес критериев, которые сами HR могут оценить. Отсюда и актуализация софт-скиллов.

Но эта версия, честно говоря, слишком банальна. А потому и не интересна 😎

вторник, 20 августа 2024 г.

[prog.c++] Вынесу из комментариев на Хабре про дебилизм с std::launder и std::start_lifetime_as

В комментариях к статье на Хабре зашел разговор об уместности использования std::launder и std::start_lifetime_as.

И, насколько я смог понять из разговора, вот в такой ситуации у нас нет сейчас простого и понятного способа в точке (1) сделать каст указателя к Demo*:

#include <iostream>
#include <functional>
#include <new>
#include <cstddef>
#include <cstring>
#include <memory>

struct Data {
    int _a{0};
    int _b{1};
    int _c{2};
};

using CreateFn = std::function<void(std::byte*)>;

void make_and_use_object(CreateFn creator) {
    alignas(Data) std::byte buffer[sizeof(Data) * 3];
    std::byte * raw_ptr = buffer + sizeof(Data);
    creator(raw_ptr);
    Data * obj = std::launder(reinterpret_cast<Data *>(raw_ptr)); // (1)
    std::cout << "obj: " << obj->_a << ", " << obj->_b << ", " << obj->_c << std::endl;
}

int main() {
    make_and_use_object([](std::byte * ptr) {
        new(ptr) Data{._a = 1, ._b = 2, ._c = 3};
    });
    make_and_use_object([](std::byte * ptr) {
        Data data{ ._a = 25, ._b = 16, ._c = 890};
        std::memcpy(ptr, &data, sizeof(data));
    });
}

Суть в том, что когда make_and_use_object вызывается с первой лямбдой и по указателю ptr новый объект Data создается через placement new, то затем raw_ptr нельзя просто так скастить к Data*. Тут требуется именно std::launder. Ну вот требуется и все. Иначе UB.

Тогда как при использовании второй лямбды в точке (1) вообще не нужно вызывать ни std::launder, ни std::start_lifetime_as. Вроде как все дело в том, что std::memcpy неявно начинает время жизни нового объекта. И результирующий указатель можно просто скастить к Data* и все.

Но, допустим, что во второй лямбде я не использую std::memcpy, а заполняю переданный мне буфер собственной функцией побайтового чтения из COM-порта. Что-то вроде:

 make_and_use_object([](std::byte * ptr) {
     com_port_reader reader;
     reader.init();
     while(reader.has_data()) {
         *ptr = reader.read_next_byte();
         ++ptr;
     }
 });

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

Соответственно, что делать в make_and_use_object после завершения работы лямбда-функции?

Если содержимое объекта внутри буфера было сформировано побайтовым чтением из COM-порта, то std::launder не поможет, тут нужен как раз std::start_lifetime_as (который завезли в C++23, но который, если не ошибаюсь, пока нигде не реализован).

Т.е. мы оказываемся в ситуации, когда у нас есть корректно размещенный в памяти байтовый буфер, внутри которого лежит корректно сформированное значение объекта. Но мы не можем одним простым действием взять и получить легальный указатель на этот объект.

Хотя, казалось бы, C++ -- это такой язык, в котором подобные операции должны были бы делаться легко и просто. Ага, щаз... 🥴 С подачи компиляторописателей в язык напихали столько UB, что подобный кастинг указателя стал отнюдь не простым.

Для полноты картины: там же в комментариях указали на наличие пропозала от Антона Полухина. Смысл этого пропозала -- отказаться от необходимости использовать std::launder вот в таких простых ситуациях:

alignas(T) std::byte storage[sizeof(T)];
auto* p1 = ::new (&storage) T();
auto* p2 = reinterpret_cast<T*>(&storage);
bool b = p1 == p2;  // b will have the value true.

Если этот пропозал примут, то ситуация окажется совсем веселая:

  • до C++26 подобный кастинг без std::launder будет считаться UB. Т.е. если вы пишете под C++17 или C++20, то должны использовать std::launder, иначе в вашем коде формальный UB;
  • начиная с C++26 это уже не UB и можно std::launder не писать.

А теперь представим проект (какую-нибудь библиотеку, вроде RapidJSON), который должен собираться и под C++14, и под C++17, и под C++20, и под C++23, и под C++26. И как в таком проекте быть со всеми этими std::launder и std::start_lifetime_as? Кроме как прятать подобные фокусы за фасадом макросов мне ничего в голову не приходит.

Но пусть даже у нас есть набор нужных вспомогательных макросов... Вернемся к самому первому примеру в посте. Как там понять, требуется ли std::launder, std::start_lifetime_as или же вообще ничего не требуется?

Есть ощущение, что никак. Подобный код нужно перепроектировать. А это мне лично не нравится, ибо тогда мы начинаем заниматься не решением собственных задач, а ублажением компилятора, чтобы компилятор нидайбох не подумал, что у нас есть UB, которое можно эксплуатировать.

Собственно, чего хотелось бы иметь:

  • чтобы std::launder оставался только для случаев, когда пересоздается объект. Т.е. был объект типа A и на него были указатели, затем на том месте, где был объект A, создали новый объект A (или даже какой-то отнаследованный от него B -- пример), старые указатели "протухли", нужно их "отмыть" через std::launder. Все. Больше ни для чего std::launder не нужен;
  • чтобы std::start_lifetime_as использовался для случая, когда у нас есть std::byte* или char*, и мы хотим сказать компилятору, что по этому указателю реально живет объект A.

И строго так, без всяких неявных умолчаний, что мол memcpy или malloc начинает время жизни.

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

PS. Да, я в курсе, что практику с неявным началом времени жизни при использовании ряда функций (вроде malloc или memcpy) в C++20 ввели для того, чтобы узаконить говнокод, написанный в древности или даже вообще на чистом Си (как в случае с GCC -- сперва это был Сишный код, а потом еще стали компилировать как C++). Но ведь C++ все равно потихоньку отказывался от атавизмов, например, ключевое слово auto кардинально поменяло свой смысл, а ключевое слово register сейчас нельзя использовать по его первоначальному назначению. Так что лично для меня этот аргумент из категории "ну такое себе". А если какие-то комитетчики настаивают на то, что этот говнокод нужно оставить как есть и сделать легальным (да еще и за счет умолчаний, про которые мало кто знает), то хочется сказать таким комитетчикам: ну так оставьте легальным и тот говнокод, в котором std::launder не используется.

PPS. На всякий случай ссылки на посты двухгодичной давности, в которых обсуждалась проблематика std::launder: "Продолжение темы про передачу C++объектов через shared memory. Промежуточные выводы" и "В склерозник: ссылки на тему std::launder"

четверг, 15 августа 2024 г.

[prog.c++] Отличная статья про UB в C++

На сайте PVS-Studio давеча была опубликована очередная статья серии про Undefined Behaviour в C++. Там и две первые были отличные, ну а третья, на мой взгляд просто шикарная. Если вы программируете на C++ и еще не читали, то настоятельно рекомендую.

Пара главных впечатлений от прочтения.

Во-первых, я сам уже давно нахожусь в раздумьях о том, а хорошо ли то, что в C++ так много неявных преобразований типов. С одной стороны, наверное, есть в этом и положительные стороны. Типа такого:

void do_something(std::string_view what) {...}
...
do_something("simple-string-literal");

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

Но, с другой стороны, эти неявные преобразования могут вести к таким непредсказуемым последствиям, что мама не горюй.

И чем больше с такими последствиями сталкиваешься, тем меньше хочется иметь этих самых неявных преобразований. Поэтому чем дальше, тем больше я становлюсь сторонником того, чтобы эти самые неявные преобразования из C++ потихонечку устраняли бы. Даже не глядя на потерю совместимости с уже написанным кодом в новых стандартах.

А данная статья со своими примерами кладет несколько лишних камешков на чашу именно этой точки зрения.

Во-вторых, количество приводимых примеров UB наводит на мысль о том, что нормальный человек просто не в состоянии оценивать все те грабли, которые можно собрать программируя на C++.

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

Тут скорее в другую сторону имеет смысл глянуть: любые собеседования по C++, на которых собеседуемого просят посмотреть на код и рассказать, что получится в итоге, превращаются в откровенный цирк. Не, ну реально, в безобидном с виду коде могут оказаться такие грабли, о которых можно вообще никогда не догадаться. Просто потому, что мало кто знает C++ от и до, и способен удержать все эти тонкие моменты в своей голове.

Так что даже не знаю как поведу себя, если когда-нибудь придется проходить собеседование на C++ разработчика и меня попросят рассказать что сделает тот или иной код. Вроде как самым естественным было бы спросить "Ребят, а вы в своем уме вообще?" Но, боюсь, окажусь не понятым 🤣

пятница, 9 августа 2024 г.

[prog.c++.flame] Краткие впечатления от туториала wharehouse-backed от авторов CAF-а

Некоторое время назад авторы CAF-а (он же C++ Actor Framework) выкатили туториал warehouse-backend. Заглянул любопытства ради. Поделюсь некоторыми крайне субъективными наблюдениями.

Сперва небольшой дисклеймер. Поскольку я сам много лет делаю инструмент программирования на C++ в стиле асинхронного обмена сообщениями, то не могу быть объективным. Так что если ниже по тексту вам покажется, что меня заносит и я перебарщиваю с категоричностью, то значит вам не показалось, а меня занесло на самом деле 😉

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

Впечатление первое. Одно из главных

Глядя на код примеров ощущаю себя старым пердуном, застрявшим в начале 1990-х со своим ООП, и ничего не понимающим в красоте функционального кода.

Мне бы обычных классов с методами. А не лямбды внутри лямбды внутри лямбды, объединенные монадическими цепочками .filter().map().observe().transform().subscribe() (или это не монады вообще?)

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

Впечатление второе. Нехороший привкус vendor lock-in

Очень смущает обилие того, что берется из пространства имен caf. Тут тебе и caf::net, и caf::cow_string, и caf::json_object, и caf::logger, и пятое, и десятое.

Такое ощущение, что авторы CAF-а создали вокруг своего инструмента целую замкнутую и самодостаточную экосистему.

Наверное, непритязательным пользователям удобно: берешь один CAF, а вместе с ним получаешь и сеть (включая TLS и HTTP-сервер), и JSON, и конфиги, и еще кучу всего полезного. И, полагаю, это хорошо для подсаживания пользователей на иглу конкретного продукта. Кто сказал vendor lock-in? 😉

Мы когда-то давно вокруг SObjectizer-а тоже пытались что-то подобное построить -- у нас и so_log был, и so_sysconf, и еще что-то. Это было хорошо двадцать лет назад, когда мы были в замкнутом мире внутренних продуктов компании Интервэйл, в которой SObjectizer когда-то появился.

Но сейчас, когда в мире C++ столько всего разного и конкурирующего друг с другом, мне показалось более разумным сконцентрироваться только на ядре SObjectizer-а. А уже пользователь может выбрать для себя Asio или libuv, spdlog или Boost.Log, RESTinio или Beast, nlohmann::json или simdjson, и т.д., и т.п.

Так что я лично придерживаюсь мнения, что инструмент должен делать лишь одну вещь, а остальные потребности, будь то парсинг JSON или прием/отсылка UDP-датаграмм, закрывается внешними инструментами.

Впечатление третье. Незначительное

Есть в туториале пример с typed_actor. На первый взгляд, штука прикольная, когда мы на уровне типа описываем то, на что актор реагирует.

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

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

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

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

Вместо заключения

Признаюсь честно, лет 7-8 назад у меня еще были и время, и силы, и желание брать и смотреть на чужие похожие разработки (будь то CAF, Akka или Orleans). Теперь уже нет ни того, ни другого, ни третьего.

Все эти реализации модели акторов получаются настолько разными, что смысл подобного сравнения от меня ускользает. Ну вот есть в CAF фича X, а в Akka фича Y. Ну есть и что?

Совсем другое дело, когда ко мне приходят с конкретным вопросом. Типа вот у меня есть такая проблема, как я могу ее решить с помощью SObjectizer-а? И могу ли вообще?

Ну или что-то вроде: вот я в CAF/Akka/Orleans делаю вот это вот так-то, а как это будет в SObjectizer?

Вот это для меня предметный разговор, из этого наверняка можно что-то полезное извлечь. На это я готов тратить свое время.

Но вот на то, чтобы посмотреть что где-то там новенькое появилось... Увы, не хочется.

И, возможно, это и есть для меня самое главное впечатление 🙂

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


Признаюсь, я уже давно не рассматриваю CAF или Akka в качестве конкурентов SObjectizer-у. Это все настолько разные продукты, что общего в них мне видится очень и очень мало. По сути, это как сравнивать обычный слесарный молоток, кувалду и отбойный молоток. Вроде как все это разновидности молотков, но не так уж и много случаев, когда они могут быть взаимозаменяемы.

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

[prog] Похоже, использование табуляции становится пережитком старых (не)добрых времен

Я уже более 30 лет являюсь приверженцем табуляции для отступов в коде. Но вынужден признать, что часть преимуществ табуляции сейчас уже сложно оправдать (а то и объяснить).

Во-первых, это в 1990-ом году, на 5.25" дискете емкостью 360Kb (да и даже 720Kb) приходилось место экономить. Поэтому было критично, что исходный файл с табуляцией может занимать от 1/3 до 1/2 меньше места, чем файл с пробелами. Сейчас, когда терабайты умещаются на MicroSD карте, об этом даже смешно говорить.

Во-вторых, сейчас сложно найти редактор кода, не говоря уже об IDE, который не умеет сдвигать блоки кода влево/вправо на нужное число позиций. Тогда как раньше в каком-нибудь редакторе Turbo C 2.0 такой фичи не было как класса. Хочешь сдвинуть несколько строк влево? Удаляй лишние символы в начале строки вручную. Хочешь сдвинуть вправо? Добавляй. Опять же вручную. Понятное дело, что табуляция здесь гораздо удобнее, чем пробелы.

В-третьих, экраны сейчас не в пример лучше, везде графические интерфейсы, можно поставить какой хочешь шрифт какого хочешь размера. А раньше, в текстовом режиме на каком-нибудь убогом терминале от ЕС-1840 в режиме 80x25 особо не разгонишься. И если на таком экране исходный код с отступами по 2 или 4 пробела выглядит убого, то ничего ты уже не сделаешь. Другое дело с табуляцей -- настроил под себя, хоть в единичку, хоть в восьмерку, и радуйся.

В-четвертых, раньше мне гораздо чаще приходилось извлекать фрагменты кода и вставлять их в какую-то документацию. Да даже больше: раньше код даже часто печатали на бумаге, просто чтобы поразбираться в спокойной обстановке оставляя пометки на полях (в "спокойной обстановке" ты оказывался еще и потому, что твое машинное время закончилось и нужно уступить компьютер коллеге). Возможность поменять размер отступов не трогая сам текст и здесь была очень в тему. Многие ли сейчас пишут документацию, куда вставляются фрагменты реального кода? Кто-нибудь сейчас печатает код на бумаге, чтобы покорпеть над ним с красным карандашом в руках?

Ну и не могу не поделиться еще одной своей болью последних нескольких месяцев: web-интерфейс Google Mail удаляет табуляции из фрагментов кода когда их вставляешь в текст письма копипастой, весь такой фрагмент оказывается выровнен по левому краю 🙁
Хорошо хоть Google Doc такой фигней пока(?) не страдает...

PS. Прошу понять меня правильно. Я не пытаюсь развести очередной срач на тему "tabs vs spaces". Просто как-то грустно от осознания как давно я в программизме и как много поменялось за это время.

PPS. Xотя, конечно, же, единственный верный способ -- это табуляция, все остальное должно быть предано анафеме и выжжено каленым железом, адназначна! (Желающих мне возразить прошу обратить внимание не тег Humour)

четверг, 1 августа 2024 г.

[life.cinema] Очередной кинообзор (2024/07)

Подошло время отчета о просмотренных за минувший месяц фильмах. Традиционно в начале каждого из списков идет то, что понравилось больше, а в конце -- шлак, на который можно не тратить свое время.

Фильмы

Наследник (Gwigongja, 2023). На удивление неплохо. С экшОном, с юмором, местами с фантастикой, местами с явным уклоном в азиатчину. Но в целом отличный комедийный боевик на один раз.

Угроза (Todos los nombres de Dios, 2023). Откровенно слабое кино. Посмотреть можно разве что если ничего другого нет от слова совсем.

Охотники из рая (The Hunted, 2024). Очень бюджетно, примитивно, местами предсказуемо, а местами и откровенно фантастично.

Выкуп – миллиард (Take Down, 2016). Полная ерунда. Можно смело не смотреть.

Сериалы

Пацаны (The Boys, четвертый сезон, 2023). Первые серий пять смотрелось так себе, как будто пережевывание всего того, что было в предыдущих сезонах. Но вот к последним трем сериям действие раскочегарилось, так что общее впечатление осталось хорошим. Однако, очень надеюсь, что пятый сезон, как и обещают, окажется завершающим.

Высшая мера (первый сезон, 2022). Можно глянуть. Атмосфера ретро неплохо передана. Несмотря на 12 серий привычной сериальной затянутости не чувствуется. Но! Во-первых, главного злодея вычислить удалось чуть ли не во второй серии. Во-вторых, в развязке на зрителя вывалили ну очень уж много "роялей в кустах", что еще сильнее испортило впечатление. Поэтому впечатления средненькие, к сожалению.

Убить Риту (первый сезон, 2023). В принципе, бодренько, красочно, с большим количеством перипетий в сюжете. Но... Во-первых, лучше не вдумываться в логику происходящего. Во-вторых, нужно как-то свыкнутся с сексуальной озабоченностью главных героев. В-третьих, нужно как-то убедить себя, что 40-летняя актриса, играющая 26-летнюю героиню -- это нормально.

Король Талсы (Talsa King, первый сезон, 2022). Какая-то наивная и примитивна сказочка про типа благородного и благообразного преступника.

Расплата (первый сезон, 2023). Мне показалось очень посредственным кино. Посмотреть можно разве что если больше смотреть ну вот вообще нечего.

Идентификация (первый сезон, 2022). Редкая дрянь. И по сюжету, и по художественному воплощению. Ну и не могу не добавить, что не смог вспомнить ни одного вменяемого персонажа -- все более-менее значимые действующие лица больные на голову.

Вне категории

Сто лет тому вперёд (2024). Потраченного времени не жалко, но как кино совершенно не зашло. Слишком уж детский фильм, поэтому даже и не знаю как его оценивать.

[prog.c++.wtf] Еще немного про самый странный паттерн в коде

Продолжение вот этой темы. За прошедшие месяцы регулярно сталкивался с этим паттерном в разных кодовых базах, написанных разными разработчиками. Такое ощущение, что выросло новое поколение программистов, смотрящих на код совсем по-другому.

Вроде бы понял, в чем смысл. С обычными return-ами часто пишут так:

int some_func(some_arg & arg) {
  if(!is_valid(arg))
    return -1;

  if(!approriate_state(arg))
    return -1;

  if(!some_another_condition(arg))
    return -1;

  ... // Ну а здесь уже какие-то действия.
}

Похоже, этот подход пытаются переложить на тело цикла, только вместо преждевременных return-ов используют continue:

for(auto & item : collection) {
  if(!is_valid(item))
    continue;

  if(!appropriate_state(arg))
    continue;

  if(!some_another_condition(arg))
    continue;

  ... // Ну а здесь уже какие-то действия.
}

Понять-то я понял, а вот принять не получается 🙁

Во-первых, дело в том, что в реальном коде все это выглядит не так опрятно и понятно, как в моих псевдопримерах. Между if-ами, зачастую, еще какие-то действия выполняются, а сами if-ы могут быть и вложенными, и кроме continue еще встречаются и break, и даже return...

В общем, когда пытаешься вчитаться в незнакомый код вот с такими вот continue, то приходится в голове таблицу переходов вручную выстраивать. Невольно вспоминаются времена программирования на Basic-е с нумерацией строк и ассемблере 😁

Во-вторых, недавно довелось несколько циклов преобразовать в лямбды, которые передаются во что-то типа `std::ranges::for_each`, т.е. было:

for(const auto & item : detect_collection_for_processing()) {
  ... // Тут обработка с continue/break-ами.
}

а стало:

for_all_ready_to_processing_items(token, [&](const auto & item) {
  ... // Тут обработка, но уже без continue/break.
});

Так вот, оказалось, что тела тех циклов, которые были написаны по-человечески, без continue и break, трансформируются в лямбды легко и непринужденно. Ну вот вообще не припомню, чтобы нужно было делать что-то кроме замены for-а на вызов функции с лямбдой.

Тогда как тела циклов с continue -- это вот прям минное поле 😔
Хорошо хоть, что компилятор бьет по рукам когда видит continue вне цикла. Несколько раз это меня выручало, т.к. в сложных if-ах я continue просто не замечал.

Посему в очередной раз приходится делать вывод о том, что куски кода, оформленные в виде "одна точка входа, одна точка выхода", оказываются наиболее простыми и в изучении, и в сопровождении. Деды, которые учили меня структурному программированию 35 лет назад, были таки правы.


Не хочу создать впечатление, что в моем коде только правило "единственного return-а" и ничего больше.

Это далеко не так.

И break-и использую, и несколько return-ов из функций. В том числе, бывает, сочетаю в теле цикла и break, и return.

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

А вот continue в моем коде, действительно, найти тяжело. Практически не использую. Чего и вам желаю 😎

пятница, 26 июля 2024 г.

[dev.hiring.flame] Посмотрел и разоблачение Антона Назарова и реакцию на это разоблачение от самого Антона Назарова

Продолжение и, надеюсь, завершение недавней темы. В догонку к ролику от HR-а про "волков" и "волчат" (далее "HR-овский ролик") осилил и стрим, на котором Антон Назаров смотрит и реагирует на "HR-овских ролик" (далее "волчий стрим").

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

Конечно же, авторы первого, "HR-овского ролика", откровенно подставились. Была попытка в сатирическом ключе разоблачить Антона Назарова и в его лице все явление накрутки опыта и обмана собеседующих. Хотели выступить стиле BadComedian, по факту вышло на уровне конкурса художественной самодеятельности 7-го класса общеобразовательной школы, если не ниже.

Чем и воспользовались Антон со своим соведущим по стриму -- с удовольствием обстебали ролик Леси и Глеба, причем, местами, вполне себе успешно и по делу.

При этом смотреть запись стрима с Антоном и Никитой мне было тяжело и вызывало когнитивный диссонанс: с одной стороны, два малолетних гопника с соответствующими манерами и лексикой, с другой стороны, вроде как с мозгами и эрудицией. Кстати говоря, посмотрев телеграмм соведущей "НR-овского ролика", Леси Набока, и попробовав посмотреть еще одно видео с ее канала "Два стула", у меня сложилось впечатление, что Леся такой же "малолетний дебил гопник" (вот в качестве иллюстрации один из постов, который зачем-то был написан с использованием нецензурных выражений). Так что, к сожалению, "борьба была равна, сражались два говна".

На "волчьем стриме" правильно отметили: "HR-овский ролик" оказался откровенной рекламой сообщества "Осознанная меркантильность". Ведь Назаров и Ко декларирует что? Существующая система найма имеет ряд серьезных недостатков из-за которых целые категории соискателей испытывают проблемы с поиском работы. А раз так, то плохую систему не грех и хакнуть. Чем Назаров и Ко и занимаются. И если HR-ы снимают длинные разоблачительные ролики, значит хакают успешно. Значит "волки" все делают правильно.

Что меня искренне удивило в "волчьем стриме", так это непонимание того, чем же деятельность "волков" вредит индустрии. Поэтому вот мое видение:

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

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

А раз речь идет об экзамене, то ключевым моментом становится честность его прохождения. Ведь если оценка получена нечестным путем (удалось ли списать или же был подкуплен экзаменатор не важно), то мы не можем доверять результату. Да, у нас есть человек с хорошей оценкой в зачетке, но реально ли он что-то понимает в сданном предмете? А ведь нам нужно, чтобы понимал.

Так вот, любой мухлеж на собеседованиях неизбежно ведет к тому, что качество этих самых квалифицированных кадров снижается. Что сказывается затем на качестве и стоимости разработке в компаниях, где совершили неудачный найм. После чего компании начнут делать выводы и условия поиска и отбора работников изменятся. Возможно, не в лучшую сторону.

Disclaimer: да, я согласен с мыслью, высказанной на "волчьем стриме", что далеко не все "накрутчики опыта" неумехи и наверняка среди них есть те, кто со временем станет отличным специалистом. Но, во-первых, лишь со временем. И, во-вторых, их доля все-таки не будет доминирующей.

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

Пара серьезных недостатков текущей системы, на мой взгляд:

Во-первых, фильтры по годам опыта имеют место быть. И для успешного поиска работы бывает необходимо как-то эти фильтры обходить. Причем не обязательно в сторону увеличения опыта. Бывают случаи, когда людям приходится снижать опыт, чтобы попасть в поле зрения HR-ов. И это не нормально.

Во-вторых, собеседования, на которых распрашивают про O-большое, просят развернуть строку, решают задачки с leetcode посредством лайв-кодинга (или даже написанием кода на доске) или "проектируют" очередной YouTube, как по мне, являются откровенной профанацией. В подавляющем большинстве случаев реальная работа будет отличаться от такого собеседования как небо и земля. И это тоже не нормально.

Итак, огромное количество людей понимает, что зачастую творится откровенная фигня: ваше резюме могут даже не посмотреть из-за того, что вам не хватает трех месяцев опыта или же вас срежут на собеседовании из-за неправильного понимания сложности работы сортировки Шелла, при том, что вам со 100% вероятностью не придется использовать никакого другого sort-а, кроме того, что есть в стандартной библиотеке.

Только вот проблема в том, что нет простого и масштабируемого способа исправить эту ситуацию. Вот просто нет на данный момент. К сожалению, сейчас мы используем именно то, что показало себя наиболее выгодным для множества компаний самого разного размера (особенно большого). Посему имеем то, что имеем.

И вот на этом фоне появляются персонажи, вроде Назарова и Ко, и объявляют, что их цель -- это помочь вам обмануть систему. И создают условия для её успешного обмана. Что, как по мне, естественно. Но тоже не есть хорошо.

При этом в заявления Антона Назарова о том, что если дать ему возможность, то уж он то сделает хорошую систему отбора программистов, я лично не верю от слова совсем.

На меня Антон Назаров производит впечатление пены, образовавшейся на гребне волны интереса к ИТ. Сунулся в программизм, ничего не достиг, понял, что ему это не нравится, сумел найти и оседлать хайповую на данный момент тему. Ну OK, молодец, если главный талант -- это складно трындеть, то зачем зарывать его в землю и горбатиться над кодом?

В качестве звиздуна-собеседника на YouTube он, в принципе, хорош. Гораздо мощнее оппонирующей ему Леси Набока, кстати говоря.

Да вот только трындеть не мешки ворочать. Хотел бы (и мог бы) организовать "правильную" систему найма, превосходящую существующую, открыл бы свой HR-бизнес или же смог бы убедить в состоятельности своего метода кого-то из крупных игроков (условный СберТех или Ростелеком). И мы бы могли обсуждать результаты его деятельности на этом направлении. Но, т.к. результатов нет (и не будет, вероятнее всего), то и обсуждать нечего.


В общем, чего хочу сказать:

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