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

О блоге

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

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

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

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

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

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

пятница, 4 апреля 2025 г.

[prog.c++] Нормально на C++ программируют лишь параноики?

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

Под кодом на "современном C++" понимается код, в котором активно используются шаблоны, лямбды, исключения, контейнеры, алгоритмы, перегрузка операторов и вот это вот все. Такой код может выглядеть как вполне себе высокоуровневый, почти как современная Java, C# или даже Scala.

Проблема, однако, в том, что C++ таким высокоуровневым языков не является.

Есть ряд моментов, которые, скажем так, портят всю малину.

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

Или, что часто происходит в моей практике, люди забывают (или не знают?) про exception safety. Как результат, если вылет исключения не приводит к немедленной катастрофе, то уж утечку ресурсов или нарушение инвариантов множества объектов вызывает точно. Далеко не все программисты, к сожалению, привыкли к повсеместному RAII. А обеспечение strong exception safety обходится не бесплатно в плане сроков написания кода (особенно если это дело еще и покрывать тестами).

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

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


На самом деле проблема не в "современном C++", а именно в C++ безотносительно его версии.

Как по мне, так старый и недобрый "Си с классами" являлся еще большим рассадником багов и внимания к мельчайшим деталям при программировании на C++98 требовалось даже больше, чем сейчас.

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

Тогда как код на современном C++, с какими-нибудь ranges и coroutines может производить обманчивое впечатление того, что C++ таки вошел в одну когорту с Java, C#, Scala и, может быть, даже Python с JavaScript. Что есть опасное заблуждение.

Да, на C++ можно писать высокоуровневый код, особенно при наличии хороших прикладных библиотек. Только вот возможность нечаянно отстрелить себе ногу никуда не делась. Что принципиально отличает C++, даже в самых его свежих стандартах, от Java/C#/Python/JavaScript.

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

PS. Нахожусь при мнении, что при программировании на чистом Си параноить приходится гораздо меньше. Потому, что там нет исключений. И нет неявно вызываемых конструкторов (например, когда из строкового литерала внезапно возникает экземпляр std::string). Поэтому в мире чистого Си при использовании идиомы goto cleanup чувствуешь себя намного спокойнее.

PPS. Нормально программировать на C++ вполне себе возможно, сколько бы не пытались лаять хейтеры и ниасиляторы.

среда, 2 апреля 2025 г.

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

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

Фильмы

Контратака (Contraataque, 2025). Мне понравилось.

Компаньон (Companion, 2025). Получил удовольствие от просмотра. Редко в последние годы встречаются фильмы, которые захватывают внимание и при просмотре которых не хочется выискивать и цепляться к недостаткам. Однако, мои вкусы несколько специфичны 😀

Микки 17 (Mickey 17, 2025). На фоне того, что нормальной фантастики давно не было этот фильм заметно выделяется. Я бы сказал, свежо и оригинально, приятно было смотреть. Претензии могу высказать к визуальной части, но, возможно, это было сделано специально, чтобы сделать происходящее еще более гротескным и вычурным.

Криминальный город: Возмездие (Beomjoe dosi 4, 2024). Если вам нравятся корейские фильмы с мордобоем, то смело можно смотреть.

Криминальный шеф (What You Wish For, 2023). Смотришь, смотришь, ждешь, ждешь настоящей кульминации и картсиса, а... А ничего похожего на это и не наступает. В результате складывается впечатление, что фильм не смог реализовать свой потенциал.

Месть (2023). Очень простенько сделанное кино, местами где-то на уровне художественной самодеятельности, но бодренькое. И с неожиданным для меня лично финалом, который с лихвой компенсировал все технические и художественные недостатки.

Свидание с монстром (Woman of the Hour, 2023). Хорошая картинка и достойная актерская игра. Но вот само построение сюжета с прыжками во времени туда обратно вызывает вопросы о том, а не испортило ли это все потенциально интересную историю.

