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

О блоге

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

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

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

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

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

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

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

[prog.thoughts] Мои личные заморочки с ранними return-ами

Прежде чем перейти к основной теме поста два важных дисклеймера:

  • во-первых, это мое личное мнение. Делюсь им, но ни в коем случае не настаиваю на его непогрешимости и, уж тем более, не утверждаю, что оно единственно правильное. Может быть кто-то из прочитавших сочтет его заслуживающим внимания и постарается относиться к return-ам более ответственно, чтобы таким как я было проще читать код;
  • во-вторых, я программирую, в основном, на C++, поэтому ниже будет C++ная специфика. Возможно, сказанное ниже гораздо менее критично для других языков программирования;
  • в-третьих, любой анонимный эксперт c LOR, RSDN или Habr вам убедительно объяснит, что программировать я не умею, да и вообще редкостный дебил. Посему ошибки у меня сплошь и рядом, в том числе и с плюс-минус-единичкой.

Итак, когда я только начинал учиться программированию в 1990-ом году, то нам говорили, что умные люди масштаба Никлауса Вирта рекомендуют иметь единственный return в функции. Типа одна точка входа -- одна точка выхода.

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

Очевидный пример -- это проверка валидности аргументов функции/метода:

int copy_file(const std::string & src, const std::string & dest)
{
  if(src.empty()) // Ошибка: нет имени источника.
    return errors::empty_src_name;

  if(dest.empty()) // Ошибка: нет имени результата.
    return errors::empty_dest_name;

  ... // Далее уже непосредственно действия по копированию.
}

Этот код, имхо, гораздо понятнее, чем вот такой:

int copy_file(const std::string & src, const std::string & dest)
{
  int result = errors::unexpected_error;

  if(!src.empty())
  {
    if(!dest.empty())
    {
      ... // Далее уже непосредственно действия по копированию.
    }
    else // Ошибка: нет имени результата.
      result = errors::empty_dest_name;
  }
  else // Ошибка: нет имени источника.
    result = errors::empty_src_name;

  return result;
}

Или вот такой:

int copy_file(const std::string & src, const std::string & dest)
{
  int result = errors::unexpected_error;

  if(src.empty()) // Ошибка: нет имени источника.
    result = errors::empty_src_name;
  else if(dest.empty()) // Ошибка: нет имени результата.
    result = errors::empty_dest_name;
  else
  {
    ... // Далее уже непосредственно действия по копированию.
  }

  return result;
}

Еще один простой пример: отказ от выполнения действий, если объект находится в неподходящем состоянии. Допустим, у нас есть класс "Файл" в виде обертки над простым хэндлом, который записывает значения строго в BigEndian представлении. Что-то вроде:

class file_with_big_endian_data
{
  // Хэндл открытого файла. Значение -1 указывает, что файл не был открыт.
  int m_handle;
  ...
public:
  // Методы для записи различных типов значений.
  void write(std::span<const std::uint16_t> what);
  void write(std::span<const std::uint32_t> what);
  void write(std::span<const std::uint64_t> what);
  ...
};

В таком случае каждый метод write может иметь в начале проверку m_handle с ранним return-ом для того, чтобы ничего не делать, если файл не был открыт:

void file_with_big_endian_data::write(
  std::span<const std::uint16_t> what)
{
  if(-1 == m_handle)
    return;

  ... // Запись с перестановкой байт, если это необходимо.
}

Еще один пример подсказали в комментариях на LinkedIn: ранние return-ы для ситуаций, когда компилятор может избавиться от хвостовой рекурсии. Сам пример достаточно игрушечный, но для иллюстрации вполне себе норм. Привожу его в оригинале на Python-е (спасибо пользователю Konstantin Z.):

def classify(n, steps=0):
  if n < 0:
    return "negative"
  if n == 0:
    return f"reached zero in {steps} steps"
  if n == 1:
    return f"almost there, {steps + 1} steps"
  return classify(n - 2, steps + 1)

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

Прежде всего лично мне гораздо удобнее разбирать прикладную логику в виде лесенки if-ов. Т.е. вот это:

if(first-condition)
{
  if(second-condition)
  {
    if(third-condition)
    {
      ... основные действия, ради которых все условия проверялись.
    }
  }
}

изучать гораздо проще, чем вот это:

if(!first-condition)
  return;

if(!second-condition)
  return;

if(!third-condition)
{
  ... основные действия, ради которых все условия проверялись.
}

Видимо это какие-то особенности моего восприятия (или проявления старческого слабоумия). Но лесенка из if-ов как бы сама уводит взгляд к кульминации и сама говорит "если вот это, и вот это, и вот это, то..." Тогда как ранние return-ы эту прямую последовательность к кульминации прерывают.

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

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

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

Тогда как с ранними return-ами такого критерия нет. Может быть функция на 40 строк, в которой 10 ранних return-ов. По сути это уже говнокод, но без столь явного запаха, как у лесенки из if-ов в 5-6 уровней глубины.

Но хуже всего, когда встречаются объемные функции, в которых ранние return-ы расположены в if-ах на разной глубине. Что-то вроде:

int do_something_important(int a, int b)
{
  if(!is_first_arg_valid(a))
    return results::error_code_1;

  if(!is_second_arg_valid(b))
    return results::error_code_2;

  bool need_update_cache = false;
  if(is_specific_arg_combination(a, b))
  {
    const auto cache_id = make_cache_id(a, b);
    if(has_result_in_cache(cache_id))
    {
      take_value_from_cache(cache_id);
      return results::result_from_cache;
    }
    else
      need_update_cache = true;
  }

  int prepare_result = prepare_for_change(a, b);
  if(prepare_result != results::ok)
  {
    if(prepare_result == results::pool_overflow)
    {
      remove_oldest_from_pool();
      prepare_result = prepare_for_change(a, b);
    }
    if(prepare_result != results::ok)
      return prepare_result;
  }

  commit_change(a, b);
  if(need_update_cache)
  {
    const auto cache_id = make_cache_id(a, b);
    const auto cache_update = update_cache_for(cache_id);
    if(cache_update == results::no_resources)
      return results::ok;
    else
      return results::ok_with_warnings;
  }

  return results::ok;
}

Когда подобный код написан не тобой и ты видишь его впервые, то уследить за всей логикой с return-ами мне лично сложно.

Еще хуже, когда в центре такой объемной функции будет цикл, внутри которого будут сочетаться и return, и break, и continue. И если мне скажут, что это перебор и такого не бывает, то увы, такое бывает. Хотя да, это перебор.

Но помимо чтения подобного кода иногда у меня возникает необходимость временно добавить в подобную функцию трассировку. В простейшем случае это может быть печать "do_something_important started" при входе и "do_something_important completed" при входе.

Когда у нас есть единственный return в конце, то нет ничего проще -- одна печать на входе, вторая на выходе. Но когда return-ов много, то...

...то приходится прибегать к созданию в начале функции объекта, который в своем конструкторе будет печатать "do_something_important started", а в деструкторе -- "do_something_important completed". Для языков, в которых детерминированного вызова конструкторов-деструкторов нет, подойдет та или иная реализация Go-шного defer-а.

Но это не помогает, если строчка с "do_something_important completed" должна содержать еще и какое-то значение (например, возвращаемое значение). Вставлять такую печать перед каждым return-ом ну так себе занятие. Приходится что-то изобретать.

А вот для единственного return-а это не проблема от слова совсем. Тут вообще все тривиально и ничего не нужно изобретать.

Ну и как-то мне с необходимостью делать подобные трассировки регулярно везет. То нужно построить трассу взаимных вызовов. То засечь время работы. Или и то, и другое сразу. Благо в C++ легко делать объекты в RAII-стиле. В какой-нибудь Java сложностей было бы побольше.


Что хочется сказать в завершении.

ИМХО, в современном мире правило "один вход -- один выход" -- это уже какой-то экстремальный экстрим. Непонятно зачем отказываться от ранних return-ов, если они делают код и компактнее, и проще, и надежнее (как элемент defensive programming), и (временами) быстрее (раскрутка хвостовой рекурсии).

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

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

