четверг, 21 декабря 2023 г.

[prog.bugs] Иногда цена ошибки может быть известна достаточно точно

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

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

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

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

ЗЫ. Все совпадения с реальными людьми и событиями случайны и непреднамеренны :)


Мораль сей басни: если вызывается какая-то функция, которая возвращает код ошибки, то код ошибки обязательно должен быть проверен. Обязательно. Должен. Быть. Проверен.

Далее, в случае возникновения ошибки, по ситуации:

  • если ошибка ожидаемая, то выполняется логика ее обработки. Например, попробовали открыть файл, не получилось, что-то сделали по этому поводу. Скажем, выдали сообщение пользователю и попросили ввести новое имя файла;
  • если ошибка не сильно ожидаемая и не предполагающая путей исправления здесь и сейчас, то либо возвращаем свой код ошибки наверх (если исключения под запретом), либо выбрасываем исключения. Например, вызвали malloc, а он взял и вернул NULL. Маловероятно, но потенциально может произойти. Проверили результат malloc-а и вернули код ошибки наверх;
  • если ошибка вообще из разряда невероятных, то либо действуем как в предыдущем пункте, либо же вообще тупо зовем abort. Например, вызываем getwd, а получаем NULL, хотя казалось бы как такое возможно?

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

  • во-первых, сразу же узнаете о проблеме и
  • во-вторых, не сможете тихо "замести ее под коврик".

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


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

четверг, 14 декабря 2023 г.

[prog.c++] Несколько не очень приятных открытий...

...ну или я просто пока не понял, как это можно "правильно приготовить" :(


Впервые довелось столкнуться с Google Test (далее gTest). Ранее пользовался либо чем-то самодельным, либо Catch2, либо Doctest. А вот теперь и до Google Test очередь дошла.

В принципе, мощная штука, но одна вещь оттуда сейчас чуть ли не show-stopper-ом выступила.

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

Но засада оказалась в том, что если в SetUpTestSuite возникает исключение (например, по какой-то причине я не смог загрузить нужные мне данные), то gTest все равно пытается выполнить входящие в test-suite test-cases. Хотя без нужных данных все эти test-cases завершаются негативно. А вот гарантировать отсутствие исключений я не могу -- иногда данные могут и не загружаться.

И я пока не понял, как поступать в этих случаях. Неужели просто дергать std::abort внутри SetUpTestSuite?


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

Вот что стало по-настоящему неприятным сюрпризом, так это то, что когда инициируешь запуск тестов из Test Explorer, то студия напрочь выбрасывает все, что печатается в stderr/stdout. Грубо говоря, есть у тебя в тесте какая-то тестовая печать в std::cout... И все, забудь! Что там было напечатано из Test Explorer не видно :(

Очень надеюсь, что это я неграмотный идиот и просто не знаю куда смотреть. Может там есть какая-то волшебная кнопка, чтобы показать stderr/stdout от тестов?


В C++20 добавили char8_t. Удобная штука, чтобы конструировать, например, std::filename::path не беспокоясь о том, как это будет воспринято. Например, под Windows такая запись:

std::filename::path my_path{u8"./Файл-С-Именем-На-Русском-Языке.你好.txt"};

теперь обрабатывается уже полностью корректно. А вот в рамках C++17, насколько я помню, будет облом в run-time.

Еще в C++20 добавили u8string_view. Это как string_view, но для char8_t.

Но, оказалось, что u8string_view в рамках C++20 нельзя передать в std::format. Цынк.

Ну и да, еще и диагностика как всегда в C++ ;)

Кстати говоря, как я понял, нельзя заставить std::format делать std::u8string. Можно либо std::string, либо std::wstring.


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

среда, 13 декабря 2023 г.

[prog.json.question] Нужна ли такая фича в библиотеке (де)сериализации JSON?

Есть у нас маленькая обертка над RapidJSON под названием json_dto. Ничего выдающегося, просто попытка применить идею из Boost.Serialization для того, чтобы с RapidJSON-ом работать было удобно.

Давеча в json_dto прилетел pull request, добавляющий специфическую функциональность.

Грубо говоря, нужно (де)сериализовать элементы tuple, которые в JSON представляются массивом с элементами разных типов. Что-то вроде:

{"x": [1, "abc"]}

Где для ключа "x" значением будет tuple<int, string> содержащий (1, "abd").

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

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

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

[prog.work.flame] Много слов на тему ролика АйТиБорода "ИИ убивает программирование, и вот как"

На прошлой неделе урывками посмотрел откровенно кликбейтный ролик "ИИ убивает программирование, и вот как" от известного в узких кругах АйТиБорода.

Урывками потому, что по мере просмотра подгорало знатно, тот самый случай, когда огнеупорная сидушка была бы кстати :)

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

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

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

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

пятница, 1 декабря 2023 г.

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

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

Фильмы

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

Продавцы боли (Pain Hustlers, 2023). Неплохо.

Создатель (The Creator, 2023). Сказочка для детей. Но снято настолько красочно и убедительно, что все недостатки можно простить. Даже удивительно, что Голливуд смог в такую красивую фантастическую картинку.

