Показаны сообщения с ярлыком Поток сознания. Показать все сообщения
Показаны сообщения с ярлыком Поток сознания. Показать все сообщения

понедельник, 15 июня 2026 г.

[prog.c++.imho] Не согласен с постулатами пропозала P3097 (контракты для виртуальных методов)

Комитет по стандартизации C++ продолжает творить дичь. Сперва в C++26 были включены кастрированные контракты (нет ключевого слова old в постусловиях, нет контрактов для виртуальных методов, нет инвариантов для экземпляров классов и циклов). Для людей, знакомых с Eiffel, контракты из C++26 выглядят как "мы не осилили тему полностью, поэтому впихнули в стандарт какой-то эрзац с надеждой, что со временем допилим". Не хочу обсуждать зачем нужен эрзац вместо нормального продукта. Просто перейду к следующей дичи.

Далее в C++29 включили предложение P3097, которое описывает контракты для виртуальных методов классов. И авторы этого предложения, как по мне, покусились на святое: на сформулированное много-много лет назад для Design By Contract в Eiffel-е требование о том, что производный класс может только ослабить предусловния и ужесточить постусловия, но не наоборот.

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

На протяжении нескольких страниц пропозала эти люди пытаются приводить "аргументацию" своей точки зрения. Меня эта аргументация не убеждает от слова совсем. Скорее наводит на мысль о том, что люди толком не понимают тему, о которой пытаются рассуждать и, скорее всего, не имеют опыта разработки на языках с поддержкой Design By Contract (в первую очередь на Eiffel-е, на который в данной теме и следует равняться).

Первые эмоции после беглого прочтения P3097 я уже постил в LinkedIn. Сейчас попробую пройтись по нескольким фразам и примерам оттуда, чтобы как-то обосновать свое негативное отношение.


В разделе "3.2 Adoptability in legacy code" есть интересный заход:

среда, 15 апреля 2026 г.

[prog.sadness] Вскрик души в процессе копания в чужом коде

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

PascalCase -- отстой.

PascalCase в совокупности с длинными строками и экономией на пробелах и пустых строках -- отстой вдвойне.

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

Удачно выбранные имена классов рулят. Неинформативные имена или имена, отличающиеся всего одной буквой (например, resource_handler и resources_handler) доставляют (в худшем смысле этого слова) неимоверно.

Инкапсуляция и рулит, и бибикает. Грубо говоря, когда есть класс с приватными полями, модификация которых идет только в методах этого класса, то это гораздо лучше, чем когда есть структура, где все открыто и эта структура модифицируется в разных единицах трансляции. Еще хуже, когда у класса/структуры есть и публичные поля, и собственные методы, а модификация состояния происходит как внутри класса/структуры, так и снаружи.

Doxygen отличное подспорье когда нужно разбираться с чужими исходниками. Но, увы, не безгрешен 🙁

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

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

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

понедельник, 9 февраля 2026 г.

[business] Практические выводы из того, что успешный результат в бизнесе не гарантирован

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

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


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

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

И так снова и снова.

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

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


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

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

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


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

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

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

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


Нужно как-то научиться спокойно относиться к потере собственных денег.

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

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

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

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


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

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

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

Отсюда еще один, если можно так сказать, совет: не стоит затягивать с попыткой начать свое дело. Если в 35 вы задумались о том, что пора открывать что-то свое, то лучше не откладывать это до 45. Можно попробовать в 36 и к 45 совершить несколько попыток. Либо что-то увенчается успехом, либо вы поймете, что в найме все-таки спокойнее и денежнее и будете тихо встречать неизбежно приближающуюся старость ;)

понедельник, 2 февраля 2026 г.

[business] О чем вам не раскажут бизнес-спикеры с YouTube

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

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

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

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

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

Можно посмотреть, скажем, на велогонщиков. В гранд-турах, вроде Вуэльты или Тур-де-Франц, участвуют порядка 200 спортсменов, но победы на этапах одерживает дай бог если пара десятков, а на общую победу даже в теории претендуют только единицы. Причем это все гонщики ТОП-ового мирового уровня, на которых приходятся тысячи тех, кто даже никогда до гранд-тура не доберется в принципе.

Значит ли это, что абсолютно все те, кто не побеждает, просто недостаточно подготовлены? Что они просто мало тренируются или не соблюдают спортивный режим? Не следуют рекомендациям тренеров, спортивных врачей и других специалистов?

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

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

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

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

Ага. Как же.

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

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

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

понедельник, 26 января 2026 г.

[prog.thoughts] Как ИИ может отбирать хлеб у разработчиков библиотек

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


Еще десять лет назад у разработчиков OpenSource продуктов была такая опция для монетизации своей работы как платные консультации и платное обучение. Грубо говоря, есть открытая библиотека X за которой стоит компания Y. И если вы не можете разобраться с X самостоятельно, то обращаетесь к Y за помощью, а Y направляет к вам людей, которые учат вас, отвечают на ваши вопросы и подсказывают вам какие-то решения, которые вы не видите в силу своего незнания X.

Сейчас же большинство вопросов по любой OpenSource-библиотеке можно решить посредством того или иного ИИ-инструмента. Соответственно, нет надобности обращаться за помощью к разработчиками. А раз так, то и платные консультации/обучения отмирают за ненадобностью.

Грубо говоря, если 10 лет назад я рассчитывал на то, что вокруг OpenSource можно зарабатывать на трех вещах:

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

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


