суббота, 29 июля 2023 г.

[prog.c++] Пожалуй, вот как нужно будет себя вести, если придется искать работу наемным C++ программистом...

Зафиксирую в склерозник этот "а ля план действий". На случай, если придется искать работу C++ разработчиком ;)

В случае собеседования, на котором наниматель захочет выяснить мой уровень как программиста, попрошу людей на той стороне предварительно познакомиться с моим кодом, который есть в OpenSource. Дабы разговор был предметным. Будет очень интересно узнать, до чего в моем коде смогли "докопаться" и насколько ту сторону удовлетворят мои объяснения и вообще мой подход к разработке (в самом широком смысле, от проектных решений, до деталей реализации и выбора имен сущностей).

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

Отдельно, в качестве лакмусовой бумажки, можно будет, пожалуй, задать такой вопрос: "А используете ли вы в C++ном коде приведения типов в стиле Си? Если да, то чем это объясняется и на основании каких критериев делается выбор в пользу такого приведения?"

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


Прекрасно понимаю, что в крупные компании с формализованными процессами и стандартизированными процедурами отбора (типа "Тинькова" или "Яндекса") я никогда не попаду с таким поведением. Но так сомневаюсь, что в таких организациях смогу продуктивно работать. Так что ну не судьба, да и ладно.

А вот небольшие и совсем маленькие фирмы, думаю, вполне могли бы пойти на подобный диалог. И сэкономить друг другу кучу времени и нервов.

пятница, 28 июля 2023 г.

[prog] Будьте бдительны, если вам потребуется работать с CivetWeb из нескольких тредов

Есть такая чисто Си-шная разработка, CivetWeb, которая позволяет встроить HTTP/WebSocket сервер в ваше приложение. Насколько я понял, там используется модель thread-per-connection или что-то вроде того. Т.е. когда CivetWeb принимает подключение, он создает рабочую нить, на которой CivetWeb и будет работать с этим подключением.

Очень простая модель. Но в случае, если вам доведется обслуживать посредством CivetWeb WebSocket-подключения, нужно проявлять осторожность.

Дело в том, что CivetWeb на контексте своей нити (давайте называть ее IO-thread) будет дергать ваши callback-и: connect_handler (хотя для WebSocket это не так актуально), ready_handler, data_handler и close_handler.

ready_handler будет вызван когда CivetWeb примет WebSocket подключение с той стороны (т.е. после завершения процедуры upgrade protocol). По сути, с этого момента и начинается ваша работа именно с WebSocket-подключением. В ready_handler передается указатель на mg_connection, с которым вы будете иметь дело.

data_handler вызывается, когда CivetWeb вычитывает входящие данные из соединения и отдает их вам на обработку (как раз посредством вызова data_handler callback на контексте IO-thread). И если у вас протокол поверх WebSocket-а вида запрос-ответ, то вы можете сразу здесь, внутри data_handler, выполнить mg_websocket_write. В этом случае у вас все хорошо.

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

Вот здесь нужно проявить осторожность. Т.к. делать вызов mg_websocket_write вы будете на контексте какой-то своей рабочей нити, а не на контексте IO-thread от CivetWeb. А осторожность нужна из-за наличия close_handler callback-а.

Этот close_handler вызывается CivetWeb-ом на контексте IO-thread когда CivetWeb обнаруживает, что соединение разорвано (по инициативе удаленной стороны или из-за ошибки ввода-вывода). И, внимание, после завершения close_handler указателем на mg_connection пользоваться нельзя, он уже протух!

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

Чтобы не попадать в такую ситуацию потребуется какой-то механизм защиты для имеющегося у вас на руках mg_connection. Например, вот такой (это псевдокод, без претензии на компилябильность):

понедельник, 24 июля 2023 г.

[prog.c++.flame] Написать работающий код не фокус, фокус написать этот код нормально

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

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

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

А раз главное требование выносится за скобки (ну реально, а какой смысл рассматривать код, который не работает?), то остается все остальное. От чего многие любители писать "просто работающий код" либо отмахиваются, либо вообще не осознают.

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

Но когда встречаешь в коде что-то вроде:

class data_holder {
    opaque_data * data_;

public:
    data_holder() {
        data_ = make_data(); // Упс №1.
        if(-1 == init_data(data_)) { // Упс N2.
            throw std::runtime_error{"unable to initialize data"};
        }
    }
    ~data_holder() {
        destroy_data(data_);
    }

    void do_something();
};

или что-то вроде:

bool try_do_something() {
   ... // Какие-то действия.
   int r = call_some_pure_c_function();
   if( !r ) {
       revert_some_changes();
       return -1; // Упс №3.
   }
   ... // Еще какие-то действия.
   return true;
}

или читаешь вопрос вроде вот такого (Упс №4), то...

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


Упс №1. Результат make_data следовало бы проверять, т.к. может быть возвращен и nullptr.
Упс №2. При таком раскладе деструктор у data_holder вызван не будет. Соответственно, destroy_data так же не будет вызван.
Упс №3. Должно быть true или false, а не -1. И да, тот же GCC по рукам за такое не бьет.
Упс №4. Я на полном серьезе полагаю, что если кому-то платят за разработку на C++, то такие вопросы должны решаться самостоятельно.


Все совпадения с реальным кодом случайны и непреднамеренны.

[work.pr] Прикольно: знакомство с SObjectizer как дополнительный плюс

Не скрою, приятно увидеть такое в чужой вакансии:

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