Ограбление по-аризонски (The Last Stop in Yuma County, 2024). В принципе, я являюсь любителем такого рода фильмов. Но в данном случае нет в кино нерва, который бы вел бы зрителя от самых первых кадров до развязки (которая мне вполне понравилось). В итоге набор красивых картинок и более-менее удачных эпизодов, которые не складываются в целостное полотно.

Город демонов (Oni Goroshi, 2025). В принципе, любители жанра "кровавое мочилово" могут посмотреть. Но т.к. главный герой оказался настолько неубиваемым, что для меня лично все происходящее на экране быстро превратилось в цирк.

Клинер (Cleaner, 2025). Ничего не ждал от этого фильма, т.к. перед просмотром прочитал много отрицательных отзывов. На самом деле вполне себе средненькое современное кино про "сильных и независимых женщин" (tm). Лично мне не хватило динамики и экшОна, как-то все затянуто и уныло получилось.

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

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

Охотник за головами (The Getback, 2023). Редкая халтура, как по сюжету, так и по актерской игре. Но, особенно, в части т.н. экшОн сцен. Смело можно не смотреть.

Сериалы

Самбра: Анатомия преступления (Sambre, первый сезон, 2023). У этого сериала есть два достоинства -- полное отсутствие гламура, правда жизни как в позднем чернушном соцреализме, + очень хороший грим для героев, которые стареют по мере повествования, выглядит все очень достоверно. Но в целом это никакой не детектив, это что-то вроде социальной драмы в криминальном контексте. Хорошо сделанная, местами цепляющая, но драма, а не детектив.

ДерьмоКино вне категории

Бесстрашный (Tian long ba bu zhi qiao feng chuan, 2022). Какой-то Хон Гиль-дон только на технологиях XXI-го века. Не знаю кто и зачем будет смотреть это в здравом уме.

Мастер (A Working Man, 2025). Кино со Стейтемом превращается в отдельный жанр. Его герои настолько круты и неубиваемы, что даже неинтересно смотреть. И ладно бы там в самом экшОне были прорывы, как в первом Джоне Уике, так ведь нет. По итогу просто жалко потраченного на очередную туфту времени.

вторник, 1 апреля 2025 г.

[life.cinema] Вместо заключительного кинообзора

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

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

И сегодня на этот вопрос я убедительного ответа найти не смог.

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

Грустно, конечно. Длилась вся эта история, емнип, с октября 2012-го. Но что поделать, если кинематограф с ускорением несется по наклонной плоскости куда-то под плинтус и выдает "шедевры" вроде Капитан Америка: Новый мир, Мастер и Бесстрашный. Не хочу участвовать в этой безумной гонке наперегонки со здравым смыслом.

Upd от 2-го апреля: тем, кто воспринял этот текст слишком уж всерьез стоит обратить внимание на дату публикации ;)

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

[prog.c++] Наследие темного прошлого C++, которое сейчас стало напрягать все больше и больше

Зафиксирую несколько моментов в C++, которые в последнее время напрягают, раздражают и, как правило, ведут к эпизодически проявляющимся ошибкам. Они были унаследованы из старых темных времен, являются наследием Си, но отравляют жизнь до сих пор. Хотя, наверное, не всем, а лишь тем, кто пытается следить за качеством кода 😡


Неявные приведения типов.

В каких-то случаях это удобно. Напишешь ты что-то вроде:

void dump_value(std::string_view value) {...}

а потом спокойно пихаешь туда строковый литерал:

dump_value("Hello, World!");

и все работает.

Хотя, чем старше становлюсь и с чем большим объемом чужого кода приходится иметь дело, тем больше склоняюсь к тому, что писать так:

dump_value("Hello, World!"sv);

или даже так:

std::string calculated_value = some_calculation();
dump_value(std::string_view{calculated_value});

тоже вполне себе OK.

Но вот что откровенно вымораживает, так это то, что язык допускает неявные преобразованиями между int-ами, long-ами, float-ами, double-ами и прочими числовыми типами. Иногда нужно включать высокие уровни предупреждений для того, чтобы компилятор хоть как-то пожаловался на выражения вида:

std::vector<item> items = collect_items();
int delta = items.size() > 3 ? 2 : 0;
std::size_t limit = (items.size() - delta) * 1.75;

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

Очень хочется, чтобы компилятор запретил все подобные неявные преобразования между числовыми типами. Чтобы ошибка выдавалась даже при попытке неявно преобразовать std::uint8_t в std::uint16_t. Не говоря уже про преобразования от std::size_t к short или от double к long.


Использование "безразмерных" типов short, int, long и пр.

Да, я помню, что Страуструп агитировал за int. И он же считал ошибкой, что std::size_t в STL сделали беззнаковым.

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

Поэтому когда я сейчас вижу в программах что-то вроде:

int l = strlen(some_string);

или

int last_element_index = vec.size() - 1;

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

Поэтому я вообще склонен запретить использовать в коде типы, размерность которых никак не зафиксирована (short, unsigned short, int, unsigned int, long, unsigned long и т.п.).

Грубо говоря, int-у место разве что в примерах в старых учебниках. Но в современном предназначенном для продакшена коде, ему не должно быть места. Следует использовать либо типы с зафиксированной размерностью (std::uint8_t, std::uint16_t, std::uint32_t и т.д.), либо типы с обещанной минимальной размерностью (std::uint_fast8_t, uint_least8_t, std::uint_fast16_t, std::uint_least16_t и т.д.).


И еще один момент, которого в C++ нет, но который, имхо, был бы полезен.

ЕМНИП, в Pascal можно было объявить перечисление из, скажем, трех элементов, а затем создать массив, для индексации которого может использоваться именно это перечисление. Что-то вроде:

type
  Dimensions = (Price, Speed, Riskiness);
  Corrections = array of [Dimensions] of Real;
var
  CurrentCorrections : Corrections;
begin
  CurrentCorrections[Price] := 1.0;
  CurrentCorrections[Speed] := 0.5;
  CurrentCorrections[Riskiness] := 1.25;
  ...

При этом вы точно знаете, что ваш объект CurrentCorrections тесно связан с Dimensions. И если со временем ваш Dimensions меняется, то это сказывается и на работе с CurrentCorrections.

Тогда как в C++ такой фичи нет. Мы можем сделать так:

enum class Dimensions { Price, Speed, Riskiness };
using Corrections = std::array<double, 3>; // А вот тут уже первая неприятность.
...
Corrections current_corrections;
current_corrections[Dimensions::Price] = 1.0;
current_corrections[Dimensions::Speed] = 0.5;
current_corrections[Dimensions::Riskiness] = 1.25;

Но надежность этого кода будет исключительно на совести и внимательности программиста.

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

Поэтому со временем кто-то может модифицировать перечисление Dimensions, например, вот так:

enum class Dimensions { Price = -2, Speed = 0, Riskiness = 3, Effectiveness = 10 };

и весь старый код, включая ставшее неправильным определение типа Corrections, все равно скомпилируется. А потом хорошо, если быстро упадет.

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

суббота, 29 марта 2025 г.

[prog.kill-them-all] И снова слова проклятия в адрес любителей длинных строк в исходном коде...

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

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

Что обидно, так это то, что иллюстрационный фрагмент содержит так же и пример нормального оформления (выделен зеленым). Может быть даже с излишними переносами строк. По крайне мере у себя бы я friend и STRONG_CONSTEXPR разместил бы на одной строке.

Ну и да, привет всем тем чудикам, которые утверждают, что код читают только в IDE. Да хрен вам!

Так что вредный совет тем, кто по какой-то причине обратил внимание на этот пост: если вам начхать на удобство тех, кто будет читать и сопровождать ваш код в дальнейшем, то смело пишите строки длиной по 100, 120, 150 символов. Да и 150 не предел, зачем останавливаться?

PS. Прошу понять и простить за резкость, но за минувшую неделю пришлось слишком много проблемных фрагментов кода копипастить из исходников в email-ы, мессенджеры и GoogleDoc-документы. Где куски с длинной строки в 120+ символов превращались в совсем уж нечитаемое ХЗ что. В отличие от.

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