пятница, 27 октября 2023 г.

[prog.flame] Наглядная иллюстрация на тему "ушел рисовать каракули на бумаге"...

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

Дабы не повторяться, процитирую себя самого:

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

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

К сожалению, мой собеседник меня не понял (так уж показалось) и в итоге выдал вот такой пассаж:

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

А вчера я столкнулся с ситуацией, которая отлично иллюстрирует момент с "рисованием ничего не значащих каракулей на бумаге". Что и подтолкнуло к написанию этого поста.

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

Суть в том, что в свое время в RESTinio был добавлен механизм объединения обработчиков запросов в цепочки. Это что-то a la middleware из Express.JS (но именно что a la, т.е. по мотивам, но не один-в-один).

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

Что неудобно, например, если какому-то обработчику нужно выполнить длительную асинхронную операцию. Скажем, сходить в БД или обратиться к стороннему компоненту дабы аутентифицировать пользователя.

Поэтому сразу встал вопрос "цепочки синхронных обработчиков -- это лучше, чем ничего, но можно ли сделать их асинхронными?"

Три года назад ресурсов на решение этого вопроса не хватило.

А вот сейчас возможность представилась. Поэтому пытаюсь подступиться к этой задаче вновь.

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

Да, тот самый туман в голове с обрывками из отрывков разрозненных мыслей. Ну и каракули на бумаге, куда же без этого.

Есть ли у меня уверенность в том, что задача будет решена за неделю (а больше времени может и не быть)?

Нет, конечно.

Хочу ли я сделать первое, что придет в голову просто ради того, чтобы поставить галочку в списке фич RESTinio?

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

И что же получается?

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

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

Но, если результат вовсе не гарантирован, то что же толкает на поиск решения?

Во-первых, "есть такое слово: надо!" 😂 Поскольку продукт жив, то он должен пополняться новыми фичами. Пусть даже какие-то из них (пока) непонятно как сделать.

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

Дабы не быть голословным по поводу изобретательства. Перечитываю сейчас куски документации по RESTinio, дабы восстановить в памяти что и как у нас уже сделано. Дошел до раздела про экспериментальный easy_parser router (на русском языке про него есть статья на Хабре). И вот перечитываю, а у самого остатки волос шевелятся от увиденного: сложно поверить, что все это мы сами придумали и сделали 😎

Особенно, почему-то доставил вот этот фрагмент из раздела про easy_parser:

// A parser for grammar:
//
// communicator = "port=" ("default" | port_params)
// port_params = '(' NUMBER ':' NUMBER ',' NUMBER ')'
//
struct port_params {
   unsigned short port_index_;
   unsigned int in_speed_;
   unsigned int out_speed_;
};

auto parser = epr::produce<port_params>(
   epr::exact("port="),
   epr::alternatives(
      epr::exact("default")
         >> epr::just(port_params{10u4096u4096u})
         >> epr::as_result(),
      epr::produce<port_params>(
         epr::symbol('('),
         epr::non_negative_decimal_number_p<unsigned short>()
            >> &port_params::port_index_,
         epr::symbol(':'),
         epr::non_negative_decimal_number_p<unsigned int>()
            >> &port_params::in_speed_,
         epr::non_negative_decimal_number_p<unsigned int>()
            >> &port_params::out_speed_,
         epr::symbol(')')
      ) >> epr::as_result()
   )
);

Вот не знаю почему. Вроде бы там есть и более сложные, и более выразительные фрагменты. Но доставил почему-то этот.

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


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

четверг, 26 октября 2023 г.

[prog.c++.sobjectizer] Several aspects of integration with foreign multithreading libraries

During discussion with Marco Arena about a new article from his series "SObjectizer Tales" I was asked:

You can see that gRPC "sync API" manages threading internally. This might be a problem. Speaking of which, I am really interested in your experience on SObjectizer living together with other threading hosts. How do you usually manage things in this situation? Imagine also that gRPC threads are not "exposed" anyhow.

It's a very interesting question, and it's hard to answer in a few words. But I'll try...

Let's imagine a case when we have to use SObjectizer and some other multithreaded library in an application. What kind of problems can we encounter and what can we do with them?

In the following text I'll use the imaginary library "MegaThreads" just for convenience.

вторник, 24 октября 2023 г.

[prog.c++] Оказывается, std::enable_shared_from_this можно применять и с неполным типом

Увидел неожиданное для себя в статье "On detecting improper use of std::enable_shared_from_this":

#include <memory>

struct D;

struct B : std::enable_shared_from_this<D>
{
};

struct D : B
{
};

int main() {
    auto p = std::make_shared<D>();
    auto q = p->shared_from_this();
}

И это спокойно компилируется.

Другими словами, std::enable_shared_from_this можно использовать и для типов, которые не были еще полностью определены.

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

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

[prog.flame.c++] Ведь ни один вменяемый человек не начнет писать на C++ что-нибудь новое...

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

Но не стал озвучивать причины, по которым, заказчики сделали именно такой выбор. За что заслужил пару-тройку комментариев из категории "без доказательств нещитово!"

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

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

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

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

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

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

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

Во-вторых, страшилки про то, что выбрав С++ вы автоматически попадаете на постоянные segmentation fault и бесконечные утечки памяти, как мне кажется, не актуальны уже лет 20. Ну, если и не 20, то лет 10 точно. И причина не столько в том, что C++ за это время стал лучше и безопаснее. Как раз таки не стал (подвижки есть, но они не кардинальные). Но С++ оброс инструментарием, который позволяет минимизировать усилия на борьбу с подобными явлениями: санитайзеры, статические и динамические анализаторы. Так что и падения, и утечки памяти в C++ном коде все еще встречаются. Однако и происходит это гораздо реже, и обнаруживается уже гораздо проще.

Добавлю сюда еще и то, что в последние 20 лет шел постоянный отток разработчиков из C++ в другие языки программирования: сперва это была Java, затем C#, затем Go и сейчас вот Rust. И у этого оттока, помимо негативной составляющей, была и позитивная: большое количество криворуких программистов, которых в принципе нельзя было до C++ допускать, из C++ наконец-то ушло. Поэтому, в среднем, сейчас на C++ программируют по большей части именно те, кто понимает как нужно это делать, чтобы не было мучительно больно.

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

Ну и в завершение позволю себе озвучить один интересный аргумент против разработки на языке Go, который мне довелось услышать года четыре назад (надеюсь, что здесь я никакого NDA не нарушу). Смысл был в том, что Go-шники на рынке дорогие, все за ними гоняются, их ЗП улетели "в небеса", но при этом найти толкового Go-шника сложно, т.к. язык позволяет писать код даже посредственным программистам. В результате если взять на рынке случайного Си-шника или плюсовика, то больше шансов на то, что он будет понимать что делает и сможет сделать нормально, чем если взять случайного Go-шника, а стоить Си-шник или плюсовик будет дешевле.

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

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


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