суббота, 5 сентября 2026 г.

[prog.c++] Всегда удивляюсь, когда вижу имена членов класса без суффиксов/префиксов

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

class Foo {
    int value;
public:
    Foo(int value) { // параметр затеняет поле class
        value = value; // ничего не делает, присваивает параметр самому себе
    }
};

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

У меня бы было написано так:

class Foo {
    int m_value;
public:
    Foo(int value) { // параметр ничего не затеняет
        m_value = value;
    }
};

И подобная опечатка сразу бы оказалась видна.

Но если уж префиксы или суффиксы для членов класса оскорбляют ваше чувство прекрасного, то возьмите за правило обращаться к ним через this->:

class Foo {
    int value;
public:
    Foo(int value) { // параметр затеняет поле class
        this->value = value; // ничего не затеняет
    }
};

Тем паче, что обращение this-> зачастую бывает необходимо при написании кода шаблонов (например, когда шаблон класса наследуется от шаблона класса и обращается к унаследованным из базового класса членам/методам).

PS. Самая странная система именования членов классов, которую доводилась видеть, была такой:

class Demo {
public:
  int a; // Открытый член класса без префикса или суффикса.

protected:
  int _b; // А вот защищенные и закрытые члены с префиксом.

private:
  int _c;
};

PPS. Касательно самой статьи "Анатомия граблей": она позиционируется как сборник C++ных граблей из области игростроя. Но практически все из описанного мне доводилось видеть в разнообразных проектах, которые к игрострою не имели никакого отношения. И какие-то из них (вроде выхода за границы статических массивов) не имеют дешевых решений, увы. По поводу же той части статьи, которая касается контроля за ресурсами и ручной работы с malloc/free, то слишком мало конкретики (плюс есть у меня подозрение, что в игрострое собирается публика, которой очень и очень фиолетово качество кода, но не факт, что подозрение верное).

вторник, 1 сентября 2026 г.

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

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

Фильмы

Кривые линии господа ("God's Crooked Lines" или "Los renglones torcidos de Dios", 2021). Однозначно самое интересное из увиденного за последнее время. Некоторые твисты ну очень классные. Правда, чтобы разобраться в том, что же было показано, пришлось гуглить. И результат поисков не то, чтобы меня порадовал, лично я увидел в кино совсем другой сюжет (чисто детективный, без сильной привязки к психиатрии).

Человек-паук: Новый день (Spider-Man: Brand New Day, 2026). Очень круто сделанный очередной кино-аттракцион для любителей супергероев из "вселенной Марвел". Строго для поклонников жанра.

Чокнутые (Riff Raff, 2024). Отличное кино, получил удовольствие от просмотра. Но мои вкусы несколько специфичны, поэтому сразу предупреждаю, что зайдет оно не всем.

Колония (Gunche, 2026). Если понравился "Поезд в Пусан" от этого же режиссера, то можно смотреть и "Колонию".

