вторник, 1 января 2030 г.

О блоге

Более двадцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в 2012-2014гг как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). В настоящее время занимаюсь развитием компании по разработке ПО stiffstream, в которой являюсь одним из соучредителей. Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

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

понедельник, 31 декабря 2029 г.

[life.photo] Характерный портрет: вы и ваш мир моими глазами. Безвозмездно :)

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

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

четверг, 14 марта 2024 г.

[work] Открыт для сотрудничества в качестве C++ разработчика

В виде (суб)контракта с нашей компанией СтифСтрим.

Прикладной специализации не имею, за тридцать лет в программизме приходилось заниматься разными вещами. Очень часто это были инфраструктурные вещи -- фреймворки, библиотеки и утилиты, которые затем использовались для решения прикладных задач. В последние годы поддерживал и развивал C++ные библиотеки SObjectizer, RESTinio и json_dto.

Самостоятельно погружаюсь в проблему, нахожу решение, кодирую, тестирую, документирую. Если нужно обучаю. Если нужно сопровождаю и поддерживаю. Если нужно выступаю в качестве евангелиста (см. список публикаций на Хабре).

Работаю не быстро, но качественно, беру недорого.

Оценить мой уровень можно, например, про проекту aragata, реализованному мной практически в одиночку. Код можно увидеть на GitHub-е, на Хабре есть две статьи о том, что это и как работает: вводная статья и описание сделанных по результатам нагрузочных испытаний оптимизаций + вот этот пост.

В качестве дополнительных примеров: timertt (+ документация), so5extra (+ документация) -- эти проекты так же написанные мной самостоятельно.

Связаться со мной можно через eao197 на gmail тчк com. Если кому-то интересен профиль на LinkedIn, то вот.


Это сообщение повисит какое-то время вверху. Потом будет видно, имеет ли смысл пытаться дальше оставаться в C++.

[prog.flame] Самодокументирующися код против документированного, наглядно

Намедни в LinkedIn поиронизировал на счет "самодокументирующегося кода". В очередной раз 😎

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

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

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

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

class DefaultThreadPoolScheduler final : public Scheduler
{
   ...

private:
   struct WorkerData
   {
      std::mutex _lock;
      std::condition_variable _wakeupCondition;

      Scheduler::TaskUniquePtr _taskToRun;

      bool _shutdownInitiated{ false };
   };

   using WorkerDataContainer =
      std::vector<std::reference_wrapper<WorkerData>>;

   std::latch _allWorkersStartedLatch;

   std::mutex _lock;

   TasksContainer _tasksQueue;

   ThreadPool _threadPool;

   WorkerDataContainer _availableWorkers;

   bool _shutdown{ false };
};

void DefaultThreadPoolScheduler::doWork() noexcept
{
   WorkerData thisWorkerData;

   {
      std::lock_guard schedulerLock{ _lock };
      _availableWorkers.push_back(std::ref(thisWorkerData));

      _allWorkersStartedLatch.count_down();
   }

   bool shutdownIntitiated{ false };
   while( !shutdownIntitiated )
   {
      std::unique_lock workerLock{ thisWorkerData._lock };

      if( TaskUniquePtr taskToRun = std::move(thisWorkerData._taskToRun); !taskToRun )
      {
         shutdownIntitiated = thisWorkerData._shutdownInitiated;
         if( !shutdownIntitiated )
         {
            thisWorkerData._wakeupCondition.wait(workerLock);

            shutdownIntitiated = thisWorkerData._shutdownInitiated;
         }
      }
      else
      {
         workerLock.unlock();
         taskToRun->run(Scheduler::RunCondition::Normal);
         completeTaskThenTryGetNext(std::move(taskToRun), thisWorkerData);
      }
   }
}

понедельник, 11 марта 2024 г.

[prog.c++] Первая попытка написать собственный концепт

В проекте для одного из клиентов уже используется C++20, так что есть возможность на практике прикоснуться к некоторым фичам двадцатого стандарта. Вроде ranges и concepts.

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

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

В общем, смысл в том, что есть интерфейс диспетчера задач Scheduler и есть интерфейс отдельной задачи Task:

class Scheduler
{
public:
   class Task
   {
   public:
      enum class Status { Normal, Deleted };

      virtual ~Task() = default;

      virtual void run(Status) = 0;
   };

   using TaskUniquePtr = std::unique_ptr<Task>;

   virtual void push(TaskUniquePtr task) = 0;
};

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

class FirstTask final : public Scheduler::Task
{
  ... // Какие-то внутренности.
public:
  ... // Какой-то конструктор.

  void run(Status status) { ... /* Какие-то действия */ }
};
...
scheduler.push(std::make_unique<FirstTask>(...));

предполагалось делать как-то так:

make_task(scheduler,
  [...](Scheduler::Task::Status status) {... /* Какие-то действия */ });

И вспомогательный шаблон функции make_task должен был обернуть лямбду в некий класс-наследник Task. Что-то вроде:

