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

О блоге

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

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

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

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

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

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

четверг, 1 октября 2026 г.

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

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

Фильмы

Майк и Ник, Ник и Элис (Mike & Nick & Nick & Alice, 2026). Очень даже неплохо, мне понравилось. Но есть в этом кино некая толика укуренности в хорошем смысле этого слова. Мне такая укуренность зашла, но кому-то наверняка испортит впечатления от просмотра.

Ночной бизнес (The Get Out, 2026). Мне понравилось.

Ограбление на миллион (Verbrannte Erde, 2024). Этот фильм можно посмотреть просто ради того, чтобы увидеть насколько европейское кино отличается от голливудского. А конкретно эта картина, как будто бы, снята в традициях соцреализма перестроечных времен.

Сыграть в ящик (Just Play Dead, 2026). Нормально, можно смотреть, хотя на мой взгляд, с поворотами сюжета авторы перемудрили.

Зараза (Cold Storage, 2025). Как по мне, то неплохая черная комедия. Главное не ждать от кино чего-то серьезного.

Запретный город ("Forbidden City" или "La Città proibita", 2025). На удивление любопытный боевик с мордобоем и тетенькой в роли главного непобедимого бойца. Портит впечатление количество розовых соплей вокруг происходящего, лучше бы экшОна побольше впихнули.

Соперники Амзиа Кинга (или "Гнев улья", The Rivals of Amziah King, 2025). Купился на участие в фильме Мэттью Макконахи, но его роль там оказалась не самой главной. Фильм специфический и, как по мне, несколько странный. Посмотреть можно. По визуальной составляющей, как и по актерской игре, у меня претензий нет. А вот что касается смысла и сюжета, то у меня остались вопросы о том, кто же здесь главный злодей.

Старуха с ножом (Pagwa, 2025). Во-первых, это кино для тех, кто спокойно или положительно относится к корейскому кинематографу. Во-вторых, фильм далеко не шедевр: экшОна маловато, в сюжет лучше не вдумываться вовсе.

Обсессия (Obsession, 2025). Низкобюджетная муть, жалко потраченного времени. Мне кажется, что низкобюджетные ужастики из видеосалонов 1980-х и то были сделаны круче.

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

Сериалы

Джентельмены (The Gentelmen, второй сезон, 2026). Отличное продолжение. Мне даже показалось, что второй сезон динамичнее и интереснее первого. Посмотрел с удовольствием. Надеюсь, что будет продолжение.

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

Магазин для киллеров (Killeodeului syopingmol, второй сезон, 2026). Если понравился первый сезон, то смело можно смотреть и второй. Мне зашло, хотя временами фантастичность происходящего была где-то на уровне сказочки для детей.

Холод (первый сезон, 2026). Разочарован, ждал от сериала сильно большего. А выбор Любовь Аксёнова в главной роли, это очевидный для меня мискаст.

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

Преимущества путешествия поездами (Ventajas de viajar en tren, 2019). Как по мне, так совершенно укуренное кино. Мне такие нравятся. Однако, рекомендовать не могу, т.к. ну очень уж на любителя.

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

[life.photo] Несколько кадров из прогулки по лесу

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

воскресенье, 27 сентября 2026 г.

[comp.thoughts] Многопроцессорные ноутбуки на ARM-ах. Почему нет?

Уже почти два года с удовольствием пользуюсь 12" планшетом HONOR Pad 9. По Geekbench 7 у него не сказать, чтобы какие-то выдающиеся результаты. Что-то около 800 баллов в однопотоке и чуть больше 2800 баллов в многопотоке. Т.е. формально это откровенно тормознутый по нынешним меркам процессор.

Однако, за два года использования этого планшета для потребления контента (т.е. ролики на YouTube/RuTube/VK, чтение статей на сайтах, просмотр PDF, небольшие правки в Google Docs, простые ответы по email, переписка в Telegram, Yandex.Music в фоне и вот это вот все) никаких тормозов замечено не было. Т.е. с производительностью для обычных повседневных задач у достаточно старого по нынешним временам Snapdragon 6 Gen 1 хватает. И здесь остается только жалеть, что на таком процессоре нет легкого, тихого и долго живущего на одном заряде ноутбука.