Замри (Don't Move, 2024). Ничего не ждал, но этот оказался вполне себе добротным фильмом. Далеко не шедевр, но смотреть было интересно и никакого отрицательного послевкусия не осталось.

Соулм8йт (Soulm8te, 2026). В принципе, снято очень даже неплохо. Но все портит абсолютная предсказуемость происходящего на экране.

Шёпот за окном (The Whisper Man, 2026). Хорошие актеры и неплохой сюжет, но когда начинаешь вдумываться в происходящее, то возникает слишком много белых пятнышек, которые непонятно как объяснить. Поэтому невольно задаешься вопросом "а не втирают ли нам здесь какую-то дичь?"

Коллектор ("The Dept Collector" или "Khon Dueat Thuang Khaen", 2026). В принципе неплохой боевик. Местами предсказуемый, местами не очень, местами впадающий в какую-то Санта-Барбару. Но мне показался несколько затянутым.

Последний дом (The Last House, 2026). Даже не смотря на то, что это фантастика, в некоторые моменты настолько не верится, что это напрочь убивает всю идею происходящего на экране и сильно портит впечатление от фильма.

Точка замерзания (Dead of Winter, 2025). Хорошие актеры, хорошо играют. Но в происходящее на экране не верится от слова совсем.

Та, что сбежала: История Кары Робинсон (The Girl Who Escaped: The Kara Robinson Story, 2026). Очень и очень бюджетно, что сказывается как на картинке, так и на актерской игре -- такое впечатление, что играет одна лишь главная героиня, а всех остальных набрали из массовки по объявлению. В общем, можно смело пройти мимо.

Семь снайперов (Seven Snipers, 2026). Редкая фигня не смотря на наличие хороших (в прошлом) актеров. Не тратить на это время ни в коем случае!

Сериалы

Парижская полиция 1910 (Paris Police 1910, 2026). Отличное продолжение отличного сериала.

Убийства в Оре ("Åre Murders" или "The Åremorden", первый сезон, 2025). Не смотря на то, что всего пять небольших по хронометражу серий, типичная сериальная затянутость все равно ощущается. Но именно к детективной составляющей претензий нет, у каждого расследования неожиданная развязка.

Лаки (Lucky, первый сезон, 2026). Ждал много большего, по факту же очень и очень средний уровень, может быть даже пониже среднего. Плюс традиционная сериальная затянутость, пару серий точно можно было бы выбросить.

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

Подразделение специального назначения ("Elite Force" или "G.I.G.N", первый сезон, 2026). Ждешь жесткий боевик про крутых спецназовцев, получаешь какую-то унылую мелодраму.

Фильм вне категории

Майкл (Michael, 2026). Здесь разочаровало все -- начиная от сюжета и даже визуального ряда до попыток станцевать так же, как Майкл Джексон. Такое ощущение, что после Богемской рапсодии и РокетМена захотелось повторить тот же трюк с еще одной музыкальной суперзвездой. И, что самое страшное, это получилось с таким очень и очень никаким фильмом.

пятница, 21 августа 2026 г.

[life.memories] Ответ "А я не знаю" от преподавателя в университете

Посмотрел недавно ролик "Разбираем популярные манипуляции квантовых психологов. Алексей Семихатов и Виктория Шиманская" на YouTube (на RuTube пока нет, но, может быть, появится на этом канале). Очень понравился мне там момент в районе 41-й минуты, когда Семихатов сказал, что роль учителя в том, чтобы показать как вообще можно размышлять над сложной проблемой.

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

Для меня это был шок и разрыв шаблона.

Потому что 10 лет до этого в школе нас натаскивали на то, что ты должен знать. Математические выражения, физические законы, химические формулы, исторические даты и вот это вот все. Ты либо знаешь и молодец, либо не знаешь и "садись, два!"

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

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

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

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

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

[prog.sarcasm] Если долго сидеть на берегу, то можно увидеть как Erlang-еры...

...переписывают свой софт на Rust:

Моя идея о том, чтобы переписать наш Flussonic с Erlang на Rust оказалась совершенно прекрасной и у меня совершенно прекрасные результаты. Захват 10 000 камер на сервер - это прямо скажем серьезный результат, на рынке таких предложений мало.

Это слова Макса Лапшина, одного из самых известных пользователей Erlang-а в Рунете (ссылки на пару статей которого были даже у меня в блоге). Он же и автор мема о том, что писать на С++ -- это выгребать корки по утрам.

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

Upd.В этой теме на LOR-е еще прекрасное добавилось от Лапшина (это ответ на вопрос уперся ли Erlang в потолок производительности):

Во все потолки, какие можно было.
И производительность, и количество библиотек, и количество людей и всё прочее.
Когда стало ясно, что прийдется самому руками писать QUIC, тут я слегка устал от эрланга.
О перфомансе вида «принять 10 000 камер» и речи не идет, причем если бы не медленный сторадж, новый код можно поковырять и до 20 тыс (просто это не нужно).

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


Если говорить серьезно, то когда вам повезло работать на проекте в течении 10-15-20 и более лет, то у вас есть возможность наблюдать за тем, как принятые вами же решения, которые когда-то давно казались крутыми и удачными, через годы не просто перестают такими быть, а нуждаются в замене на что-то, что сейчас кажется крутым и удачным (и далее по рекурсии).