Миссия невыполнима: Смертельная расплата. Часть первая (Mission: Impossible - Dead Reckoning Part One, 2023). Как обычно, отлично снятый красочный аттракцион. Но, гораздо, гораздо более скучный и унылый, чем любая из предыдущих частей.

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

Медовый месяц (Elizabeth Harvest, 2018). Интересно и необычно рассказанная история. Хотя, как мне кажется, весь фильм держится больше на красивой картинке и хороших актерах, нежели на истории. О просмотре не пожалел, но, боюсь, кино специфическое и зайдет не всем.

Чужак (Farang, 2023). В принципе, неплохой боевик с мордобоем. Но слишком уж много в нем драмы и соплей, сократили бы хронометраж минут на 10-15, стало бы еще лучше.

Последний наёмник (In the Land of Saints and Sinners, 2023). Простенькая и неторопливая история с минимумом экшена и откровенно халтурными спецэффектами.

Опасный соблазн (Fatale, 2020). Красиво. Но очень уж затянуто, да и развязка какая-то убогая.

Убийца (The Killer, 2023). Прикольно рассказанная история. Но после просмотра остается стойкое ощущение "вы нам втираете какую-то дичь!", что очень и очень сильно портит впечатление от кино.

Украсть Рафаеля (Criminali si diventa, 2021). Откровенная халтура, в которой половина снимавшихся даже играть толком не пыталась.

Фантом из прошлого (L'uomo sulla strada, 2022). Во-первых, это никакой не триллер, а унылая сопливая драма. Во-вторых, это такая муть, на которую лучше не тратить свое время.

Сериалы

Химера (первый сезон, 2022). Динамично, красочно. Следить за происходящим интересно. Но вот кому-то из героев сопереживать не хочется, такое ощущение, что положительных героев там практически и нет. Ну и местами все происходящее выглядит ну очень уж сказочно.

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

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

Прощай, любимая (первый сезон, 2014). У меня лично оставил впечатление откровенной халтуры, так что рекомендую просто пройти мимо.

среда, 29 ноября 2023 г.

[prog.c++.flame] Насколько эффективно реализована split_path в C++ REST SDK от Microsoft?

Для многих анонимных экспертов, громогласно и авторитетно заявляющих о том, что C++у не место в современном мире, может быть удивительно, но C++ применяется в Web-е не так уж и редко. Может и не напрямую в Web-е, но вот для отдачи JSON-ов и прочей дряни в RESTful-микросервисной архитектуре C++ используется регулярно. И C++ных фреймворков для этих целей, разной степени продвинутости, можно сходу назвать с полдюжины (а если подумать-повспоминать, то и еще больше).

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

Но, полагаю, одна из причин -- это эффективность.

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

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

Ага, я тоже так думал.

Пока не довелось заглянуть в потроха C++ REST SDK от самого Microsoft.

понедельник, 20 ноября 2023 г.

[prog.oop] Что-то я не понял пассаж Барбары Лисков из её статьи от 1987-го года...

Сама статья "Data Abstraction and Hierarchy (OOPSLA ‘87 Addendum to the Proceedings)", раздел "6.2. Multiple Implementations" (стр.16):

It is often useful to have multiple implementations of the same type. For example, for some matrices we use a sparse representation and for others a nonsparse representation. Furthermore, it is sometimes desirable to use objects of the same type but different representations within the same program.

Object-oriented languages appear to allow users to simulate multiple implementations with inheritance. Each implementation would be a subclass of another class that implements the type. This latter class would probably be virtual; for example, there would be a virtual class implementing matrices, and subclasses implementing sparse and nonsparse matrices.

Using inheritance in this way allows us to have several implementations of the same type in use within the same program, but it interferes with type hierarchy. For example, suppose we invent a subtype of matrices called extended-matrices. We would like to implement extended-matrices with a class that inherits from matrices rather than from a particular implementation of matrices, since this would allow us to combine it with either matrix implementation. This is not possible, however. Instead, the extended-matrix class must explicitly state in its program text that it is a subclass of sparse or nonsparse matrices.

The problem arises because inheritance is being used for two different things: to implement a type and to indicate that one type is a subtype of another. These uses should be kept separate. Then we could have what we really want: two types (matrix and extended-matrix), one a subtype of the other, each having several implementations, and the ability to combine the implementations of the subtype with those of the supertype in various ways.

Что в моем вольном переводе на русском языке звучит так:

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

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

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

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

Так вот, я не понимаю почему Барбара Лисков сделала утверждение, что в рамках ООЯ мы не можем отнаследовать "расширенную матрицу" просто от класса "матрица" забив на существование подклассов "разреженная матрица" и "обычная матрицы", которые также наследуются от класса "матрица".

Очень смущает, что статья от 1987-го года, и там в списке литературы нет ни одной ссылки на описание таких языков программирования, как Eiffel (1986) и C++ (1985). Есть упоминание разве что SmallTalk, но в SmallTalk, насколько я помню, вообще не было понятия "абстрактный базовый тип" (или "интерфейс" в Java-подобных языках). Тогда как и в Eiffel, и в C++ понятие "абстрактного (базового) типа" как раз есть и в Eiffel/C++ мы запросто можем решить озвученную проблему имея просто абстрактный тип matrix и отнаследованный от него абстрактный тип extened_matrix.

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

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