Чтобы затронуть еще один важный момент нужно ответить на вопрос, а зачем вообще нужны библиотеки?

На мой взгляд, библиотеки упрощают решение конкретных задач. Например, библиотека для работы с файловой системой позволяет нам получать списки файлов, удалять и переименовывать файлы, устанавливать текущий каталог и т.д. Библиотека для файлового ввода/вывода позволяет нам читать и записывать содержимое файлов. Библиотека для работы с форматом JSON позволяет нам получать данные из JSON-представления или преобразовывать данные в JSON-представление. И т.д., и т.п.

Почему библиотеки возникли?

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

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

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

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

Действительно, зачем нужна удобная библиотека для парсинга JSON-а, если ИИ по спецификации может сгенерировать фрагмент, который будет разбирать входной поток байт прямо "по месту"? Кого вообще будет заботить, что ИИ сгенерировал "рукопашный" разбор вместо того, чтобы использовать готовую JSON-библиотеку? Особенно, если сгенерированный код справляется со своими задачами и покрыт тестами (сгенерированными тем же ИИ).

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

И чем лучше и удобнее были те самые библиотеки, тем проще было прикладным программистам.

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

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

вторник, 20 января 2026 г.

[prog.flame] Пробелы таки выигрывают у табуляции?

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

А вот когда узнал, году эдак в 1992-ом, то практически сразу же перешел с пробелов на табуляции, ибо:

  • исходные файлы с табуляцией занимали гораздо меньше места. Что было более чем критично во времена дискет на 1.2 MiB. Ведь тогда в наших палестинах самыми распространенными были 5.25" дисководы и дискеты на 360 KiB, 720 KiB и 1.2 MiB (хотя не везде диски на 1.2 MiB нормально читались и писались). Гораздо более надежные и практичные 3.5" дискеты на 1.44 MiB в нашей местности получили распространение спустя несколько лет. Архиваторы, вроде pkzip и arj уже были, но вроде бы исходники с пробелами все равно сжимались хуже;
  • текстовые редакторы для программистов тогда были гораздо более убогими, чем сейчас. Я не припомню редакторов, которые бы по клавише Tab и по сочетанию Shift+Tab двигали бы выделенный блок текста вправо или влево. Поэтому если тебе нужно было изменить выравнивание куска кода, то приходилось делать это вручную, и с табами было это гораздо быстрее и проще;
  • в те времена достаточно распространенной практикой была распечатка текстов программ. Сейчас это кажется диким, а 35 лет назад машинное время было дефицитом и ты не мог сидеть за компьютером часами на пролет в поиске какого-то заковыристого бага -- тебе этого просто не позволяли, а собственных персональных IBM PC-совместимых компьютеров в те времена практически ни у кого не было. В текстовом редакторе можно было выставить размер табуляции в 2 символа и видеть больше на тогдашних 14" EGA/VGA экранах с текстовым режимом 80x25 символов, а для печати использовать размер в 4 символа и получать более удобный для чтения формат. Тогда как с пробелами такой фокус уже не проходил.

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

Во-первых, теперь постоянно приходится пользоваться сервисами, в которых я не могу поменять размер табуляции (ну или не знаю как это сделать). Самый яркий пример -- вводишь в консоли команду git diff и git разворачивает табуляцию на 8 пробелов. Или вводишь пример кода в какой-нибудь Wiki-системе и результирующая Web-страничка заменяет табуляцию на столько пробелов, сколько ей вздумается. Получается, что ты копипастишь кусок кода из своего редактора, где все прекрасно помещается в 80 символов по ширине, но на Web-ресурсе этот же фрагмент может получиться настолько широким, что выползет за край видимой области.

Во-вторых, мне по работе периодически приходится вставлять куски кода в e-mail-ы или документы Google.Doc. Типа вот есть такой фрагмент, в нем вот здесь и вот здесь есть вот такие и такие проблемы, исправить их можно вот так и вот так, а еще лучше было бы переписать вот так или вот так. Но, к сожалению, со вставкой кусков кода с табуляцией внутри могут возникнуть проблемы. Так, если я пишу письмо прямо в Web-интерфейсе Google Mail, то при отправке письма все табы вырезаются и форматирование кода оказывается полностью сломано -- весь текст просто прижимается к левому краю 😡 Если фрагмент вставляется в Google.Doc, то форматирование более-менее сохраняется, но вносить в такой фрагмент правки -- это то еще приключение.

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

В итоге получается, что в современном мире гораздо проще использовать пробелы для форматирования исходного кода:

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

Преимуществ у табов, по факту, не осталось. Кроме привычки. Зато код с пробелами везде выглядит одинаково и забот с пробелами намного меньше.

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

А вот для новых проектов, похоже, имеет смысл выбирать именно пробелы.

среда, 6 августа 2025 г.

[prog.c++.thoughts] Design By Contract и приватные методы классов в C++

В C++26 будут включены контракты. Т.е. можно будет сказать, что элементы Design By Contract наконец-то доберутся и до C++.

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

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

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


Технология Design By Contract (DbC) берет свое начало в языке Eiffel (вот еще одно описание от первоисточника). Если вы не знакомы с Eiffel хотя бы в части понимания DbC, то вы, к сожалению, многое упустили в своей профессиональной подготовке. Если же знакомы, то, надеюсь, понимать написанное ниже будет проще.