template<typename H>
class LamdaAsTask final : public Scheduler::Task
{
   H _handler;

public:
   explicit LamdaAsTask(H handler) : _handler{std::move(handler)} {}

   void run(Status condition) override
   {
      _handler(condition);
   };
};

template<typename H>
void make_task(
   Scheduler & scheduler,
   H && handler)
{
   scheduler.push(std::make_unique<LamdaAsTask<H>>(std::forward<H>(handler)));
}

Но ситуация была чуть сложнее. Нужно было использовать разные фабрики для Task. Т.е. в make_task передавались не только лямбда и ссылка на Scheduler, но еще и некая фабрика. Именно фабрика брала лямбду и возвращала указатель на новый Task. Т.е. шаблон make_task выглядел так:

template<typename Factory, typename H>
void make_task(
   Scheduler & scheduler,
   Factory && factory,
   H && handler)
{
   scheduler.push(factory(std::forward<H>(handler)));
}

И место для улучшения кода я видел в том, чтобы ограничить параметры шаблона make_task концептами.

Вырисовывалось два концепта.

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

template<typename H>
concept Handler_C = requires(H h)
{
   { h(Scheduler::Task::Status::Normal) } -> std::same_as<void>;
};

Второй концепт описывает фабрику Task-ов:

template<typename F, typename H>
concept TaskFactory_C = requires(F f)
{
   requires Handler_C<H>;

   { f(H{}) } -> std::convertible_to<Scheduler::TaskUniquePtr>;
};

Т.е. этот концепт зависит от параметра H, который описывает лямбду. Эта лямбда должна удовлетворять требованиям концепта Handler_C. А фабрика сама по себе должна поддерживать вызов operator() с передачей туда лямбды H и возвратом чего-то приводимого к TaskUniquePtr.

Благодаря таким концептам make_task приняла вид:

template<Handler_C H, TaskFactory_C<H> Factory>
void make_task(
   Scheduler & scheduler,
   Factory && factory,
   H && handler)
{
   scheduler.push(factory(std::forward<H>(handler)));
}

В общем, я получил то, что хотел.

Под катом полный код автономного примера, который всю эту кухню демонстрирует.

У меня же пока впечатления от концептов смешанные. Вероятно, вещь хорошая и, местами, полезная. Но вот ВАУ эффекта пока не случилось.

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

четверг, 7 марта 2024 г.

[work.culture] Использование жестких, на грани грубости, оценок в работе

Подавляющую часть своей карьеры я работал в отечественных компаниях (не делаю здесь разницы между компаниями из РБ и РФ). И привык к тому, что в нашей производственной культуре нередко используются грубые оценки свершившегося, происходящего и/или грядущего.

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

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

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

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

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

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

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

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

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

Мат (если без него не обойтись) должен использоваться только в исключительных случаях.

И ни в коем случае нельзя использовать мат в отношении подчиненных. Да, я понимаю, что временами очень тяжело удержаться от вопроса "ты что долбо*б?" или "а не ох*ел ли ты?", но нужно. Да и клиентов, в их отсутствие, так же лучше матами не обкладывать. И не только потому что везде есть уши 🙂

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

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

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


В общем, к чему это я?

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

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

понедельник, 4 марта 2024 г.

[prog.flame] Пример серьезного, на мой взгляд, просчета в API библиотеки libconfig

Впервые довелось столкнуться с библиотекой libconfig. Довольно таки популярной, насколько я могу судить. Тем удивительнее было обнаружить там описанный ниже косяк.

Чтобы получить значение целочисленного параметра нужно использовать функцию config_setting_get_int:

int config_setting_get_int (const config_setting_t * setting)
long long config_setting_get_int64 (const config_setting_t * setting)

These functions return the value of the given setting. If the type of the setting does not match the type requested, a 0 value is returned.

Т.е. если мы пытаемся получить значение параметра, а нам возвращают 0, то этот ноль может означать:

  • что значение не получено, т.к. оно имеет другой тип. Например, вместо my_param=0 задано my_param="0" или my_param="zero";
  • что значение получено и это таки ноль. Просто ноль.

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

Аналогичная проблема есть и с семейством функций config_lookup_int/config_lookup_int64 и прочих вариантов lookup-чего-то-там. Эти функции возвращают CONFIG_FALSE и в случае, если параметр вообще не был найдет, и в случае, если был найден, но содержит не тот тип (например, строка вместо числа).

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

Т.е. получив 0 из config_setting_get_int или CONFIG_FALSE из config_lookup_int у меня нет возможности разобраться с тем есть ли ошибка и, если есть, какая она.

Хотя, как по мне, избежать этой проблемы можно было бы очень просто, если бы у config_setting_get_int был другой формат:

int config_setting_get_int(
  // Где искать значение.
  const config_setting_t * setting,
  // Куда помещать значение.
  int * value_receiver)

И возвращаемое значение означало бы признак успешности, вроде такого: 0 -- все OK, -1 -- значение имеет другой тип, -2 -- значение слишком большое, чтобы уместиться в int и т.д.

Очевидная, вроде бы, вещь. Но почему-то не сделанная... 🙁