Почему раньше не было ноутбуков на ARM-ах из мобильных телефонов понятно: хоть Windows когда-то в 1990-е и начиналась как ОС с поддержкой нескольких типов архитектур, но довольно быстро десктопная версия Windows сконцентрировалась исключительно на x86, а всякие MIPS и Alpha пошли лесом, т.к. рыночек порешал. Этот же рыночек вместе с эффективным менеджментом из Microsoft и Nokia порешал и мобильную Windows CE (или как она там в последние годы своей жизни назвалась).

Тем не менее, мегауспешный переход Apple на свои собственные процессоры подтолкнул Microsoft к портированию десктопной Windows на ARM-ы. И уже года четыре как Windows доступна на ноутбуках с ARM. Например, был вот такой совсем чахлый TCL Book 14 Go на Snapdragon 7C Gen.1, который, по отзывам, был не просто тормозной, а очень тормозной. Возможно, не из-за процессора, а совсем крошечного по нынешним временам объема RAM, но все таки.

Первый блин Windows-ноутбуков на ARM в лице вот таких вот TCL Book 14 Go вышел комом, поэтому дальше эволюция пошла привычным для ИТ способом -- дождались выхода гораздо более мощных Snapdragon X от Qualcomm и пошли клепать не самые дешевые модели с этим новым процессором, большим объемом памяти, крутыми экранами и вот этим вот всем. Т.е. как будто бы основной прицел был в сегмент повыше среднего. Встречаются недорогие ASUS Vivobook-и и Lenovo IdeaPad-ы из среднего сегмента, но там и разновидности Snapdragon X не самые мощные.

Уж не знаю, есть ли какой-то успех у Windows-ноутбуков на ARM, сам пока не отважился на покупку чего-то подобно (хотя, наверное, надо бы рискнуть, чтобы было на чем проверять свои OpenSource разработки). Но давеча задумался вот о чем: а почему не пробуют сделать ARM ноутбуки с несколькими процессорами?

Грубо говоря, почему бы вместо дорогих и горячих Snapdragon X не взять 2-3-4 более старых, но вполне себе годных Snapdgragon 6 Gen 1, как в моем HONOR Pad 9? Пусть каждый из них сидит в своем сокете и располагает собственными 6 или 8 GiB RAM.

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

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

Что-то мне кажется, что не надо.

Смысл же в том, что если сделать ноутбук на одном Snapdragon 6 Gen 1 с 8 GiB RAM, то его ограниченность (как в плане быстродействия, так и в плане доступной памяти) быстро выплывет на поверхность.

Но есть таких процессоров будет уже два, а памяти не 8, а 16, то это уже совсем другая история. А если не 2, а 3 процессора и 24 GiB RAM? Это же еще лучше. Ну а 4-е процессора -- это уже совсем серьезная штука 😀

Любопытно, почему по этому пути не пошли? Что является основным препятствием? Железо или софт?

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

пятница, 25 сентября 2026 г.

[prog.question] А что сейчас понимают под "архитектурой программного обеспечения"?

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

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

Как бы вопрос не праздный, поскольку последние лет 10, а может и побольше, мне приходится, в основном, колупаться в коде: искать проблемы, что-то переписывать, что-то дописывать, что-то выбрасывать и т.д, и т.п. Но уже давно не доводилось заниматься проектированием "по большому", типа того, что здесь у нас будет такая-то СУБД и в ней будет храниться то-то и то-то, здесь у нас будет такой компонент, который с БД будет общаться напрямую, а здесь вот такой компонент, который будет общаться не с БД, а с первым компонентом, а здесь у нас будет вот такой компонент, который будет вот так-то следить за первыми двумя компонентами, а здесь будет еще вот это и вот это... В последние годы если что-то и приходится проектировать, так это небольшие библиотеки или даже отдельные классы, которые упрощают те или иные задачи.

Складывается впечатление, что под "архитектурой ПО" подразумевается рисование квадратиков и связей между ними, как это принято делать на system design interview, где соискателя просят "спроектировать" очередной клон Facebook-а или YouTube.

Если я прав, то непонятно почему кто-то надеется, что ИИ не заберет эту задачу у человека точно так же, как забирает обычное кодирование.

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

понедельник, 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-ов, чтобы не плодить их сверх меры.