Ключевыми для нашего разговора будут три вещи (на самом деле в Eiffel есть еще и инварианты для циклов, и утверждения check, похожие на C-шный assert, но их мы спокойно можем игнорировать):

суббота, 28 июня 2025 г.

[prog.c++.dreams] В очередной раз выяснил, что в C++ using это не strong typedef :(

В С++ есть отличная штука под названием using. Позволяет вводить удобные псевдонимы для сложночитаемых имен типов или же прикрывать тонкой завесой детали реализации.

Но, к сожалению, using не может сделать совершенно новый тип. Поэтому если у вас есть что-то вроде:

using type_a = ...;
using type_b = ...;

void do_something(type_a v) {...}
void do_something(type_b v) {...}

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

Например, раньше было:

using small_data = std::map<some_key, some_value>;
using large_data = std::unordered_map<special_key, some_value>;

using type_a = small_data::iterator;
using type_b = large_data::iterator;

А в один прекрасный момент стало:

using special_key = some_key;

using small_data = std::map<some_key, some_value>;
using large_data = std::map<special_key, some_value>;

И все 🙁
Типы type_a и type_b оказались одинаковыми.

Недавно в очередной раз наступил на подобные грабли, но немного в другом контексте. Было что-то вроде:

namespace processing
{

class processor {...};

template<typename Data>
void
handle(const processor & how, const Data & what)
{
  // Должна быть найдена подходящая функция за счет ADL.
  apply(what, how);
}

// namespace processing

namespace data_type_a
{

struct data {...};

void apply(const data & what, const processing::processor & how) {...}

// namespace data_type_a

namespace data_type_b
{

struct data {...};

void apply(const data & what, const processing::processor & how) {...}

// namespace data_type_b

И т.д.

Т.е. смысл в том, что в конкретном пространстве имен data_type_X должна быть функция apply, который компилятор посредством ADL находит для вызова внутри processing::process.

Все шло хорошо до момента, пока не появились data_type_i и data_type_j, в которых тип data был определен через using:

namespace data_type_i
{

using data = std::map<...>;

void apply(const data & what, const processing::processor & how) {...}

// namespace data_type_i

namespace data_type_j
{

using data = std::vector<...>;

void apply(const data & what, const processing::processor & how) {...}

// namespace data_type_j

И вот когда эти типы начали отдавать в processing::process, то код перестал компилироваться. Причем далеко не сразу удалось понять, почему ни одна из определенных в правильных пространствах имен apply не выбиралась компилятором как подходящая.

А дело в том, что если заменить псевдонимы, то получались функции вида:

void apply(const std::map<...> &, const processing::processor&);
void apply(const std::vector<...> &, const processing::processor&);

И естественно, что ADL не мог их найти ни в пространстве имен std, ни в processing.

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

В очередной раз захотелось, чтобы using в С++ мог работать как strong typedef. Чтобы можно было написать что-то вроде:

namespace data_type_i
{

using(new) data = std::map<int, std::string>;

// namespace data_type_i

И чтобы компилятор начал считать, что data и std::map<int, std::string> теперь разные типы. И что тип data теперь принадлежит пространству имен data_type_i, а не std.

PS. В свете добавления в C++ рефлексии может оказаться, что наколхозить какой-то нестандартый strong_typedef_for, типа:

namespace data_type_i
{

using data = my::strong_typedef_for< std::map<int, std::string> >;

// namespace data_type_i

через рефлексию будет быстрее и проще, чем дождаться появления using(new) в стандарте. Обычная традиция C++: если что-то можно собрать своими руками дендро-фекальным методом, то включать в стандарт удобный и нормальный вариант никто не будет.

понедельник, 16 июня 2025 г.

[prog.thoughts.flame] Прочел давеча статью про вайб-кодинг с использованием LLM

На RSDN-е поделились ссылкой на статью "Вайб-кодинг: практика, о которой почему-то не говорят" в которой рассказывается про опыт разработки некого проектика на Go посредством "искусственного интеллекта" (Sonnet 3.7).

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

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

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

Первый грубый пример, который сходу вспоминается, -- это мода на всяческие Wizard-ы, которые начали появляться в IDE когда стало возникать само понятие IDE (середина-конец 1990-х). Помнится, в каком-нибудь условном Borland C++ 2.0 кучу кода нужно было написать вручную прежде чем у тебя появится примитивное приложение с одним окошком. А в каком-нибудь Borland C++ 4 или VisualStudio 98 ты кликнул на несколько кнопок в Wizard-е и получил готовый каркас приложения с тем самым одним окошком.

Еще один вспомнившийся пример: простые декларации модели данных средствами ActiveRecord в Ruby-On-Rails скрывают за собой кучу ORM-кода.

Более сложный пример, который, возможно, не все сочтут релевантным, -- это генераторы парсеров, вроде yacc/bison, coco/r, ragel или ANTLR. Ведь чтобы вручную написать разбор даже относительно несложной грамматики придется написать не одну сотню строк кода. Тогда как в случае с bison-ом ты описываешь лишь то, что тебе нужно, на специальном DSL, и получаешь здоровенную простыню автоматически сгенерированного кода.

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

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

Только вот вместо собственноручного написания условных Wizard-ов для своих задач, они использовали LLM в качестве такого Wizard-а.


Не могу не заострить внимание на нескольких фразах из статьи, касательно которых хочется сказать пару слов.

Рутинные задачи, написание бойлерплейта, стандартных функций — всё это автоматизируется. Время освобождается. Фокус смещается с написания строк кода на архитектуру, декомпозицию задач, правильную постановку технического задания, которое теперь превращается в промптинг.

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

Грубо говоря, если у вас задача просуммировать значения в столбцах матрицы 1M на 1M, то придется написать кучу бойлерплейта на чистом Си и обойтись всего лишь несколькими строчками в каком-то специализированном DSL.

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

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

Т.е. высокоуровневыми инструментами становятся не ЯП/DSL/фреймворки, а генеративные модели, которые запросто выплевывают мегатонны кода на уже существующих языках общего назначения. А исходником становится текст промпта для AI.

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

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

У новых людей меняется отношение к коду. Он становится расходным материалом, а не домашним питомцем — если что-то пошло не так, часто проще откатиться, доработать промпт и сгенерировать заново (иногда и 10 раз), чем пытаться исправить сложный баг в машинном коде.

А вот тут у меня возникают опасения.

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

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

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

Это первый момент, связанный с данной цитатой.

Второй же момент возникает из того, что автор статьи пишет в других местах:

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

Что-то я весьма скептически отношусь к такой вещи, как "умение читать чужой код".

Если писать код нас худо-бедно учат, то вот с развитием навыка "чтения кода", боюсь, есть большие проблемы.

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

Насколько хорошо вы решаете такие задачи?

А если код не одна страничка текста, а 150-200-250 и более строк?

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

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

Но этого не происходит.

Поэтому-то мне кажется, что когда AI начнет генерировать для нас все больше и больше кода, то мы не сможем достаточно тщательно этот код вычитывать на предмет потенциальных проблем.


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

Другой вопрос: а что будет с теми, кто занимается не рутиной, а какими-то уникальными велосипедами? Например, сможет ли AI в обозримое время заменить тех, кто сделал Roaring Bitmap?

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

В общем, не знаю чего ожидать. Есть ощущение, что ничего хорошего.

PS. Убежден в том, что развитие тех самых IDE из середины 1990-х с их Wizard-ами, автоматизированными рефакторингами, интеллектуальным автодополнением и мгновенной навигацией по коду сделало ситуацию в программизме только хуже: стало больше говнокодеров и, соответственно, говнокода. А производство кода посредством AI лишь усугубит дело -- это как самые продвинутые IDE на конских дозах стероидов + массовое непонимание того, что творится в сгенерированном коде. Так что да, есть ощущение, что ничего хорошего таких как я не ждет.

пятница, 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++ вполне себе возможно, сколько бы не пытались лаять хейтеры и ниасиляторы.

понедельник, 13 января 2025 г.

[prog.c++] Интересная постановка вопроса: если люди испытывают сложности с многопоточностью, то не забанить ли многопоточность совсем?

Этот вопрос всплыл на днях на r/cpp. Позволю себе процитировать значимую часть поста с reddit-а без перевода:

Hi all,

Had an interesting discussion today and would appreciate some advice.

Multithreaded code can be especially tricky for medium to junior level developers to write correctly. When a mistake is made, it can result in difficult to find bugs.

If the application you are working on needs to be very reliable (but isn't safety critical), what would you recommend for medium/low experience c++ teams?

Assume that the application will need to do 4 things at once and can't use state machines or coroutines. The various "threads" need to regularly exchange less than 10 KB of data a second.

Do you ban threads?

A few approaches come to mind.

#1 train the team to know the dangers and let them use threads with best practices. More experienced (and paranoid/diligent) developers carefully audit.

Any suggestions for books/resources for this team?

#2 and/or use a tool or technique to detect concurrency issues at compile time or during test? Thread sanitizer? cppcheck? ???

#3 ban threads and force concurrency to be implemented with multiple processes. 4 processes each with 1 thread. The processes will communicate with some form of IPC.

Т.е. смысл в том, что для разработчиков уровня middle/junior написание мультипоточного кода зачастую оказывается слишком сложным. И если приложение должно быть надежным, то возникает вопрос: как же быть? Может быть проще вообще запретить многопоточность в пользу многопроцессности? А если не запрещать, то что? Учить людей? Использовать какие-то инструменты для тестирования и анализа корректности кода?

Признаться, комментарии на reddit-е к этому посту я не читал, только просмотрел мельком и не увидел того, чего хотел 🙁

Поэтому выражу эмоции в этом блог-посте.

Хватить себя обманывать -- писать низкоуровневый многопоточный код сложно не только middle/junior-ам, но и senior-ам. Не устану повторять, что многопоточность на голых нитях, mutex-ах, condition_variable и, ниприведихоспади, atomic-ах -- это пот, боль и кровь.

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

Однако, такая рекомендация звучит очень похоже на банальность вида "хочешь быть здоровым -- будь им". Столь же бесполезная.

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

Если нужно, значит наш путь лежит в многопоточность. Если не нужно, то выдыхаем и не паримся (до поры до времени).

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

Ну а раз не обойтись, что вопрос уже не в том, запрещать многопоточность или нет. Вопрос в том, как уменьшить количество головной боли при использовании многопоточности. И ответ на него достаточно простой: использовать инструменты уровнем повыше, чем голые нити и примитивы синхронизации. Акторы, сопрограммы, CSP-шные каналы, task-и и вот это вот все. Плюс идеология shared nothing во главе угла (в том смысле, что чем меньше у вас разделяемых мутабельных данных, тем меньше у вас проблем).

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

Конечно же, ни акторы, ни CSP, ни task-и не избавляют нас от абсолютно всех проблем, и у этих подходов так же есть свои подводные камни. Тем не менее, многопоточность на акторах или CSP-шных каналах гораздо проще и безопаснее. Многократно проверено на людях.


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

  • обеспечение надежности работы в условиях ненадежности используемых программных компонент. Например, мы вынуждены полагаться на стороннюю библиотеку, которая время от времени падает и роняет весь процесс, в котором ее используют. Библиотека не наша, мы не можем "довести ее до ума", можем только минимизировать причиняемый ею ущерб;
  • обеспечение надежности работы в условиях ненадежности внешнего оборудования и/или внешних сервисов. Например, к компьютеру подключено устройство, которое может зависнуть. И единственный способ вернуть его к жизни -- это убить процесс, который общался с устройством, переинициалировать устройство и начать работать с ним заново. Аналогично и со сторонними сервисами, с которыми мы можем общаться через HTTP(S) или еще какую-то форму IPC. Бывает, что такой сервис набирает от нас N запросов и перестает подавать признаки жизни до тех пор, пока все эти N запросов не будут принудительно прекращены;
  • возможность жесткого прерывания длительных операций. Например, какой-то математический расчет, который может занимать часы и который не так-то просто прервать "изнутри". Но вот если вынести этот расчет в отдельный процесс, то этот процесс легко "прибить" в случае необходимости;
  • простота и удобство реконфигурации "на лету". Иногда работающий в режиме 24/7 сервис нужно переконфигурировать без его останова, но из-за внутренней кухни и/или особенностей использованных в нем библиотек, сделать такую переконфигурацию затруднительно.

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

  • в Unix-ах нужно избегать возникновения зомби-процессов;
  • после принудительно убитого дочернего процесса могут оставаться различные следы жизнедеятельности (в виде .tmp-файлов, которые не были вовремя удалены), которые нужно подчищать;
  • если взаимодействие идет через shared-memory, то нужно как-то определить а доверяем ли мы текущему содержимому блока разделяемой памяти или же внезапно умерший дочерний процесс оставил там какой-то мусор...

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

Но аргументы "за" такой переход точно не должны быть из категории "однопоточное программирование проще многопоточного".


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

вторник, 17 декабря 2024 г.

[prog.thoughts] Программист: хороший, плохой, крутой. Что под этим вообще понимается?

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

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

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

Соответственно, с плохим программистом все просто: это противоположность "хорошего программиста".

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

Так что, думаю, что с "хороший" и "плохой" все более-менее просто (хотя, т.к. это не количественные оценки, а качественные, то всегда остается вопрос того "а где же грань между хорошим и плохим?"). А вот что такое "крутой"?

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

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

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


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

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


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

среда, 11 декабря 2024 г.

[prog.flame] Комментарии в коде нужны. Без них тяжено, проверено на собственном опыте

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

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

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

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

  • почему это сделано именно так, а не иначе?
  • зачем вообще это было сделано?

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

Первоначально я хотел на этом и закончить, но встретил в статье про переиздание знаменитой книги "Мифический человеко-месяц" вот такой абзац:

Однако есть и другая точка зрения на этот вопрос. Например, Мартин (Роберт, а не Джордж), автор книги «Чистый код», утверждает, что комментарии — это зло. Даже будучи среди исходного кода они всё равно могут стать неактуальными. «Не трать время на написание комментариев», – говорит Мартин. Вместо этого он предлагает более тщательно подходить к процессу именования переменных, методов и классов. Если всё делается правильно, то и необходимость в комментариях отпадает.

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

Disclaimer. Приведенный код -- это черновик, который еще не компилировался и не тестировался.

Итак, вот фрагмент без комментариев:

template<typename T>
void
doAttemptToSwitchFromReadOnlyToReadWrite(T * objectToLock)
{
   auto & globalList = getGlobalListFor(objectToLock);
   std::unique_lock globalListLock{ globalList._lock };
   auto it = globalList._objects.find(objectToLock);
   if(it != globalList._objects.end())
   {
      auto & lockInfo = it->second;
      if(AccessMode::ReadOnly != lockInfo._currentMode)
         throw std::runtime_error{
               "doAttemptToSwitchFromReadOnlyToReadWrite: "
               "lockInfo._currentMode != AccessMode::ReadOnly "
               "in the global lock list"
            };

      if(1u == lockInfo._ownersCount)
      {
         lockInfo._currentMode = AccessMode::ReadWrite;
      }
      else if(isWaitingQueueEmpty(lockInfo))
      {
         OwnerCountForLockingUpgrateTrx ownerCounterTrx{ lockInfo };

         const auto requestResult = makeWaitingAccessorInfoThenWait(
               globalListLock,
               lockInfo,
               AccessMode::ReadWrite);
         if(AccessRequestResult::granted != requestResult)
            throw PossibleDeadlock{
                  "doAttemptToSwitchFromReadOnlyToReadWrite: "
                  "unable to upgrade ReadOnly lock to ReadWrite lock even "
                  "after waiting"
               };

         ownerCounterTrx.commit();
      }
      else
      {
         throw PossibleDeadlock{
               "doAttemptToSwitchFromReadOnlyToReadWrite: "
               "there is no possibility to upgrade ReadOnly lock "
               "to ReadWrite lock"
            };
      }
   }
   else
   {
      throw std::runtime_error{
            "doAttemptToSwitchFromReadOnlyToReadWrite: there is no "
            "information about objectToLock in the global lock list"
         };
   }
}

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

вторник, 3 декабря 2024 г.

[work] Формы рабочей коммуникации в удаленном формате: что, когда и для чего

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

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

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

Так что электронная почта отличный вариант, когда нужно обмениваться информацией объемом более 2-3 строк и нам не нужно получить ответ от собеседника в течении ближайших 5-10 минут.

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

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

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

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

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

И вот когда приходит время вернуться к отложенному "на потом", то наличие следов первичных обсуждений в почте или Telegram очень сильно облегчает возвращение в контекст. Если же серьезное предварительное обсуждение велось в устном формате, то лично я через полгода уже точно ничего не вспомню. Могу даже забыть и про сам факт такого обсуждения :(

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

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

Тем не менее, живое общение голосом имеет пару-тройку критически важных преимуществ.

Во-первых, максимальная оперативность. Здесь и сейчас в прямом смысле этого слова.

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

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

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

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

В общем, резюмируя:

Почта -- когда много и не срочно.
Мессенджеры -- когда немного и оперативно.
Телефон и он-лайн созвоны -- когда срочно и/или нужно общение по-человечески.


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

Так что описанное выше -- это не отлитые в граните незыблемые правила, скорее просто выводы из моего личного опыта.

четверг, 28 ноября 2024 г.

[soft.dev.wtf] Мир сходит с ума... На примере маразма с "The Undefined Behaviour Question"

Просто зафиксирую эту историю здесь, т.к. мне она кажется показательной. Если кто не в курсе, то суть, как я ее понял, в том, что в комитет по стандартизации C++ было подано предложение под названием "P3403 The Undefined Behaviour Question". Кому-то из комитетчиков в названии увиделась аналогия с "еврейским вопросом" (Jewish Question) времен гитлеровской Германии. И автора предложения попросили поменять название. Он сперва раздумывал об этом, но потом решил оставить все как есть, т.к. придерживается мнения, что вопрос об undefined behaviour в языке программирования не имеет ничего общего с геноцидом по этническому признаку. После чего (как я понял) его предложение отказались пропускать через комитетскую бюрократию, а его самого подвергли обструкции.

Эта история всплыла на Reddit-е, в разделе /r/cpp, откуда была удалена, но осталась в другом разделе. А потом и сам автор предложения "The Undefined Behaviour Question" подробно изложил свою версию происходивших событий: First-hand Account of “The Undefined Behavior Question” Incident.

Ну что здесь остается сказать?

Сперва ветку master в git-е переименовали в main из-за того, что когда-то в США "master"-ом называли рабовладельцев, а сейчас git checkout master вызывает жуткие страдания у современных чернокожих программистов (ага, ага). Теперь вот это. Верной дорогой, товарищи. Прям как во времена СССР -- а нет ли в названии художественного произведения скрытой антисоветчины?

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

Это тем более дико на фоне того, что разработчикам из РБ и РФ обнуляют аккаунты на BitBucket-е, отказываются принимать от них pull request-ы, блокируют доступ к техническим ресурсам в Европе и США. Просто по географическому признаку: мол, если ты из РБ, то ты подлежишь обструкции. Просто потому что.

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

В общем, чем дальше, тем больше начинает казаться, что дистанцирование от загнивающего Запада, не самая плохая штука в современных-то реалиях. Заимствовать оттуда нужно технологии, а не идеологию. А то докатимся до того, что отменим типы разъемов "папа" и "мама" из-за очевидного сексизма, мужского доминирования и объективизации женщин. Тьфу, срамота!

PS. Если данный пост позволил кому-то рассмотреть во мне замшелого ватника, застрявшего в СССР, то поздравляю, ваши розовые очки в очередной раз треснули от столкновения с реальностью.

суббота, 2 ноября 2024 г.

[prog.c++] Новые версии SObjectizer и so5extra: 5.8.3 и so5extra. Мои личные впечатления

Мы зафиксировали новые версии SObjectizer и so5extra: 5.8.3 и 1.6.2. На Хабре опубликована статья с описанием нововведений, так что не буду здесь повторяться. Озвучу здесь свои личные впечатления от работы над этими релизами.


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

Получилось не сказать, чтобы красиво и эргономично, поэтому не удивлюсь, если msg_hierarchy в итоге останется невостребованным. Возможностей C++17 (и моих знаний этих самых возможностей) хватило только на этот вариант. Была бы в C++ рефлексия, можно было бы сделать и покрасивши. Так что будем ждать принятия рефлексии в C++26, затем дождемся года 2029-го или даже 2030-го, чтобы безопасно перейти на компиляторы с нормальной поддержкой C++26... 🙂

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

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

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

Ну а дальше уже дело техники, как-то все само-собой раскрутилось. Отдельным вопросом был, конечно же, способ обхода иерархии классов в run-time. Но и с этим удалось разобраться, хоть и получилось достаточно коряво из-за ограничений C++.

Принципиальным моментом была именно идея о разных receiving_mbox-ах. Которую пришлось ждать больше трех лет и которая не желала появляться на свет "на заказ".

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


Когда писал статью для Хабра об этом релизе, то поймал себя на неожиданном ощущении.

В 2016-ом, когда я только начал публиковать на Хабре статьи о SObjectizer-е, маркетинговая составляющая была одной из главных. Все-таки мы хотели организовать бизнес вокруг своего OpenSource и нужно было показать "товар лицом". Конечно же, это не были чисто продажные статьи из категории "покупайте наших слонов", но начинающий маркетолог внутри нашептывал, что мол нужно показать насколько у нас все зашибись и какие мы сами крутые, все из себя такие опытные и умелые.

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

А вот сейчас работая над текстом, даже мысли такой не возникало.

Как бы не первый год пилим и пилим. Кому могли "продать", тем уже "продали". Миллионов нам SObjectizer не принес. Ну так чего выделываться?

Внезапное было ощущение, как будто даже полегче стало. Не скажу, что гора с плеч, но некоторый кусочек этой горы точно свалился.

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

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


На этом, пожалуй, все. Спасибо всем, кто дочитал. Если вы еще и прочитали и статью на Хабре, то вообще замечательно.

В завершении вынужден повторить банальность: если вдруг вам что-то потребовалось от SObjectizer-а, а вы этого там не нашли, то дайте знать. Мы не сможем сделать то, о чем даже и не подозреваем. А вот если нам сказать, то кто знает. Может года через три поймаем очередное "озарение" ;)

пятница, 11 октября 2024 г.

[life.work] 30 лет профессионального программизма

Как же все-таки летит время. Вроде бы недавно публиковал аналогичный пост, но про 25 лет, а тут хоба! И уже 30.

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

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

пятница, 4 октября 2024 г.

[prog.c++] Использование одного аргумента шаблона (aka Traits) вместо нескольких

В догонку ко вчерашнему посту про недостающую функциональность в std::vector.

Понятное дело, что std::vector уже не сможет получить каких-то дополнительных шаблонных аргументов, потому что это поломает кучу существующего кода.

Кстати говоря, есть у меня ощущение, что в любом C++ном проекте, который писали обычные люди, вроде меня, а не монстры, вроде Девида Вандервуда или Барри Ревзина, куча кода поломается, если в std::vector начнут использовать собственные аллокаторы. Поломается потому, что код написан в стиле:

void do_something(const std::vector<int> & data) {...}

И даже шаблонный код написан вот так:

template<typename T>
void do_something(const std::vector<T> & data) {...}

а не вот так (хотя бы вот так):

template<typename T, template Allocator>
void do_something(const std::vector<T, Allocator> & data) {...}

Впрочем, это уже совсем другая история...

Но если пофантазировать?

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

Это подход на базе Traits. Уже не помню, откуда про него узнал, не удивлюсь, если из книг Александреску. Но подход уже старый и мы, например, применяем его в RESTinio.

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

Например, для std::vector это могло бы выглядеть так:

template<typename T>
struct default_vector_traits {
  using allocator = std::allocator<T>;
};

template<typename T, typename Traits = default_vector_traits<T> >
class vector {...};

И если бы со временем нам бы потребовалось добавить в шаблон std::vector еще один параметр (тот же нужный мне growth_policy), то это можно было бы сделать не меняя списка параметров для std::vector:

struct default_vector_growth_policy {
  std::size_t operator()(std::size_t current_capacity) const {
    // Код примерный, прошу помидорами не бросаться ;)
    return (current_capacity > 1 ? static_cast<std::size_t>(capacity * 1.5) : 2);
  }
};

template<typename T>
struct default_vector_traits {
  using allocator = std::allocator<T>;
  growth_policy = default_vector_growth_policy;
};

template<typename T, typename Traits = default_vector_traits<T> >
class vector {...};

И если бы мне захотелось использовать с векторами собственную политику роста емкости, то мне бы потребовалось всего лишь:

struct my_growth_policy {
  std::size_t operator()(std::size_t current_capacity) const {...}
};

template<typename T>
struct my_vector_traits : public std::default_vector_traits<T> {
  using growth_policy = my_growth_policy;
};

using my_int_vector = std::vector<int, my_vector_traits<T>>;

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

среда, 2 октября 2024 г.

[life.cinema] О фильмах про "Чужого"

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

Мне очень нравятся первые три фильма, каждый из них шедеврален. Хотя первый "Чужой" на мой взгляд, самый слабый из них. Но тут обязательно нужно делать поправку на время, в которое он был снят. Полагаю, в конце 1970-х сложно было сделать лучше. А вот "Чужих" и "Чужой-3" пересматриваю регулярно. Причем чем старше становлюсь, тем больше мне нравится именно третья часть.

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

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

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

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

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

И, предположу, что там обязательно главным борцом с очередным чужим окажется девушка или молодая женщина. Ну такой сильный женский персонаж, который все превозмогёт и всех победит. Как же может быть иначе? ;) Что делает потенциально продолжение немного предсказуемым.

Еще, как бы это странно не звучало, мне зашел "Чужой против Хищника". Конечно же кино специфическое и по своему уровню ему далеко как до первых трех "Чужих", так и до первого "Хищника". Но зато качественно сделанное. Как мне показалось с любовью и уважением к двум исходным франшизам. Жаль что вторая часть этого противостояния оказалась отстоем, хотя этого и можно было ожидать.

вторник, 20 августа 2024 г.

[prog.c++] Вынесу из комментариев на Хабре про дебилизм с std::launder и std::start_lifetime_as

В комментариях к статье на Хабре зашел разговор об уместности использования std::launder и std::start_lifetime_as.

И, насколько я смог понять из разговора, вот в такой ситуации у нас нет сейчас простого и понятного способа в точке (1) сделать каст указателя к Demo*:

#include <iostream>
#include <functional>
#include <new>
#include <cstddef>
#include <cstring>
#include <memory>

struct Data {
    int _a{0};
    int _b{1};
    int _c{2};
};

using CreateFn = std::function<void(std::byte*)>;

void make_and_use_object(CreateFn creator) {
    alignas(Data) std::byte buffer[sizeof(Data) * 3];
    std::byte * raw_ptr = buffer + sizeof(Data);
    creator(raw_ptr);
    Data * obj = std::launder(reinterpret_cast<Data *>(raw_ptr)); // (1)
    std::cout << "obj: " << obj->_a << ", " << obj->_b << ", " << obj->_c << std::endl;
}

int main() {
    make_and_use_object([](std::byte * ptr) {
        new(ptr) Data{._a = 1, ._b = 2, ._c = 3};
    });
    make_and_use_object([](std::byte * ptr) {
        Data data{ ._a = 25, ._b = 16, ._c = 890};
        std::memcpy(ptr, &data, sizeof(data));
    });
}

Суть в том, что когда make_and_use_object вызывается с первой лямбдой и по указателю ptr новый объект Data создается через placement new, то затем raw_ptr нельзя просто так скастить к Data*. Тут требуется именно std::launder. Ну вот требуется и все. Иначе UB.

Тогда как при использовании второй лямбды в точке (1) вообще не нужно вызывать ни std::launder, ни std::start_lifetime_as. Вроде как все дело в том, что std::memcpy неявно начинает время жизни нового объекта. И результирующий указатель можно просто скастить к Data* и все.

Но, допустим, что во второй лямбде я не использую std::memcpy, а заполняю переданный мне буфер собственной функцией побайтового чтения из COM-порта. Что-то вроде:

 make_and_use_object([](std::byte * ptr) {
     com_port_reader reader;
     reader.init();
     while(reader.has_data()) {
         *ptr = reader.read_next_byte();
         ++ptr;
     }
 });

Естественно, ничего из этого в стандарте не описано и компилятор понятия не имеет, начинается ли здесь какой-нибудь lifetime или нет.

Соответственно, что делать в make_and_use_object после завершения работы лямбда-функции?

Если содержимое объекта внутри буфера было сформировано побайтовым чтением из COM-порта, то std::launder не поможет, тут нужен как раз std::start_lifetime_as (который завезли в C++23, но который, если не ошибаюсь, пока нигде не реализован).

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

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

Для полноты картины: там же в комментариях указали на наличие пропозала от Антона Полухина. Смысл этого пропозала -- отказаться от необходимости использовать std::launder вот в таких простых ситуациях:

alignas(T) std::byte storage[sizeof(T)];
auto* p1 = ::new (&storage) T();
auto* p2 = reinterpret_cast<T*>(&storage);
bool b = p1 == p2;  // b will have the value true.

Если этот пропозал примут, то ситуация окажется совсем веселая:

  • до C++26 подобный кастинг без std::launder будет считаться UB. Т.е. если вы пишете под C++17 или C++20, то должны использовать std::launder, иначе в вашем коде формальный UB;
  • начиная с C++26 это уже не UB и можно std::launder не писать.

А теперь представим проект (какую-нибудь библиотеку, вроде RapidJSON), который должен собираться и под C++14, и под C++17, и под C++20, и под C++23, и под C++26. И как в таком проекте быть со всеми этими std::launder и std::start_lifetime_as? Кроме как прятать подобные фокусы за фасадом макросов мне ничего в голову не приходит.

Но пусть даже у нас есть набор нужных вспомогательных макросов... Вернемся к самому первому примеру в посте. Как там понять, требуется ли std::launder, std::start_lifetime_as или же вообще ничего не требуется?

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

Собственно, чего хотелось бы иметь:

  • чтобы std::launder оставался только для случаев, когда пересоздается объект. Т.е. был объект типа A и на него были указатели, затем на том месте, где был объект A, создали новый объект A (или даже какой-то отнаследованный от него B -- пример), старые указатели "протухли", нужно их "отмыть" через std::launder. Все. Больше ни для чего std::launder не нужен;
  • чтобы std::start_lifetime_as использовался для случая, когда у нас есть std::byte* или char*, и мы хотим сказать компилятору, что по этому указателю реально живет объект A.

И строго так, без всяких неявных умолчаний, что мол memcpy или malloc начинает время жизни.

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

PS. Да, я в курсе, что практику с неявным началом времени жизни при использовании ряда функций (вроде malloc или memcpy) в C++20 ввели для того, чтобы узаконить говнокод, написанный в древности или даже вообще на чистом Си (как в случае с GCC -- сперва это был Сишный код, а потом еще стали компилировать как C++). Но ведь C++ все равно потихоньку отказывался от атавизмов, например, ключевое слово auto кардинально поменяло свой смысл, а ключевое слово register сейчас нельзя использовать по его первоначальному назначению. Так что лично для меня этот аргумент из категории "ну такое себе". А если какие-то комитетчики настаивают на то, что этот говнокод нужно оставить как есть и сделать легальным (да еще и за счет умолчаний, про которые мало кто знает), то хочется сказать таким комитетчикам: ну так оставьте легальным и тот говнокод, в котором std::launder не используется.

PPS. На всякий случай ссылки на посты двухгодичной давности, в которых обсуждалась проблематика std::launder: "Продолжение темы про передачу C++объектов через shared memory. Промежуточные выводы" и "В склерозник: ссылки на тему std::launder"