<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Баги и ошибки</title>
    <description/>
    <link>https://tproger.ru/tag/bagi-i-oshibki</link>
    <atom:link href="https://tproger.ru/tag/bagi-i-oshibki/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Fri, 23 Oct 2026 17:06:22 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Баги и ошибки</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Баги, которые не ловятся тестами: три расследования и общая методика</title>
      <link>https://tproger.ru/articles/bagi-kotorye-ne-lovyatsya-testami-tri-rassledovaniya-i-obshhaya-meto</link>
      <comments>https://tproger.ru/articles/bagi-kotorye-ne-lovyatsya-testami-tri-rassledovaniya-i-obshhaya-meto?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/bagi-kotorye-ne-lovyatsya-testami-tri-rassledovaniya-i-obshhaya-meto</guid>
      <description><![CDATA[<p>Три расследования редких дефектов: гонка в SQLite, потеря сообщений в TCP и гонки в биллинге. Разбираем общую методику поиска невоспроизводимых багов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/bagi-kotorye-ne-lovyatsya-testami-tri-rassledovaniya-i-obshhaya-meto">Баги, которые не ловятся тестами: три расследования и общая методика</a>»</p>]]></description>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Сетевые протоколы]]></category>
      <category><![CDATA[Отладка]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 09 Sep 2026 09:00:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Есть класс дефектов, которые проходят весь конвейер проверок и всплывают только в продакшене. Объединяет их не сложность кода. В двух случаях виновата форма проверки, написанной вежливой там, где реальность к сервису вежлива не бывает; в третьем проверка не поймала бы дефект вообще.</p><p>Разбираем три расследования — пропавшую запись в базе, три процента сообщений, терявшихся только на чистой машине, и гонки в биллинге, которые видит одна поддержка. У всех трёх разные предметные области и одна общая методика поиска невоспроизводимых багов.</p><p>Отсутствие закономерности само по себе является находкой: если сбой не привязан ни к шарду, ни к клиенту, ни к времени суток, это указывает на гонку, а не на данные.</p><p>Когда воспроизвести дефект синтетически нельзя, остаётся пассивная телеметрия в продакшене и последовательное отсечение гипотез данными.</p><p>Счётчики на каждом слое находят виновника быстрее логов: разрыв между двумя соседними числами прямо называет слой, в котором теряются сообщения.</p><p>Вежливый тест, который шлёт запрос и ждёт ответа, скрывает целый класс ошибок пакетирования. Пачечный тест их обнажает.</p><p>Отсутствие ошибок не доказывает, что дефект исправлен. Доказательством служит положительный сигнал: сработавшее предупреждение о том, что опасные условия возникли, а сбоя при этом не случилось.</p><h2>Случай первый: запись, которой не могло не быть</h2><p>Управляющий слой Tailscale разбит на шарды, у каждого своя база SQLite, и обращается к ней ровно один процесс. Такой однописательный режим — именно то, для чего SQLite и предназначен, что делает дальнейшее особенно неприятным.</p><p>Резервное копирование снимало полную копию базы каждые несколько минут и складывало файл в объектное хранилище. Работало это без единого происшествия с начала 2023 года, пока конвейер, читавший резервные копии, не сообщил об ошибке. Проверка встроенной командой контроля целостности подтвердила худшее: база повреждена.</p><p>Первый случай сочли единичным. Базу починили, причину не нашли. Потом это повторилось. И ещё раз. Всего до устранения первопричины набралось <a href="https://tailscale.com/blog/sqlite-wal-reset-bug">19 отдельных инцидентов за полгода</a>, и каждый означал простой: процесс на шарде останавливался, пока базу чинили или восстанавливали. На ранних инцидентах простой превышал час, и всё это время у клиентов на шарде не работали консоль администратора и API.</p><h3>Почему он не поддавался обычным приёмам</h3><p>Дефект сопротивлялся всем стандартным подходам сразу:</p><ul><li>Не на что было списать: низкоуровневый код работы с базой никто не трогал годами, а внимательное чтение ничего не дало.</li><li>Не нашлось общих факторов. Повреждения не привязывались ни к конкретному шарду, ни к клиенту, ни к функции продукта, ни ко времени суток, ни к уровню нагрузки.</li><li>Он не воспроизводился синтетически. Надёжного триггера не было, поэтому оставалось только развернуть пассивную телеметрию в боевой среде и ждать следующего случая.</li></ul><p>Расписания у инцидентов тоже не было: они случались то через часы, то через недели. Между октябрём и декабрём наступила шестинедельная тишина, после которой повреждения вернулись.</p><p>Команда сделала два шага, которые и составляют суть этой истории. Купила контракт поддержки у разработчиков SQLite, получив прямой доступ к людям, написавшим саму базу. И методично составила список гипотез, отсекая их данными, а не рассуждениями. В списке были сломанные блокировки при закрытии файла, неверная работа с памятью, принадлежащей базе, и обращение из нескольких потоков при отключённой потокобезопасности. Каждый инцидент приносил данные, каждая гипотеза отпадала по очереди.</p><h3>Улика: транзакция, которой не стало</h3><p>Пока первопричину искали, платформу надо было держать живой. Команда автоматизировала жёсткую остановку при обнаружении повреждения, поставила монитор, непрерывно проверявший целостность резервных копий, и переписала инструкции по восстановлению. Время восстановления упало ниже часа.</p><p>А затем построила конвейер журналирования транзакций. Идея простая: писать каждый изменяющий базу запрос в отдельный журнал. Поскольку писатель один, а транзакции сериализуемы, история изменений линейна и детерминирована, и её можно проиграть поверх последней заведомо целой копии, обойдя повреждённые страницы. Приём годится там, где порядок фиксаций известен: при единственном писателе он получается сам собой. Наивный журнал запросов на многописательной базе этого не даёт, потому что конкурентные транзакции переплетаются и без явного порядка фиксаций проигрывание не восстановит то же состояние.</p><p>Конвейер сработал и выдал улику. В двух инцидентах журналы не проигрывались чисто: данные, записанные и зафиксированные одной транзакцией, оказывались необъяснимо невидимы для последующих. Запись исчезла бесследно и без единой ошибки.</p><p>Этого не может быть. В сериализуемой однописательной базе зафиксированная запись не может пропасть. Значит, виноват слой с достаточной конкурентностью, чтобы такое спрятать, а такой слой ровно один — контрольные точки.</p><h3>Шестнадцатилетняя гонка</h3><p>SQLite с журналом предзаписи работает с двумя файлами. База — это набор страниц; при изменении данных новые страницы пишутся не в основной файл, а сначала в журнал. Позже контрольная точка переносит их в базу. Обычно момент переноса SQLite выбирает сам, но Tailscale управляла контрольными точками вручную, чтобы снимать быстрые согласованные копии. Именно этот нестандартный, хотя и полностью документированный выбор и вывел их на дефект.</p><p>Метрики во время инцидентов показывали, что SQLite сообщает о переносе большего числа страниц, чем в журнале вообще было. Разработчики базы как раз готовили инструмент для этого слоя: обёртку над виртуальной файловой системой, которая пишет дополнительные трассировочные журналы. Обёртку развернули в продакшене, и ждать пришлось недолго.</p><p>Журналы показали редкую гонку данных (race condition) между контрольной точкой и пишущей транзакцией. Если запись происходит в определённый момент работы контрольной точки, та сбивается: считает часть страниц перенесёнными в основной файл, хотя перенос не состоялся. Данные теряются навсегда, а страницы, которые на них ссылаются, например индексные, записываются как ни в чём не бывало. Файл становится структурно повреждённым, что проверка целостности всё это время и фиксировала.</p><p>Дефекту дали имя по сбросу журнала предзаписи и оценили его возраст минимум в шестнадцать лет. Он прожил так долго именно из-за редкости, это классический heisenbug: чтобы поймать его в тестовой среде, разработчикам пришлось дописать код, вызывающий гонку намеренно.</p><h3>Ложная тревога, чуть не сорвавшая исправление</h3><p>Исправление вышло в версии 3.52.0, и Tailscale раскатывала его осторожно, начав с нескольких канареечных шардов. После общей раскатки монитор резервных копий немедленно покраснел, сообщив о повреждении в тринадцати базах.</p><p>Настоящего повреждения не было. В ту же версию попала оптимизация, слегка изменившая округление при переводе текста в число с плавающей точкой, а Tailscale хранила метки времени высокой точности текстом и превращала их в числа в вычисляемом столбце. Индекс по вычисляемому выражению перестал соответствовать новому результату вычисления, и проверка целостности честно назвала это повреждением. Канареечные шарды дефект пропустили просто потому, что на них не оказалось меток времени, попадающих под изменённое округление.</p><p>Разбирали это с трёх сторон сразу. Разработчики SQLite отозвали версию целиком и выпустили 3.51.3, содержавшую только исправление гонки. Tailscale перешла на хранение меток времени целыми секундами, поскольку перевод текста в целое число однозначен. А в 3.53.0 появилось автоматическое восстановление таких индексов, чтобы проблема не возникала впредь.</p><p><b>Главный урок этого эпизода:</b><br />Канареечная выкатка проверяет только те формы данных, которые в канарейке есть. Если на канареечных узлах нет тех же значений, что в проде, канарейка не подтверждает ничего. Это касается и обновлений самой базы, драйверов и инструментов миграции, а не только кода приложения.</p><h3>Как доказали, что дефект действительно исправлен</h3><p>Самая дисциплинированная часть расследования пришлась на финал. Отсутствие повреждений доказательством не считалось: команда уже пережила шесть недель обманчивой тишины. Поэтому драйвер базы пропатчили так, чтобы он выдавал предупреждение всякий раз, когда пишущая транзакция и сброс журнала пересекаются во времени.</p><p>Логика прямая: если предупреждение сработает, а база останется целой, значит, исправление сработало ровно там, где раньше был бы инцидент. Ждали два месяца. Предупреждение сработало, подтвердив, что условия для гонки в их среде действительно возникают. С того момента прошло ещё четыре месяца без единого происшествия с базами.</p><h2>Случай второй: три процента, терявшиеся только на чистой машине</h2><p>Второй сюжет проще по масштабу и полезнее в быту. Сервис представлял собой небольшой пересыльщик TCP: принять соединение, читать текстовые строки, передавать дальше. На тестовом стенде входило 97 004 строки, а выходило 94 183. Ни ошибок, ни исключений, просто пропавшие сообщения.</p><p>На ноутбуке автора всё воспроизводилось идеально: сто тысяч строк на входе, сто тысяч на выходе. Это расхождение и было первой уликой — тест проверял не то, что делает продакшен.</p><h3>Сменить машину раньше, чем код</h3><p>Первый час, по его собственному признанию, ушёл впустую: менялись и бинарник, и машина, и профиль нагрузки разом, а выводов это не давало. Дальше автор поменял ровно одну переменную: взял чистый сервер с тем же бинарником и тем же скриптом нагрузки. Потери появились снова, 97 128 строк из ста тысяч. Среда меняла вероятность проявления, но не сам факт дефекта, а чистая машина без истории и без накопленных настроек работает как микроскоп для ошибок синхронизации.</p><h3>Считать, а не логировать</h3><p>Дальше нужен был не ещё один прогон, а способ понять, на каком слое теряются сообщения. Логи рассказывают истории, счётчики складываются. Автор расставил счётчик на каждом слое: клиент считает отправленные строки, сервер считает разобранные переводы строки, сервер считает отправленные ответы, клиент считает полученные ответы. Один прогон, четыре числа, и разрыв между соседними прямо называет виновный слой.</p><p>Сервер разобрал всё до последней строки, а ответов отправил меньше, чем разобрал. Это отпечаток пальца конкретного дефекта, и дальше оставалось найти его в коде ответа.</p><h3>read() ничего не обещает про сообщения</h3><p>Виновником оказалась одна строка: сервер отправлял по одному ответу на каждое чтение, а не на каждую разобранную строку.</p><p>TCP — это поток байтов, а не очередь сообщений. Вызов чтения не имеет понятия о том, что такое одно сообщение в вашем протоколе. Ядро вправе слить десяток сообщений в один сегмент, и одно чтение заберёт все десять, а ответ уйдёт один.</p><p>Вежливый локальный тест отправлял сообщение, дожидался подтверждения и отправлял следующее. Одно сообщение на сегмент — дефект спал. Нагрузка на стенде шла пачками, тысячами сообщений в секунду, ядро их группировало, одно чтение проглатывало сотню строк.</p><p>Исправление переносит ответ внутрь цикла по строкам и накапливает остаток в буфере, чтобы пережить строку, разорванную между двумя чтениями. Это вторая половина того же семейства ошибок, и без неё починка неполная:</p><p><b>Вывод автора расследования:</b><br />Локальный тест это не нагрузочный тест, ноутбук это не сервер, а одно чтение из сокета это не одно сообщение.</p><h2>Третий сюжет: гонки, которые видит только поддержка</h2><p>Третий сюжет про биллинг кредитов на бессерверном Postgres, где транзакции по условиям задачи были недоступны. Автор наткнулся на четыре гонки, разберём две самые показательные. Все они, по его собственной формулировке, относятся к тому сорту дефектов, которые не показываются в тестах и показываются в почте поддержки.</p><h3>Классический двойной расход</h3><p>Наивная версия, которую пишут первой:</p><p>Два одновременных запроса читают баланс, равный пяти, оба проходят проверку, оба записывают четыре. Пользователь получил две операции по цене одной. Дефект существует ровно между чтением и записью, и последовательный тест в это окно не попадает никогда.</p><h3>Инвариант переезжает в условие запроса</h3><p>Общее решение — сделать проверку и запись одним оператором, перенеся условие корректности в WHERE:</p><p>Одиночный UPDATE в Postgres атомарен. Не хватило баланса — условие не совпало, вернулось ноль строк, и вы точно знаете, что списание не произошло. Транзакция для этого не нужна вовсе. Приём обобщается: перенесите инвариант в условие выборки, и запись просто не случится, когда инвариант нарушен.</p><h3>Вебхук, доставленный дважды</h3><p>Платёжные провайдеры повторяют доставку уведомлений при таймаутах, пятисотках и сетевых сбоях, а иногда дублируют событие и в штатном режиме. Если обработчик начисляет кредиты, доставка «хотя бы один раз» означает начисление хотя бы один раз.</p><p>Обычное решение с таблицей обработанных событий требует транзакции, а её нет: падение между двумя операторами либо начислит дважды при повторе, либо потеряет начисление совсем. Рабочий вариант в один оператор использует изменяющее данные обобщённое табличное выражение, где вставка в журнал операций работает воротами: не прошла вставка, значит, не выполнилось и обновление баланса.</p><p>Деталь, которую стоит забрать отдельно: уникальный индекс под эту схему должен быть частичным. Начисления от администратора приходят с произвольным идентификатором из скрипта, и глобальный уникальный индекс рисковал бы столкнуть их друг с другом или с идентификатором операции другого типа. Приветственные начисления идут вообще без идентификатора, и с ними коллизии не будет в любом случае: Postgres не считает два пустых значения равными. Ограничение индекса двумя типами операций от платёжного провайдера держит проверку уникальности ровно там, где она нужна.</p><h2>Что из этого складывается</h2><p>Первые два расследования дают общую методику поиска, и она изложена ниже. Третий случай стоит особняком: это не детектив, а набор приёмов, которые убирают целый класс гонок ещё на этапе проектирования, до того как искать станет нечего.</p><h2>Что забрать с собой</h2><p>Общего у трёх расследований больше, чем различий. Во всех трёх случаях проверка была написана в форме, которая не воспроизводит реальность: последовательный запрос вместо пачки, одиночный вызов вместо конкуренции, одна машина вместо двух. И во всех трёх дефект спокойно проходил через тесты, а в случае с SQLite прожил так шестнадцать лет.</p><p>Соседний сюжет про то, как тесты перестают отражать реальность, разбирали в материале о том, <a href="https://tproger.ru/articles/pochemu-statichnye-moki-ubivayut-testirovanie--i-chto-my-s-etim-sdel">почему статичные моки убивают тестирование</a>.</p><p>Полезнее всего здесь дешёвые привычки. Счётчик на границе каждого слоя ставится за час. Пачечный клиент пишется за вечер. Датчик, доказывающий исправление положительным сигналом, добавляется одной строкой в драйвер. Всё это стоит несопоставимо меньше, чем полгода расследования.</p><p>Три постмортема, на которых строится разбор: <a href="https://tailscale.com/blog/sqlite-wal-reset-bug">разбор Tailscale о поиске шестнадцатилетнего бага в SQLite</a>, <a href="https://dev.to/datacpp_3670/the-3-drop-that-only-showed-up-on-a-clean-server-a-debugging-retrospective-1g3c">ретроспектива потери трёх процентов сообщений</a> и <a href="https://dev.to/xiaojun_mao_c154743594bc9/credit-billing-without-transactions-4-race-conditions-i-hit-on-serverless-postgres-4730">четыре гонки в биллинге на бессерверном Postgres</a>.</p><p>Возьмите самый подозрительный сервис и запустите по нему пачечный тест вместо последовательного. Это самый дешёвый способ узнать, какой из ваших дефектов сейчас спит.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как мы нашли баг в HTTP-библиотеке hyper</title>
      <link>https://tproger.ru/translations/kak-my-nawli-bag-v-http-biblioteke-hyper</link>
      <comments>https://tproger.ru/translations/kak-my-nawli-bag-v-http-biblioteke-hyper?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/kak-my-nawli-bag-v-http-biblioteke-hyper</guid>
      <description><![CDATA[<p>Перестраивая Images binding, команда Cloudflare случайно обнаружила баг в открытой библиотеке hyper, который существовал сразу в нескольких мажорных версиях.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/kak-my-nawli-bag-v-http-biblioteke-hyper">Как мы нашли баг в HTTP-библиотеке hyper</a>»</p>]]></description>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Сетевые протоколы]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 25 Jun 2026 15:29:36 GMT</pubDate>
      <content:encoded><![CDATA[<p>Перевод статьи Дины Лам (Deanna Lam), Диретнана Домнана (Diretnan Domnan) и Мэтта Льюиса (Matt Lewis) из Cloudflare Blog, оригинал: <a href="https://blog.cloudflare.com/hyper-bug/">https://blog.cloudflare.com/hyper-bug/</a></p><p>Сервис Images, написанный на Rust и работающий в Workers, запущен на каждой машине в edge-сети Cloudflare. Чтобы обрабатывать клиентские соединения, мы используем hyper — открытую HTTP-библиотеку для Rust.</p><p>В прошлом году мы представили Images binding — он позволяет создавать кастомные программные сценарии обработки удалённых изображений в Workers. В конце 2025 года мы перестроили binding, чтобы сделать связь между Workers runtime и сервисом Images более прямой и локальной.</p><p>Вскоре после выкатки мы получили сообщения о том, что запросы на трансформацию из binding падают — но только иногда и только для больших изображений. Ещё страннее было то, что ответы на эти запросы возвращали статус 200, и в логах не было никаких ошибок. Данные изображения просто обрывались: ответ, который должен был весить два мегабайта, мог прийти всего в несколько сотен килобайт.</p><p>Мы потратили шесть недель на охоту за почти невидимым багом — состоянием гонки, которое проявлялось только при определённых условиях, — в библиотеке hyper, который влиял на то, как Images binding возвращает обработанные изображения клиенту. В итоге исправление заняло четыре строки кода.</p><p>Cloudflare Images binding перешёл на прямое локальное соединение через Unix-сокеты в декабре 2025 года.</p><p>После выкатки большие изображения стали иногда возвращаться обрезанными: HTTP 200, но тело ответа короче Content-Length.</p><p>Причина — состояние гонки в hyper: цикл poll_loop отбрасывал результат poll_flush, даже если тот возвращал Poll::Pending.</p><p>Баг существовал в hyper 0.14.x, 1.7 и 1.8; прежний посредник FL читал данные достаточно быстро, чтобы он не проявлялся.</p><p>Исправление заняло четыре строки кода: flush теперь выполняется перед shutdown.</p><h2>Прыжки, передачи и hyper</h2><p>Когда разработчики создают приложения на Cloudflare, они собирают full-stack приложения из набора платформенных сервисов, доступных Workers через bindings. Bindings предоставляют прямые API к ресурсам Developer Platform: вычислениям, хранилищам, ИИ-инференсу и обработке медиа.</p><p>Images binding отделяет оптимизацию изображений от доставки: вы можете транскодировать, компоновать или изменять изображения, не возвращая результат в виде HTTP-ответа. Также он позволяет применять параметры оптимизации в любом порядке, а не в фиксированной последовательности, которую навязывает URL-интерфейс. Вот пример: воркер передаёт данные изображения напрямую в Images API, объединяет операции в цепочку и получает обработанный результат в виде потока:</p><p>На высоком уровне так выглядит движение данных через наши сервисы:</p><figure><img src="https://media.tproger.ru/user-uploads/133946/2026-06-23/4ed76cb3-414e-4ac0-bf31-81e0ba01bb2e.webp" alt="Схема движения данных между Workers runtime, посредником и сервисом Images" /><figcaption>Схема движения данных между Workers runtime, посредником и сервисом Images при использовании Images binding</figcaption></figure><p>Труба на схеме обозначает сокетное соединение между посредником и Images, через которое данные передаются от одного процесса к другому через буфер ядра.</p><p>Binding взаимодействует с Images через сокетное соединение, управляемое Workers runtime. Сокет — это канал связи между двумя процессами. У каждого конца сокета есть буферы, управляемые ядром операционной системы; эти буферы — временные области, где данные остаются после записи одной стороной, но до чтения другой.</p><p>Hyper управляет соединением на стороне сервиса Images: читает входящие запросы из сокета и пишет ответы обратно в него.</p><p>Когда запрос использует Images binding, сервис Images читает входные данные, выполняет запрошенные операции оптимизации и кодирует результат. Затем он передаёт всё закодированное изображение hyper как один непрерывный блок в памяти.</p><p>Hyper записывает эти данные ответа в свой внутренний буфер. На этом моменте hyper считает кодирование завершённым, потому что у него есть все байты, которые нужно отправить. Следующий шаг — сбросить внутренний буфер в исходящий буфер сокета, переместив данные от сервиса Images к посреднику на другом конце.</p><p>Если читатель на другом конце быстрый, hyper может сбросить всё за один проход — в исходящем буфере будет место, потому что читатель потребляет данные по мере поступления. Как только все данные отправлены, hyper вызывает shutdown на сокете, сигнализируя, что соединение завершено и больше данных записано не будет. Но если читатель медленнее (хотя бы на несколько миллисекунд), исходящий буфер заполняется, и hyper должен ждать, пока появится место, чтобы продолжить запись.</p><h2>Локальное соединение</h2><p>Весь входящий трафик в сети Cloudflare проходит через FL — внутренний сервис-посредник, который включает функции безопасности и производительности и маршрутизирует запросы к нужному бэкенду. Когда мы только запустили binding, данные изображений шли из Workers runtime через FL в сервис Images.</p><p>Этот путь хорошо подходил для первого релиза и повторял архитектуру URL-интерфейса. Но со временем связь с FL стала ограничением: любое изменение binding приходилось согласовывать с циклом релизов FL.</p><p>В декабре 2025 года команда Images заменила FL новым посредником — внутренним worker binding, который работает на той же машине. В исходной архитектуре данные шли через FL по сетевым сокетам; этот путь нёс накладные расходы полного конвейера обработки FL, такие как DNS-lookup'ы и маршрутизация.</p><p>Внутренний binding заменил их на Unix-сокеты, чтобы напрямую соединить сервисы на одной машине, минуя FL и накладные расходы сетевого стека. Это ускорило путь запроса к Images и дало команде независимый контроль над релизами binding.</p><p>В течение нескольких дней после выкатки мы получили первый отчёт от клиента.</p><h2>200 OK (не OK)</h2><p>Первый признак проблемы пришёл от клиента с нестандартной конфигурацией: два уровня обработки изображений, где один pipeline был вложен в другой.</p><p>Сначала их воркер с помощью Images binding компоновал несколько больших исходных изображений из R2 — JPEG-фон и PNG-оверлеи — в один объединённый JPEG. Затем результат дополнительно сжимался, транскодировался и изменялся размер через URL-интерфейс.</p><p>Баг возникал на обратном пути внутреннего pipeline, где ответ обрезался до того, как достигал внешнего.</p><p>Внутренний pipeline (transformation binding) отвечал за компоновку. Внешний pipeline (transformation URL) отвечал за оптимизации доставки: масштабирование и конвертацию формата. Такая многоуровневая схема означала, что когда внутренний pipeline молча возвращал обрезанный ответ, видимая ошибка появлялась на уровень выше:</p><p>Внешний pipeline получал от внутреннего HTTP 200 с заголовком Content-Length, который обещал несколько мегабайт. Фактическое тело было лишь частью от этого: в одном запросе из ожидаемых 3,3 МБ пришло всего около 200 КБ. Ошибка всплывала во внешнем pipeline, но обрезка могла произойти в binding, посреднике, сервисе Images или где-то между ними.</p><p>Когда браузер получает обрезанное изображение, результат виден невооружённым глазом. В зависимости от формата изображение либо отрисовывается частично (например, с отсутствующей или серой нижней частью), либо полностью не декодируется и отображается как битое.</p><h2>Отладка вслепую</h2><p>Отсюда мы двигались вглубь по пути запроса, проверяя каждый уровень, чтобы локализовать место обрезки. Некоторые усилия зашли в тупик; другие оставили зацепки, которые сузили поиск:</p><ul><li><b>Сборка репродукции.</b> Мы собрали воркер, повторяющий вложенную схему клиента, и постепенно убирали уровни, пока не смогли воспроизвести баг только с binding. Небольшой скрипт отправлял запросы пачками. На одном из первых прогонов 19 из 25 запросов упали. Объём данных, которые всё-таки приходили, — примерно 200 КБ — настораживающе близко к размеру сокетного буфера в продакшене. Это подтвердило, что проблема не связана с конфигурацией клиента, и дало надёжный способ воспроизводить баг по запросу.</li><li><b>Проверка таймаутов.</b> Сначала мы подозревали, что обрезка связана с поведением таймаутов (то есть соединение закрывалось по истечении лимита времени). Эта теория не подтвердилась: обрезка не коррелировала с длительностью запроса.</li><li><b>Обновление версии hyper.</b> Когда баг впервые зарепортили, мы использовали 0.14.x, а последняя версия hyper была около 1.8.x. Мы протестировали версии 0.14, 1.7 и 1.8 — на случай, если самый очевидный ответ окажется правильным (и самым простым). Но баг проявлялся в каждой версии, значит, upstream-исправления не было.</li><li><b>Локальное воспроизведение.</b> Мы запускали интеграционные тесты на macOS и Debian-виртуалке. Даже под значительной нагрузкой локальные запросы никогда не падали. Прямые curl-запросы к сокету binding и повтор отловленных запросов всегда работали. Баг проявлялся только на полном продакшен-пути при реальной конкурентности и реальном клиенте Workers runtime на другом конце сокета. Это натолкнуло нас на подозрение, что виноват сам runtime.</li><li><b>Исключение Workers runtime.</b> Мы изучили HTTP-клиент, который Workers runtime использует для связи с Images через сокет binding. Ни на одной из сторон соединения в трассировках не было системных вызовов, указывающих на неожиданное закрытие или преждевременное завершение. Клиент вёл себя корректно, и несколько других сервисов использовали того же клиента без проблем.</li><li><b>Распределённая трассировка.</b> Изучив сквозные трассировки запросов, мы подтвердили, что обрезанное тело уже присутствовало до того, как достигало внешнего уровня трансформации в схеме клиента. Это сузило проблему до внутреннего pipeline — пути binding через сервис Images.</li><li><b>Инструментирование посредника.</b> Мы добавили инструментирование в посредник, чтобы измерять размеры тел перед пересылкой ответа. Тела уже были обрезаны к моменту выхода из сервиса Images, так что посредник был исключён.</li><li><b>Углублённая трассировка внутри Images.</b> На уровне сервиса запрос обрабатывался, изображение корректно кодировалось, и ответ отправлялся с HTTP 200.</li></ul><p>Единственный постоянный сигнал заключался в том, что баг зависел от тайминга: он появлялся только на продакшен-пути, при реальной конкурентности и только для больших изображений.</p><h2>Зерно истины</h2><p>Инструменты отладки на уровне приложения показывали лишь то, что система <i>считала</i>, что делает. Но по мнению системы всё было в порядке: трассировка утверждала, что ответ отправлен; логи не сообщали об ошибках; сервис Images возвращал 200 на каждый запрос.</p><p>Чтобы увидеть, что система делала на самом деле, мы подключили strace к сервису Images. strace записывает системные вызовы, которые процесс делает ядру; это позволило показать, какие именно байты были записаны, когда вызывался shutdown и посылал ли клиент сигнал завершения.</p><p>Настройка трассировки была деликатной. strace работает, перехватывая системные вызовы по мере их выполнения, что добавляет небольшие накладные расходы по времени к каждому вызову. Фильтрация узкого набора системных вызовов держала эти накладные расходы минимальными. Однако расширение фильтра замедляло процесс ровно настолько, чтобы сдвинуть тайминг между flush и проверкой shutdown — и баг полностью исчезал. Одно это уже подкрепляло теорию о тайминг-зависимости проблемы.</p><p>Используя воркер-репродукцию, мы спровоцировали баг и сравнили вывод системных вызовов между успешными и упавшими запросами.</p><p>При успешном запросе ответ пишется кусками по мере освобождения сокетного буфера, а shutdown вызывается только после отправки всех данных. Например, это может выглядеть так:</p><p>Когда мы воспроизвели баг, упавший запрос выглядел так:</p><p>Здесь есть только одна запись — лишь заголовки и крошечная часть тела — перед немедленным вызовом shutdown. Из ответа в 14,9 МБ было отправлено около 219 КБ. Оставшиеся ~14,8 МБ данных изображения никогда не покидали внутренний буфер hyper, и не было никакого сигнала завершения от клиента между записью и shutdown. Вместо этого сервис Images преждевременно закрывал соединение самостоятельно, искренне полагая, что работа завершена.</p><p>Упавшие запросы подтвердили, что баг — состояние гонки, которое срабатывало непостоянно. Успех или провал зависели от того, перекрывались ли операции flush и shutdown, и это менялось от запроса к запросу. Когда буфер был полон в тот самый момент, когда hyper решил, что соединение завершено, данные терялись.</p><p>Когда читатель потребляет медленнее, чем hyper пишет, исходящий буфер заполняется. Если hyper закрывает соединение до того, как буфер опустеет, то лишь часть ответа попадает к посреднику; эти неполные данные пересылаются обратно в Workers runtime и клиенту.</p><p>Перестройка в декабре не ввела этот баг — он существовал в hyper годами в нескольких мажорных версиях. Но новый посредник изменил того, кто читал ответ на другом конце сокета. Наша рабочая теория: прежний посредник FL потреблял данные достаточно быстро, чтобы сокетный буфер редко заполнялся во время ответа. Новый читатель работал в темпе, который иногда позволял буферу заполняться при больших ответах.</p><p>Этих нескольких миллисекунд обратного давления, внесённых улучшением, которое ускорило всё остальное, хватило, чтобы выявить изъян, который скрывался на виду.</p><h2>Внутри цикла dispatch</h2><p>Жизненный цикл HTTP/1-соединения в hyper управляется конечным автоматом в файле под названием dispatch.rs. Он выполняет цикл, который читает запросы, пишет ответы, сбрасывает буфер записи в сокет и решает, когда закрываться. В упрощённом виде:</p><p>Точнее, именно let _ перед poll_flush — это место, где живёт баг.</p><p>В Rust let _ = expr отбрасывает результат выражения, включая Poll::Pending — сигнал о том, что flush ещё не завершён. В буфере flush может остаться несколько мегабайт, но цикл об этом никогда не узнаёт.</p><p>Когда запрос падает, последовательность событий выглядит так:</p><ol><li>Сервис Images завершает кодирование изображения и передаёт весь ответ hyper как один блок в памяти.</li><li>Hyper записывает блок во внутренний буфер и помечает состояние записи как Writing::Closed. С точки зрения кодирования работа сделана — кодировать больше нечего.</li><li>Hyper вызывает poll_flush, чтобы перенести буферизованные данные в сокет. В нашем примере сокет принял около 219 КБ. Оставшиеся ~14,8 МБ остаются в буфере hyper. Сокет полон, поэтому ядро возвращает Poll::Pending.</li><li>poll_loop отбрасывает Poll::Pending с помощью let _.</li><li>Он проверяет wants_read_again(). Полный запрос уже получен, поэтому возвращается false.</li><li>poll_loop возвращает Poll::Ready(Ok(())), сигнализируя, что цикл завершён, хотя flush ещё не сделан.</li><li>Срабатывает poll_shutdown(). Выполняется системный вызов SHUT_WR.</li><li>Клиент получает 219 КБ и EOF (end-of-file), указывающий, что соединение закрыто, хотя он ожидает 14,9 МБ.</li></ol><p>На втором шаге hyper помечает операцию записи как завершённую, как только тело ответа оказывается в буфере (то есть когда кодирование закончено), а не когда данные фактически сброшены. В большинстве случаев flush завершается за один проход, и это различие незаметно. В редких случаях, когда сокетный буфер полон, flush приходится ждать — но hyper не ждёт. Байты всё ещё сидят в буфере hyper, ожидая сброса в сокет. Hyper при этом закрывает соединение с этими данными всё ещё в буфере.</p><p>Это также объясняет, почему curl никогда не воспроизводил баг. Curl читает данные так быстро, как они приходят: сокетный буфер никогда не заполняется, flush всегда завершается мгновенно, и отброшенное возвращаемое значение безобидно. Продакшен-путь с читателем, который иногда паузил на несколько миллисекунд, был единственной конфигурацией, где буфер заполнялся в нужный момент.</p><h2>Не забывайте сбрасывать буфер</h2><p>После недель расследования само исправление было концептуально простым. Hyper должен был проверять, завершён ли flush, прежде чем двигаться дальше.</p><p>Наш reproduction-воркер подтвердил, что баг существует, но не мог объяснить, почему падает конкретный запрос. Прежде чем писать исправление, нам нужен был тест, который мог бы спровоцировать точные сокетные условия внутри hyper.</p><p>Мы знали условия, вызывающие баг: сокет, который принимает один кусок данных, а затем блокируется. Для контролируемого сценария мы построили обёртку вокруг TCP-потока, имитирующую полный сокетный буфер. Обёртка принимала 8 КБ при первой записи, а затем возвращала Poll::Pending на каждой последующей записи, имитируя читателя, который перестал опустошать буфер.</p><p>Тест отправлял 500 КБ ответа через этот ограниченный сокет и проверял, вызывает ли hyper shutdown, пока в буфере остаётся 492 КБ. Без исправления — вызывал. С исправлением — ждал.</p><p>Сначала мы применили исправление в цикле dispatch hyper. Вместо отбрасывания результата poll_flush мы проверяли, завершён ли flush на самом деле:</p><p>Если flush не завершён, цикл возвращает Poll::Pending асинхронному runtime. Runtime ждёт, пока сокет не станет доступен для записи, а затем будит задачу, чтобы продолжить flush. Соединение закрывается только после того, как все данные отправлены.</p><p>Когда мы выкатили это исправление, мы увидели, что записан каждый байт, а shutdown вызывался только после того, как буфер действительно опустел. Клиент, который сделал первый репорт, тоже подтвердил, что проблема исчезла.</p><p>Хотя первоначальное решение работало, цикл dispatch был неправильным местом для исправления. Ранний возврат Poll::Pending мог замедлять другие операции на том же соединении, уменьшая частоту опроса чтения и вызывая нежелательное обратное давление. Также это корректно не обрабатывает keepalive-соединения, где одно соединение обрабатывает несколько запросов подряд — они должны оставаться пригодными к использованию, даже пока предыдущий ответ всё ещё сбрасывается. Ни одна из этих проблем не затрагивала наш сервис (где keepalive отключён), но обе могли повлиять на других пользователей hyper, если бы исправление было предложено upstream.</p><p>Мы проследили жизненный цикл соединения hyper и нашли более точечный подход. Вместо изменения поведения цикла dispatch мы применили исправление в том месте, где shutdown вызывается на самом деле. Перед закрытием сокета hyper должен сначала сбросить оставшиеся данные в буфере:</p><p>Это оставляет цикл dispatch без изменений. Flush добавляется только в тот точный момент, где иначе произошла бы потеря данных — непосредственно перед shutdown.</p><h2>Что осталось с нами</h2><p>Ни один из инструментов на уровне приложения не выдавал ошибок, падений или полезных записей в логе. Наблюдаемость на уровне приложения может иметь слепое пятно для багов, которые живут ниже её уровня осознанности.</p><p>Сбой происходил непостоянно, масштабировался с размером ответа, не воспроизводился простыми инструментами вроде curl и исчезал, когда мы наблюдали за системой внимательнее. Эти сигналы указывали на тайминг-зависимый баг в слое соединения, а не в логике приложения.</p><p>Прорыв случился благодаря инструментарию уровня ядра — strace, единственному слою, который фиксирует, что на самом деле происходило на сокете. Базовый баг жил в нескольких миллисекундах между частичным flush и преждевременным shutdown — окне, которое открылось только после того, как мы ускорили систему.</p><p>Мы влили исправление и детерминированный тест в hyperium/hyper через PR #4018. Оно появится в будущем релизе hyper, гарантируя, что любой сервис, использующий HTTP/1-реализацию hyper, не потеряет данные ответа из-за того же состояния гонки.</p><p>Пока мы используем внутренний форк с применённым патчем. Это исправление стабилизировало архитектуру binding, создав надёжную основу для расширения его функциональности.</p><p>Изначально Images binding покрывал только трансформации удалённых изображений. В начале этого месяца мы объявили, что Images binding теперь поддерживает операции для hosted-изображений, давая разработчикам единый способ строить медиа-насыщенные приложения на Cloudflare.</p><p>Подробнее о том, как работает binding, — в нашей <a href="https://developers.cloudflare.com/images/worker-bindings/">документации</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как ломаются мобильные приложения</title>
      <link>https://tproger.ru/articles/kak-lomayutsya-mobilnye-prilozheniya</link>
      <comments>https://tproger.ru/articles/kak-lomayutsya-mobilnye-prilozheniya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дима Дмитриев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-lomayutsya-mobilnye-prilozheniya</guid>
      <description><![CDATA[<p>Пять кейсов из практики мобильного тестирования: промокоды с разными требованиями, баг RatingBar на Samsung, пуши на iPad, UI без скролла и висящие WebSocket. Как баги прячутся на стыке систем.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-lomayutsya-mobilnye-prilozheniya">Как ломаются мобильные приложения</a>»</p>]]></description>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 20 May 2026 05:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>В мобильной разработке баг часто появляется не в одном экране, а на стыке нескольких частей системы. Веб создаёт данные, бэкенд их обрабатывает, мобильное приложение показывает результат пользователю, а релиз зависит от App Store, Google Play, устройства и версии ОС. Когда требования к каждой из этих частей пишутся отдельно — и никто не проверяет их вместе — появляются баги.</p><p>В Centicore Group разобрали несколько кейсов из практики мобильного тестирования: промокоды, которые нельзя активировать, рейтинг, который считал лишнюю звезду, пуши на iPad, UI, который ломал рабочий сценарий, и WebSocket, который оставался висеть после выхода из чата.</p><h2>Промокод, который нельзя ввести</h2><p>Медицинское приложение: есть врачи, пациенты и записи на услуги. В какой-то момент понадобились промокоды. Механика простая: пациент активирует промокод — подключается подписка на мониторинг, например, по гипертензии на месяц или три. Аналитик написал спецификацию, разработчики сделали ровно то, что написано. Фичу разбили на две итерации: сначала создание промокодов в вебе, потом активация в мобилке.</p><p>Проблема была в том, что требования к двум частям фичи никто не сравнивал между собой. Поле для активации в мобилке покрыто валидацией: максимум 20 символов, только латиница, никаких пробелов и спецсимволов. А форма создания в вебе — без каких-либо ограничений. Врач или администратор клиники мог написать в префиксе что угодно: кириллицу, кавычки, восклицательный знак, теоретически SQL-инъекцию (на боевом сервере не проверяли). Промокоды выпускались пачками и уходили пациентам.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2026-05-19/3a9a2d14-e892-4d01-9aad-b8b3cd4727b4.webp" alt="" /></figure><p>При формальной проверке обе части могли пройти тесты. Веб соответствовал документации первой итерации, мобильное приложение — документации второй. Никто не прошёл полный маршрут от создания до активации. А в реальной жизни врач отправляет промокод, пациент вводит и система ломается на стыке двух платформ с разными требованиями.</p><p>Фиксить нужно мобилку, потому что генерация уже работает. А мобилка на тот момент полностью нативная, без динамических обновлений, значит полный цикл хотфикса: собрать сборку, отправить на ревью в App Store и Google Play, дождаться одобрения, выпустить. Многие крупные игроки к тому времени уже работали с динамическими обновлениями, это приложение нет.</p><p>Нашли вовремя, потому что тестировщик прошёл полный пользовательский маршрут от создания промокода в вебе до активации в мобилке — чего не делали в рамках формальной проверки каждой итерации отдельно.</p><h2>Samsung, одна звезда и RatingBar</h2><p>В финтех-приложении стандартная функция обратной связи — Voice of Client: пользователь оценивает консультацию в чате звёздами. Тестировщик нажимает на первую звезду — выбираются две. Нажимает на вторую — выбирается третья.</p><p>Первая гипотеза — кривой экран. Проверили на других устройствах: у коллег всё работает нормально. Версия ОС у всех Android 15, тестовый пользователь один и тот же, сборка идентичная. Добавить тестовое устройство в изолированную сеть банковского приложения непросто, но сделали. После нескольких итераций проверок выяснили, что баг воспроизводится только на живом железе Samsung — на эмуляторах и других производителях чисто.</p><p>Причина нашлась в устройстве компонента: Android RatingBar глубоко в иерархии наследует ProgressBar, а звёзды в нём скорее декоративный слой. Когда пользователь касается звезды, компонент вычисляет рейтинг через округление на основе параметра stepSize. На Samsung это округление стабильно уходило в большую сторону: касание по краю четвёртой звезды давало рейтинг 5. Фикс простой — stepSize выставляется в 0.01, а итоговое округление до целого числа делается вручную в листенере. После этого поведение стало одинаковым на всём железе.</p><p>В команде уже было три устройства на тестировщика, в таких условиях регресс занимает около двух недель и тысячу проверок. Покрыть весь зоопарк Android-производителей нереально, поэтому такие баги остаются невидимыми до тех пор, пока кто-то не возьмёт в руки телефон конкретной модели и версии.</p><h2>iPadOS, которого не существовало</h2><p>На iOS тестировать одно удовольствие: Apple настолько плотно контролирует железо, что ситуация, когда баг есть на iPhone 13, но нет на iPhone 15, практически невозможна. Граница, на которой что-то ломается в яблочной экосистеме, проходит между iPhone и iPad.</p><p>В финтех-приложении перестали работать пуш-уведомления на iPad. Сборка идентичная с iPhone, версия та же, настройки одинаковые. На iPhone регистрация в пуш-сервисе проходила штатно: приложение отправляло запрос на регистрацию устройства и получало подтверждение. На iPad в ответ прилетал код 400 с ошибкой «Provider UUID not found».</p><p>Первое подозрение было на заголовки запроса, но заголовки оказались чистыми. Причина нашлась в теле JSON-запроса: iPad и iPhone используют разные названия операционной системы: на телефоне это iOS, на планшете — iPadOS. Приложение передавало это название через атрибут OSName как есть, без нормализации. Бэкенд знал только «iOS» — при получении «iPadOS» возвращал ошибку, потому что такого значения в его словаре не существовало. Одна строка в JSON оставила целую категорию устройств без уведомлений.</p><p>Пофиксили на бэке: при получении значения «iPadOS» оно автоматически заменялось на «iOS» перед любой дальнейшей обработкой. Решение в лоб, зато надёжное. Могло быть значительно сложнее: Huawei без Google-сервисов с пуш-уведомлениями это отдельный класс проблем, где просто заменить одну строку — не получится.</p><h2>Когда никто не проверял крайние сценарии</h2><p>В медицинском приложении есть форма регистрации врача со списком специальностей в виде интерактивных тегов-кнопок (в мобильном UI их называют чипсами). Разработчик написал компонент и проверил на тестовых данных с двумя-тремя тегами — всё выглядело нормально. Но никто не проверял сценарий, где врач ведёт восемь специализаций. При большом количестве тегов список не помещается в поле и должен появляться скролл — он не появлялся. Часть специальностей просто не попадала в видимую область, и зарегистрировать такого врача нормально не получалось.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2026-05-19/d1818840-d638-4584-9add-0d6d5952db77.webp" alt="" /></figure><p>Та же логика, другое приложение. В финтехе бот поддержки при вопросе о выводе средств предлагал выбрать счёт через кнопку в нижней части экрана. При стандартном количестве счетов кнопка с фиксированным позиционированием отображалась нормально. Если клиент открыл счетов много, список вытеснял кнопку за границу экрана, прокрутить до неё было нельзя. Клиент видел незавершённый диалог с ботом — и кнопку, до которой физически не добраться.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2026-05-19/da54536e-0817-4ae2-9f2c-94589276755c.webp" alt="" /></figure><p>В том же финтех-приложении чат поддержки работал через WebSocket: одно соединение на сессию, весь обмен сообщениями через него. По стандарту при выходе из чата соединение закрывается кодом «1000», при следующем входе открывается новое. В этом приложении соединение не закрывалось: код не отправлялся, сокет оставался активным. При повторном входе открывалось второе соединение, потом третье. Висящие сокеты начали копиться, а приложение падало.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2026-05-19/82bd09f9-c6b7-419d-83b3-0b0680122584.webp" alt="" /></figure><p>Источник проблемы оказался в архитектурном решении: приложение переиспользовало библиотеку основного банковского продукта. Команда, которая поддерживает эту библиотеку, разрабатывает её под свои задачи и не обязана думать о поведении в чужом приложении. Обновление в основном продукте может сломать что угодно в зависимом, и узнают об этом только на регрессе.</p><h2>Где прячутся баги, которых нет в документации</h2><p>Все кейсы объединяет одно: формально каждая часть системы работала корректно. Промокоды создавались по спецификации, рейтинг рендерился правильно на эмуляторах, пуши регистрировались на iPhone, чат отправлял сообщения.</p><p>Баги появлялись только тогда, когда две части системы впервые встречались в реальных условиях — с реальными данными, железом и пользователем.</p>]]></content:encoded>
    </item>
    <item>
      <title>Баг в Bun мог стать причиной утечки исходного кода Claude Code — его знали за 20 дней до инцидента</title>
      <link>https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--</link>
      <comments>https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей Михайлишин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--</guid>
      <description><![CDATA[<p>Баг в Bun генерировал source maps в production mode. Через 20 дней утекли 500 000+ строк кода Claude Code через npm. Разбираем хронологию, причины и уроки.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/bag-v-bun-mog-stat-prichinoj-utechki-ishodnogo-koda-claude-code--">Баг в Bun мог стать причиной утечки исходного кода Claude Code — его знали за 20 дней до инцидента</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Утечка данных]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 01 Apr 2026 03:16:48 GMT</pubDate>
      <content:encoded><![CDATA[<p>31 марта 2026 года через npm утекли более 500 000 строк исходного кода Claude Code — флагманского ИИ-инструмента Anthropic. Причиной стал файл source map (карта исходного кода, связывающая минифицированный бандл с оригинальными файлами), который не должен был попасть в публичный пакет. Мы <a href="https://tproger.ru/news/ishodnyj-kod-claude-code-utyok-cherez-source-map-v-npm---512-000-s">уже разбирали</a>, что нашли внутри. Теперь сообщество обратило внимание на возможную техническую причину — баг в Bun, рантайме, который Anthropic сама <a href="https://bun.com/blog/bun-joins-anthropic">приобрела</a> четырьмя месяцами ранее.</p><ul><li>За 20 дней до утечки в баг-трекере Bun зафиксирован <a href="https://github.com/oven-sh/bun/issues/28001">баг #28001</a>: source maps отдаются в production mode, хотя документация утверждает обратное.</li><li>Claude Code собирается и работает на Bun — рантайме, который Anthropic приобрела в декабре 2025 года.</li><li>Утечка произошла через файл source map размером 59,8 МБ, попавший в npm-пакет @anthropic-ai/claude-code версии 2.1.88.</li><li>Anthropic назвала инцидент «ошибкой при упаковке релиза, вызванной человеческим фактором». Формальный постмортем не опубликован.</li><li>Прямого подтверждения связи между багом #28001 и утечкой нет, но техническое совпадение слишком точное, чтобы его игнорировать.</li></ul><h2>Хронология: от покупки Bun до утечки</h2><p><b>2 декабря 2025</b> — Anthropic <a href="https://bun.com/blog/bun-joins-anthropic">объявляет о покупке Bun</a>, JavaScript-рантайма, на котором работает Claude Code. Создатель Bun Джаред Самнер (Jarred Sumner) переходит в Anthropic. Компания обещает сохранить Bun как open-source проект с MIT-лицензией. К этому моменту Claude Code уже <a href="https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone">приносит $1 млрд</a> годовой выручки.</p><p><b>11 марта 2026</b> — в баг-трекере Bun появляется <a href="https://github.com/oven-sh/bun/issues/28001">issue #28001</a>: при использовании Bun.serve() с параметром development: false source maps всё равно генерируются и отдаются клиенту. Документация Bun явно указывает, что в production mode source maps должны быть отключены. Баг зафиксирован на версии 1.3.10.</p><p><b>31 марта 2026</b> — разработчик Чаофан Шоу (Chaofan Shou, <a href="https://x.com/shoucccc">@shoucccc</a>) обнаруживает, что в npm-пакете @anthropic-ai/claude-code версии 2.1.88 лежит файл cli.js.map размером 59,8 МБ. Внутри — полный исходный код: более 500 000 строк TypeScript в ~1900 файлах. Твит набирает более 21 миллиона просмотров.</p><h2>Что за баг в Bun</h2><p>Баг #28001 зафиксирован в подсистеме Bun.serve() — встроенном HTTP-сервере Bun с функцией бандлинга. Суть проблемы: даже когда разработчик явно задаёт development: false, source maps всё равно генерируются и включаются в собранные файлы. Это противоречит <a href="https://bun.com/docs/bundler/fullstack#fullstack-dev-server">документации Bun</a>.</p><p>Claude Code собирается в один файл для публикации в npm через бандлер Bun. Формально баг #28001 описывает проблему в Bun.serve(), а не в автономном бандлере bun build. Однако обе подсистемы используют один и тот же механизм генерации source maps, и сообщество считает вероятным, что проблема затрагивает обе. На момент утечки issue оставался открытым и без фикса.</p><p>Само по себе наличие source map в сборке — ещё полбеды. Проблема в том, что в конвейере публикации Claude Code не было ни записи *.map в .npmignore, ни ограничения поля files в package.json. Если бы любой из этих защитных механизмов работал, source map не попала бы в npm-пакет даже при наличии бага. Сочетание двух факторов — баг в рантайме и отсутствие фильтрации при публикации — привело к утечке.</p><h2>Почему сообщество обратило на это внимание</h2><p>На <a href="https://www.reddit.com/r/programming/comments/1s8t8hp/">Reddit</a> и Hacker News историю обсуждают не столько из-за технической сложности бага — он тривиален. Внимание привлекла ирония ситуации:</p><ul><li>Anthropic купила Bun в декабре 2025 года. Claude Code — главный продукт компании, и он работает именно на Bun.</li><li>Баг в Bun, связанный с source maps, был известен за 20 дней до утечки, но не был исправлен.</li><li>Anthropic позиционирует себя как «safety-first» ИИ-компанию — и при этом допустила два инцидента с утечками за одну неделю: утечка черновиков блога о модели Mythos 26 марта и source map 31 марта.</li><li>Компания активно рассказывает, что её ИИ пишет значительную часть кода Bun — и именно в этом коде нашёлся баг, приведший к утечке кода ИИ.</li></ul><blockquote>Случайно отправить source map в npm — ошибка, которая кажется невозможной, пока не вспомнишь, что значительная часть кодовой базы, вероятно, была написана тем самым ИИ, которого ты релизишь.</blockquote><h2>Что нашли внутри: краткая сводка</h2><p>Мы подробно разбирали содержимое утечки в <a href="https://tproger.ru/news/ishodnyj-kod-claude-code-utyok-cherez-source-map-v-npm---512-000-s">предыдущем материале</a>. Ключевые находки:</p><ul><li><b>KAIROS</b> — нерелизованный режим автономного фонового агента, который работает как демон, подписывается на GitHub-вебхуки и «видит сны» ночью, консолидируя память.</li><li><b>Undercover Mode</b> — режим, при котором Claude скрывает свою природу ИИ и убирает атрибуцию Co-Authored-By в коммитах. Принудительного отключения нет.</li><li><b>ANTI_DISTILLATION</b> — внедрение фейковых инструментов в API-запросы для отравления обучающих данных конкурентов.</li><li><b>BUDDY</b> — тамагочи в терминале с 18 видами существ, уровнями редкости и RPG-характеристиками.</li><li>Внутренние бенчмарки, кодовые имена моделей (Capybara, Fennec, Numbat) и 44 фича-флага.</li></ul><h2>Реакция Anthropic</h2><p>Anthropic удалила npm-пакет в течение нескольких часов и <a href="https://www.theregister.com/2026/03/31/anthropic_claude_code_source_code/">заявила</a>, что утечка — «ошибка при упаковке релиза, вызванная человеческим фактором, а не брешь в безопасности». Пользовательские данные, ключи API и учётные записи не были скомпрометированы. Компания начала рассылать DMCA-уведомления зеркалам на GitHub, но формальный постмортем не опубликовала.</p><p>Тем временем корейский разработчик Сигрид Джин (Sigrid Jin) создал clean-room переписку архитектуры Claude Code на Python под названием <a href="https://github.com/instructkr/claw-code">claw-code</a>. Репозиторий набрал 50 000 звёзд за два часа — <a href="https://github.com/instructkr/claw-code">по заявлению авторов</a>, это рекорд GitHub. Сейчас проект переписывается на Rust.</p><h2>Что это значит для разработчиков</h2><p>Если вы используете Bun для сборки production-кода, стоит проверить свой конвейер публикации:</p><ol><li>Проверьте, не попадают ли файлы .map в ваши npm-пакеты. Используйте npm pack --dry-run для аудита содержимого перед публикацией.</li><li>Добавьте *.map в .npmignore или явно перечислите разрешённые файлы в поле files в package.json. Второй вариант надёжнее — вместо списка исключений вы задаёте список включений.</li><li>Следите за <a href="https://github.com/oven-sh/bun/issues/28001">issue #28001</a> в репозитории Bun — на момент публикации баг не исправлен.</li><li>Если публикуете пакеты через CI/CD — добавьте шаг валидации, который проверяет отсутствие source maps в артефакте.</li></ol><h2>Итог</h2><p>История с утечкой Claude Code — наглядный пример того, как цепочка мелких упущений приводит к масштабному инциденту. Возможный баг в рантайме, отсутствие фильтрации при публикации, недостаточный аудит CI/CD-пайплайна — по отдельности каждая из этих проблем тривиальна. Вместе они обнажили более 500 000 строк проприетарного кода компании с оценкой выше $100 млрд.</p><p>Для разработчиков урок простой: не доверяйте дефолтам инструментов сборки и всегда проверяйте, что именно попадает в ваши пакеты. Команда npm pack --dry-run занимает секунды и может предотвратить утечку, которая изменит историю вашего продукта.</p>]]></content:encoded>
    </item>
    <item>
      <title>Microsoft признала: после январского обновления Windows 11 некоторые ПК перестали загружаться</title>
      <link>https://tproger.ru/news/microsoft-priznala--posle-yanvarskogo-obnovleniya-windows-11-nekot</link>
      <comments>https://tproger.ru/news/microsoft-priznala--posle-yanvarskogo-obnovleniya-windows-11-nekot?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/microsoft-priznala--posle-yanvarskogo-obnovleniya-windows-11-nekot</guid>
      <description><![CDATA[<p>Microsoft признала баг: январское обновление Windows 11 вызывает UNMOUNTABLE_BOOT_VOLUME, и ПК перестают загружаться после</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/microsoft-priznala--posle-yanvarskogo-obnovleniya-windows-11-nekot">Microsoft признала: после январского обновления Windows 11 некоторые ПК перестали загружаться</a>»</p>]]></description>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[Microsoft]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Windows 11]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 26 Jan 2026 02:11:11 GMT</pubDate>
      <content:encoded><![CDATA[<p>Свежее обновление безопасности от <b>Microsoft</b> обернулось очередными проблемами для пользователей <b>Windows 11</b>.</p><p>Компания официально <a href="https://www.neowin.net/news/windows-11-patch-tuesday-horrors-continue-as-some-pcs-now-cannot-boot/" rel="nofollow">подтвердила</a>: после установки патчей за январь 2026 года, часть компьютеров <b>перестает загружаться</b> и уходит в цикл ошибки с кодом UNMOUNTABLE_BOOT_VOLUME.</p><h2>Что происходит с системой</h2><p>Речь идет об обновлении, начиная с <b>KB5074109</b>, выпущенного 13 января 2026 года, а также о более поздних внеплановых патчах. После установки апдейта, система может не дойти до рабочего стола и показать черный экран с сообщением:</p><p><i>Your device ran into a problem and needs a restart.</i></p><p>Дальше — тупик. Windows не может завершить загрузку и требует ручного восстановления.</p><h2>Microsoft подтверждает проблему</h2><p>Информация появилась в <b>Microsoft Admin Center</b>, где компания сообщила, что получила «ограниченное число отчетов» о сбое загрузки:</p><blockquote>Устройства не могут загрузиться из-за ошибки “UNMOUNTABLE_BOOT_VOLUME” после установки январского обновления безопасности Windows 11 и последующих апдейтов. На текущий момент система не может завершить запуск и требует ручных шагов восстановления.</blockquote><h2>Какие версии Windows 11 затронуты</h2><p>По данным Microsoft, проблема касается:</p><ul><li>Windows 11 24H2;</li><li>Windows 11 25H2;</li><li>локальных клиентских ПК (не серверов).</li></ul><p>Речь идет именно о пользовательских машинах, а не корпоративных VDI или облачных конфигурациях.</p><h2>Что делать пользователям</h2><p>Пока не то чтобы есть универсальные варианты действий. Microsoft прямо говорит о необходимости <b>“ручных шагах восстановления”</b>, но <b>не публикует конкретную инструкцию</b>, как именно восстановить систему. Компания лишь сообщает, что расследует первопричину и поделится деталями позже.</p><p>На практике это означает:</p><ul><li>загрузку в WinRE;</li><li>попытки восстановления загрузочного раздела;</li><li>откат обновлений или ручной ремонт системы — для опытных пользователей и администраторов.</li></ul><h2>Ирония ситуации: Quick Machine Recovery не спасает</h2><p>Особенно иронично это выглядит на фоне того, что еще в 2024 году Microsoft анонсировала механизм <b>Quick Machine Recovery</b>. Это система экстренного восстановления Windows через WinRE для таких вот критических сбоев. Тогда же компания даже «переосмыслила» легендарный BSOD.</p><p>Но в реальности, как показывает январь 2026-го, этот механизм <b>пока не помогает автоматически</b>: Microsoft все еще ищет причину, а пользователи остаются один на один с не загружающейся системой.</p>]]></content:encoded>
    </item>
    <item>
      <title>cURL отказалась платить за найденные уязвимости из-за наплыва ИИ-мусора</title>
      <link>https://tproger.ru/news/curl-otkazalas-platit-za-najdennye-uyazvimosti-iz-za-naplyva-ii-musora</link>
      <comments>https://tproger.ru/news/curl-otkazalas-platit-za-najdennye-uyazvimosti-iz-za-naplyva-ii-musora?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/curl-otkazalas-platit-za-najdennye-uyazvimosti-iz-za-naplyva-ii-musora</guid>
      <description><![CDATA[<p>cURL отключает bug bounty из-за наплыва ИИ-отчетов: мейнтейнеры устали от мусора и хотят защитить проект от выгорания команд</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/curl-otkazalas-platit-za-najdennye-uyazvimosti-iz-za-naplyva-ii-musora">cURL отказалась платить за найденные уязвимости из-за наплыва ИИ-мусора</a>»</p>]]></description>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 21 Jan 2026 04:44:47 GMT</pubDate>
      <content:encoded><![CDATA[<p>Один из самых известных open-source проектов в мире — <b>cURL</b> — прекращает выплаты <i>bug bounty</i>.</p><p>Причина звучит непривычно, но показательно для 2026 года: проект захлебнулся в ИИ-сгенерированных отчетах об уязвимостях, подавляющее большинство которых оказалось бесполезным просто шумом.</p><h2>«Смерть от тысячи слопов»</h2><p>Мейнтейнер cURL <b>Дэниел Стенберг</b> еще в прошлом году описал ситуацию формулой <i>Death by a thousand slops</i>. ИИ-инструменты сделали подачу баг-репортов дешевой и массовой. Но от того глупой и зачастую пустой.</p><p>По его словам, разбор таких отчетов отнимает огромное количество времени. Поэтому с конца января cURL полностью отключает денежные вознаграждения за найденные уязвимости.</p><p>За все время существования программы было выплачено около <b>$101 000 за 87 отчетов</b>. Сумма скромная, но достаточная, чтобы создать неправильные стимулы у любителей халявы.</p><h2>Не весь ИИ — мусор, но проверять приходится все</h2><p>Важный момент: Стенберг подчеркивает, что ИИ-помощь сама по себе не зло. Он знает <b>более сотни полезных баг-репортов</b>, где ИИ действительно помог найти реальные проблемы.</p><p>Проблема в другом: чтобы понять, хороший отчет или бессмысленный, мейнтейнеру все равно нужно вручную разбираться в коде. И когда «мусорных» отчетов становится на порядки больше, проект платит за это своим временем и выгоранием.</p><h2>Неожиданный союзник: охотник за уязвимостями</h2><p>Любопытно, что решение cURL поддержал известный исследователь безопасности <b>Джошуа Роджерс</b>, который сам активно использует ИИ для поиска багов. Правда, он делает это успешно.</p><p>При этом есть ключевое отличие его от тех, из-за кого cURL свою программу сворачивает — Роджерс <b>не отправляет отчеты вслепую</b>. ИИ для него — инструмент анализа, а не автоматический генератор тикетов.</p><p>По его мнению, отключение выплат — давно назревший шаг:</p><h2>Деньги — не главный стимул</h2><p>Роджерс также считает, что исчезновение наград не приведет к катастрофе. Для серьезных исследователей главная награда — <b>репутация</b>, а <b>не $5000 – $10 000</b>.</p><p>При этом он признает перекос: для людей из бедных регионов даже небольшая выплата может быть значимой. Но ценность самой уязвимости для проекта от этого не меняется — а вот нагрузка на разработчиков растет одинаково.</p>]]></content:encoded>
    </item>
    <item>
      <title>Microsoft сломала localhost в Windows 11 — миллионы разработчиков не могут локально тестить проекты</title>
      <link>https://tproger.ru/news/microsoft-slomala-localhost-v-windows-11---milliony-razrabotchikov-ne-mogut-lokalno-testit-proekty</link>
      <comments>https://tproger.ru/news/microsoft-slomala-localhost-v-windows-11---milliony-razrabotchikov-ne-mogut-lokalno-testit-proekty?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/microsoft-slomala-localhost-v-windows-11---milliony-razrabotchikov-ne-mogut-lokalno-testit-proekty</guid>
      <description><![CDATA[<p>Обновление Windows 11 KB5066835 сломало localhost: HTTP.sys перестал работать, и миллионы разработчиков не могут тестировать проекты</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/microsoft-slomala-localhost-v-windows-11---milliony-razrabotchikov-ne-mogut-lokalno-testit-proekty">Microsoft сломала localhost в Windows 11 — миллионы разработчиков не могут локально тестить проекты</a>»</p>]]></description>
      <category><![CDATA[Stack Overflow]]></category>
      <category><![CDATA[Отладка]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[Microsoft]]></category>
      <category><![CDATA[Windows 10]]></category>
      <category><![CDATA[.NET]]></category>
      <category><![CDATA[Asp.NET]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Windows 11]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 17 Oct 2025 10:31:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Не прошло и двух дней после официального завершения поддержки Windows 10, как пользователи <b>Windows 11</b> <a href="https://www.techpowerup.com/341976/microsoft-breaks-localhost-with-windows-11-october-update-users-forced-to-revert">столкнулись</a> с серьезной ошибкой.</p><p>Последнее октябрьское обновление системы — <b>KB5066835</b> — нарушило работу localhost, что фактически лишило разработчиков возможности тестировать веб-приложения локально.</p><p>Проблему активно обсуждают на <b>Microsoft Support Forums</b>, <b>Stack Overflow</b> и <b>Server Fault</b>: серверы перестали отвечать на запросы, отвалилась отладка в Visual Studio, а ASP.NET-приложения не компилируются.</p><p>По оценкам экспертов, сбой затронул <b>миллионы веб-разработчиков и инженеров</b>, использующих Windows 11 в качестве локальных серверов и рабочих станций.</p><h2>Что пошло не так</h2><p>Пакет <b>KB5066835</b> был задуман как <b>обновление безопасности</b>, улучшавшее сентябрьский релиз <b>KB5065789</b>, но именно он привел к поломке <b>HTTP.sys</b> — системного компонента ядра Windows, отвечающего за маршрутизацию локального HTTP-трафика.</p><p>После установки апдейта, <b>localhost перестал отвечать на запросы</b>, а соединения по HTTP/2 начали обрываться.</p><h2>Как временно исправить</h2><p>Microsoft пока официально не подтвердила проблему, но сообщество уже нашло обходное решение:</p><ol><li>Удалить октябрьское обновление <b>KB5066835</b>.</li><li>Если не помогло — удалить и предыдущее <b>KB5065789</b>.</li><li>После этого localhost начинает работать штатно. Однако это <b>временная мера</b> и пользователи рискуют остаться без последних исправлений безопасности.</li></ol><h2>Неудачи Microsoft продолжаются</h2><p>Это уже <b>второй крупный сбой за неделю</b>. В понедельник компания случайно «сломала» <b>Media Creation Tool</b> — утилиту для установки Windows — всего за день до окончания поддержки Windows 10.</p><p>А на прошлой неделе Microsoft окончательно обязала пользователей <b>входить в Windows 11 только через онлайн-аккаунт</b>, что вызвало волну критики.</p>]]></content:encoded>
    </item>
    <item>
      <title>Cloudflare поймала редкий баг в компиляторе Go для arm64: падения при разворачивании стека</title>
      <link>https://tproger.ru/news/cloudflare-pojmala-redkij-bag-v-kompilyatore-go-dlya-arm64--padeniya-pri-razvorachivanii-steka</link>
      <comments>https://tproger.ru/news/cloudflare-pojmala-redkij-bag-v-kompilyatore-go-dlya-arm64--padeniya-pri-razvorachivanii-steka?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/cloudflare-pojmala-redkij-bag-v-kompilyatore-go-dlya-arm64--padeniya-pri-razvorachivanii-steka</guid>
      <description><![CDATA[<p>На arm64 Go обнаружен баг: async preemption могла прервать корректировку стека и вызвать краш в runtime.(*unwinder).next. Исправлено в Go 1.24.6, 1.23.12 и 1.25.0+.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/cloudflare-pojmala-redkij-bag-v-kompilyatore-go-dlya-arm64--padeniya-pri-razvorachivanii-steka">Cloudflare поймала редкий баг в компиляторе Go для arm64: падения при разворачивании стека</a>»</p>]]></description>
      <category><![CDATA[Компиляторы]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Cloudflare]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 09 Oct 2025 11:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Cloudflare опубликовала технический <a href="https://blog.cloudflare.com/how-we-found-a-bug-in-gos-arm64-compiler/">разбор</a> редкой, но критичной проблемы в экосистеме Go: из-за ошибки в генерации кода для arm64 в некоторых случаях возникала гонка на уровне одной инструкции, приводившая к крашам рантайма при разворачивании стека (stack unwinding) в runtime.(*unwinder).next.</p><p>Cloudflare связала учащение «фатальных паник» на arm64 с кодом, где выполнялась работа с Netlink, но корневая причина оказалась глубже — в связанке компилятор ↔ ассемблер Go для arm64 и в том, как разбивалось большое смещение стека на две инструкции ADD со «сдвигом на 12 бит». Если прерывание попадало между этими двумя ADD, стековый указатель оказывался «наполовину скорректирован», и трассировщик стека считывал нерелевантные данные как адрес возврата, что заканчивалось segfault/фатальной паникой. Схожие аварии ранее <a href="https://github.com/golang/go/issues/73259">отмечали</a> и другие команды, в частности Datadog: именно в runtime.(*unwinder).next на Linux/arm64 (Go 1.23.x/1.24.x).</p><h2>Что именно ломалось</h2><ul><li>Сценарий сбоя: асинхронная предвыборка (preemption) <a href="https://github.com/golang/go/issues/63830">попадает</a> между двумя инструкциями корректировки SP (split ADD), после чего GC/трассировка начинают разворачивать некорректный фрейм, обращаясь по невалидному SP. Итог — падение в (*unwinder).next.</li><li>Подтверждение на практике: сходные трассы стеков и «рандомные» крэши на arm64 фиксировались у сторонних команд; проблема оформлена в ишью #73259 в репозитории Go и была <a href="https://github.com/golang/go/issues/73259">признана</a> кандидатом на бэкпорт.</li></ul><h2>Как починили (и какие версии содержат фикс)</h2><p>Исправление прошло через бэкпорт и доступно в поддерживаемых ветках Go:</p><ul><li>Go 1.24.6 — явное упоминание бэкпорта по <a href="https://github.com/golang/go/issues/74694">ишью #73259</a>.</li><li>Go 1.23.12 — <a href="https://go.dev/doc/devel/release">минор</a> с правками рантайма (включая бэкпорт по связанным arm64-сбоям).</li><li>Go 1.25.0 и новее — <a href="https://tip.golang.org/doc/go1.25">фиксация</a> на мажорной ветке.</li></ul><p>Суть изменения: вместо двух поочерёдных ADD для большого смещения теперь собирают оффсет в временный регистр и выполняют одну атомарную корректировку SP.</p><p>Это исключает «окно» между ADD-инструкциями и делает разворачивание стека предсказуемым даже при async preemption. (Подробные обсуждения и сопутствующие arm64-проблемы рантайма см. также в #<a href="https://github.com/golang/go/issues/63830">63830</a>, #<a href="https://github.com/golang/go/issues/65449">65449</a>, #<a href="https://github.com/golang/go/issues/73413">73413</a>.)</p><h2>Кому это важно</h2><ul><li>Все сервисы Go на arm64, где возможны большие стековые фреймы и активна асинхронная предвыборка (по умолчанию с Go 1.14+). В условиях продакшн-нагрузок редкая гонка становится статистически вероятной. Читайте подробнее: https://unskilled.blog/posts/preemption-in-go-an-introduction/</li></ul><h2>Что делать инженерам прямо сейчас</h2><ol><li>Обновиться до: Go 1.25.x или минимум до 1.24.6 / 1.23.12 (если «зажаты» веткой).</li><li>Пересобрать сервисы, критичные по доступности, и раскатить обновления в первую очередь на arm64-инфраструктуру.</li><li>Если апгрейд временно невозможен — минимизируйте большие стек-фреймы (вынос больших буферов в heap), снизьте вероятность попадания preemption в эпилог и проверьте зависимости на предмет собственного asm/unsafe-кода.</li><li>Отслеживайте релизы Go и связанные тикеты об unwinding/arm64 (см. #73259 и бекпорт #74694).</li></ol><p>Источники и материалы:</p><ul><li>Обсуждение с примерами падений: runtime: segfaults in runtime.(*unwinder).next (GitHub, #73259).  https://github.com/golang/go/issues/73259</li><li>Бэкпорт фикса в ветку 1.24: (GitHub, #74694) — релиз Go 1.24.6. https://github.com/golang/go/issues/74694</li><li>История релизов, минор Go 1.23.12 с правками рантайма. <a href="https://go.dev/doc/devel/release?utm_source=chatgpt.com">https://go.dev/doc/devel/release</a></li><li>Release notes Go 1.25 (фикс присутствует в мажорной ветке).  https://tip.golang.org/doc/go1.25</li><li>Ранние разборы проблем разворачивания стека/прерываний на arm64 https://github.com/golang/go/issues/63830</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Баг в BIOS ASUS превращает ноутбуки за $3000 в «глючное нечто» на уровне бюджетников</title>
      <link>https://tproger.ru/news/bag-v-bios-asus-prevrashhaet-noutbuki-za--3000-v--glyuchnoe-nechto--na-urovne-byudzhetnikov</link>
      <comments>https://tproger.ru/news/bag-v-bios-asus-prevrashhaet-noutbuki-za--3000-v--glyuchnoe-nechto--na-urovne-byudzhetnikov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/bag-v-bios-asus-prevrashhaet-noutbuki-za--3000-v--glyuchnoe-nechto--na-urovne-byudzhetnikov</guid>
      <description><![CDATA[<p>В BIOS ноутбуков ASUS ROG найден баг: прошивка вызывает Sleep() в прерываниях и пытается отключить GPU, превращая топовые модели в «глючные»</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/bag-v-bios-asus-prevrashhaet-noutbuki-za--3000-v--glyuchnoe-nechto--na-urovne-byudzhetnikov">Баг в BIOS ASUS превращает ноутбуки за $3000 в «глючное нечто» на уровне бюджетников</a>»</p>]]></description>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[NVIDIA]]></category>
      <category><![CDATA[YouTube]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Discord]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 17 Sep 2025 05:22:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Пользователи ноутбуков серии ASUS ROG (Strix, Zephyrus, Scar и др.) уже несколько лет жалуются на постоянные микрофризы: мышка «подвисает» на доли секунды, аудио в Discord трещит, YouTube лагает.</p><p>Эти проблемы затрагивают даже топовые модели с RTX 4080 и Core i9. Обновления драйверов, переустановка Windows и переход на Linux — ничего не помогает.</p><p>Профилировщик <b>LatencyMon</b> <a href="https://github.com/Zephkek/Asus-ROG-Aml-Deep-Dive">показывает</a> конкретного виновника: <b>ACPI.sys</b> — компонент Windows, который исполняет код из BIOS. И именно этот код, как показало расследование, устроен катастрофически неправильно.</p><h2>Что не так: прерывания, которые засыпают</h2><p>Расследование показало, что примерно каждые 30–60 секунд BIOS запускает метод _GPE._L02, вызываемый при аппаратных событиях. Его задача — опросить статус батареи и питания.</p><p>Но он делает это <b>внутри обработчика прерывания</b>, вызывая Sleep() прямо в контексте ядра. То есть прошивка буквально приказывает процессору «поспать» на 25–100 мс — в момент, когда он должен работать максимально быстро.</p><p>Такой подход блокирует работу системных таймеров, мешает другим драйверам и вызывает те самые замирания, которые замечают пользователи.</p><h2>Вишенка на торте: попытка отключить единственный активный GPU</h2><p>Еще один баг прошивки: она <b>регулярно пытается отключить дискретную видеокарту (dGPU)</b>, даже когда та — <b>единственный</b> источник вывода изображения (в режиме MUX/Ultimate).</p><p>Это приводит к циклическим попыткам «погасить» и «включить» видеокарту, на что драйвер NVIDIA отвечает зависанием, а в худшем случае — синим экраном (WIN32K_POWER_WATCHDOG_TIMEOUT).</p><h2>Что говорит код</h2><p>Декомпиляция ACPI-таблиц BIOS показала:</p><ul><li>Код нарушает базовые принципы: использует Sleep() внутри прерываний.</li><li>Метод ECLV не завершает прерывание быстро, а задерживает его до 100 мс.</li><li>Система искусственно переинициирует прерывания, создавая бесконечный цикл.</li><li>Нет проверки текущего состояния MUX-переключателя — прошивка не понимает, что dGPU нельзя выключать.</li></ul><h2>Масштаб: страдают миллионы устройств</h2><p>Симптомы описаны в сообщениях пользователей с 2021 года. Проблема затрагивает десятки моделей ROG и TUF: от G15 до Zephyrus M16.</p><p>Даже на свежих моделях 2023–2024 года пользователи фиксируют те же баги, и они документированы в логах LatencyMon и трассировках Windows Performance Analyzer.</p><h2>ASUS пока молчит</h2><p>На момент публикации <b>ASUS не прокомментировала</b> результаты технического расследования. Ошибка, по сути, заключается в одной строке кода, и легко исправляется в будущих версиях BIOS.</p><p>Однако пока производитель не признает проблему официально, пользователи остаются один на один с нерешаемыми фризами.</p>]]></content:encoded>
    </item>
    <item>
      <title>Исправили баг — получили два новых</title>
      <link>https://tproger.ru/articles/ispravili-bag---poluchili-dva-novyh</link>
      <comments>https://tproger.ru/articles/ispravili-bag---poluchili-dva-novyh?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Виталий Попов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/ispravili-bag---poluchili-dva-novyh</guid>
      <description><![CDATA[<p>Даже простейший ИТ-проект способен превратиться в настоящий квест, где исправленный баг моментально оборачивается двумя новыми проблемами. Что с этим делать? Рассказываем в статье.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/ispravili-bag---poluchili-dva-novyh">Исправили баг — получили два новых</a>»</p>]]></description>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 13 Sep 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Как «простые» ИТ-проекты превращаются в бесконечный квест и что с этим делать</h2><p>Когда проект выглядит максимально понятным — например, «расширить дисковое пространство» или «заменить ИБП в филиале», — именно здесь большинство команд и заказчиков расслабляются. Но опыт снова и снова доказывает: даже простейший ИТ-проект способен превратиться в настоящий квест, где исправленный баг моментально оборачивается двумя новыми. Позвольте рассказать на собственных примерах, как это работает в реальности, и главное — что с этим делать.</p><figure><img src="https://media.tproger.ru/user-uploads/117585/2025-09-04/ebfb7484-c60c-4318-997c-b27bab710f6e.jpg" alt="" /></figure><h2>Кейс первый: СХД, гипервизор и эффект домино</h2><p>В конце 2023 года мы расширяли инфраструктуру крупного клиента — требовалось добавить две новые системы хранения данных, чтобы покрыть рост виртуализации. Проект был изначально «на сжатых сроках»: решение по вендору и модели принялось практически «с колес», согласование деталей заняло считанные дни, техническая валидация конфигурации перед отгрузкой по сути не проводилась.</p><p>На первый взгляд, задача элементарная: подготовить оборудование, доставить, смонтировать и «прикрутить» к существующей виртуализации. Но уже на этапе подключения стало ясно: всё идет не по сценарию. Гипервизор просто не видит новые тома. Операционная система выдает сбои на уровне multipath — механизма многопутевого доступа, который в теории должен обеспечивать отказоустойчивость, а на практике создает непредсказуемые задержки и зависания. Производительность хранилища оказалась в два раза ниже обещанных показателей, а отдельные LUN работали с такой задержкой, что у нас складывалось ощущение: кто-то поставил лимит на скорость «черепахой».</p><p>В процессе разбора выяснилась и причина: в multipath были прописаны значения, которые на практике приводят к тому, что при потере доступа к какому-то пути хост будет бесконечно ждать его восстановления. Это не только парализует доступ, но и блокирует все очереди ввода-вывода — система перестает реагировать. Решение проблемы потребовало вовлечения сразу двух производителей: со стороны СХД нам доказывали, что конфигурация корректна, а со стороны разработчика виртуализации — что никакой совместимости с этим железом они не гарантируют вообще. Для того чтобы добиться рабочей связки, пришлось организовать совместное тестирование с обеими сторонами и буквально вручную настраивать параметры, сверяясь с документацией и внутренними тестами вендоров. Только после совместных испытаний удалось получить рабочую конфигурацию и официальное подтверждение совместимости.</p><p>Победа? В какой-то мере. Потому что как только мы закрыли первый блок проблем, тут же «выстрелили» новые. Например, вдруг выяснилось, что лицензии на ряд функций должны были идти в комплекте, но по какой-то причине отсутствуют в поставке. Параллельно отвалилась система оповещений: email-уведомления и SNMP больше не доставляли критические алерты. Мониторинг оказался «слепым» в тот самый момент, когда риски были максимальны. Добавьте к этому периодические зависания контроллеров и проблемы с FC-коммутаторами — и стандартное расширение дискового пространства превращается в длинную череду непредвиденных доработок.</p><p>Самый главный урок из этого кейса, как ни парадоксально, не про «плохое железо» и даже не про баги в прошивках. Главная проблема — отсутствие комплексной технической проверки до старта, согласование параметров совместимости и четких регламентов передачи оборудования в продуктив. Когда каждая сторона работает «по письму», а не по живому документу с реальными версиями прошивок, драйверов и прошитыми сценариями отказов — результат становится лотереей.</p><p>В подобных ситуациях крайне важно не только согласовать специфику интеграции, но и зафиксировать ответственность за проверку работоспособности всех компонентов. Мы пришли к тому, что даже для простых проектов нужен «микро-пилот»: стендовый прогон ключевых функций в максимально похожих условиях, с реальной версией гипервизора, драйверов, топологией подключения. Это не только экономит время при внедрении, но и позволяет сразу выявить несовместимости, которые на бумаге могут быть незаметны.</p><p>Также мы всегда фиксируем технические параметры в отдельном документе: параметры multipath, значения таймаутов, поведение в случае деградации путей. Все эти данные передаются и в эксплуатацию, и в техническую поддержку — это позволяет резко снизить количество скрытых ошибок и ускоряет локализацию проблем, если они всё же появляются.</p><p>Наконец, не менее важно помнить: после любого изменения в инфраструктуре система должна пройти полный цикл тестов, включая проверку оповещений, мониторинга и поведения в аварийных ситуациях. Даже если кажется, что обновление минимальное, на стыках всегда прячутся баги.</p><figure><img src="https://media.tproger.ru/user-uploads/117585/2025-09-04/9d423675-3de7-48d4-bdd9-33e8c2902209.jpg" alt="" /></figure><h2>Кейс второй: «Элементарный» проект с ИБП, сервером и байпасом</h2><p>Второй кейс — классика жанра для любого интегратора. Задача выглядит просто: поставить в стойку один ИБП, сервер и байпас. Всё оборудование приехало в срок, инженеры тоже — что может пойти не так?</p><p>На этапе монтажа выясняется: на одном из батарейных кабинетов повреждён разъём. Кабели байпаса оказались значительно короче, чем требуется по нормативной документации, а использовать альтернативные варианты не получается — разъёмы проприетарные, длина кабеля заводская, никаких универсальных решений нет. Более того, архитектура стойки и разводка кабелей не позволяют обойти проблему другим размещением. Наконец, отдельная группа сотрудников заказчика — именно тех, кто потом будет эксплуатировать оборудование — высказывает своё недовольство по поводу выбора комплектации.</p><p>В поиске выхода обратились к производителю байпаса: получили официальное письмо, где подтверждалась возможность наращивания кабелей через клеммные колодки без потери гарантии. Закупили нужную фурнитуру, подготовили площадку для монтажа, однако на этом этапе заказчик предложенное решение отверг. Требование простое — заменить кабели на цельные необходимой длины или даже сам байпас на другую модель другого производителя. Пуско-наладка была остановлена.</p><p>Дальнейшие события напомнили бюрократический квест. Заказчик оказался «раздроблен»: сотрудники, участвующие в закупке, были совсем не теми, кто будет принимать объект в эксплуатацию. Каждый стейкхолдер предъявлял свои требования и не был готов брать на себя ответственность за компромисс. В результате, чтобы согласовать решение, ушло больше двух недель переписки, эскалаций, согласований с руководством — и только после долгих переговоров удалось закрепить технически и юридически корректное решение.</p><p>Но даже на этом этапе сложности не закончились. Необходимость заменить повреждённый батарейный модуль по гарантии уперлась в формальности: чтобы отправить оборудование в сервис, требуется его юридическая приемка заказчиком, а акт приёмки подписан ранее не был. Формально без подписи представителя заказчика в накладной оборудование считается не переданным — а значит, и в сервис его отправить невозможно. Добавьте сюда стоимость повторных выездов специалистов в разгар курортного сезона и ограничения по бюджету, и вы получите классическую ситуацию: даже самый простой проект может «заморозиться» на этапе передачи между отделами.</p><p>В подобных ситуациях опыт подсказывает: всегда нужно заранее включать в рабочую группу не только сотрудников по закупке, но и реальных эксплуатантов. Их требования, даже если кажутся избыточными, на практике оказываются решающими для успешной приемки. На договорном уровне мы всегда фиксируем конкретное ответственное лицо со стороны заказчика и условия приемки. Только так можно избежать затяжных споров и переноса сроков.</p><p>Не менее важно не пренебрегать технической приемкой оборудования до отгрузки. Любой дефект — разъем, кабель, несовместимость длины — должен быть выявлен и устранен до момента доставки. Мы внедрили обязательную лабораторную проверку всего оборудования перед отправкой заказчику, что позволяет избежать претензий на финальном этапе.</p><p>И наконец, юридическая чистота в документах — отдельный пункт. Подписи, доверенности, правильные формулировки в актах приема-передачи экономят огромное количество времени и денег. Именно из-за мелких формальностей проекты часто «зависают» на ровном месте.</p><h2>Несколько экспертных выводов — из практики и личного опыта</h2><p>Оба кейса показывают, что технические ошибки обычно легко исправимы — главное, чтобы процессы были выстроены правильно. Гораздо опаснее слабое взаимодействие между участниками, отсутствие документально закрепленных процедур, невнимание к деталям на стадии пресейла и передачи оборудования.</p><p>В идеале у любого проекта — даже самого простого — должен быть отдельный этап «микро-пилота»: быстрое стендовое тестирование в боевых условиях с замером производительности, поведения системы при ошибках, тестами всех коммуникационных каналов и оповещений. Все параметры конфигурации (особенно связанные с отказоустойчивостью, multipath, поведением при ошибках) фиксируются в виде единого документа и передаются эксплуатационной команде.</p><p>Любой проект по внедрению ИТ-оборудования обязательно должен предусматривать участие всех групп заказчика — от закупок до эксплуатации, а порядок согласования изменений и полномочий сторон стоит фиксировать в договоре максимально прозрачно. Обязательно закладывайте запас времени и бюджета на непредвиденные доработки, командировки, дооснащение. Это не излишество, а здравый подход к снижению проектных рисков.</p><p>Важно не забывать и о юридической гигиене: передача права собственности, порядок обмена и возврата, наличие всех необходимых документов, согласованных критериев приемки и возможности принимать решения на объекте оперативно.</p><h2>Итог</h2><p>Исправить баг — это не победа, а старт новой волны работы. Настоящий успех проекта — когда его архитектура, процессы согласования, документы и коммуникации изначально построены так, чтобы любые баги (а они неизбежны) решались не героизмом отдельных инженеров, а системной работой всей команды.</p><p>Постоянная работа над процессами, вовлечение всех заинтересованных сторон, техническая и юридическая подготовка, прозрачность в документации и коммуникациях — это тот самый фундамент, который превращает хаотичный квест «исправили баг — получили два новых» в управляемый, предсказуемый и безопасный для бизнеса процесс.</p>]]></content:encoded>
    </item>
    <item>
      <title>Google-цензура по ошибке: как баг в поиске помог скрыть журналистские материалы</title>
      <link>https://tproger.ru/news/google-cenzura-po-owibke--kak-bag-v-poiske-pomog-skryt-zhurnalistskie-materialy</link>
      <comments>https://tproger.ru/news/google-cenzura-po-owibke--kak-bag-v-poiske-pomog-skryt-zhurnalistskie-materialy?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/google-cenzura-po-owibke--kak-bag-v-poiske-pomog-skryt-zhurnalistskie-materialy</guid>
      <description><![CDATA[<p>В поисковой системе Google обнаружена уязвимость, позволяющая скрывать статьи из выдачи с помощью инструмента «Обновить устаревший контент». Ошибка использовалась для удаления журналистских материалов о громком уголовном деле.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/google-cenzura-po-owibke--kak-bag-v-poiske-pomog-skryt-zhurnalistskie-materialy">Google-цензура по ошибке: как баг в поиске помог скрыть журналистские материалы</a>»</p>]]></description>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Google]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 10 Aug 2025 15:59:52 GMT</pubDate>
      <content:encoded><![CDATA[<p>В сети выявили уязвимость в поисковой системе Google, которая позволяет фактически «стирать» статьи из выдачи, используя инструмент для обновления устаревшего контента. Инцидент уже назвали мечтой компаний по управлению репутацией.</p><h2>Что произошло</h2><p>Журналист Джек Поулсон, пишущий о технологиях и национальной безопасности, заметил, что две его публикации исчезли из поиска Google, даже если вводить точные заголовки в кавычках. Оба материала касались генерального директора Premise Data Делвина Мориса Блэкмана, арестованного в 2021 году по обвинению в домашнем насилии.</p><p>Поулсон обратился в Фонд свободы прессы (Freedom of the Press Foundation, FPF). Там выяснили, что исчезновение связано с неизвестной ранее ошибкой в инструменте Google «Обновить устаревший контент». Об этом написал <a href="https://www.techspot.com/news/108880-google-search-flaw-allows-articles-vanish-through-clever.html">Techspot</a>.</p><h2>«Хитрый» трюк с URL</h2><p>Злоумышленник отправил в Google несколько запросов на обновление страниц, указывая искажённые URL — с изменённым регистром букв. Когда поисковый робот пытался переиндексировать такие ссылки, он получал ошибку 404. Но вместо того, чтобы просто проигнорировать несуществующую страницу, алгоритм удалял и корректный адрес из индекса.</p><p>Манипуляцию повторяли до полного исчезновения оригинальных публикаций из поиска.</p><h2>Реакция Google</h2><p>Google признал наличие ошибки, но отказался сообщить, сколько раз этот баг использовался. По мнению FPF, такая уязвимость открывает путь для незаметной цензуры под видом технического обслуживания. Её легко могут применять компании по управлению репутацией или частные исполнители в интересах влиятельных лиц.</p><p><i>«Мы хотели бы, чтобы Google и другие платформы были более прозрачны в диалоге с организациями, защищающими свободу прессы и права человека», — заявил Ахмед Зидан, заместитель директора FPF.</i></p><p>Случай с Поулсоном показывает, что такая тактика способна уничтожать критические материалы без официальных блокировок или уведомлений, лишая пользователей доступа к важной информации.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как эффективно дебажить баги</title>
      <link>https://tproger.ru/articles/kak-effektivno-debazhit-bagi</link>
      <comments>https://tproger.ru/articles/kak-effektivno-debazhit-bagi?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Baskon]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-effektivno-debazhit-bagi</guid>
      <description><![CDATA[<p>В статье разбираем баг-трекеры, отладчики, логгеры, авто-тесты, профилировщики и статический анализ. Учимся быстро находить и устранять баги.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-effektivno-debazhit-bagi">Как эффективно дебажить баги</a>»</p>]]></description>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Отладка]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 27 Jul 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>В 1947 году инженеры, работавшие с компьютером Mark II в Гарварде, сделали знаменитую запись в журнале: «First actual case of bug being found» («Первый реальный случай обнаружения жучка»). Причиной сбоя оказалась моль, застрявшая между контактами реле. Хотя сама фраза прижилась, концепция «жучков» (bugs) как причин неисправностей в машинах гораздо старше: ещё в 1878 году Томас Эдисон<a href="https://www.techinsider.ru/popmem/756223-kogda-byl-obnaruzhen-pervyy-v-mire-kompyuternyy-bag/"> жаловался</a> на них в своих телеграфных аппаратах.</p><p>Сегодня под «багами» понимают уже не насекомых, а любые ошибки в работе систем. Этот термин прочно вошёл в обиход, давно выйдя за пределы IT.</p><p>Ошибки в программах неизбежны, но ключ к качественной разработке — умение быстро их находить и исправлять. <a href="https://queue.acm.org/detail.cfm?id=3068754#:~:text=Software%20developers%20spend%2035,embrace%20debugging%20as%20an%20exercise">Исследования показывают</a>, что разработчики тратят 35–50% рабочего времени на тестирование и отладку, а эти этапы поглощают 50–75% бюджета проекта.</p><p>В этой статье мы разберем современные инструменты и подходы к поиску и устранению багов.</p><h2>1. Трекеры багов</h2><p>История инструментов для отслеживания ошибок уходит корнями в прошлое. Ещё в 1970-х разработчики Bugtraq в AT&amp;T Bell Labs использовали физические баг-тикеты, прикрепляя их к пробковым доскам. Знаковый скачок произошёл в 1998 году, когда Mozilla открыла доступ к первому веб-трекеру — Bugzilla. Дальнейшая эволюция привела к появлению Jira от Atlassian в 2002 году и прототипа Sentry на GitHub в 2008-м.</p><h3>Зачем нужен баг-трекер ?</h3><p>Исправление ошибки начинается с её фиксации. На небольшом проекте с одним разработчиком запомнить пару багов возможно. Но по мере роста числа дефектов ручное отслеживание становится неэффективным. Баг-трекер — это специализированная система, где для каждой ошибки создаётся запись (тикет, задача). В тикете должны быть ответы на самые важные вопросы:</p><ul><li>Что пошло не так?</li><li>Что должно было происходить?</li><li>Как вызвать ошибку?</li><li>Какой контекст у ошибки ?</li></ul><p>Баг-трекеров существует великое множество, ведь по сути это автоматизированные цифровые записные книжки. Баги можно отслеживать с помощью Jira, Redmine, Битрикса, Yandex Tracker, GitHub Issues, Sentry, BugHerd и др.</p><p>Баг-трекеры выбираются исходя из потребностей команды. В больших компаниях популярны решения внутри их корпоративного трекера; часто в open-source проектах баг-трекером для комьюнити выступает GitHub Issues, а в небольших командах таковым выступает закреп в чатах TG-группы.</p><p>Без баг-трекера сложно держать всё под контролем. Даже в маленькой команде со временем начинают теряться детали и приоритеты. Трекеры дают структуру, прозрачность и позволяют планомерно исправлять ошибки.</p><h2>2. Отладчики</h2><p>Отладчики (дебаггеры) — это специализированные программы, позволяющие исследовать и контролировать выполнение другой программы (целевой) с целью поиска и исправления ошибок. Их основной функционал — исполнить программу построчно с фиксацией состояния памяти после каждого шага.</p><p>Отладчики с графическим интерфейсом по умолчанию встроены во все популярные IDE. Там можно напрямую в коде поставить точку остановки, удобно смотреть значения переменных и стек вызовов. Хоть и когда-то роль отладчиков выполнялась с помощью ручки, тетрадки и собственной памяти.</p><p>Основная задача отладчика — это предоставить информацию о ходе выполнения программы.</p><h3>Как эффективно использовать ?</h3><h4>Сформулируйте гипотезу и не одну</h4><p>Подумайте, какую информацию вам нужно получить, предположите, с какой строчки ход исполнения идёт неверно.</p><h4>Расставьте точки останова для проверки гипотезы</h4><p>Поставьте точки останова непосредственно перед строчками с ошибкой, чтобы пропускать участки с кодом, в котором вы уверены, и который оттестирован.</p><h4>Идите по шагам и следите за переменными с неправильными значениями</h4><p>Используйте бинарный поиск для локализации ошибки: сначала поставьте точку останова в середине подозрительного участка, проверьте состояние данных. Если ошибка «левее» — переместите точку в середину левой половины, иначе — в правую. Повторяйте до победного.</p><h4>Меняйте значения переменных по ходу выполнения</h4><p>Во время паузы в отладчике (на точке останова/шаге) вы можете изменить значение любой переменной вручную → продолжить выполнение → мгновенно увидеть последствия без перезапуска программы. Это нужно, чтобы проверить гипотезу о том, как исправить найденный баг или найти краевые случаи.</p><p>Отладчик показывает, что происходит в программе «внутри», но польза от него только тогда, когда у тебя есть тактика поиска.</p><h2>3. Профилировщики</h2><p>Смысл профилировщиков в том, чтобы измерять производительность программы (время выполнения функций, использование памяти, CPU, дисковых операций, сети) для выявления узких мест и оптимизации. Основной смысл профилировщиков — поиск багов, связанных с производительностью.</p><h3>Как эффективно использовать ?</h3><h4>Определите метрики и точки измерения</h4><p>Подумайте и поймите, какой именно показатель у вас проблемный (тестировать загрузку CPU, искать утечку памяти, следить за нагрузкой на процессор) и какие процессы вам нужно контролировать.</p><h4>Воссоздайте проблемную ситуацию</h4><p>Загрузите своё ПО массивом данных, имитирующим по объёму целевой. Убедитесь, что профилированная нагрузка репрезентативна.</p><h4>Оцените вызываемую нагрузку</h4><p>Не пытайтесь оптимизировать всё сразу. Начните с 1–2 самых проблемных функций или процессов, дающих наибольший выигрыш. Используйте принцип Парето, т.е. сначала займитесь проблемами, у которых соотношение улучшение производительности/время на починку самое лучшее.</p><h4>Изучите алгоритмы и структуры данных</h4><p>Чтобы уметь исправлять боттлнекс, надо знать, как эффективно работать с данными, какие алгоритмы существуют и в каких ситуациях применимы. Даже небольшая база знаний в этой области значительно ускорит вам профилирование программ.</p><p>Профилировщик — инструмент, помогающий экономить ресурсы компьютера. С ним понятно, где есть проблемы и наибольшее тормоза, и можно не тратить время на оптимизацию того, что и так работает быстро или ни на что не влияет.</p><h2>4. Логгеры</h2><p>Логгеры — инструменты для записи информации (логов) о ходе выполнения программы в файлы или консоль. Их основная задача — предоставить детальную историю работы программы после её выполнения или в реальном времени. Это критично для мониторинга, аудита, анализа ошибок (особенно тех, что сложно воспроизвести в отладчике) и понимания поведения системы в различных условиях. Логи помогут сформулировать сценарии возникновения ошибки для её воспроизведения.</p><p>Как правило, это первый инструмент поиска ошибок у всех разработчиков, который они реализуют через функции типа print().</p><h3>Как эффективно использовать ?</h3><h4>Структурируйте логирование</h4><p>Распределите логи по типам. Например, вы можете отслеживать действия пользователей или фиксировать выполнение ключевых функций. Пишите логи разных типов в разные файлы и директории — вам потом будет проще в них копаться.</p><h4>Выстройте иерархию логирования</h4><p>Стандартная такая: DEBUG, INFO, WARN, ERROR, FATAL.</p><p>Иерархия нужна, чтобы быстрее понимать, когда всё идёт в бездну, и быстрее реагировать. Неочевидно, но также это нужно вам, чтобы расставить приоритеты событий, происходящих в ПО.</p><h4>Добавляйте в логи контекст</h4><p>Фиксируйте время, пользователей, состояние ПО и любую другую информацию, потому что иначе логи перестанут быть полезными и читаемыми. Ваша задача — вложить максимально полезной информации, чтобы можно было чётко восстановить последовательность событий.</p><h4>Думайте о производительности</h4><p>Помните предыдущий пункт, но не переборщите: логи не должны забивать вам всю систему. Если вы будете фиксировать выполнение каждой строчки и дату/время — перезагрузите всю систему, и ваше ПО просто перестанет работать. Помните: вывод в консоль или файл — это дорогая операция.</p><h4>Пользуйтесь ИИ</h4><p>Все современные популярные модели достаточно умны, чтобы проанализировать код и добавить в него логи. Главное — заранее объяснить ей стратегию логирования. Воспользуйтесь для этого Cursor, WindSurf, Codex и иными ИИ-ориентированными IDE, чтобы они знали контекст.</p><p>Хорошие логи способы заменить все прочие инструменты поиска багов, но также способны и засыпать читающего тонной ненужных данных, а заодно и растратить ресурсы компьютера.</p><h2>5. Авто-тесты</h2><p>Авто-тесты (автоматизированные тесты) — это программный код, написанный для проверки корректности работы другого кода (продукта) без ручного вмешательства. Их основная задача — быстро, надёжно и повторяемо верифицировать функциональность, предотвращать регрессии (появление старых ошибок при внесении изменений) и документировать ожидаемое поведение системы.</p><p>Даже если вы не тестировщик — пишите автотесты. Вы значительно сократите себе часы жизни на ручной проверке результатов. Чем больше проект, тем важнее писать автотесты, чтобы избежать фразы: «А раньше работало?».</p><p>Регрессионные авто-тесты помогают найти баги при их появлении, убить жучка в зародыше, чтобы это насекомое не въелось в ваш код и не отложило там яйца, плодя проблемы каскадом.</p><h3>Какие бывают тесты ?</h3><p>Небольшое введение в классификацию тестов, чтобы лучше понимать, что именно можно тестировать и что проверять.</p><h4>Модульные тесты (Unit Tests)</h4><p>Проверяют отдельные мельчайшие части кода (функции, методы, классы) в изоляции от зависимостей (которые заменяются заглушками). Нужны, чтобы проверить корректность логики отдельного компонента. Быстрые, дешёвые, дают мгновенную обратную связь.</p><h3>Интеграционные тесты (Integration Tests)</h3><p>Проверяют взаимодействие нескольких модулей, компонентов или систем (например, проверить доступность API или успешное чтение/запись в БД). Нужны, чтобы убедиться, правильно ли соединённые части работают вместе, а также выявить проблемы взаимодействия.</p><h4>Сквозные тесты (End-to-End / E2E Tests):</h4><p>Тестируют всю систему целиком с точки зрения пользователя, имитируя его действия в реальной среде (браузер, мобильное приложение). Воспроизводят сценарии использования, например, покупку товара или загрузку Excel-файла из БД. Нужны, чтобы проверить работоспособность всей системы. Могут быть очень затратны.</p><h3>Как эффективно использовать ?</h3><h4>Пишите тесты по принципам FIRST</h4><ul><li><b>Fast</b> – тесты должны быть быстрые, иначе замедлят разработку.</li><li><b>Independent </b>– тесты должны быть изолированы друг о друга, чтобы не падать, как домино.</li><li><b>Repeatable</b> – воспроизводимые, при одинаковых вводных давать одинаковый результат.</li><li><b>Self-Validating</b> – результат строго бинарный: успех/не успех.</li><li><b>Timely</b> – тест нужно писать заранее, чтобы сэкономить себе часы жизни в будущем, а не писать их после того, как тысячу раз рукам сам проверял, что всё ок.</li></ul><h4>Следуйте Пирамиде тестирования</h4><p>Тесты должны выполняться в порядке от простых и быстрых к наиболее сложным и долгим, так, чтобы 90% ошибок попадали в первые и быстрые тесты, экономя вам время в ожидании окончания тестирования.</p><h4>Следите за покрытием тестов</h4><p>Отслеживайте, насколько ваш код покрыт тестами — так, чтобы минимальным количеством тестов покрыть максимальное количество кода.</p><h4>Настройте CI/CD сами или попросите вашего DevOps</h4><p>Интегрируйте запуск тестов в процесс сборки (CI/CD-пайплайн). Тесты должны запускаться автоматически при каждом коммите и пулл-реквесте. Это же всё-таки АВТО тесты.</p><p>Даже если вы не тестировщик, то неработающие функции  исправлять придётся вам же.</p><h2>6. Статический анализ кода</h2><p>Статический анализ кода — это процесс автоматической проверки исходного кода без его выполнения. Инструменты статического анализа (линтеры, SAST — Static Application Security Testing) сканируют код, ищут потенциальные ошибки, уязвимости безопасности, нарушения стиля кодирования, сложные для понимания конструкции.</p><p>По умолчанию есть во всех популярных IDE.</p><h3>Как эффективно использовать ?</h3><h4>Изучите их</h4><p>Изучите, какие анализаторы кода бывают, зачем они нужны и как работают. После чего принимайте решение: нужны ли они вам в проекте или будут только замедлять процесс CI/CD.</p><p>Если вы новичок, то можете узнать для себя много нового — например, самые банальные ошибки вроде SQL-инъекций.</p><h4>Настройте их</h4><p>После того как разобрались в том, как они работают и зачем нужны, настройте инструменты статического анализа под ваши задачи. Тот же линтер без единых настроек форматирования для всех будет просто вреден для проекта.</p><h4>Запускайте их как авто-тесты</h4><p>Статическая проверка кода — это тоже набор тестов, только написанный не вами и тестирующий не то, как ваш код работает, а то, как он написан.</p><p>Статические анализаторы кода — специфичные инструменты отлова ошибок. В большинстве своём хитрый баг они не найдут, но надо понимать, что на данный момент лучший статический анализатор кода — это мозг разработчика.</p><h2>Итог</h2><p>Баги — неизбежные спутники разработки, но их цена растет как снежный ком с каждым этапом жизненного цикла ПО.</p><p>Эффективная отладка — это не искусство, а системный подход, опирающийся на правильные инструменты и методики. Используйте описанные выше инструменты и подходы, чтобы отлавливать баги, но главное, не оставляйте их на потом, тогда <a href="https://www.youtube.com/watch?v=mS9LCR5P5wI">ваш Гомер из будущего</a> скажет спасибо.</p>]]></content:encoded>
    </item>
    <item>
      <title>Баг в конфиге может положить целую Windows — инженер Microsoft разобрал реальный случай</title>
      <link>https://tproger.ru/news/bag-v-konfige-mozhet-polozhit-celuyu-windows---inzhener-microsoft-razobral-realnyj-sluchaj</link>
      <comments>https://tproger.ru/news/bag-v-konfige-mozhet-polozhit-celuyu-windows---inzhener-microsoft-razobral-realnyj-sluchaj?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/bag-v-konfige-mozhet-polozhit-celuyu-windows---inzhener-microsoft-razobral-realnyj-sluchaj</guid>
      <description><![CDATA[<p>Инженер Microsoft показал, как один флаг в конфиге .NET-приложения может вызвать утечку памяти и «положить» Windows. Реальный кейс и разбор</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/bag-v-konfige-mozhet-polozhit-celuyu-windows---inzhener-microsoft-razobral-realnyj-sluchaj">Баг в конфиге может положить целую Windows — инженер Microsoft разобрал реальный случай</a>»</p>]]></description>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[Microsoft]]></category>
      <category><![CDATA[.NET]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 22 May 2025 10:57:41 GMT</pubDate>
      <content:encoded><![CDATA[<p>Инженер Microsoft Мэтт Хэмрик <a href="https://www.neowin.net/news/microsoft-engineer-shows-how-bad-code-can-lead-to-your-windows-pc-slowing-down/">опубликовал</a> разбор ошибки, которая может вызвать утечку памяти, замедление системы и даже крах всей Windows — и все это из-за одной строчки в конфигурационном файле .NET-приложения.</p><p>Речь идет о параметре reloadOnChange, который отвечает за автообновление настроек из файла конфигурации.</p><p>Проблема возникает, если установить reloadOnChange: true в неподходящем месте — например, в контроллере или middleware-компоненте. В этом случае каждый вызов создает нового «наблюдателя» за файлом, и память начинает стремительно заполняться.</p><h2>Что делает reloadOnChange</h2><p>Этот параметр нужен для динамической подгрузки настроек без перезапуска приложения. Но его стоит использовать только при старте и только для нестандартных файлов, которые .NET не мониторит по умолчанию.</p><p>Если включить его не там и не вовремя — приложение начнет плодить объекты, следящие за одним и тем же файлом, и сборщик мусора просто не справится.</p><p>По словам Хэмрика, ошибка накапливается медленно, но неотвратимо: система становится все менее отзывчивой, программы начинают сбоить, а в тяжелых случаях падает и сама Windows.</p><h2>Как Microsoft нашла баг</h2><p>Хэмрик обнаружил проблему, анализируя дампы памяти .NET, снятые через WinDbg и другие инструменты. Он увидел, как объект конфигурации разрастается в памяти — и каждый экземпляр привязан к новой копии FileSystemWatcher, который отслеживает изменения одного и того же файла.</p><p>И хотя пример относится к .NET 7, ошибка не зависит от версии: аналогичная утечка может случиться и в более новых релизах .NET, если допустить такую же архитектурную оплошность.</p><h2>Почему это важно</h2><p>Такие баги коварны: они не приводят к ошибке сразу. Но если приложение активно, баг способен положить всю систему — и администратор потратит часы на поиски причины, не подозревая, что виноват всего один флаг в конфиге.</p><p>Инженеры Microsoft советуют использовать reloadOnChange осознанно и только там, где это действительно необходимо. Особенно в продакшене.</p><h2>Разработчики против пользователей</h2><p>Этот кейс — пример того, как даже маленькая ошибка в коде может превратиться в системную проблему. И это особенно актуально на фоне разговоров о производительности Windows, медленной работе старых устройств и резком росте требований к «железу».</p><p>Как отмечает сам Хэмрик, часто дело не в слабом компьютере, а в неоптимизированном коде. И если раньше таким ошибкам просто не придавали значения, то сейчас они напрямую сказываются на стабильности всей системы.</p>]]></content:encoded>
    </item>
    <item>
      <title>Популярные ошибки в Golang и как их избежать</title>
      <link>https://tproger.ru/articles/populyarnye-owibki-v-golang-i-kak-ih-izbezhat</link>
      <comments>https://tproger.ru/articles/populyarnye-owibki-v-golang-i-kak-ih-izbezhat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Марина Орлова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/populyarnye-owibki-v-golang-i-kak-ih-izbezhat</guid>
      <description><![CDATA[<p>Дмитрий Королев расскажет про распространённые ошибки при работе со слайсами, каналами и другими структурами в Go. Научимся предупреждать их исправлять на примерах.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/populyarnye-owibki-v-golang-i-kak-ih-izbezhat">Популярные ошибки в Golang и как их избежать</a>»</p>]]></description>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 10 Dec 2024 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Всем привет! Меня зовут Дмитрий Королёв, я бэкенд-разработчик в Авито.</p><p>Go известен своей лаконичностью и простотой синтаксиса, но даже в нём есть множество подводных камней, с которыми можно столкнуться в работе. В этой статье я сделаю разбор распространённых ошибок с примерами и расскажу, как их можно избежать.</p><h2>Массивы и слайсы</h2><p>Начнём с базовых концепций:</p><ul><li>Массив — последовательность элементов определённого типа и фиксированной длины. Это неизменяемая структура данных и его capacity всегда равна его длине.</li><li>Слайс — своеобразная надстройка поверх массива с возможностью изменения длины.</li></ul><h2>Распространённые ошибки при работе со слайсами</h2><p>Чтобы понять, как работают слайсы, нужно понимать их структуру. В коде ниже видны поля про длину и вместимость и указатель на массив, на основе которого построен слайс.</p><p>Про длину и capacity слайсов стоит помнить две вещи:</p><ol><li>Когда мы создаём новый слайс, его длина равна его capacity, если не указано другое.</li><li><a href="https://go.googlesource.com/go/+/2dda92ff6f9f07eeb110ecbf0fc2d7a0ddd27f9d">Как именно растет вместимость слайса начиная с версии go 1.20.</a></li></ol><p>Поскольку в Go все аргументы передаются в функции по значению, то при передаче слайсов в качестве аргумента передаётся значение его структуры. Иными словами, копируется только ссылка на массив, на основе которого построен слайс. Сам массив со всеми содержащимися в нём данными не копируется. Если этого не знать, можно получить неожиданный результат.</p><p>Дальше рассмотрим примеры.</p><h2>Перевыделения памяти под новый массив</h2><p>Здесь в main объявлен слайс, состоящий из одного нуля. После чего этот слайс передаётся в функцию с говорящим названием changeSliceValues. Там с ним происходят некие действия, в текущем примере в нулевой индекс записывается единица. Если в main вывести слайс на экран до и после вызова функций, то ожидаемо увидим [0] и [1] соответственно.</p><p>Теперь немного изменим пример — в changeSliceValues после записи в нулевой индекс с помощью append добавим в конец слайса 2, после чего запишем в нулевой индекс 3. И несмотря на применённые нами изменения, принты в main все так же выведут [0] и [1]. Размер слайса не изменился, несмотря на то, что мы добавили append. Вторая запись тоже не применилась.</p><p>На самом деле всё станет понятно, если вспомнить озвученные ранее факты о слайсах и структуру слайса. В самом начале мы создали слайса с длинной, которая равна его capacity, то есть единице.</p><p>В момент вызова changeSliceValues в качестве аргумента передаётся значение структуры слайса. Он указывает на тот же самый нижележащий массив, что и слайс в main. По этой причине первая запись на нулевом индексе применяется на изначальном массиве, который был создан при инициализации слайса в main.</p><p>Однако дальше, когда мы делаем append, поскольку у слайса длина равна capacity, происходит перевыделение памяти под новый массив. Туда эвакуируются все значения из старого, и дальнейшая работа с этим слайсом уже на изначальный слайс в main не оказывает никакого эффекта.</p><p>Следующая запись в нулевой индекс происходит опять же на новый массив. На изначальный массив, который был создан при объявлении слайса в main, это никак не влияет.</p><h2>Копирование слайсов</h2><p>С этой же проблемой можно столкнуться, если попытаться скопировать слайс.</p><p>В примере в переменную newSlice с помощью слайсинга копируются данные из оригинального слайса, в том числе и указатель на массив с данными. При выполнении append данные перетираются в оригинальном массиве, потому что newSlice указывает на оригинальный массив из первого слайса.</p><p>В Go есть специальная встроенная функция copy, которая позволит безопасно копировать любые слайсы.</p><p>На картинке выше видно, что, использовав copy, мы перенесли элементы из исходного слайса в новый. Теперь можно безопасно делать append, не боясь перетереть изначальные данные.</p><h2>Работа со строками и рунами</h2><p>Допустим мы хотим парсить какой-то новостной портал и для каждой новости класть в кэш первые сто символов, чтобы показывать пользователю превью.</p><p>В бесконечном цикле мы получаем новые новости, после берём первые сто рун и отдаём в некую функцию storeArticlePreview, которая заботится о том, чтобы сохранить руны в кэш.</p><p>Однако, проблема в том, что когда сервис запустится, он будет съедать намного больше оперативной памяти, чем планировалось, потому что в нём есть утечка. Операция получения первых ста рун от новости создаёт срез длиной в сто элементов, но его capacity остаётся такой же, как у изначального слайса. В итоге весь массив с текстом новости остаётся лежать в памяти, даже если в итоге мы имеем доступ только к первым ста элементам.</p><p>Кстати, почему в этом примере мы скастили строку к массиву рун, прежде чем брать от неё первые 100 символов? Покажу на примере, чем отличаются руны от байтов.</p><p>Берём стандартную строку Hello World и делаем отдельную переменную с рунами. По задумке хотим запринтить первые пять символов, то есть слово Hello.</p><p>Если посмотреть на вывод, то не в нем не будет ничего необычного. Сначала принтятся сами руны, потом, когда мы конвертируем это в строку, выводится слово Hello. Когда берём первые пять элементов от строки, снова выводится Hello.</p><p>Кажется, что разницы между рунами и байтами нет. Но вот, что будет, если поздороваться на китайском языке.</p><p>По задумке должны были вывестись первые 2 иероглифа, но обычный слайсинг строки не работает — вместо иероглифов выводится некорректный символ.</p><p>Помним, что строки в Go состоят из UTF-8 символов, каждый из которых может быть представлен более чем одним байтом. Если взять слайс по строке, то работать мы будем с байтами, а не с её символами. Поэтому когда мы пытаемся взять первые два символа от строки, мы берём первые два байта.</p><p>Большинство операций со строками работает с их байтами, но есть и исключения.. Слайсинг строки отдаёт байты, метод len также покажет длину в байтах. Цикл for range в качестве индекса берёт индекс байта, с которого начинается символ, но в значении будет лежать не байт, а руна, которая начинается в этом индексе.</p><p>Часто можно просто кастить строчку к слайсу рун и работать уже с ним. Но не стоит забывать про оверхэд, который мы можем получить в этом случае. На каждую строчку будут существовать две переменные: одна из них хранит исходную строку, а вторая — массив рун. Если строк много и они длинные, это также может иметь значение. К счастью, в Go есть процессорные оптимизации, которые в определенных ситуациях позволяют его избежать.</p><h2>Каналы</h2><p>Каналы — это примитив синхронизации, который даёт возможность одной горутине отправлять данные в другую и обеспечивает безопасный доступ к общим данным.</p><p>При работе с каналами возникают два вопроса: кто должен их закрывать и нужно ли вообще это делать. Важно знать, что может случиться при работе с каналом в разных его состояниях.</p><figure><img src="https://media.tproger.ru/user-uploads/109956/2024-12-10/bd89d6c2-9dde-47bc-83ec-6c498890ee17.png" alt="" /></figure><p>На этой таблице, описано, что мы получим при совершении разных операций. Например, с каналом в закрытом состоянии, чтение из него не вызовет проблем — мы просто получим дефолтное значение и false, что сигнализирует о том, что прочитать из канала не удалось. Однако, запись в закрытый канал и закрытие уже закрытого канала вызовет панику. Вывод: закрывать канал должна та горутина, что в него пишет.</p><p>Теперь попробуем ответить на вопрос: «зачем закрывать канал?». Для этого обратимся к документации: «отправитель может закрыть канал, чтобы указать, что значения больше не будут отправляться». Если отправитель закрывает канал, значит это может понадобится кому-то, кроме него — например, читателю канала. Рассмотрим пример, когда это может быть полезно:</p><p>Здесь есть говорящая функция writeToChan, в которой происходит запись в канал и в main цикл по этому каналу — в нём мы высчитываем значения. Если не закрыть канал, цикл никогда не закончится и случится deadlock. Если закрыть — for range успешно завершается и логика программы идёт дальше.</p><p>Закрывать канал стоит только в тех ситуациях, когда читатель никак не должен реагировать. Ничего страшного, если канал не закрыт. Сборщик мусора сможет от него избавиться даже в таком состоянии.</p><h2>time.After</h2><p>Раз уж обсудили каналы, поговорим о структурах, которые используют каналы. Одна из них — time.After. Это функция возвращает канал, который закроется после заданной задержки времени. Она обычно используется для создания таймеров или установки таймаутов на выполнение определенной логики в программах.</p><figure><img src="https://media.tproger.ru/user-uploads/109956/2024-12-10/c1da2a32-8fe2-4093-a405-82df24d6ab09.png" alt="" /></figure><p>Вот пример, где мы обрабатываем какие-то события, которые поступают из некой очереди. Если за 15 минут мы не встретили ни одного события в канале, то кинем warning, что что-то случилось.</p><p>Если запустить код, он будет рабочим. Но если вдруг есть какой-то дашборд, в котором мы измеряем потребление памяти и событий большое число, то мы легко обнаружим, что есть утечка памяти. При среднем потоке в миллион сообщений в 15 минут утечка составит порядка 200 Мб. Один канал Go весит около 200 байт. Получается, что на каждое событие убегает новый канал.</p><p>Почему это происходит, если сборщик мусора сможет удалить и не закрытый канал, а канал, созданный time.after, выходит из зоны видимости после каждого нового события?</p><p>Обращаемся к документации: «таймер не будет собран сборщиками мусора до тех пор, пока он не отработает». То есть time.after, который мы ставили на 15 минут, на это время остаётся висеть мертвым грузом, даже если он уже не находится в области видимости.</p><h2>Горутины</h2><p>Горутина — это лёгкий поток выполнения в userspace, пока потоки операционной системы живут в kernel space. Горутины управляются средствами Go, а потоки — средствами операционной системы.</p><p>Горутины спроектированы так, чтобы быть более эффективными, чем традиционные потоки операционной системы. Но у них тоже есть несколько ловушек, в которые можно попасть при работе.</p><p>В примере ниже мы создаём слайс из цифр от 1 до 5 и горутины в цикле, и в каждой из них прибавляем своё число из слайса к переменной sum. Можно подумать, что в выводе будет число 15, то есть сумма чисел от 1 до 5 — однако это не так.</p><p>Здесь проблема состоит в замыканиях — функциях, которые захватывают переменные из внешней области видимости. В примере анонимная функция, которая создаётся в цикле, является замыканием, потому что она захватывает переменную value из внешней области видимости.</p><p>Особенность их работы в том, как используется захваченная переменная. Горутины не захватывают значения переменных на момент их создания — они захватывают ссылку на переменную. Поэтому, когда горутины начинают выполняться, цикл зачастую уже прошёл, и переменная value имеет последнее значение из слайса. По нему мы итерируемся, хотя гарантии того, что цикл завершится раньше, чем начнёт работу одна из горутин — нет. Это приводит к тому, что в переменной sum оказывается не значение 15.</p><p>Это настолько распространённая проблема, что мейнтейнеры Go решили изменить семантику переменных цикла for, чтобы предотвратить их непреднамеренное использование в замыканиях и горутинах на каждой итерации. В версии 1.21 появился соответствующий эксперимент, а с версии 1.22 эта проблема уже полностью перестала воспроизводиться. Но, поскольку версия 1.22 свеженькая, и ещё не все успели обновиться, берите себе на заметку эту особенность работы замыканий.</p><h2>Пакеты sync и atomic</h2><p>В примерах выше мы использовали sync WaitGroup, чтобы дождаться выполнения горутин и, кстати, сделали это неправильно. Признавайтесь, кто не заметил?) Стоит обратить внимание на то, где мы делаем wg.Add, и подумать, чем это черевато.</p><p>Давайте разбираться. Посмотрим на устройство wait group:</p><p>В структуре WaitGroup из интересного мы видим семафор и некий noCopy. Сначала поговорим про семафор, а точнее про то, что по сути WaitGroup — это простенькая обёртка над семафором с тремя методами:</p><ol><li>.Add(delta int) увеличивает значение семафора на переданное значение.</li><li>.Done() уменьшает значение семафора на единицу.</li><li>.Wait() блокирует выполнение до тех пор пока значение семафора не станет равно 0.</li></ol><p>Так вот, проблема — в запущенных нами горутинах заключается в том, что нет гарантии, что они запустятся до того, как будет вызван .Wait. Это значит, .Wait может завершиться до выполнения .Add. Поскольку гарантии порядка запуска горутин нет, в итоге мы рискуем неверно решить, что все горутины завершили работу, хотя некоторые её ещё даже не начали.</p><p>Теперь вернёмся к структуре WaitGroup и присмотримся к полю noCopy, с таким же типом noCopy — что же это такое? По названию можно догадаться, что это что-то, что нельзя копировать, подобное поле есть в большинстве структур пакета sync. Давайте посмотрим, что же произойдёт, если всё же скопировать структуру с этим полем. Для примера будем использовать мьютекс, в нем также есть поле noCopy с типом noCopy.</p><p>В этой программе у нас есть структура Counter, которая хранит в себе мапу, а также мьютекс, который должен по задумке защищать мапу от параллельной записи. В мьютексе так же, как в waitGroup, присутствует noCopy.</p><p>На структуре Counter определены два метода: один увеличивает значение определенного ключа на единицу, другой — сразу на переданное значение. Есть мейн, в котором мы инициализируем структуру счётчика и запускаем две горутины для увеличения значения одного и того же ключа, делаем слип, чтобы дождаться выполнения горутин, и принтим значения, которые окажутся в мапе нашего счётчика. Но вот принт, к сожалению, мы так и не увидим, потому что упадём с паникой.</p><p>Проблема с кодом в том, что всякий раз, когда вызывается increment, в него копируется наш Counter c, поскольку increment определён на типа Counter, а не на *Counter. Другими словами, это value receiver, а не pointer receiver. Следовательно, increment не может изменять оригинальную переменную типа Counter, которую мы создали в main. Поэтому при каждом вызове increment происходило копирование счётчика со всем его содержимым, в том числе и мьютексом.</p><p>А теперь вспомним, что мьютекс — это просто обёртка над семафором, и, когда мы его копируем, мы копируем и семафор. При этом копия и оригинал могут жить своими отдельными жизнями, и ничто не помешает им конкурировать за операции с одним и тем же блоком памяти. Поэтому копирование мьютекса неправильно.</p><p>Так вот, за счет того самого noCopy есть возможность пометить любую структуру как невозможную к копированию (многие структуры из пакета sync так и отмечены). Тогда с помощью команды go vet можно будет обнаружить места, в которых отмеченная структура копируется, и найти потенциальную проблему в коде своего приложения.</p><h2>Atomic</h2><p>Теперь перейдем к ещё одному распространённому примитиву синхронизации — атомикам. Они предоставляют возможность безопасного доступа к общей памяти для операций чтения, записи и модификации переменных. Кроме того, в общем случае операции с атомиками быстрее, чем операции с мьютексом, за счёт использования специального набора процессорных инструкций. Однако, с этим преимуществом приходит и недостаток, про который периодически забывают: операции с атомиками атомарны по отдельности, но не атомарны все вместе.</p><figure><img src="https://media.tproger.ru/user-uploads/109956/2024-12-10/7684743d-1132-47c2-a238-6cefd3107699.png" alt="" /></figure><p>В этой программе запускается горутина, которая постоянно в бесконечном цикле увеличивает на единицу значение переменной num. В то же время, в main находится бесконечный цикл, который проверяет, чётное ли число, и если это условие выполняется — выводит его на экран. Однако мы видим, что при запуске вывелось число 287, а оно, как ни странно, нечётное. Это происходит из-за того, что после прохождения num проверки на чётность его значение никак не защищено от изменений, и горутина, инкрементирующая num, успевает изменить его значение до того, как число выводится на экран.</p><h2>defer</h2><p>defer позволяет отложить выполнение блока кода до окончания функции, в которой он был вызван. Обычно он используется для обеспечения освобождения ресурсов, например, закрытия файла или разблокировки мьютекса, независимо от того, как функция завершает работу — из-за обычного возврата, паники или ошибки.</p><p>Здесь мы видим структуру профиля и несколько возможных типов для него, а также метод GetBalance(), в котором, в зависимости от типа профиля, выбирается тот или иной метод подсчета баланса. Допустим, теперь мы хотим добавить лог с итоговым балансом, полученным при подсчёте:</p><p>И в результате добавленного нами лога мы всегда будем видеть запись «profile balance: 0». Почему так?</p><p>Давайте внимательнее посмотрим на то, что написано про defer в документации языка: «The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes». Значение аргументов для функции в defer (в том числе и ресивер метода) вычисляются в момент исполнения defer, а не в момент исполнения функции. В нашем примере на момент исполнения defer в переменной balance у нас по дефолту лежит 0 — вот с этим значением наш принт и выполняется. А для того, чтобы достичь результата, который мы и хотели получить, то есть для того, чтобы в принте фигурировала итоговая сумма расчета, можно использовать концепцию, с которой мы уже встречались — замыкания.</p><p>Анонимная функция не имеет никаких аргументов, переменная balance расположена в теле этой функции. Соответственно, будет сохранена ссылка на эту переменную, и фактическое значение будет получено при выполнении анонимной функции по сохранённой ссылке.</p><h2>Интерфейсы</h2><p>Интерфейсы в Go обеспечивают гибкость кода, позволяя писать универсальные функции, которые могут работать с разными типами данных, реализующими один и тот же интерфейс. Однако и с ними не все гладко. Посмотрим на такой пример кода:</p><p>У нас есть интерфейс реквестера, который делает какой-то запрос и в респонсе отдает int — допустим, статус-код нашего запроса. Есть конкретный тип concrete requester, который реализует интерфейс реквестера. Ещё для интерфейса реквестера есть функция, которая позволяет его инициализировать в зависимости от переданного значения value.</p><p>Если оно больше нуля, то мы возвращаем instance concret requester’а. Если оно меньше или равно нулю — просто возвращаем не проинициализированную переменную. В main есть логика, которая сначала принтит реквестер, а затем делает ещё один принт в зависимости от того, nil он или нет.</p><p>Если запустить программу, то мы увидим забавный вывод:</p><p>Мы получили реквестер nil, но при этом он не nil.</p><p>Чтобы разобраться, нам нужно внимательнее присмотреться к интерфейсам – а точнее к тому, как они устроены под капотом.</p><p>Под капотом есть две структуры для интерфейсов: eface для пустого и iface, если определён набор методов, которым должен соответствовать тип. Сейчас нас интересуют общие для них поля — а именно тип данных, которые реализует интерфейс, и ссылка на область в памяти, где находится его значение. Для того, чтобы две переменные интерфейс-типа были равны, нужно чтобы у них были равны оба этих поля.</p><p>Теперь посмотрим</p><p>что именно лежит в этих полях для нашей переменной requester:</p><p>Ага! Вот отсюда ноги и растут. Несмотря на то, что фактическое значение у переменной — это nil, тип таковым не является, что и приводит к тому, что сравнение requester == nil — это false.</p><p>Это поведение связано с так называемым value boxing и заслуживает отдельной статьи, которая уже в общем-то и так любезно написана <a href="https://go101.org/article/interface.html#boxing">здесь</a>.</p><h2>Особенности вендоринга</h2><p>Представим, что вы завели библиотеку на Go, в которой должны происходить какие-то сетевые запросы. Внутри этой библиотеки вы реализовали некий клиент, который умеет делать запросы, получать в респонсе какие-то данные и отдавать их в виде структур, описанных в папке models.</p><figure><img src="https://media.tproger.ru/user-uploads/109956/2024-12-10/24bb998f-481b-4a39-aeca-1b2b4879b4d3.png" alt="" /></figure><p>Теперь попробуем использовать эту библиотеку в каком-нибудь сервисе. Добавили её в go.mod, прописали в консоли go mod tidy, go mod vendor, решили заглянуть в vendor, а там, неожиданно лежит только часть файлов и папок вашей библиотеки.</p><figure><img src="https://media.tproger.ru/user-uploads/109956/2024-12-10/5ea7d697-c283-4913-b317-79a4fc67dc75.png" alt="" /></figure><p>Для тех, кто не изучал, как работает вендоринг, это будет казаться чем-то странным. Что ж, за ответами отправляемся в документацию языка:</p><p>“The <a href="https://go.dev/ref/mod#go-mod-vendor">go mod vendor</a> command constructs a directory named vendor in the <a href="https://go.dev/ref/mod#glos-main-module">main module”s</a> root directory containing copies of all packages needed to build and test packages in the main module.”</p><p>И вновь всё встает на свои места: в вендоре оказываются только те пакеты, которые нужны для успешного билда и тестирования приложения. То есть, если мы инициализируем клиент из библиотеки где-то в сервисе, в котором мы эту библиотеку подключили, у нас подтянутся требуемые для этого пакеты.</p><p>Сама по себе эта ситуация может показаться просто неожиданной особенностью языка. На самом деле, это тонкий намек на то, что возможно заносить имплементацию логики похода во внешний сервис внутрь библиотеки — это не лучшая мысль. Ведь таким образом мы увеличиваем связность логики, а также уменьшаем возможности сервисов-потребителей в плане кастомизации взаимодействия библиотеки с внешними сервисами.</p><p>На этом все! Напишите в комментариях, есть ли ещё какие-то ошибки, о которых не рассказал в статье? Обсудим их в следующий раз!</p>]]></content:encoded>
    </item>
    <item>
      <title>10 ошибок в архитектуре, которые ломают проекты. Проверьте, не совершаете ли вы их</title>
      <link>https://tproger.ru/articles/10-owibok-v-arhitekture--kotorye-lomayut-proekty--proverte--ne-soverwaete-li-vy-ih</link>
      <comments>https://tproger.ru/articles/10-owibok-v-arhitekture--kotorye-lomayut-proekty--proverte--ne-soverwaete-li-vy-ih?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/10-owibok-v-arhitekture--kotorye-lomayut-proekty--proverte--ne-soverwaete-li-vy-ih</guid>
      <description><![CDATA[<p>
Рассмотрим 10 распространённых ошибок в архитектуре, способных негативно повлиять на проект. Узнаем о ключевых рисках и даем рекомендации по их предотвращению, чтобы сделать разработку надежной и эффективной.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/10-owibok-v-arhitekture--kotorye-lomayut-proekty--proverte--ne-soverwaete-li-vy-ih">10 ошибок в архитектуре, которые ломают проекты. Проверьте, не совершаете ли вы их</a>»</p>]]></description>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Архитектура приложений]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 06 Nov 2024 10:00:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Ваша архитектура — это фундамент, на котором строится успех проекта, и даже малейшая ошибка может перерасти в серьёзные сложности. Сегодня узнаем о 10 типичных ошибках, которые могут разрушить даже самый многообещающий стартап и поможем их избежать. Готовы проверить, не попались ли вы в одну из ловушек? Давайте разбираться!</p><h2>Ошибка 1: Вы зависите от одного провайдера облачных услуг</h2><p>Зависимость от одного облачного провайдера может стать серьезной проблемой, если в будущем возникнет потребность изменить инфраструктуру, мигрировать на другой сервис или если провайдер столкнётся с техническими сбоями, изменит условия обслуживания и т.д.</p><h3>Реальные примеры ошибки</h3><p>1. <a href="https://kinsta.com/blog/aws-outage/">AWS Outage в 2017 году:</a></p><p>В феврале 2017 года Amazon Web Services (AWS) испытал длительное отключение в одном из своих дата-центров, что повлияло на множество приложений и сервисов, зависящих от их облачных услуг. Многие компании — Slack, Trello и Airbnb, столкнулись с проблемами, что привело к сбоям в их сервисах. Этот случай продемонстрировал уязвимость зависимых систем.</p><p>2. <a href="https://engineering.linkedin.com/blog/2016/06/linkedins-journey-to-the-cloud">Опасности «lock-in» в облачных платформах:</a></p><p>Компания LinkedIn, которая изначально использовала облачные сервисы одного провайдера, столкнулась с проблемами при попытке перейти на другой сервис, когда осознала, что большая часть их архитектуры была завязана на конкретных инструментах этого провайдера. Это создало дополнительные трудности и увеличило затраты на миграцию.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Используйте многооблачную стратегию:</b></p><p>Многооблачная архитектура позволяет распределить ресурсы между несколькими провайдерами (например, AWS, Google Cloud, Microsoft Azure). Это снизит зависимость от одного поставщика и сделает систему более устойчивой и гибкой.</p><p><b>2. Стандартизуйте API и интерфейсы:</b></p><p>Проектирование системы с использованием стандартных интерфейсов (например, RESTful API) позволяет легче менять облачные услуги без значительных нововведений в коде.</p><p><b>3. Обрабатывайте данные в гибридной модели:</b></p><p>Некоторые данные могут храниться локально, что позволяет избежать потери доступа в случае отключения облачного провайдера. Также команды могут контролировать вопросы безопасности.</p><p><b>4. Используйте контейнеризацию:</b></p><p>Использование контейнеров (например, Docker) и оркестрация (например, Kubernetes) позволяет переносить приложения между различными облачными провайдерами без значительных изменений в инфраструктуре.</p><h2>Ошибка 2: У вас нет кэширования</h2><p>Отсутствие механизма кэширования в приложении может привести к завышенным требованиям к производительности и увеличению времени отклика, особенно в условиях высокой нагрузки. Если система не использует кэширование для часто запрашиваемых данных, это может создать различные проблемы — задержки в обработке запросов и высокие затраты на ресурсы.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://netflixtechblog.com/how-we-optimize-cost-performance-on-the-cloud-4a9d9c64e132">Netflix</a>:</b></p><p>Netflix сталкивался с проблемами производительности, когда пользователи запрашивались множество однотипных данных (метаданные о фильмах, например). Это привело к значительным нагрузкам на базы данных и увеличивало время отклика.</p><p><b>2. <a href="https://medium.com/pinterest-engineering/scaling-pinterest-how-we-manage-our-caching-strategy-baf474d19f9a">Pinterest</a>:</b></p><p>Pinterest на начальной стадии своего развития не использовал эффективное кэширование, что приводило к медленной загрузке страницы и, соответственно, изображений. После внедрения системы кэширования, основанной на Redis, они смогли ускорить загрузку и значительно улучшить пользовательский опыт.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Выбирайте подходящий механизм кэширования:</b></p><p>Команда должна определить, какой тип кэширования лучше всего подходит для их приложения: кэш на уровне сервера (например, Memcached, Redis) или кэширование на стороне клиента (например, браузерное).</p><p><b>2. Кэшируйте часто запрашиваемые данные:</b></p><p>Определите «горячие» данные и кэшируйте их, чтобы сократить время доступа. Например, конфигурационные параметры, метаданные и т.д.</p><p><b>3. Используйте HTTP-кэширование:</b></p><p>Здесь помогут заголовки HTTP Cache-Control, ETag и Last-Modified, чтобы управлять кэшированием на уровне сервера и клиента.</p><p><b>4. Обновляйте кэш:</b></p><p>Разработайте стратегию для обновления кэша, например, с использованием TTL (времени жизни кэша), или при изменении данных в базе, чтобы актуализировать информацию. Конечно, не забывайте периодически кэш чистить (почувствуй себя уточкой с метлой из Телеграма).</p><h2>Ошибка 3: Вы неправильно структурировали базу данных</h2><p>Неправильное проектирование структуры базы данных может ухудшить производительность, дублировать данные и плохо  поддерживать их целостность. Неправильная нормализация (или наоборот, чрезмерная нормализация) может усугубить ситуацию, делая взаимодействие с базой данных неэффективным.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://www.forbes.com/sites/bernardmarr/2019/05/21/how-delta-air-lines-uses-the-cloud-and-data-to-enhance-passenger-experience/?sh=174d285b7cb5">Delta Airlines</a>:</b></p><p>Delta Airlines столкнулась с проблемами производительности своей базы данных. Причиной стало плохое проектирование, что замедлило обработку онлайн-бронирований. Для решения проблемы компании пришлось значительно переработать архитектуру базы данных — оптимизировать структуру таблиц и индексов.</p><p><b>2. <a href="https://engineering.fb.com/2014/06/24/data-infrastructure/the-evolution-of-facebooks-storage-infrastructure/">Facebook</a>*:</b></p><p>В ранние годы Facebook испытывал проблемы с масштабированием своей базы данных, которая была неправильно нормализована. Пришлось перепроектировать архитектуру и внедрять более сложные алгоритмы кэширования, чтобы поддерживать эффективность работы с данными из-за числа пользователей.</p><p><i>*Компания Meta признана экстремистской на территории РФ</i></p><h3>Как командам избежать этой ошибки</h3><p><b>1. Проектируйте БД на основе требований:</b></p><p>Это база! (данных). Перед началом проектирования важно анализировать бизнес-требования, чтобы понять, как данные будут использоваться. Это поможет определить, какие таблицы и связи необходимы.</p><p><b>2. Следуйте правилам нормализации:</b></p><p>Проектирование базы данных с учётом принципов нормализации снижает риск дублирования и обеспечивает их целостность. При этом учитывайте, что чрезмерная нормализация может требовать более сложных соединений и замедления запросов.</p><p><b>3. Используйте индексы:</b></p><p>Установка индексов на поля, которые часто используются в условиях WHERE и JOIN, может значительно улучшить производительность запросов. Но следует также осторожно подходить к индексированию, чтобы не перегружать систему.</p><p><b>4. Поддерживайте архивирование и шардирование данных:</b></p><p>Если БД становится слишком большой, следует рассмотреть возможность шардирования (разделение базы на части) или архивирования старых данных.</p><p><b>5. Используйте инструменты моделирования баз данных:</b></p><p>Применение инструментов по типу ER-диаграмм для визуализации структуры базы данных может помочь лучше понять связи между данными и выявить потенциальные проблемы до их реализации.</p><h2>Ошибка 4: Вы не используете микросервисную архитектуру для масштабируемых систем</h2><p>Использование монолитной архитектуры вместо микросервисной в масштабируемых системах может привести к проблемам с гибкостью и управляемостью приложения. Монолитные системы часто становятся трудными для обновления и поддержания, так как все компоненты плотно связаны, а изменения в одном месте могут повлиять на другие части системы.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://engineering.atspotify.com/2018/02/07/breaking-free-from-monolith-how-spotify-scaled-agile/">Spotify</a>:</b></p><p>В своих ранних версиях Spotify использовал монолитную архитектуру, что привело к сложности в развертывании новых функций. Каждый релиз требовал тестирования всей системы, что замедляло процесс обновлений. В результате команда приняла решение перейти к микросервисам, что позволило ускорить цикл разработки и улучшить возможность развертывания.</p><p><b>2. <a href="https://www.computerworld.com/article/2940509/paypal-transforms-itself-by-embracing-microservices.html">PayPal</a>:</b></p><p>PayPal изначально была построена как монолитная архитектура, что стало проблемой, когда компания начала испытывать взрывной рост. Это привело к заторам в разработке и  управлении разными компонентами. Переведя свою систему на микросервисы, они смогли значительно улучшить гибкость и сильно сократить время на внедрение новых функций.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Определите границы сервисов:</b></p><p>Чёткое определение доменных границ для каждого микросервиса поможет избежать дублирования функций и повысить устойчивость системы. Каждый микросервис должен быть ответственен только за конкретный набор задач.</p><p><b>3. Используйте API для взаимодействия:</b></p><p>Микросервисы должны общаться друг с другом через API. Что важно, это способствует независимости одного сервиса от другого и делает каждый более масштабируемым.</p><p><b>4. Внедряйте DevOps и CI/CD:</b></p><p>Для успешного развертывания и управления микросервисами необходимо внедрить практики DevOps и непрерывной интеграции и доставки (CI/CD). К ним относится автоматизация тестирования, деплоймента и управления версиями.</p><p><b>5. Переходите к микросервисам постепенно:</b></p><p>Вместо полной миграции на микросервисную архитектуру стоит рассмотреть возможность постепенного перехода, выделяя более связанные части приложения, что упростит процесс и уменьшит риски.</p><h2>Ошибка 5: Вы игнорируете принцип единственной ответственности (Single Responsibility Principle)</h2><p>Принцип единственной ответственности (SRP) предполагает, что каждый класс или модуль должен выполнять одну функциональную задачу. Игнорирование этого принципа может привести к чрезмерной связности и сложности кода, что затрудняет его тестирование, отладку, модификацию и поддержку.</p><h3>Реальные примеры ошибки</h3><p><a href="https://kinsta.com/blog/aws-outage/">1. Одна из</a><a href="https://martinfowler.com/books/refactoring.html"> E-commerce платформ</a><a href="https://martinfowler.com/books/refactoring.html"></a></p><p>В ранней версии e-commerce платформы класс Order обрабатывал и управление заказами, и логику расчёта налогов, и взаимодействие с платёжным процессором. Это усложняло управление кодом и добавляло риски при любых изменениях. После переработки системы с использованием SRP была создана отдельная служба расчёта налогов и платёжная служба, что значительно упростило код и улучшило его тестируемость.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Определите задачи каждого модуля или класса:</b></p><p>На этапе проектирования приложения важно чётко определить задачи каждого модуля или класса. Они должны быть узко сфокусированы, что поможет упростить логику.</p><p><b>2. Используйте абстракции и интерфейсы:</b></p><p>Следует использовать интерфейсы и абстракции для определения контрактов, что позволит организовать код вокруг обязанностей и снизит связанность между компонентами.</p><p><b>3. Применяйте паттерны проектирования:</b></p><p>Использование паттернов проектирования, таких как Strategy или Observer, может помочь разделить ответственности между классами и облегчить модификацию и расширение функциональности.</p><h2>Ошибка 6: У вас нет системы мониторинга и логирования</h2><p>Отсутствие адекватной системы мониторинга и логирования ведёт к тому, что команда не может быстро идентифицировать и устранять проблемы в приложении. Если нет возможности собирать и анализировать данные, сложно понять, где возникают узкие места, как ведёт себя приложение под нагрузкой и какие ошибки возникают у пользователей.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://eng.uber.com/the-evolution-of-ubers-engineering-culture/">Uber</a>:</b></p><p>В начале своего развития Uber сталкивался с проблемами, связанными с отслеживанием производительности своей системы. Отсутствие эффективного мониторинга привело к тому, что команда долго не могла выяснить причины нестабильной работы приложения.</p><p><b>2. <a href="https://engineering.linkedin.com/blog/2014/12/how-linkedin-scales-lessons-in-monitoring">LinkedIn</a>:</b></p><p>LinkedIn несколько лет назад испытывал проблемы с масштабированием своей инфраструктуры. При отсутствии надлежащих инструментов мониторинга они не могли быстро определить, в чём именно возникают проблемы, и это привело к простою сервиса.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Используйте мощные фреймворки логирования:</b></p><p>Использование мощных фреймворков логирования по типу Log4j или Serilog позволяет эффективно записывать важную информацию о работе приложения.</p><p><b>2. Мониторьте в реальном времени:</b></p><p>Необходимо использовать инструменты мониторинга —  Prometheus, Grafana или New Relic для наблюдения за производительностью серверов, баз данных и приложений в реальном времени</p><p><b>3. Определите ключевые показатели производительности (KPI):</b></p><p>Чёткое определение и отслеживание KPI (время отклика, нагрузка на сервер и количество ошибок) предоставит командам доступ к важной информации о здоровье системы.</p><p><b>4. Анализируйте логи:</b></p><p>Создание процессов для анализа логов поможет командам выявлять паттерны ошибок, а не лишь быстро решать точечные проблемы. Для этого можно использовать решения, такие как ELK Stack (Elasticsearch, Logstash, Kibana).</p><p><b>5. Настройте уведомления и алерты:</b></p><p>Настройка автоматизированных уведомлений о сбоях или превышении порогов производительности позволит вам быстро реагировать на проблемы, минимизируя время простоя.</p><p><b>6. Тестируйте с нагрузкой:</b></p><p>Проводить нагрузочное тестирование нужно, ведь это может помочь предсказать возможные проблемы с производительностью.</p><h2>Ошибка 7: У вас есть заблуждение о «сделай всё сразу» и нет итеративного подхода</h2><p>Заблуждение о необходимости реализации всего функционала сразу и отсутствие итеративного подхода могут привести к созданию избыточных и крайне сложных систем, которые сложно тестировать, поддерживать и развивать. Это часто затрудняет адаптацию к изменениям в требованиях, замедляет процесс разработки и увеличивает риск ошибок.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://www.fastcompany.com/90347999/what-microsoft-learned-from-launching-the-surface">Microsoft</a>:</b></p><p>В попытке создать продукт с широким функционалом, Microsoft когда-то запускала проекты, в рамках которых все функции разрабатывались одновременно. Это обернулось задержками и перевыполнением бюджета, так как продукт не мог быть выведен на рынок, пока не были завершены все его части. В результате компания перешла к более агильному и итеративному подходу, который позволил выпускать версии продукта поэтапно.</p><p><b>2. <a href="http://www.autonews.com/article/20190304/OEM02/303049866/volkswagen-all-or-nothing-approach-leaves-quality-lags">Volkswagen</a>:</b></p><p>Во время разработки платформы для своих автомобилей Volkswagen столкнулся с практикой «всё или ничего», когда компании пытались одновременно интегрировать множество новых технологий. Это привело к проблемам, связанным с качеством и безопасностью, и компании пришлось отложить выпуск новых моделей.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Планируйте и приоритезируйте задачи:</b></p><p>Необходимо четко определить основные требования и приоритизировать задачи. Используйте методики, например, MoSCoW (Must have, Should have, Could have, Won't have), чтобы определить важность функционала.</p><p><b>2. Не забывайте про итеративную разработку:</b></p><p>Внедряйте циклы разработки, основанные на итеративных подходах, по типу Scrum или Kanban. Это позволит командам адаптироваться и быстро реагировать на изменения в требованиях.</p><p><b>3. Управляйте изменениями:</b></p><p>Создайте процесс управления изменениями, который позволит команде внедрять новые требования без необходимости существенного вмешательства в уже разработанный функционал.</p><p><b>4. Используйте подход Lean:</b></p><p>Рассмотрите внедрение Lean-подхода, который акцентирует внимание на устранении потерь в процессе разработки и оптимизации всех этапов создания продукта.</p><h2>Ошибка 8: Вы неправильно обрабатываете ошибки</h2><p>Неправильная обработка ошибок приводит к тому, что программы могут неожиданно завершаться, не предоставляя пользователю адекватной информации о проблеме или путях ее решения. Это может вызвать разочарование у пользователей, а также усложнить процесс отладки для разработчиков. Игнорирование или неправильное логирование ошибок затрудняет диагностику проблем и снижает общую надёжность системы.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://www.gao.gov/assets/230/226572.pdf">NASA</a>:</b></p><p>Проект Mars Climate Orbiter был потерян из-за ошибки в обработке данных, связанной с несоответствием единиц измерения. В то время как одни команды использовали метры, другие работали в дюймах. Эта ошибка не была должным образом обработана, что привело к тому, что аппарат вошёл в атмосферу Марса под неправильным углом и сгорел. Неправильная обработка ошибок ввела в заблуждение команду, которая не могла вовремя диагностировать проблему.</p><p><b>2. <a href="https://www.businessinsider.com/the-little-known-mistake-that-cost-knight-capital-440-million-2012-8">Knight Capital Group</a>:</b></p><p>В 2012 году трейдинговая компания Knight Capital Group потеряла 440 миллионов долларов за 45 минут из-за ошибки в коде. Ошибка, которая была вызвана неправильной обработкой исключений, привела к некорректным трейдам. Система продолжала работать, не показывая значительных признаков сбоя, что усложняло реакцию команды и в итоге привело к коллапсу.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Единообразно обрабатывайте ошибки:</b></p><p>Разработайте единую стратегию обработки ошибок для вашего приложения. Например, создайте централизованный модуль для логирования и уведомлений об ошибках.</p><p><b>2. Разделяйте ошибки на категории:</b></p><p>Классифицируйте ошибки по уровням важности. Это поможет чёткое понять серьёзность проблем и поможет команде быстрее реагировать на критические ситуации.</p><p><b>3. Используйте адаптивное логирование:</b></p><p>Логирование должно фиксировать не только ошибки, но и контекст их возникновения. Используйте уровни логирования (DEBUG, INFO, WARN, ERROR) для записи информации, которая может помочь в диагностике.</p><p><b>4. Настройте грацию исключений:</b></p><p>Научите команду правильно использовать и обрабатывать исключения с помощью конструкций try/catch/finally. Проверяйте, чтобы исключения не просто «гасились», но также создавались и корректные сценарии обработки.</p><p><b>5. Тестируйте сценарии ошибок:</b></p><p>Разработайте тесты, которые бы проверяли обработку различных ситуаций, включая неправильный ввод, сбои в сети и прочие исключительные состояния. Это поможет выявить слабые места в обработке ошибок.</p><h2>Ошибка 9: Вы игнорируете кроссбраузерность и мобильную адаптивность</h2><p>Игнорирование кроссбраузерности и мобильной адаптивности может привести к тому, что пользователи будут испытывать трудности при использовании приложения. Это может снизить пользовательский опыт, увеличить уровень отсева и негативно сказаться на репутации компании.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://www.forbes.com/sites/hannahbaar/2018/03/15/expedia-website-redesigns-what-went-wrong/?sh=141022116f1e">Expedia</a>:</b></p><p>В 2018 году, после неудачного редизайна своего сайта, Expedia заметила значительное снижение конверсий, особенно на мобильных устройствах. Дополнительные тесты показали, что новый интерфейс выглядел и работал плохо в определённых мобильных браузерах. Отказ от полноценной поддержки мобильной адаптивности привёл к значительным финансовым потерям.</p><p><b>2. <a href="https://www.bbc.co.uk/rd/blog/2015-02-lessons-from-the-bbc-why-browser-compatibility-matters">BBC</a>:</b></p><p>BBC столкнулась с проблемами, связанными с кроссбраузерностью, когда некоторые функции их сайта не работали в старых версиях популярных браузеров. Это вызвало негативные отзывы от пользователей и потребовало от команды много времени на исправления, что замедлило процесс внедрения новых функций.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Используйте фреймворки и библиотеки:</b></p><p>Рассмотрите использование популярных фреймворков и библиотек по типу Bootstrap или Foundation, которые включают встроенные механизмы для кроссбраузерности и мобильной адаптивности.</p><p><b>2. Проводите автоматизированное тестирование:</b></p><p>Внедрите автоматическое тестирование с помощью инструментов, например, Selenium или BrowserStack, чтобы проводить кроссбраузерное тестирование на различных устройствах и платформах.</p><p><b>3. Изучайте стандарты и спецификации:</b></p><p>Ознакомьтесь со стандартами W3C и другими рекомендациями по веб-разработке, чтобы убедиться, что ваш код соответствует современным требованиям и легко адаптируется к различным браузерам.</p><h2>Ошибка 10: Вы оптимизируете на ранних стадиях разработки</h2><p>Оптимизация на ранних стадиях разработки — распространённая ошибка, которая заключается в попытке улучшить производительность кода или системы еще до того, как будет ясно, какие именно компоненты требуют оптимизации. Это может привести к излишнему усложнению кода, потере фокуса на ключевых функциональных требованиях и снижению общей эффективности. Стремление к ранней оптимизации часто занимает драгоценное время и ресурсы, не всегда ведет к заметным улучшениям и может отвлечь команду от более важных задач.</p><h3>Реальные примеры ошибки</h3><p><b>1. <a href="https://www.gao.gov/assets/230/226572.pdf">NASA</a>:</b></p><p>В проекте Mars Climate Orbiter обнаружили, что разработчики слишком увлеклись оптимизацией кода обработки данных. Это привело к потере фокуса на основном функционале и неправильным единицам измерения. Результатом стало то, что не было видно проблемы, когда аппарат не вошел в атмосферу Марса, что обернулось его разрушением. Позже NASA поняла, что важнее сосредоточиться на правильной реализации основных функций, прежде чем наносить на них оптимизацию.</p><h3>Как командам избежать этой ошибки</h3><p><b>1. Возводите функционал в приоритет:</b></p><p>Сосредоточьте усилия на разработке основных функций и требований системы. Основное внимание должно быть уделено созданию работающего прототипа или MVP перед тем, как задевать оптимизацию.</p><p><b>2. Развивайте продукт итеративно:</b></p><p>Используйте итеративные и альфа-версии разработки, чтобы идентифицировать и оптимизировать проблемные места по мере их появления, а не в самом начале. Такой подход позволяет фокусироваться на реальных данных.</p><p>Поделитесь вашим опытом в комментариях!</p>]]></content:encoded>
    </item>
    <item>
      <title>Пишем хорошие баг-репорты: рекомендации</title>
      <link>https://tproger.ru/articles/piwem-horowie-bag-reporty--rekomendacii</link>
      <comments>https://tproger.ru/articles/piwem-horowie-bag-reporty--rekomendacii?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Evgeny Domnin]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/piwem-horowie-bag-reporty--rekomendacii</guid>
      <description><![CDATA[<p>Евгений Домнин делится тем, как писать понятные отчеты о багах, улучшая коммуникацию и ускоряя исправление ошибок. Практические советы для тестировщиков и специалистов по качеству.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/piwem-horowie-bag-reporty--rekomendacii">Пишем хорошие баг-репорты: рекомендации</a>»</p>]]></description>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 15 Oct 2024 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Представьте: вы разработчик, и тестировщик приносит баг, который найден в ходе регресса. Хочется поправить этот баг, и вы просите оформить тикет. Уже представляете, как возьмете его в работу, залинкуете к нему пулл-реквесты и проставите эстимейты, чтобы не было вопросов у продакт-менеджера.</p><p>Спустя время появляется новый тикет, но внутри лишь пара строчек и скриншот. Вздохнув, вы пробуете воспроизвести баг по этим данным, но ошибки нет. Пробуете несколько раз, но в итоге пишете тестировщику, что баг не воспроизводится и начинается новый цикл уточнений. Тратите время, которое могли потратить на решение новых задач, исправление других багов, просмотр аниме.</p><p>Меня зовут Евгений Домнин, я QA и постараюсь поделиться видением, что делает баг-репорт хорошим. Прошу простить за долгое вступление, давайте начнем.</p><h2>Заголовок</h2><p>Старайтесь ответить в названии тикета на три вопроса:</p><ol><li>Где происходит?</li><li>Что происходит?<br /></li><li>При каких условиях?<br /></li></ol><p>Опытному разработчику достаточно будет только взглянуть на заголовок, чтобы понять в чем дело. Например,</p><p><i>Страница Входа: Не подсвечивается поле при вводе неверного пароля</i></p><h2>Окружение</h2><p>Сталкивался с тем, что в тикете тестировщики часто забывают уточнить, а на каком окружении это произошло. Особенно это актуально в тикетах, связанных с UI, где не видно адрес сайта или запроса. Всегда указывайте это. Если есть отдельное поле в тикете, супер, указывайте туда, если нет — пишите в шагах воспроизведения, например,</p><p><i>Войти на стейдже под аккаунтом админа.</i></p><p>К слову о шагах.</p><h2>Шаги воспроизведения</h2><p>Один из главных разделов, инструкция по воспроизведению бага. Я выделю два подраздела, на которые обратим внимание. Это оформление шагов (визуальное) и наполнение (данные внутри).</p><h3>Визуальная часть</h3><h4>Сохраняйте структуру</h4><p>Есть разные вариации баг-репорта, но классически в нем должны быть следующие подразделы:</p><ol><li>Шаги</li><li>Ожидаемый результат<br /></li><li>Фактический результат<br /></li></ol><p>Используйте эту структуру и придерживайтесь ее всегда. Это как раз тот случай, когда единообразие поможет структурировать ваши мысли при описании</p><h4>Используйте нумерованный список</h4><p>Разделяйте шаги через нумерованный список. Иногда тестировщики пишут подробно, но одним сплошным текстом. Не будьте такими. Всем будет гораздо проще читать с разделением.</p><p>По возможности, пишите без грамматических ошибок.</p><p>А теперь к наполнению этих шагов.</p><h3>Наполнение</h3><h4>Здравый смысл при описании</h4><p>Вы не должны разбивать каждое движение в отдельный пункт, если оно не является ключевым в воспроизведении бага — это довольно трудно читать и использовать в работе. Не бойтесь в один пункт добавить сразу несколько действий. Что я имею ввиду?</p><p><b>Плохо:</b></p><ol><li><i>Зайти на test.com/login</i></li><li><i>Кликнуть в поле login<br /></i></li><li><i>Ввести логин<br /></i></li><li><i>Кликнуть в поле password<br /></i></li><li><i>Ввести пароль</i><br /></li></ol><p><b>Хорошо:</b></p><ol><li><i>Перейти на test.com/login и войти под любой учетной записью</i></li></ol><p>Мы не дробим пункты и шаги на то, что и так сделает разработчик, проходя по стандартному флоу. Когда я начинал, то немного зависал на этом пункте — мне казалось, что каждое движение должно быть под пунктом. Не стоит этого делать.</p><h4>Избегайте неоднозначности</h4><p>Всегда дополняйте шаги запросом, на который нужно посмотреть на конкретном шаге, пишите конкретную кнопку, на которую нужно нажать (если она с одинаковым названием).</p><h4>Добавляйте тестовые данные</h4><p>Напишите данные для входа, если ошибка на вашем аккаунте, не поленитесь добавить тестовый пейлоад, что помогает воспроизвести ошибку.</p><h4>Пройдите по созданным шагам еще раз</h4><p>Бывает, что вы укажете шаги сразу после появления бага, но может оказаться, что не хватает какого-то шага для полного понимания, или баг не воспроизведется еще раз, и нужно будет искать более точные шаги.</p><h2>Ожидаемый результат</h2><p>Отдельным блоком идет ожидаемый результат, где мы описываем (неожиданно) результат, что ожидается при воспроизведении шагов. Особых рекомендаций здесь нет, кроме того, что этот блок должен быть — разработчику важно понимать к какому поведению следует привести функциональность. Формулировки по типу «все должно быть норм» не подходят, пишите конкретное поведение</p><h2>Фактический результат</h2><p>Здесь мы пишем, а что произошло на самом деле, когда прошлись по шагам. Здесь тоже важна конкретика. Не стоит писать, что все поломалось (хотя наверное так оно и было). Стоит описать, что за индикаторы говорят, что все сломалось. Например:</p><p><i>Возвращается 500 ошибка по запросу GET /accounts и блокируется UI. Пользователь не может выйти со страницы или кликнуть на элементы. Обновление страницы приводит к повторному запросу и ошибке.</i></p><p>То есть описываем реальный эффект и его влияние на флоу юзера.</p><h2>Дополнительные файлы</h2><p>Отдельный раздел, что стоит упомянуть. Раздел, где вы прикладываете дополнительную информацию, что описывает баг. Знаю некоторых разработчиков, что не фанаты читать шаги воспроизведения — они сразу смотрят на фактический результат и дополнительные материалы, которые его раскрывают.</p><h3>Скриншоты ошибки</h3><p>Лучше один раз увидеть, чем сто раз услышать. Отличный способ наглядно показать, что именно происходит и в каком месте. Всегда старайтесь приложить скриншот.</p><h3>Запрос</h3><p>Если есть запрос, в котором произошла ошибка и всегда стоит включить его в тикет. Однако в запросе много разных параметров. Я выделяю следующие важные к указанию.</p><h4>URL ошибки</h4><p>Собственно сам запрос, можете взять из раздела Network в браузере, котором проходит тестирование.</p><h4>Метод</h4><p>GET, POST, TRACE, OPTION и так далее — методов немало, как и запросов с одинаковым URL, но разными методами. Не забудьте указать его в тикете.</p><h3>Код ошибки</h3><p>Тоже важный момент. Скорее всего, вы не пропускаете его, но не забудьте отметить, что за код вернулся от сервера.</p><h4>Payload</h4><p>Это полезная нагрузка запроса, то есть данные, что мы отправили в нашем запросе на сервере. Это есть не в каждом запросе (например, HEAD или GET ими не обладают), но там могут быть причины ошибки.</p><h4>Response</h4><p>Ответ от сервера. В части случаев туда попадает вся ошибка, где даже написано, где произошла ошибка, а порой только дефолтная заглушка, что настроена на бекенде для такого типа ошибок. Обязательно указывайте – сохраните много времени разработчика.</p><h4>Логи из консоли</h4><p>Порой есть ошибки, что находятся в консоли, и их можно добавить к тикету. Возможно, вы делали это и до меня, просто отмечу, что большой блок текста всегда можно сохранить в файл типа .log и добавить к тикету. Улучшает читабельность самих логов и тикета.</p><h2>Это все хорошо, но...</h2><blockquote>Это все хорошо, но где у нас столько времени, чтобы все это красиво оформлять? Сроки горят, менеджер ругается, на проде блокер, а мне предлагается сидеть и еще расписывать все? Просто напишу разрабу в ЛС и все.</blockquote><p>Это логичный аргумент, который может возникнуть. На самом деле я не питаю иллюзий об идеальном мире тестировщика, где времени на тестирование закладывается с запасом, все идет по процессам и ведется подробная и качественная документация. Все понимаю, нередко это период цейнтнота, горящих ...кхм, глаз и попыток все успеть.</p><p>Мелкие ошибки имеют свойство копиться, занимать больше времени на переключение контекста, а также приводить к плохим практикам. Если мы начинаем постепенно вводить улучшения и смотреть, как они работают, то  сможем прийти к процессу, который стабилен и стандартизирован и прогнозируем для участников. Проджект будет понимать, что происходит с продуктом, не дергая всех подряд, а увидев качественный репорт, разработчику не придется уточнять у тестировщика условия воспроизведения и отвлекать его от тестирования, а стейкхолдеры будут понимать, как движется продукт.</p><p>Эта статья больше ориентирована на новичков, кто собирается или уже начал путь в тестировании. Верю, что маленькие шаги приводят к большим результатам, а рекомендации выше — к более качественным баг-репортам.</p><p><i>Если у вас есть вопросы, предложения, несогласия, возмущения — пишите в комментарии, интересно услышать мнение!</i></p>]]></content:encoded>
    </item>
    <item>
      <title>iPad Pro начали массово «окирпичиваться» после обновления на iPadOS 18</title>
      <link>https://tproger.ru/news/ipad-pro-nachali-massovo--okirpichivatsya--posle-obnovleniya-na-ipados-18</link>
      <comments>https://tproger.ru/news/ipad-pro-nachali-massovo--okirpichivatsya--posle-obnovleniya-na-ipados-18?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/ipad-pro-nachali-massovo--okirpichivatsya--posle-obnovleniya-na-ipados-18</guid>
      <description><![CDATA[<p>Apple временно приостановила установку iPadOS 18 на устройства iPad Pro с чипом M4 из-за многочисленных жалоб пользователей. После обновления многие планшеты перестали включаться, а в некоторых случаях устройства требуют полной замены</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/ipad-pro-nachali-massovo--okirpichivatsya--posle-obnovleniya-na-ipados-18">iPad Pro начали массово «окирпичиваться» после обновления на iPadOS 18</a>»</p>]]></description>
      <category><![CDATA[Apple]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 18 Sep 2024 07:36:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Apple приостановила возможность установки обновления iPadOS 18 для моделей iPad Pro с чипом M4.</p><p>Теперь пользователи больше не могут загрузить и накатить этот апдейт, пока компания не устранит возникшую проблему.</p><p>Как мы и говорили в своем недавнем <a href="https://tproger.ru/news/vywla-ios-18--pochemu-ne-stoit-na-nee-obnovlyatsya-i-luchwe-podozhdat-ios-18-1">материале</a>, не стоит торопиться — первые версии свежих iOS (и, видимо, iPadOS) очень часто бывают с багами и дырами.</p><h2>Массовые сбои устройств</h2><p>Причиной остановки стали многочисленные жалобы владельцев iPad Pro, чьи устройства были повреждены после обновления.</p><p>На Reddit появилось множество сообщений от тех, чьи планшеты перестали включаться. В том числе из-за сбоев в процессе установки iPadOS 18. В некоторых случаях проблему можно было решить только полной заменой устройства.</p><h2>Возможные причины проблемы</h2><p>Интересно, что проблема коснулась не всех владельцев iPad Pro.</p><p>Некоторые эксперты считают, что сбой может быть связан с предварительной установкой обновления iPadOS 17.7 перед обновлением на iPadOS 18.</p><p>Как бы то ни было, пока Apple не устранит эту проблему, загрузка свежего обновления системы на iPad остается недоступной.</p><h2>Официальный комментарий Apple</h2><p>Apple уже дала <a href="https://www.macrumors.com/2024/09/17/apple-pulls-ipados-18-m4-ipad-pro/">официальный комментарий</a> порталу MacRumors:</p><p>«Мы временно сняли обновление iPadOS 18 для моделей iPad Pro с чипом M4, так как работаем над устранением проблемы, которая затрагивает небольшое количество устройств».</p>]]></content:encoded>
    </item>
    <item>
      <title>Самый дорогой компьютерный баг в истории: $5.18 млн/сек, $8.65 млрд за 28 минут</title>
      <link>https://tproger.ru/news/--samyj-dorogoj-kompyuternyj-bag-v-istorii---5-18-mln-sek---8-65-mlrd-za-28-minut</link>
      <comments>https://tproger.ru/news/--samyj-dorogoj-kompyuternyj-bag-v-istorii---5-18-mln-sek---8-65-mlrd-za-28-minut?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/--samyj-dorogoj-kompyuternyj-bag-v-istorii---5-18-mln-sek---8-65-mlrd-za-28-minut</guid>
      <description><![CDATA[<p>Самый дорогой баг в истории: в 2012 году Knight Capital потеряла $8.65 млрд за 28 минут из-за ошибки в коде торгового бота. Неправильный ввод данных при релизе привел к безудержным покупкам акций. Компания не смогла справиться с последствиями и закрылась, оставив уроки для разработчиков по всему миру.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/--samyj-dorogoj-kompyuternyj-bag-v-istorii---5-18-mln-sek---8-65-mlrd-za-28-minut">Самый дорогой компьютерный баг в истории: $5.18 млн/сек, $8.65 млрд за 28 минут</a>»</p>]]></description>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Дата-центр]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 06 Aug 2024 06:35:35 GMT</pubDate>
      <content:encoded><![CDATA[<p>Чуть более 10 лет назад, 1 августа 2012 года, произошел самый дорогой софтверный баг в истории, оставивший в прошлом компанию Knight Capital. За 28 минут они потеряли $8.65 млрд — это примерно $5.18 млн в секунду.</p><p>Об этом случае <a href="https://www.quora.com/What-is-the-costliest-coding-error-anyone-has-ever-made/answer/Theodore-Smith-9?prompt_topic_bio=1">вспомнил</a> Теодор Смит из PHP Experts.</p><h2>Как это случилось</h2><p>Разработчики Knight Capital срочно переносили своего высокочастотного торгового бота на новый сервис NYSE, который должен был запуститься через 33 дня.</p><p>Они работали по 80 часов в неделю, чтобы успеть к дедлайну. В коде бота они оставили старые методы и классы, чтобы избежать необходимости пересборки.</p><p>Также разработчики убрали старый тестовый код и вставили новый рабочий. Тесты прошли успешно, и утром 1 августа в 8:00 код развернули в продакшн. Всё выглядело отлично.</p><h2>Катастрофа</h2><p>Во время утреннего совещания в 9:30 рынок открылся и бот начал безудержно покупать акции по самым высоким ценам. За две минуты системы Knight Capital обнаружили, что деньги компании стремительно исчезают.</p><p>Связаться с разработчиками не удавалось, так как они находились в удаленном офисе и оставили свои телефоны на столах.</p><p>Через 11 минут боты уже скупили акции на сумму свыше $3 млрд, а компания оказалась на грани краха.</p><p>Единственным решением оказалось физическое уничтожение серверов в дата-центре с помощью топоров. Торги прекратились через 28 минут, оставив бумажные убытки в $10.8 млрд.</p><h2>Почему это произошло</h2><p>Расследование показало, что ошибка была вызвана неправильным вводом данных при релизе. Один из операторов ввел неверную команду rsync, из-за чего один из серверов остался с тестовым кодом, который скупал акции по самым высоким ценам.</p><p>После выявления ошибки разработчики решили откатиться к предыдущему релизу, что лишь усугубило ситуацию. В итоге за 28 минут было потеряно $8.65 млрд.</p><h2>Последствия</h2><p>Эта катастрофа привела к потере всех корпоративных клиентов Knight Capital и компания закрылась через несколько недель.</p>]]></content:encoded>
    </item>
    <item>
      <title>Создатель Windows объяснил истоки сбоя CrowdStrike, который «уронил» 8.5 млн компьютеров по всему миру</title>
      <link>https://tproger.ru/news/--sozdatel-windows-rasskazal--kak-bag-crowdstrike--uronil--8-5-mln-kompyuterov-po-vsemu-miru</link>
      <comments>https://tproger.ru/news/--sozdatel-windows-rasskazal--kak-bag-crowdstrike--uronil--8-5-mln-kompyuterov-po-vsemu-miru?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/--sozdatel-windows-rasskazal--kak-bag-crowdstrike--uronil--8-5-mln-kompyuterov-po-vsemu-miru</guid>
      <description><![CDATA[<p>В конце прошлой недели произошел один из крупнейших сбоев Windows за всю историю IT. Тогда пострадали миллионы компьютеров по всему миру</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/--sozdatel-windows-rasskazal--kak-bag-crowdstrike--uronil--8-5-mln-kompyuterov-po-vsemu-miru">Создатель Windows объяснил истоки сбоя CrowdStrike, который «уронил» 8.5 млн компьютеров по всему миру</a>»</p>]]></description>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[Microsoft]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 23 Jul 2024 11:33:32 GMT</pubDate>
      <content:encoded><![CDATA[<p>Мир все еще восстанавливается после одного из крупнейших сбоев в истории IT, который привел к выходу из строя 8.5 млн компьютеров под управлением Windows.</p><p>Дэйв Пламмер, бывший инженер-программист из Microsoft, объяснил на своем YouTube-канале, как именно произошел этот сбой.</p><h2>Причины сбоя</h2><p>По словам Пламмера, операционная система использует кольцевую систему для разделения кода на два типа: Ring 0 (ядро) и Ring 1 (пользовательский режим).</p><p>Ядро управляет основными функциями, такими как связь с оборудованием и управление памятью, в то время как пользовательский режим отвечает за работу приложений.</p><p>Если в пользовательском режиме происходит сбой, то приложение просто закрывается, но сбой в ядре приводит к краху всей системы.</p><h2>Ошибка в драйвере CrowdStrike</h2><p>Проблема возникла из-за обновления программного обеспечения Falcon от CrowdStrike, которое работает на уровне ядра.</p><p>Falcon отслеживает и идентифицирует новые атаки через мониторинг приложений, что требует привилегированного доступа.</p><p>В новом обновлении драйвера Falcon содержались файлы, которые вызывали ошибку на уровне ядра, что приводило к краху системы и появлению «Синего экрана смерти».</p><h2>Недостаточная проверка и последствия</h2><p>Как отметил Пламмер, проблемный драйвер CrowdStrike не был должным образом проверен на наличие возможных ошибок перед выпуском.</p><p>Более того, драйвер был помечен как «boot-start driver» — это означает, что он необходим для запуска ОС. Это и привело к тому, что сбой драйвера вызывал постоянные циклы перезагрузки системы. Для исправления ситуации требовалось физическое вмешательство.</p><h2>Восстановление работы систем</h2><p>Для исправления проблемы пользователи должны были загрузить систему в безопасном режиме Windows, удалить проблемные драйверы CrowdStrike и перезагрузить компьютер.</p><p>Хотя это простое решение, оно требовало значительного времени и усилий, особенно если от исправления зависели тысячи компьютеров.</p>]]></content:encoded>
    </item>
    <item>
      <title>5 ошибок Python-разработчиков, которые выдают новичка</title>
      <link>https://tproger.ru/articles/5-owibok-python-razrabotchikov--kotorye-vydayut-novichka</link>
      <comments>https://tproger.ru/articles/5-owibok-python-razrabotchikov--kotorye-vydayut-novichka?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Вика Овсянникова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/5-owibok-python-razrabotchikov--kotorye-vydayut-novichka</guid>
      <description><![CDATA[<p>Рассказали, какие основные ошибки делает начинающий Python-разработчик, как их можно избежать и на что в целом обращать внимание.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/5-owibok-python-razrabotchikov--kotorye-vydayut-novichka">5 ошибок Python-разработчиков, которые выдают новичка</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 20 Feb 2024 08:20:02 GMT</pubDate>
      <content:encoded><![CDATA[<h2>1. Неряшливость в коде</h2><p>« from main.tasks import task »</p><p>Это не только код по PEP, сколько отсутствие видимой логики и структуры в коде.</p><p>Главное, что надо всегда помнить, что код должен быть в первую очередь читаемым, а в идеале — еще и понятным. Тут нет предела совершенству, но в целом есть несколько простейших рекомендаций, которые позволят избежать даже не ошибок, сколько нелепых небрежностей.</p><p>По тексту я использую синтаксис Django ORM, так как он визуально проще, чем тот же SQL, и его можно представить как псевдокод для SQL.</p><h3>Давайте понятные имена функции и переменным</h3><p>Вместо c = count()</p><p>Вместо q = Model.objects.all()</p><p>Если будут большие связки из фильтров и прочего, можно</p><p>Вместо for x, y in _dict.items()</p><p>Вместо count = User.objects.count()</p><p>Кто-то решит, что лучше count_user, но я думаю, это индифферентно, главное, чтобы было понятно.</p><p>Бывает обратная ситуация, когда название становится слишком длинным, чтобы вынести всю логику. Например, произвольная функция</p><p>может быть существенно упрощена несколькими способами.</p><p>Нормально:</p><p>Здесь у нас «хардкод», который лучше избегать всегда, кроме сonst-значений. Тем не менее, в отличие от довольно спорной оригинальной функции, эти имеют шанс на переиспользование.</p><p>Хорошо:</p><p>Теперь функция не привязана к конкретным типам покемонов, мы можем переиспользовать ее много раз. Жаль только, что в случае, если нам нужно посчитать в целом несколько типов за раз.</p><p>Отлично:</p><p>Строгий пример полиморфизма, мы больше не привязаны ни к конкретным типам покемонов, ни к тому, скольких надо посчитать за раз. Нужны пикачу? Отправляем одного пикачу, нужны пикачу и чармандеры, отправляем их списком и получаем нужный результат.</p><p>Поэтому я бы использовал окончательно вот такой вариант:</p><p>В предыдущем варианте происходит как минимум на одну проверку больше, если вводные данные не подходят по типу. К тому же там функция, у которой происходит возврат данных. Хочется, чтобы он был в конце, потому что туда обычно и смотрит программист, что пришло, что вернул, а потом уже в тело.</p><p>Если не получается вложить весь смысл в наименование функции или переменной, не пишите «x = » или «def func», а попробуйте разнести логику.</p><h2>2. Отсутствие базовых знаний Linux/Unix</h2><p>Наверняка в англоязычных зарубежных компаниях встречается разработка на Windows, но все же Python, как скриптовый язык, работает на Unix и Linux.</p><p>Оптимальная разработка проходит на Mac или Ubuntu (и других любимых дистрибутивах), с целью выложить код на сервер, который, скорее всего, будет на Ubuntu. Более того, в вакансиях начиная с мидла, а иногда с джуна, спрашивают базовые знания Linux. В идеале вы должны уметь использовать еще несколько утилит, которые помогут в работе или деплое, уметь создавать пользователей, группы, менять группы и права директориям.</p><h3>Утилиты которые точно пригодятся в использовании</h3><p><b>Supervisor</b></p><p>Позволяет запускать и отслеживать процессы. Например, чтобы при перезапуске системы у вас всегда запускался проект.</p><p><b>Cron</b></p><p>Утилита, которая позволяет выполнять команды стартуя их в конкретный момент времени, например, парсить сайт каждый понедельник, в два часа ночи.</p><p><b>Nginx</b></p><p>Широко используемый веб-сервер. Если у вас веб-проект, без него практически не обойтись.</p><h2>3. Слабые знания СУБД</h2><p>Джуна могут взять без опыта или знаний работы с СУБД, но в целом полезно знать, для чего обычно используется та или иная база данных, какие лучше использовать и в каких случаях.</p><h3>Нереляционные СУБД</h3><p>С нереляционными попроще, мы вот используем Redis, MongoDB и Clickhouse.</p><p>Redis — это key-value хранилище. Если провести аналогию с питоновскими типами — словарь, который отлично подходит для хранения кэша. Удобно использовать для межкомпонентного взаимодействия.</p><p>MongoDB — это хранилище JSON-документов, по такой же аналогии — список json’ов. Отлично подходит, если у вас где-то генерируется большое количество данных, которые вы не хотели бы терять. Например, если бы все эти данные писали в реальном времени в реляционную: это долго, могут случится ошибки записи, плюс нужна валидация и прочее. Mongo же пишет эти данные как есть, а в соседнем сервисе вы можете спокойно загрузить все данные в реляционную базу в своем темпе, пройдя все нужные валидации и проверки.</p><p>Clickhouse — можно сказать, новинка отечественных разработчиков, использует SQL язык, но при этом не является реляционной БД. Основная задача — хранение гигантского количества данных и возможность с ними работать. Собрать миллиард записей и посчитать сумму тех или иных записей будет быстрее, чем в обычной реляционной БД. Хорошо сжимает данные, можно разворачивать кластером, привычный SQL язык.</p><p>Подойдет, чтобы смотреть статистику, основанную на больших объемах данных.</p><h3>Реляционные СУБД</h3><p>Это, в первую очередь, MySQL и Postgres. Скорее всего, разработка на Python при участии БД будет проходить с помощью ORM, Django ORM или SQLAlchemy. Если это SQLAlchemy, это не так страшно, так как написание запросов в этой ORM так или иначе следует логике SQL. Django ORM максимально своеобразный.</p><p>Вот один и тот же способ получить имя пользователя и название его работы</p><ul><li>в Django ORM:</li></ul><ul><li>в SQLAlchemy:</li></ul><ul><li>чистый SQL:</li></ul><p>В «Алхимии» видно, что мы что-то все-таки присоединили, Django ORM же работает с другими абстракциями. Надо понимать, как работают запросы и чистый SQL. Например, в случае профилирования, если тормозит страница, и экран профилирования покажет некий запрос, который вызывается либо несколько раз, либо отъедает время загрузки. Тогда нужно этот запрос отыскать в коде. Опять же, если SQL из примера будет съедать 10 секунд загрузки, выше видно, как он будет выглядеть в «Алхимии» или в «Джанго ОРМ».</p><h2>4. Забивание на чистоту GIT</h2><p>Частая проблема, которая не просто запускается, но еще и поддерживается коллективом. У вас мастер-ветка выглядит как</p><p>«fix -&gt; fix -&gt; fix -&gt; try -&gt; featyre -&gt; fix -&gt; revert» ?</p><p>Самое популярное оправдание, которое я слышал на эту тему, мол, что привязался, работает же. Фактически да, так оно и есть, но код это не только про «работает же». Это еще и его сопровождение в будущем. Представьте, вас просят что-нибудь найти или пофиксить спустя кучу времени, а вы натыкаетесь на странный кусок кода. В истории GIT одно «feature-&gt; feature -&gt; feature»? И коллега, судя по аннотации GIT, даже работает, но тоже не может вспомнить, в каком бреду это было написано и по чьей просьбе.</p><h3>Поэтому, как по мне, близкое к идеалу ведение коммитов:</h3><p><b>Одна задача — один коммит.</b> Несколько разработчиков на одной задаче? От каждого разработчика по коммиту. В тексте коммита ссылка на трекер задач на конкретную задачу. В будущем, если возникнут проблемы, можно легко посмотреть, в рамках чего писался тот или иной код.</p><p>Помимо самих коммитов, есть еще использование rebase и merge.</p><p>Самое оптимальное использование <b>merge — только для мастера</b>. Во всех остальных случаях отлично показывает ребейз, история будет чище и меньше конфликтов.</p><p>Начинаете работу над задачей? <b>Делаете новую ветку от мастера</b> (или как скажет тимлид).</p><p>Заканчиваете работу над задачей? <b>Делайте финальный ребейз на мастер и посквошьте коммиты</b>. Работаете с коллегой в одной ветке? Вместо мержа origin-ветки в локальную, делайте ребейз на ориджин-ветку. История коммитов будет чище, сложно будет в ней теряться.</p><h2>5. Написание тестов</h2><p>Я не буду про TDD. Это приходит с годами. Выдаст в вас новичка не только отсутствие тестов, так и написание тестов на каждый чих. Нужно сразу понять, где начинается бизнес-логика, и покрывать ее тестами.</p><p>Это зависит от компании, но я встречал следующий паттерн: бизнес-логику покрывают тестами, какие-то рядовые действия — нет. Бизнес-логикой компания зарабатывает там, где ошибки принесут финансовые результаты. Да, может, если не получится сохранить VK в профиле, клиент обидится, и вы его потеряете, но лучше потратить время на обкладывание тестами того функционала, которым зарабатывает ваш клиент.</p><p>Понимание, что является бизнес-логикой, а что нет, это придет с опытом. Могу лишь порекомендовать чаще расспрашивать менеджеров, чем именно занимается компания и как выглядят процессы снаружи кода, с точки зрения обычного персонала. Универсальный совет, поможет вам лучше понимать свой продукт.</p>]]></content:encoded>
    </item>
    <item>
      <title>Сделаем bugreport чуточку полезней</title>
      <link>https://tproger.ru/articles/kak-podruzhit-razrabotku-s-menedzharami</link>
      <comments>https://tproger.ru/articles/kak-podruzhit-razrabotku-s-menedzharami?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[ihor]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-podruzhit-razrabotku-s-menedzharami</guid>
      <description><![CDATA[<p>СТО команды Tproger рассказал, как оформлять репорт багов так, чтобы сэкономить время разработчиков и ускорить исправление ошибок.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-podruzhit-razrabotku-s-menedzharami">Сделаем bugreport чуточку полезней</a>»</p>]]></description>
      <category><![CDATA[Организация разработки]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 03 Oct 2023 12:06:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Привет, меня зовут Игорь, я СТО команды Tproger.ru. Расскажу, как мы оформляем отчёты об ошибках.</p><h2>Зачем нужны отчёты</h2><p>Зачем менеджеру составлять какой-то отчёт об ошибке и тратить на него своё время, а не просто написать о проблеме?</p><p>Дело в том, что исправление ошибки напрямую зависит от того, кто с ней пришёл, и насколько эффективно он про неё сообщит. Если отчёт описан максимально корректно, то и шансы на исправление ошибки становятся больше, а времени, соответственно, меньше.</p><p>Посмотрим на примере: допустим, наш продукт успешно работает, и ничего не предвещало беды, как вдруг вылез баг.</p><figure><img src="https://media.tproger.ru/user-uploads/10203/2023-10-02/cf8f730c-94f5-47a7-9b0a-c5a4a8be42c6.jpeg" alt="" /><figcaption>Разработчик, у которого работает сайт без багов</figcaption></figure><p>Он может быть незначительный, а может быть и критическим. В любом случае, его, скорее всего, придется исправить во избежание проблем в будущем.</p><p>В идеальном мире существуют системы багрепорта с полноценными формами с обязательными полями, которые падают готовыми задачами в таск-менеджер.</p><p>Но, по разным причинам, не у всех команд есть полноценный таск-менеджер для этих целей. Зачастую это отдельный канал или чат, куда всё сваливают разработке.</p><p>Пример того, как зачастую происходит коммуникация между разработчиками и менеджерами при неотлаженных процессах:</p><blockquote>Эй, разработчик, у нас не работает кнопка для формирования отчета!</blockquote><p>В таком случае команда разработки выделяет ресурсы на анализ ситуации и действует по алгоритму:</p><ol><li>Проверяет работу кнопки, пытаясь опытным путём повторить ошибку.</li><li>Находит ошибку, описывает спецификацию, оценивает сроки.</li><li>Ошибка берется в работу.</li></ol><p>Когда ошибка пойдет в работу, зависит от её сложности, приоритета и того, как она влияет на работу продукта.</p><p>Но казалось бы, это такой простой путь, и что вообще может пойти не так, и почему бы просто не исправить ошибку. Проблема в пункте №1: из-за отсутствия достаточного количества информации поиск проблемы может занять неопределённое время, что естественно повлияет на весь срок выполнения.</p><p><b>Итого: </b>разработчики тратят полезные ресурсы только на поиск ошибки, затягивая время правки, а менеджеры, в свою очередь, недовольны медленной работой разработки.</p><p>Чтобы избежать никому не нужной затяжки исправлений или хотя бы сократить это время, необходимо выстроить между разработчиками и менеджерами систему отчётности по ошибкам.</p><p>Для себя мы вывели несколько правил-рекомендаций написания хорошего отчёта. Они подходят как при простом багрепорте через каналы, так и для форм таск-менеджеров.</p><h2>Рекомендации написания отчёта об ошибке</h2><ol><li><b>Воспроизведение:</b> прежде чем передать ошибку в разработку, убедиться, что она повторяется, желательно несколько раз. Воспроизвести процесс и описать действия шаг за шагом.</li><li><b>Проверка на существование:</b> перед созданием отчёта поищите в бэклоге. Возможно, кто-то уже нашел ошибку, и разработчики уже знают о ней.</li><li><b>Краткое описание:</b> минимум слов, только по делу. Одна проблема, один отчёт. По краткому описанию удобно вести навигацию среди большого бэклога.</li><li><b>Полное описание:</b> описать ошибку и путь её достижения. Чем подробнее будет описан путь, тем легче будет повторить ошибку, найти и исправить.</li><li><b>Окружение:</b> ОС, браузер, устройство, иные технические моменты, которые могли повлиять на баг (VPN, режим Инкогнито, Adblock и прочее).</li><li><b>Вместо тысячи слов:</b> приложить к отчёту скриншоты или запись с экрана. Зачастую такая информация содержит больше полезной информации, нежели текстовое описание.</li><li><b>Ожидание и результат:</b> описать, как все должно работать, какое поведение и результат были получены, какой ожидаемый результат и путь к нему.</li><li><b>Место происшествия: </b>указать источник ошибки. Если это веб-приложение, указать полный url. Если иное, указать окно\вкладку и т.д.</li><li><b>Журналы ошибок:</b> любые локальные логи помогут быстрее разобраться в ошибке. Сообщения в консоли браузера, ошибки в системе, любые alert’ы.</li><li><b>Приоритет:</b> указать, насколько ошибка влияет на работу продукта. Исходя из приоритета, команда разработки сможет принять решение, как быстро приступить к её исправлению.</li></ol><p>Плохой пример:</p><ul><li>Не могу подписаться на тег.</li><li>Нужно срочно починить.</li></ul><p>Хороший пример:</p><ul><li>Убедился несколько раз в ошибке, смог повторить.</li><li>В беклоге не нашел ничего похожего, можно создавать.</li><li>Не могу подписаться на тег java.</li><li>Залогинен, открываю любую статью с тегом java, кликаю на тег для подписки, но ничего не происходит. При этом на остальные теги могу подписаться без ошибок.</li><li>ОС linux mint, Google Chrome Версия 112.0.5615.165 (Официальная сборка), (64 бит), Адблок был и включен, и выключен.</li><li>Скриншот или видео.</li><li>Ожидаю, что после клика смогу подписаться на тег java и получать уведомления о выходе новых постов на сайте с этим тегом.</li><li><a href="https://tproger.ru/articles/kak-poyavilsya-s-i-pri-chyom-tut-konflikt-sun-i-microsoft">Статья с тегом java</a>.</li><li>Failed to load resource: the server responded with a status of 404 ()</li><li>Баг очень важный, его нужно сделать вчера.</li></ul><p>Таким образом, потратив не намного больше времени на подготовку отчёта, менеджер принимает непосредственное участие, создав понятную задачу, которую разработчик может сразу взять в работу, не тратя ресурсы на её изучение.</p><p>Поделитесь, как вы проводите обработку ошибок в своей команде.</p>]]></content:encoded>
    </item>
    <item>
      <title>The Daily WTF — площадка для обмена глупейшими историями из IT</title>
      <link>https://tproger.ru/articles/the-daily-wtf-ploshhadka-dlya-obmena-glupejwimi-istoriyami-iz-it</link>
      <comments>https://tproger.ru/articles/the-daily-wtf-ploshhadka-dlya-obmena-glupejwimi-istoriyami-iz-it?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рафаил Агазода]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/the-daily-wtf-ploshhadka-dlya-obmena-glupejwimi-istoriyami-iz-it</guid>
      <description><![CDATA[<p>The Daily WTF – это сайт, на котором разработчики публикуют самые глупейшие случаи из собственной практики в IT.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/the-daily-wtf-ploshhadka-dlya-obmena-glupejwimi-istoriyami-iz-it">The Daily WTF — площадка для обмена глупейшими историями из IT</a>»</p>]]></description>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Юмор]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 13 Jul 2023 10:29:28 GMT</pubDate>
      <content:encoded><![CDATA[<p>The Daily WTF – это сайт, основанный в 2004 году, на котором разработчики публикуют самые глупейшие случаи из собственной практики в IT. Разработчики рассказывают на примерах из жизни, как не стоит разрабатывать программное обеспечение.</p><p>Здесь публикуются самые глупые истории о катастрофичных разработках, начиная полным провалом управления проектами и заканчивая написанием непонятного кода.</p><p>В этой статье мы разберем несколько историй, с которыми столкнулись разработчики с The Daily WTF.</p><h2>Ручная база данных</h2><p>Разработчик С. Уайлс работает в крупной компании, которая занимается медицинским страхованием. Он разрабатывает SAS-приложение для своего работодателя.</p><p>Вдруг разработчику <a href="https://thedailywtf.com/articles/yearly-updates">поступила задача</a>: нужно открыть базу данных и поменять в ней формат записи даты: сделать так, чтобы запись месяца состояла из двух цифр, а не из одной. К примеру, январь должен быть записан как «01».</p><p>Итак, Уайлс открывает базу данных и видит в ней это:</p><p>Оказалось, что все данные об обновлениях дат вносятся в БД вручную для составления запроса. Форматирование даты не было сделано на уровне БД, и для неё не была написана отдельная функция. Однако вместо этого скрипт обновляется вручную. Каждый год.</p><h2>Сломанная валидация данных</h2><p>Кэрол много лет работает во фронтенде веб-приложения. Ей <a href="https://thedailywtf.com/articles/terned-off-validations">достался код</a>, который нужно было поддерживать и который отвечал за проверку данных, введённых, по всей видимости, в поле пароля для входа на сайт.</p><p>Кажется, что написать такую функцию просто. По крайней мере, правила для составления пароля довольно просто описать устно: должен быть один знак вроде восклицательного, длина пароля должна быть не меньше 8 символов, и так далее.</p><p>На деле написать такой код самостоятельно — довольно непростая задача, потому что код может быть громоздким.</p><p>Вот, какой код достался Кэрол:</p><p>Кэрол обратила внимание на две вещи. Первая — функция validateGeneral выглядит главной и помечена как асинхронная. На деле же она не выполняет ничего значимого и не является асинхронной.</p><p>Оказалось, что разработчик, который писал функцию, пометил функцию асинхронной, потому что они обернули все условные операторы в вызов Promise.all, явно пытаясь заставить однопоточный JavaScript выполнять многопоточную проверку.</p><p>Когда это не сработало, они удалили вызов Promise.all, но не удалили async.</p><p>Раньше функция выбрасывала ошибки по цепочке (хотя возврат значения имел бы больше смысла), но при отладке другой разработчик обернул все это в try/catch. После этого отладочный код не был удалён, а функция сломалась.</p><p>Функция прошла код-ревью и тестирование, после чего была выпущена в таком состоянии на продакшн. При этом никто не заметил неисправностей и не сообщил об этом разработке.</p><h2>Нерабочий код, который сработал</h2><p>Обычно программисты хорошо понимают, как работает цикл for — это довольно полезный инструмент, который позволяет отсортировать данные.</p><p>Дж. Банана <a href="https://thedailywtf.com/articles/twice-thrice">столкнулся с задачей</a>: нужно было отсортировать ключи и значения токенов. Вот решение, которое ему предложили:</p><p>Если присмотреться, можно заметить, что оператор ++ появляется три раза, а не два, как следовало бы. Однако код работает!</p><p>Оказалось, что разработчик допустил ошибку, но при этом получил рабочее решение. i = i++ выполняет постинкремент. Он устанавливает i равным значению, хранящемуся в i, а затем инкрементирует значение, которое раньше хранилось в i. Это то же самое, что и i=i. Получилось что-то вроде тавтологии.</p><p>Кажется, разработчик написал код и обнаружил, что тот не работает. Затем он стал добавлять случайные символы и всё же сделал так, чтобы код заработал. Он писал код как ChatGPT, только менее осознанно.</p><h2>Заключение</h2><p>От ошибок никто не застрахован. Это нормально: люди учатся на ошибках. Но куда проще, интереснее и безболезненнее учиться на чужих ошибках, а не на собственных, и The Daily WTF поможет вам в этом.</p><p>Сталкивались ли вы с такими же глупыми ошибками? Напишите об этом в комментариях или <a href="https://tproger.ru/editor/?utm_source=desktop-header-button">напишите для нас статью</a>! 🙂</p>]]></content:encoded>
    </item>
    <item>
      <title>9 ошибок, которые выдают в Python-разработчике новичка</title>
      <link>https://tproger.ru/articles/9-owibok-kotorye-vydayut-v-python-razrabotchike-novichka</link>
      <comments>https://tproger.ru/articles/9-owibok-kotorye-vydayut-v-python-razrabotchike-novichka?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[МТС]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/9-owibok-kotorye-vydayut-v-python-razrabotchike-novichka</guid>
      <description><![CDATA[<p>Все через это проходили, но теперь вам будет проще. Добавляйте в закладки — точно пригодится.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/9-owibok-kotorye-vydayut-v-python-razrabotchike-novichka">9 ошибок, которые выдают в Python-разработчике новичка</a>»</p>]]></description>
      <category><![CDATA[Языки программирования]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 12 May 2023 09:52:31 GMT</pubDate>
      <content:encoded><![CDATA[<p>Опытный Python-разработчик может с лёгкостью определить код новичка. Магия? Нет, просто начинающие программисты обычно совершают одни и те же ошибки.</p><p>Все проходили через это — и проводили часы на StackOverflow из-за неправильных отступов, двоеточий и других банальных опечаток.</p><p>Чтобы сэкономить ваше время, в этой статье мы собрали распространённые ошибки новичков. Начнём с самого простого и постепенно перейдём к более сложным темам.</p><h2>1. Неправильные отступы</h2><p>В Python отступы используют не только для улучшения читаемости кода, но и чтобы обозначить, что к чему относится. Например, ниже можно увидеть два цикла. В первом случае print c текстом сработает после завершения цикла, а во втором, благодаря отступу, — на каждой итерации.</p><p>Здесь текст<i> End of the cycle</i> мы увидим при завершении работы цикла:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/35902214-f20e-402b-bee7-85d78abcbc9f.png" alt="" /></figure><figure><img src="https://media.tproger.ru/uploads/2023/05/9970592f-4739-4c57-bafe-53799c72353c.png" alt="" /></figure><p>А тут — на каждой итерации цикла:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/a17e6aa7-fd43-401c-8930-4bc761b4a799.png" alt="" /></figure><figure><img src="https://media.tproger.ru/uploads/2023/05/e3e5d3fa-8cc7-4629-9d7e-a082df5fd21c.png" alt="" /></figure><p>Поэтому если код работает не так, как вы планировали,<br />проверьте отступы.</p><h2>2. Зарезервированные слова для имён переменных</h2><p>В Python есть <a href="https://docs.python.org/release/3.0.1/reference/lexical_analysis.html#id8">список</a> ключевых (или зарезервированных) слов, которые нельзя использовать в качестве имён переменных.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/4fe4a6b6-6923-404a-b71f-035b56a7703c.png" alt="" /></figure><p>Если нарушить это правило, вы получите сообщение об ошибке <i>SyntaxError: invalid syntax</i>. Например, вот так делать нельзя:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/8afa8ab6-f788-4b75-a7fe-bd4f822cf03a.png" alt="" /></figure><h2>3. Лишняя точка с запятой</h2><p>В отличие от JavaScript, где точка с запятой используется для<br />отделения инструкций друг от друга, в Python она просто не нужна.</p><p>Это настолько частая ошибка, что многие интерпретаторы игнорируют «;» и выполняют код так, как будто её нет. Например, ниже можно<br />увидеть, что VS Code просто подсветил точки с запятой красным цветом.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/9e2b75da-cbec-4251-a6ad-9f51d36df580.png" alt="" /></figure><p>Однако системы со строгими требованиями такой код не устроит.<br />Компьютер остановит выполнение программы с ошибкой — чтобы продолжить работу, точку с запятой надо будет убрать.</p><h2>4. Операторы «++» и «—»</h2><p>Если вы начали учить Python после других языков программирования, можете удивиться, что в нём нет привычных операторов инкремента «++» и декремента «—». При их использовании вы увидите ошибку <i>SyntaxError: invalid syntax</i>.</p><p>Вместо этого для приращения значения переменной можно использовать оператор «+=» или просто переназначить её через «x=x+1».</p><figure><img src="https://media.tproger.ru/uploads/2023/05/5c6d2ca5-047e-4d13-98e9-e3be9c33edd0.png" alt="" /></figure><p>Аналогично всё работает и при уменьшении значения переменной:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/d69f7d12-71bc-4672-b5a1-c649600b0adf.png" alt="" /></figure><h2>5. Отсутствие двоеточия</h2><p>Во многих языках программирования после объявления циклов и<br />функций идут фигурные скобки, а в них необходимый код. В Python такие скобки не используются, зато есть двоеточие, о котором многие забывают. Это настолько распространённая ошибка, что большинство редакторов кода подсвечивают места, где двоеточие пропущено.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/24ac146d-48c7-4c83-8481-169876f67c9b.png" alt="" /></figure><h2>6. Неправильное копирование словарей и списков</h2><p>Первое, что приходит в голову новичку, когда нужно сделать<br />копию словаря или списка — использовать оператор присваивания.<br />Например, так:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/6a23df09-b6a3-40d1-839b-2f269e7494d7.png" alt="" /></figure><p>Проблема в том, что благодаря оператору присваивания теперь<br />при изменении dict_b словарь dict_a тоже будет изменён.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/ae46a4ad-e622-41fc-a2d5-77f4aecb150e.png" alt="" /></figure><p>Чтобы такого не происходило, нужно использовать методы copy()<br />или deepcopy().</p><figure><img src="https://media.tproger.ru/uploads/2023/05/427e564f-f43e-4949-9a7b-910d1b86e9aa.png" alt="" /></figure><p>Теперь мы видим, что изменения вносятся только во вторую<br />библиотеку.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/67231f32-27c4-48ec-a505-470166189255.png" alt="" /></figure><h2>7. Оператор «+» для конкатенации</h2><p>«+» — простой и понятный для новичков оператор, но он не<br />всегда является самым эффективным решением для конкатенации. Чем больше строк вам нужно соединить в одну, тем больше плюсов придётся добавить, а это засоряет код. Поэтому для конкатенации лучше использовать f-строки.</p><p>Сравним два варианта на примере:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/772705c5-7512-4e18-8bc4-ffd10df1a732.png" alt="" /></figure><p>Во втором случае мы видим более «чистый» код, с которым проще<br />работать. А результат выполнения программы будет одинаковым.</p><h2>8. Злоупотребление импортом</h2><p>Начинающие разработчики часто импортируют все имена из<br />модулей. Например, вот так:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/bfb9d549-8a77-4e11-b77e-87f8eb48faa3.png" alt="" /></figure><p>Так делать не стоит, потому что модуль может содержать большое количество объектов, и импорт всего списка замедлит работу программы. Кроме того, иногда это приводит к дублированию определений идентификаторов, что усложняет чтение кода.</p><p>Советуем импортировать конкретные имена, например, так:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/5bbf01d4-0770-4069-94b0-75ea3f178ffd.png" alt="" /></figure><h2>9. Непонимание правил области видимости в Python</h2><p>Работа областей видимости в Python основывается на правиле<br />LEGB (Local, Enclosing, Global и Built-in). Эта аббревиатура обозначает<br />локальный, вложенный, глобальный и встроенный уровни.</p><p>К локальной области видимости относятся имена, которые вы<br />определяете внутри функции, ко вложенной — определяемые во вложенных функциях, к глобальной — имена из верхнего уровня модуля, доступные из любой части кода, а ко встроенной — имена, встроенные в Python (open, range, SyntaxError и так далее).</p><p>Казалось бы, всё просто, но не тут-то было. Такой код завершится с ошибкой <i>UnboundLocalError: local variable‘someVar’ referenced before assignment on line 4</i>:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/2ef70a06-6c1f-4281-8c3b-0fd9299d753b.png" alt="" /></figure><p>Здесь вспоминаем правило LEGB. Сначала ищется переменная в<br />локальной области видимости, но someVar определена только после функции print, значит, выводить на экран нам нечего. Поэтому мы и получили ошибку.</p><p>Будьте внимательны при написании кода и не забывайте про<br />правило LEGB.</p><h2>Подведём итоги</h2><p>Порой самые банальные ошибки в коде отнимают больше всего<br />времени. Надеемся, что наша статья поможет снизить их число.</p><p>Пишите в комментариях свои истории о самых обидных ошибках,<br />на исправление которых вы потратили бессонные ночи. Поддержать коллегу — тоже бесценно!</p>]]></content:encoded>
    </item>
    <item>
      <title>Старый баг в MySQL. Детективная история</title>
      <link>https://tproger.ru/articles/staryj-bag-v-mysql-istoriya-detektiva</link>
      <comments>https://tproger.ru/articles/staryj-bag-v-mysql-istoriya-detektiva?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Slava Rozhnev]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/staryj-bag-v-mysql-istoriya-detektiva</guid>
      <description><![CDATA[<p>В этой статье я хочу описать баг, который я обнаружил в MySQL, и предостеречь вас от проблем, к которым он может привести.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/staryj-bag-v-mysql-istoriya-detektiva">Старый баг в MySQL. Детективная история</a>»</p>]]></description>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 24 Apr 2023 14:54:41 GMT</pubDate>
      <content:encoded><![CDATA[<p>В этой статье я хочу описать одну ошибку, которую я обнаружил в MySQL (не первый, но тем не менее), и предостеречь вас от проблем, к которым она может привести.</p><p>Один парень из моего чата в <a href="https://t.me/sqlize">Telegram </a>спросил меня: «Как я могу увидеть все внешние ключи, связанные с определенной таблицей?» Я дал ему быстрый ответ: «Посмотрите в `information_schema`». Я был полностью уверен в ответе, но всё равно решил убедиться лишний раз.</p><p>Я создал простую таблицу `test` и таблицу `ref1` с полем `test_id`, связанным с полем id:</p><p>Кажется, просто, потому что я люблю этот короткий синтаксис. После этого я посмотрел на таблицу информационной схемы `REFERENTIAL_CONSTRAINTS` и не нашел никаких записей.</p><p>Что за?! Это невероятно! Я люблю этот краткий синтаксис для создания внешних ключей и использовал его много раз!</p><p>Я создал еще одну связанную таблицу `ref2` используя стандартный синтаксис.</p><p>Снова проверил. И чудо свершилось! Я увидел запись в таблице `REFERENTIAL_CONSTRAINTS`. Как это возможно?</p><p>Я был разочарован и решил провести ещё один тест. Я подумал что возможно ограничение всё-таки создается, но только не отображается.</p><p>Теперь я думал, что если я попробую удалить первую строку из `test` то получу ошибку (я все еще сохраняю надежду на лучшее).</p><p>Но к моему ужасу MySQL спокойно удаляет эту строку без какого-либо предупреждения.  Как же так?! Мой мир рухнул. Я же постоянно использовал эту конструкцию в надежде, что целостность данных обеспечена.</p><p>Чтобы вернуть равновесие, делаю второй тест. Пытаюсь удалить вторую строку, связанную со второй таблицей.</p><p>И получаю долгожданную ошибку:</p><p>Итак, это не фича, это реальный БАГ!  MySQL позволяет создавать таблицы с использованием сокращенного синтаксиса без предупреждения, но ограничения не создаются и не защищают ваши данные от несогласованности.</p><p>Я поискал в Google и нашел <a href="https://bugs.mysql.com/bug.php?id=4919">открытую ошибку с 2004 года</a>, которую до сих пор не исправили. Ребята, что вы делаете?</p><p>Для своего душевного спокойствия я решил запустить этот тест на других базах данных и обнаружил, что все базы данных, на которых я мог проверить на <a href="https://sqlize.online/sql/mysql80/0295e1d928b15c3e54f5cadcda10a750/">SQLize.online</a>, позволяют использовать короткий синтаксис, и все они (MariaDB, PostgreSQL, SQL Server, Oracle) создают ограничения внешнего ключа в обоих случаях, кроме MySQL и SQLite.</p><p>Если Вам понравилась статья, Вы можете <a href="https://pay.cloudtips.ru/p/60214a3d">поддержать автора</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>OpenAI платит 20 000 $ за поиск ошибок в API — но не за взлом сценариев ChatGPT</title>
      <link>https://tproger.ru/articles/openai-platit-20000-za-poisk-owibok-v-api-no-ne-za-vzlom-scenariev-chatgpt</link>
      <comments>https://tproger.ru/articles/openai-platit-20000-za-poisk-owibok-v-api-no-ne-za-vzlom-scenariev-chatgpt?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рафаил Агазода]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/openai-platit-20000-za-poisk-owibok-v-api-no-ne-za-vzlom-scenariev-chatgpt</guid>
      <description><![CDATA[<p>OpenAI предлагает денежное вознаграждение в размере до 20 000 долларов за раскрытие уязвимостей в своих API, включая ChatGPT.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/openai-platit-20000-za-poisk-owibok-v-api-no-ne-za-vzlom-scenariev-chatgpt">OpenAI платит 20 000 $ за поиск ошибок в API — но не за взлом сценариев ChatGPT</a>»</p>]]></description>
      <category><![CDATA[Нейронные сети]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[OpenAI]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 12 Apr 2023 08:27:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>OpenAI предлагает денежное вознаграждение в размере до 20 000 долларов за раскрытие уязвимостей в своих API, включая ChatGPT, <a href="https://www.theverge.com/2023/4/12/23679964/openai-bug-bounty-chatgpt-no-jailbreak">сообщает</a> The Verge.</p><p>Компания OpenAI запустила конкурс “bug bounty”, поощряя пользователей находить  уязвимости в своих сервисах, включая ChatGPT.</p><p>Вознаграждение варьируется от 200 долларов за несерьёзные баги до 20 000 долларов за исключительные баги.</p><p>Отчёты о найденных уязвимостях можно отправлять через <a href="https://bugcrowd.com/openai">краудсорсинговую платформу кибербезопасности Bugcrowd</a>.</p><p>При этом вознаграждение за создание вредоносного кода или текста через ChatGPT не предусмотрено.</p><blockquote>Проблемы, связанные с содержанием подсказок и ответов модели, строго не входят в сферу применения и не вознаграждаются</blockquote><p>Взлом ChatGPT обычно подразумевает ввод в чат-бота сложных сценариев, которые позволяют ей обойти собственные фильтры безопасности.</p><p>Такие сценарии поощряют чат-бота к ролевой игре в качестве его “злого двойника”, позволяя пользователю вызывать запрещенные ответы. К примеру, от злого двойника ChatGPT можно добиться инструкций по изготовлению оружия (категорически осуждаем).</p><p>OpenAI утверждает, что такие проблемы не очень подходят для вознаграждения за ошибки, поскольку они не являются дискретными ошибками, которые можно исправить напрямую.</p><p>Компания отмечает, что “решение таких проблем часто требует значительных исследований и более широкого подхода”, и сообщения о сбоях в ChatGPT следует отправлять через страницу обратной связи.</p><p>Хотя такие взломы демонстрируют уязвимость ИИ, они, вероятно, являются меньшей проблемой для OpenAI по сравнению со сбоями в системе безопасности.</p><p>Например, в прошлом месяце хакер rez0 <a href="https://twitter.com/rez0__/status/1639259413553750021">смог обнаружить 80 “секретных плагинов”</a> для API ChatGPT. Это были ещё не выпущенные или экспериментальные дополнения для чат-бота. Rez0 отметил, что уязвимость была исправлена в течение дня после того, как о ней написали в Twitter.</p><figure><img src="https://media.tproger.ru/uploads/2023/04/c72edc78-d95e-4851-8796-a8b9402d316b-scaled.jpeg" alt="" /></figure><p>Как тогда <a href="https://twitter.com/naglinagli/status/1639275221705170945">ответил</a> один из пользователей в Твиттере: “Если бы у них только была платная программа #BugBounty, я уверен, что толпа могла бы помочь им поймать эти крайние случаи в будущем : )”.</p>]]></content:encoded>
    </item>
    <item>
      <title>Баг Windows 10: системой нельзя пользоваться без подписки Microsoft 365</title>
      <link>https://tproger.ru/news/bag-windows-10-sistemoj-nelzja-polzovatsja-bez-podpiski-microsoft-360</link>
      <comments>https://tproger.ru/news/bag-windows-10-sistemoj-nelzja-polzovatsja-bez-podpiski-microsoft-360?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Павел Федоренко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/bag-windows-10-sistemoj-nelzja-polzovatsja-bez-podpiski-microsoft-360</guid>
      <description><![CDATA[<p>После обновления Windows 10 до 22H2 система предлагает оформить пробную подписку на Microsoft 365, и отказаться от неё нельзя.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/bag-windows-10-sistemoj-nelzja-polzovatsja-bez-podpiski-microsoft-360">Баг Windows 10: системой нельзя пользоваться без подписки Microsoft 365</a>»</p>]]></description>
      <category><![CDATA[Microsoft]]></category>
      <category><![CDATA[Windows 10]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 07 Feb 2023 04:13:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Пользователи обратили внимание, что после обновления Windows 10 до 22H2 система предлагает оформить пробную подписку на Microsoft 365.</p><p>Но дело в том, что предложение открывается в полноэкранном режиме. А если попытаться его скипнуть, вам предложат ввести данные карты.</p><p>Оказалось, что разработчики перепутали действие кнопок «Нет спасибо» и «Попробовать бесплатно».</p><p><b>Если вы столкнулись с проблемой, есть два способа её решить:</b></p><ol><li>Нажать «Попробовать бесплатно» — окно закроется.</li><li>Железобетонный вариант — сначала оформить пробную версию, а потом отменить подписку, чтобы избежать списания 109 долларов в месяц.</li></ol><p>Официально Microsoft пока не признала проблему, но ожидается, что сбой будет исправлен в будущем накопительном обновлении.</p><p>Не зря Microsoft изменили название «Моего компьютера» на «Этот компьютер». Ждём, когда уже честно назовут «Компьютер Microsoft».</p>]]></content:encoded>
    </item>
    <item>
      <title>Подборка мемов от Tproger — кранчи, Stack Overflow и зарплаты джунов ☕</title>
      <link>https://tproger.ru/articles/podborka-memov-ot-tproger-kranchi-stack-overflow-i-zarplaty-dzhunov</link>
      <comments>https://tproger.ru/articles/podborka-memov-ot-tproger-kranchi-stack-overflow-i-zarplaty-dzhunov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рафаил Агазода]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/podborka-memov-ot-tproger-kranchi-stack-overflow-i-zarplaty-dzhunov</guid>
      <description><![CDATA[<p>Собрали подборку лучших мемов за прошлую неделю из наших юмористических каналов — IT Юмора в Телеграме и /dev/null в VK.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/podborka-memov-ot-tproger-kranchi-stack-overflow-i-zarplaty-dzhunov">Подборка мемов от Tproger — кранчи, Stack Overflow и зарплаты джунов ☕</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Stack Overflow]]></category>
      <category><![CDATA[IDE]]></category>
      <category><![CDATA[Ruby on Rails]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Юмор]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 12 Dec 2022 13:21:30 GMT</pubDate>
      <content:encoded><![CDATA[<p>Собрали подборку лучших мемов за прошлую неделю из наших юмористических каналов — <a href="https://t.me/+ANwhTyPVDARhNzAy">IT Юмора</a> в Телеграме и <a href="https://vk.com/tnull">/dev/null</a> в VK.</p><p>Налейте любимый напиток в огромную кружку и украсьте понедельник мемами!</p><h2>Ищем сотрудника. Кранчей (не) будет</h2><p>«Если это календарь вам о чём-нибудь говорит, то просканируйте QR-код»</p><p>В комментариях обратили внимание на работу в выходные:</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Snimok-jekrana-ot-2022-12-12-15-25-47.png" alt="" /></figure><h2>Немного о синдроме самозванца</h2><p>«Не знаю, спроси у челов на Stack Overflow»</p><p>Эй, а в чём проблема с веб-разработкой?..</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Snimok-jekrana-ot-2022-12-12-15-26-47.png" alt="" /></figure><h2>В погоне за KPI</h2><p>Хорошо ведь, вышел в плюс получается</p><p>Постарайтесь не выгореть, любимые читатели!..</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Snimok-jekrana-ot-2022-12-12-15-27-52.png" alt="" /></figure><h2>Хорошо, когда у джуна есть зарплата</h2><p>Редкое фото, на котором джуниор получает свою первую зарплату:</p><p>Однажды Эрнест Хемингуэй поспорил, что сможет написать самый короткий рассказ, способный растрогать любого. Он выиграл спор, написав рассказ из трёх строк:</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Snimok-jekrana-ot-2022-12-12-15-29-31.png" alt="" /></figure><h2>Блокнот — тоже IDE?</h2><p>Дорогая IDE, не лети впереди паровоза.</p><p>Вспомнил известный рассказ про <a href="https://ru.wikipedia.org/wiki/%D0%AD%D0%BB%D0%B8%D0%B7%D0%B0_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0)">Элизу</a>. В рассказе код писали в тетрадке:</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Snimok-jekrana-ot-2022-12-12-15-30-34.png" alt="" /></figure><h2>Опыт разработки — 71 год</h2><p>Очень быстрый набор опыта</p><p>Но есть способ и похитрее:</p><figure><img src="https://media.tproger.ru/uploads/2022/12/Snimok-jekrana-ot-2022-12-12-15-31-32.png" alt="" /></figure><p>Делитесь своими мемами под этой записью, и пусть ваш понедельник будет задорным. ?</p>]]></content:encoded>
    </item>
    <item>
      <title>Обработка ошибок в Go</title>
      <link>https://tproger.ru/articles/obrabotka-oshibok-v-go</link>
      <comments>https://tproger.ru/articles/obrabotka-oshibok-v-go?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Мария Кривоченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/obrabotka-oshibok-v-go</guid>
      <description><![CDATA[<p>Рассказываем и показываем на практических примерах, как обрабатывать различные ошибки в языке программирования Go.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/obrabotka-oshibok-v-go">Обработка ошибок в Go</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 09 Aug 2022 15:31:47 GMT</pubDate>
      <content:encoded><![CDATA[<p>Механизм обработки ошибок в Go отличается от обработки исключений в большинстве языков программирования, ведь <a href="https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right">в Golang ошибки исключениями не являются</a>. Если говорить в целом, то ошибка в Go — это возвращаемое значение с типомerror, которое демонстрирует сбой. А с точки зрения кода — интерфейс. В качестве ошибки может выступать любой объект, который этому интерфейсу удовлетворяет.</p><p>Выглядит это так:</p><p>В данной статье мы рассмотрим наиболее популярные способы работы с ошибками в Golang.</p><ol><li><a href="https://tproger.ru/#part1">Как обрабатывать ошибки в Go?</a></li><li><a href="https://tproger.ru/#part2">Создание ошибок</a></li><li><a href="https://tproger.ru/#part3">Оборачивание ошибок</a></li><li><a href="https://tproger.ru/#part4">Проверка типов с Is и As</a></li><li><a href="https://tproger.ru/#part5">Сторонние пакеты по работе с ошибками в Go</a></li><li><a href="https://tproger.ru/#part6">Defer, panic and recover</a></li><li><a href="https://tproger.ru/#part7">После изложенного</a></li></ol><h2>Как обрабатывать ошибки в Go?</h2><p>Чтобы обработать ошибку в Golang, необходимо сперва вернуть из функции переменную с объявленным типом error и проверить её на nil:</p><p>Если метод возвращает ошибку, значит, потенциально в его работе может возникнуть проблема, которую нужно обработать. В качестве реализации обработчика может выступать логирование ошибки или более сложные сценарии. Например, переоткрытие установленного сетевого соединения, повторный вызов метода и тому подобные операции.</p><p>Если метод возвращает разные типы ошибок, то их нужно проверять отдельно. То есть сначала происходит определение ошибки, а потом для каждого типа пишется свой обработчик.</p><p>В Go ошибки возвращаются и проверяются явно. Разработчик сам определяет, какие ошибки метод может вернуть, и реализовать их обработку на вызывающей стороне.</p><h3>Создание ошибок</h3><p>Перед тем как обработать ошибку, нужно её создать. В стандартной библиотеке для этого есть две встроенные функции — обе позволяют указывать и отображать сообщение об ошибке:</p><ul><li>errors.New</li><li>fmt.Errorf</li></ul><p>Метод errors.New() создаёт ошибку, принимая в качестве параметра текстовое сообщение.</p><p>С помощью метода fmt.Errorf можно добавить дополнительную информацию об ошибке. Данные будут храниться внутри одной конкретной строки.</p><p>Такой способ подходит, если эта дополнительная информация нужна только для логирования на вызывающей стороне. Если же с ней предстоит работать, можно воспользоваться другими механизмами.</p><h3>Оборачивание ошибок</h3><p>Поскольку Error — это интерфейс, можно создать удовлетворяющую ему структуру с собственными полями. Тогда на вызывающей стороне этими самыми полями можно будет оперировать.</p><p>Представим другую ситуацию. У нас есть метод, который вызывает внутри себя ещё один метод. В каждом из них проверяется своя ошибка. Иногда требуется в метод верхнего уровня передать сразу обе эти ошибки.</p><p>В Go есть соглашение о том, что ошибка, которая содержит внутри себя другую ошибку, может реализовать метод Unwrap, который будет возвращать исходную ошибку.</p><p>Также для оборачивания ошибок в fmt.Errorf есть плейсхолдер %w, который и позволяет произвести такую упаковку.:</p><h3>Проверка типов с Is и As</h3><p>В Go 1.13 в пакете Errors появились две функции, которые позволяют определить тип ошибки — чтобы написать тот или иной обработчик:</p><ul><li>errors.Is</li><li>errors.As</li></ul><p>Метод errors.Is, по сути, сравнивает текущую ошибку с заранее заданным значением ошибки:</p><p>Если это будет та же самая ошибка, то функция вернёт true, если нет — false.</p><p>errors.As проверяет, относится ли ошибка к конкретному типу (раньше надо было явно приводить тип ошибки к тому типу, который хотим проверить):</p><p>Помимо прочего, эти методы удобны тем, что упрощают работу с упакованными ошибками, позволяя проверить каждую из них за один вызов.</p><h3>Сторонние пакеты по работе с ошибками в Go</h3><p>Помимо стандартного пакета Go, есть  различные  внешние библиотеки, которые расширяют функционал. При принятии решения об их использовании следует отталкиваться от задачи — использование может привести к падению производительности.</p><p>В качестве примера можно посмотреть на пакет pkg/errors. Одной из его способностей является логирование stack trace:</p><h2>Defer, panic and recover</h2><p>Помимо ошибок, о которых позаботился разработчик, в Go существуют аварии (похожи на исключительные ситуации, например, в Java). По сути, это те ошибки, которые разработчик не предусмотрел.</p><p>При возникновении таких ошибок Go останавливает выполнение программы и начинает раскручивать стек вызовов до тех пор, пока не завершит работу приложения или не найдёт функцию обработки аварии.</p><p>Для работы с такими ошибками существует механизм «defer, panic, recover»</p><h3>Defer</h3><p>Defer помещает все вызовы функции в стек приложения. При этом отложенные функции выполняются в обратном порядке — независимо от того, вызвана паника или нет. Это бывает полезно при очистке ресурсов:</p><h3>Panic</h3><p>Panic сигнализирует о том, что код не может решить текущую проблему, и останавливает выполнение приложения. После вызова оператора выполняются все отложенные функции, и программа завершается с сообщением о причине паники и трассировки стека.</p><p>Например, Golang будет «паниковать», когда число делится на ноль:</p><p>Также панику можно вызвать явно с помощью метода panic(). Обычно его используют на этапе разработки и тестирования кода — а в конечном варианте убирают.</p><h3>Recover</h3><p>Эта функция нужна, чтобы вернуть контроль при панике. В таком случае работа приложения не прекращается, а восстанавливается и продолжается в нормальном режиме.</p><p>Recover всегда должна вызываться в функции defer. ​​Чтобы сообщить об ошибке как возвращаемом значении, вы должны вызвать функцию recover в той же горутине, что и паника, получить структуру ошибки из функции восстановления и передать её в переменную:</p><h2>После изложенного</h2><p>Можно ли игнорировать ошибки? В теории — да. Но делать это нежелательно. Во-первых, наличие ошибки позволяет узнать, успешно ли выполнился метод. Во-вторых, если метод возвращает полезное значение и ошибку, то, не проверив её, нельзя утверждать, что полезное значение корректно.</p><p>Надеемся, приведённые методы обработки ошибок в Go будут вам полезны. Читайте также статью о <a href="https://tproger.ru/articles/5-oshibok-junior-razrabotchika/">5 главных ошибках Junior-разработчика</a>, чтобы не допускать их в начале своего карьерного пути.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как в С++ обрабатывать ошибки в конструкторах без исключений?</title>
      <link>https://tproger.ru/articles/kak-obrabatyvat-oshibki-v-konstruktorah-bez-iskljuchenij</link>
      <comments>https://tproger.ru/articles/kak-obrabatyvat-oshibki-v-konstruktorah-bez-iskljuchenij?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[juliajenna]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kak-obrabatyvat-oshibki-v-konstruktorah-bez-iskljuchenij</guid>
      <description><![CDATA[<p>Конструктор C++ должен сообщить об ошибке в приложении без поддержки исключений; для такого сценария нужен отдельный способ обработки сбоя.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kak-obrabatyvat-oshibki-v-konstruktorah-bez-iskljuchenij">Как в С++ обрабатывать ошибки в конструкторах без исключений?</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 29 Dec 2021 08:55:17 GMT</pubDate>
      <content:encoded><![CDATA[<p>Просматривая <a href="https://www.reddit.com/r/cpp">сабреддит С++</a>,  я встретил следующий комментарий:</p><p>Преимущества использования исключений сомнительны, но мои претензии к ним не в этом. Исключения — экстраординарный инструмент, которые делает код более непонятным и запутанным. А прямую локальную обработку — слишком пространной. Исключения стали для меня худшим средством обработки ошибок. Печально, что создание объектов в С++ основано на их применении (позднее этот пост был отредактирован — прим.пер.).</p><p>Я не стану встревать с критикой в дискуссию об исключениях,  развернувшуюся сейчас в обсуждении к приведённому выше комментарию. Я собираюсь сконцентрироваться на той части, где автор сетует на то, что конструкторы С++ требуют применения исключений для обработки ошибок. Итак, давайте представим, что у вас в приложении нет поддержки исключений и есть конструктор, который должен сообщить об ошибке. Как вы поступите?</p><p>Предупреждение. Если вы являетесь страстным поклонником исключений: я не выступаю против их использования.Предупреждение. Если вы являетесь ярым приверженцем политики отказа от применения исключений: я не призываю к их использованию.</p><h2>Проблема</h2><p>Самый очевидный способ обработки ошибок — это возврат значений. Но конструкторы не возвращают значения, поэтому так поступить нельзя. Это и было одной из причин, по которой исключения появились в С++.</p><p>Однако есть несколько способов возврата значения из функции. Например, можно использовать выходные параметры:</p><p>Функция принимает дополнительный аргумент — выходной параметр. Когда инициализация не удаётся, вместо генерации исключения мы просто устанавливаем код ошибки. Вызывающая программа затем может сверить код ошибки и обработать её.</p><p>Однако у этого метода множество недостатков. Основной в том, что ничто не мешает не проверить код ошибки: забыть сделать это очень легко. Есть ещё один — и его коварство заключается в незаметности: если исключение передано в конструктор, объект не был создан. Это значит, что его деструктор не будет вызван. Более того, получить доступ к объекту в состоянии ошибки невозможно. Исключение немедленно удаляет локальную переменную.</p><p>Если возврат вызова конструктора был успешным, то объект считается валидным. Это делает возможной идиому RAII (<i>«Получение ресурса есть инициализация» — прим.пер.</i>). Рассмотрим класс, в распоряжении которого есть некий ресурс. Конструктор получает его, а деструктор разрушает. Мы хотим обеспечить выполнение гарантии непустого объекта (never-empty guarantee): каждый объект класса должен иметь ресурс.</p><p>Допустим, <a href="http://foonathan.net/blog/2016/08/24/move-default-ctor.html">мы решили проблему семантики переноса</a>. Тогда мы легко можем создать конструктор:</p><p>Благодаря гарантии это обеспечивает каждый объект ресурсом. Когда генерируется исключение, объекта не существует.</p><p>Всё это недоступно вам, если вы используете выходной параметр, чтобы установить код ошибки. Тогда деструктору приходится иметь дело со всеми возможными состояниями ошибки. А пользователь должен соблюдать осторожность и не использовать объект в состоянии ошибки. Это делает невыполнимой гарантию непустого объекта. Каждый объект имеет по крайней мере два состояния: валидное и невалидное.</p><h2>Обход проблемы</h2><p>Исключения и коды ошибки — это исправимые техники обработки ошибок. Они сообщают об ошибке вызывающему коду и позволяют программе продолжить выполнение. Тем не менее, исправимые техники в строгом порядке требуют наличия способа сообщить об ошибке.  Если мы не применяем исключения, то тогда это просто невозможно без потери гарантии объекта.</p><p>Поэтому самый лёгкий способ обработки ошибок в конструкторе — это просто не пользоваться исправимыми техниками обработки ошибок. Используйте методы, не допускающие продолжения работы программы. Такие как, например, сообщение в stderr и вызов abort( ).</p><p>Как было изложено в <a href="http://foonathan.net/blog/2016/09/07/error-handling-strategy.html">этом посте</a>, метод больше подходит, например, для программистских ошибок. Поэтому вместо генераций исключения invalid_argument в случае, если int оказался  отрицательным числом, делайте отладку с помощью <a href="http://foonathan.net/blog/2016/09/16/assertions.html">оператора утверждения</a>. Или выбирайте надлежащий <a href="http://foonathan.net/blog/2016/09/27/error-handling-types.html">тип</a>, чтобы у ошибки не было шанса возникнуть.</p><p>Существуют ошибки, неисправимые по своей природе: такие, например, как нехватка памяти. В этом случае просто вызывайте какую-нибудь управляющую функцию и аварийно завершайте работу программы. Программист может модифицировать то, каким образом сообщение отображается пользователю, но обработать ошибку вряд ли выйдет.</p><p>Также можно использовать метод, который я окрестил <a href="http://foonathan.net/blog/2016/06/27/flexible-error-handling.html">обработчиком исключений</a> (exception handler). Но его можно  применить, только если отключить исключения с помощью оператора if.</p><p>Но, это всё только пути обхода проблемы. Так давайте же решим её.</p><h2>Решение</h2><p>Если вы не можете обработать исправимую ошибку в конструкторе без исключений, тогда не используйте конструктор.</p><p>Подождите, дайте мне сказать.</p><p>Я не предлагаю функцию init( ) или что-то вроде неё.  Если вы примените её, то потеряете все гарантии RAII, а также, наверное, вам придётся вызывать функцию destroy( ), так как для невалидных объектов будет вызван деструктор. И тогда с таким же успехом можно написать API для Си.</p><p>RAII нетрудный, делает жизнь намного легче и не имеет недостатков — если не считать ситуацию с исключениями в конструкторе.</p><p>Одна из особенностей С++ в том, что все возможности языка могут осуществляться самостоятельно — компилятор соберёт для вас то, что написано. А теперь, давайте взглянем на конструкторы.</p><p>По сути, есть два шага. Первый — выделить свободную память для объекта. Второй — вызвать конструктор для создания объекта в этой области памяти. Если на втором шаге выдаётся исключение, вводится раскрутка стека. Или планируется вызов деструктора.</p><p>Так же работает подход с применением методов init( ) и  destroy( ): конструктор объекта ничего не делает, поэтому компилятор только выделяет память. И на самом деле init( ) и destroy( ) затем создают объект.</p><p>Но мы не хотим создать два состояния отдельно от самого объекта.  Каждый созданный объект должен быть валидным. Вся сложность невалидного состояния и проблемы, которые она с собой несёт, должна быть передана куда-нибудь в другое место. Нам нужна оболочка, которая будет представлять нам невалидное состояние, когда объект отсутствует.</p><p>Оболочка optional, например. Вместо конструктора, который мы не поддерживаем — что делает невозможным создание объектов. Единственный способ создать объект — с помощью статической функции, например. Но это регулярная функция, следовательно, мы можем использовать оператор возврата. В частности, она возвращает объект optional:</p><p>Если всё прошло успешно, мы можем вернуть объект. Но в случае ошибки, возвращать невалидный объект не нужно. Вместо этого мы можем вернуть пустой optional. API может выглядеть как-нибудь так:</p><p>Теперь каждый раз, когда мы получаем объект, он гарантированно валидный. Невалидный объект передаётся туда, где его обработка может быть выполнена лучшим образом. Таким образом каждой функции-члену и деструктору нет необходимости иметь дело с невалидным объектом. Так, до тех пор, пока функция make( ) только создаёт объекты, то есть вызывает конструктор, ничего не может пойти не так.</p><h2>Лучшее представление отчёта об ошибках</h2><p>Возвращать значение в качестве выходного параметра — это, скажем так, не слишком изящно.<b> </b>И вовсе необязательно из-за проблем с выходными параметрами, которые <a href="http://foonathan.net/blog/2016/10/26/output-parameter.html">могут быть решены</a><i>.</i></p><p>Лучшим решением могла бы стать интеграция c возвращаемым значением. Вместо  возвращения  optional, используйте класс «значение или ошибка». Предлагаемый <a href="https://github.com/ptal/expected">здесь</a> std::expected делает это и позволяет обрабатывать ошибки более элегантным способом.</p><p>А как насчёт конструкторов копирования?</p><p>Этот метод хорош в работе с «регулярными» конструкторами, но что насчёт копирования? С выполнением этой операции всё ещё могут быть проблемы.</p><p>Есть два решения: не поддерживать операции копирования и только перемещать — что обычно удаётся — или применить тот же метод снова. Предусмотреть статическую копирующую функцию, которая делает то же самое, возвращает optional/expected, и так далее.</p><h2>Заключение</h2><p>Если у вас нет исключений, передача отчёта об ошибках из конструктора невозможна без утраты гарантий. Там, где это возможно, просто используйте альтернативный или неисправимый способ отчётов об ошибках.</p><p>Если это неприменимо, используйте статическую функцию как единственный способ создания объекта. Она возвращает напрямую не объект, а тип  optional. Тщательно прорабатывайте реализацию. Так, чтобы вызывался только актуальный приватный конструктор, когда ни одна операция просто не имеет  шанса на сбой. Тогда каждый объект будет валидным, как если бы вы использовали исключения.</p>]]></content:encoded>
    </item>
    <item>
      <title>Сквозная LDAP аутентификация, которая не получилась</title>
      <link>https://tproger.ru/articles/skvoznaja-ldap-autentifikacija-kotoraja-ne-poluchilas</link>
      <comments>https://tproger.ru/articles/skvoznaja-ldap-autentifikacija-kotoraja-ne-poluchilas?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Алексей]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/skvoznaja-ldap-autentifikacija-kotoraja-ne-poluchilas</guid>
      <description><![CDATA[<p>История о том, как запланированные 10 дней вылились в 1-2 месяца насыщенной разработки и ещё в полгода-год болезненной поддержки.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/skvoznaja-ldap-autentifikacija-kotoraja-ne-poluchilas">Сквозная LDAP аутентификация, которая не получилась</a>»</p>]]></description>
      <category><![CDATA[Конкурс Tproger]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 27 Aug 2021 11:04:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Лет 7 назад меня взяли ведущим программистом Java в один из крупнейших телеком-операторов. Мне предстояло разрабатывать приложение для менеджеров/сотрудников компании, где они ведут свои дела: заводят заявки, записывают клиентов, управляют услугами и пакетами, что-то согласовывают и т.п. В общем такой самодельный CRM федерального масштаба.</p><p>Опыта тогда у меня было немного (1,5 года QA Automation). Но программистов опытнее в команде первое время не было. И вот на первом же планировании ко мне приходят с пачкой задач, чтобы я выставил им оценки.</p><p>Одной из таких задач была сквозная аутентификация LDAP с сервером Active Directory, чтобы пользователи не вводили пароль при входе в приложение, единожды привязав свою учётку Windows. Немного подумав, я указал, что потребуется всего лишь 5 дней. Тогда я не осознавал всей сложности оценки задач и тленности бытия, но заботливый project manager увеличил срок до 10-15 дней. Да, тогда ещё не было модных story points, мы водопадили как могли.</p><h2>Разработка тихого монстра</h2><p>Задача прилетела в работу через месяц. К этому времени я уже немного размялся на формошлёпстве и почитал про LDAP. Первый прототип получился за 1-2 недели.</p><p>Он состоял из небольшого количества JS-кода и двух простейших AJAX-запросов на фронте, а также одного простенького servlet’a на бэке. Использовал я SPNEGO/Kerberos, найдя какую-то захудалую JAR’ку в интернете.</p><p>При входе пользователя в систему его перекидывало на login-форму, там то фоном и шёл один из AJAX-запросов, который проверял, кто пришёл и привязана ли его учётка Active Directory к учётной записи в приложении.</p><p>Если не привязана, то это можно было сделать в разделе «Профиль», где и был второй AJAX-запрос на валидацию и привязку учётной записи. После этого при последующем входе на форме логина не надо было вводить пароль, пользователь в принципе уже не замечал эту форму, т.к. аутентификация быстро отрабатывала и происходил редирект на главный экран приложения.</p><p>Прототип заработал локально и на dev-стенде. Он даже уже заработал у всех трёх программистов в кабинете и у аналитика из соседнего кабинета. Поэтому мы решили, что дело идёт к релизу. Но вдруг выяснилось, что у PM’a ничего не работает, а он территориально находился в Перми в то время как мы находились в Екатеринбурге. Мы по классике схлестнулись в битве «Но на моём компе же работает!», тем более таких компьютеров было аж 4.</p><p>Так, протестировав нашу поделку на ещё нескольких людях из разных регионов, мы уже поняли, что у нас может быть не один домен AD, а несколько, под каждый регион свой, и домены могут быть двух-, трёх-, четырёхуровневыми. С этими познаниями и ощущениями боли мы ушли ещё на 2-4 недели доработок. В итоге мы худо-бедно прошли ПСИ, на котором были некоторые проблемы несовместимости с IE, и пошли с нашей классной фичей и некоторыми доработками в ПРОМ.</p><h2>Серые тучи несовместимости</h2><p>Теперь начинается то, ради чего я вообще и решил рассказать эту историю. В первый же день после релиза жизнь моя омрачилась и пропиталась серостью и болью. Всё вокруг потемнело, обедать я стал с чувством некоторой обеспокоенности.</p><p>У большинства пользователей фича не просто не работала, но и крашила все приложения. В основном это было связано с очень старыми версиями IE и Windows на компьютерах сотрудников. Ведь тестировал-то всё это дело ИТ-отдел, а у нас у всех свежая винда, обновления, браузеры.</p><p>Всё омрачалось тем, что основным разработчиком приложения был крупный подрядчик, который при любом удобном случае указывал, что мы совсем безалаберные орки, которые всё тут сломали, а они высшие эльфы.</p><p>Так вот, поговорим про баги и их фиксы или безысходность.</p><ul><li>Кого-то из пользователей просто бесконечно редиректило по кругу на странице login’a. Был написан фикс, который отслеживал такие частые редиректы и правил хедеры в запросе и дальнейшую логику поведения.</li><li>Кто-то не мог заводить заявки и работать с документами. Всё это происходило, т.к. IE начинал пихать ненужные хедеры аутентификации в запросы к статике и бэку (это было видно по логам nginx) и сам же от этого помирал. Были и другие проблемы совместимости.</li><li>Но были и те, у кого всё хорошо работало, но не работала сама сквозная аутентификация, а они её очень хотели, удобно же — залогинился в Windows и больше ничего вводить не надо, киллер фича!</li></ul><p>Каждый день мы собирались на совещания с видеосвязью, и пользователи стримили нам свою боль. Через месяц таких мучений был написан подробный гайд о том, как настроить свой IE или Chrome для успешной работы, какие галочки снять, какие поставить, какие настройки поменять в ОС. Занимал он примерно две страницы в Word-документе.</p><p>В итоге со временем какие-то баги ушли, с какими-то люди научились жить и ловко работать в нескольких окнах браузера, копируя ссылки, вырезая какие-то параметры из строки и т.д. Но порой мы всё так же собирались, смотрели на чей-то квадратный экран и то как там ничего не работает. Со временем был написан тогл, который отключал все попытки фронта к LDAP-аутентификации. Он был включен по умолчанию, но пользователь мог его выключить. Однако IE и в таких случаях как-то удавалось ломаться.</p><p>За всё время работы в компании я периодически мониторил метрики использования данной фичи. Вроде как успешно ей пользовалось не более 5-10% пользователей. Надеюсь, теперь у большинства пользователей эта аутентификация работает. Или хотя бы не мешает им жить. Ещё надеюсь, что я немного сгустил краски и на самом деле всё было не так плохо.</p><p>В итоге эти запланированные 10 дней вылились в 1-2 месяца насыщенной разработки и настройки и ещё в полгода-год болезненной поддержки.</p><p>***</p><p>Эту историю наш пользователь написал в рамках участия в конкурсе, посвящённому #фичавгусту, который мы <a href="https://tproger.ru/articles/sdelali-interesnuju-fichu-rasskazhite-o-nej-i-poluchite-skidku-ili-kurs-ot-otus-v-podarok/">проводим вместе с OTUS</a>. Его участники рассказывают на страницах нашего сайта про самую интересную/смешную/странную/тупую фичу, которую вам доводилось реализовывать. Победителей мы выберем по рейтингу, который будет сформирован на основании оценок пользователей и редакции сайта.</p><p>Если вам понравилась эта история, проголосуйте за неё.</p><p>Под постом есть блок с реакциями. Если история вам понравилась, ставьте палец вверх. Только эти реакции будут учитываться при составлении рейтинга. Другие эмоции подсчитываться не будут.</p><p>А вот история другого участника — он <a href="https://tproger.ru/articles/kak-ja-napisal-kastomnyj-shrift-radi-odnogo-simvola-no-snachala-nedelju-stradal/">написал кастомный шрифт ради одного символа</a>. Но сначала неделю страдал.</p>]]></content:encoded>
    </item>
    <item>
      <title>Java Puzzler: как я в упор не видел очевидный баг</title>
      <link>https://tproger.ru/articles/java-puzzler-kak-ja-v-upor-ne-videl-ochevidnyj-bag</link>
      <comments>https://tproger.ru/articles/java-puzzler-kak-ja-v-upor-ne-videl-ochevidnyj-bag?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Тимур Мухитдинов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/java-puzzler-kak-ja-v-upor-ne-videl-ochevidnyj-bag</guid>
      <description><![CDATA[<p>История поиска бага, которая снова подтверждает, что необходимо покрывать код тестами, не пренебрегать локальными переменными и логированием.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/java-puzzler-kak-ja-v-upor-ne-videl-ochevidnyj-bag">Java Puzzler: как я в упор не видел очевидный баг</a>»</p>]]></description>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 02 Aug 2021 09:24:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Недавно готовился к демо-показу новой фичи, прокликивал наше старое GUI-приложение и увидел вот такой код. Прежде чем читать следующие параграфы, я рекомендую остановиться на мгновение и внимательно изучить код. Выделите все проблемы, которые вы видите, как если бы вы проводили ревью:</p><p>В нашем приложении метод processEntity, конечно, был сильно сложнее, но я убрал всё, что не имеет отношения к делу. Код прошел ревью у опытного разработчика, да и автора неопытным назвать нельзя. Этот код сложно назвать образцом, но проблема здесь вовсе не в красоте или чистоте.</p><p>Этот код — часть приложения, которое уже года три как собираются декоммитить. Это приложение — GUI, написанное на дореволюционной технологии. Там null-значения несут бизнес-ценность, null означает «не отображать ничего». А то, что DTO-отображения используются в бизнес-логике, — так исторически сложилось и улучшать это ресурсов и желания нет.</p><p>Проблема, о которой я пишу, — это исключение, но даже когда оно выброшено, ты не всегда понимаешь причину. NPE вылетает на строке:</p><p>Самое первое, что я начал проверять — когда valueProcessor может быть null. Через минут 15 я понял, что этого быть не может никогда. И даже после этого я не верил, что что-то ещё в этой строке не так.</p><p>Сейчас, когда я знаю проблему, я кажусь себе очень глупым. Как я мог не увидеть такой очевидный баг?! Но когда искал, я его в упор не видел. Что делают плохие программисты, когда код не работает? Правильно, перезапускают его! Но я потерян не полностью, я догадался выделить локальную переменную:</p><p>Сделал я это привычным движением, используя горячую клавишу IDE, и запустил код, даже не прочитав, что получилось. Снова NPE, но теперь на другой строке:</p><p>Я мог бы поставить брейкпоинт в IDE на выброс любого NPE, но локальной переменной оказалось достаточно. Даже моя любимая IDE мне подсказывала — она создала переменную примитивного типа, чему я не придал значения, мой мозг упростил всё — «ну проверка на null же есть».</p><p>Но на деле <i>entity.value1 </i>возвращала null, а тернарный оператор приводил null к примитивному long.</p><p>Я поигрался с разными вариациями: объявил переменную Long, поменял местами операнды, гуглил и в итоге почитал JLS. И вот там-то я <a href="https://docs.oracle.com/javase/specs/jls/se14/html/jls-15.html#jls-15.25">нашёл очень подробное описание</a> зависимости типов от операндов.</p><p>Я перечитал JLS пару лет назад, но не вспомнил о том, что существует такая особенность у тернарников. Плохо это или нет, мне сложно сказать. Я бы так никогда не написал. Вообще стараюсь обходиться примитивными значениями, возможно, поэтому я встретился с такой проблемой впервые. Если бы в этом приложении были тесты или хотя бы кто-то развивал его, то, скорее всего, я бы не встретил эту особенность.</p><p>Спасибо, что дочитали, и простите что так долго мусолил этот вопрос. Но я надеюсь, что это поможет лучше запомнить такое поведение. Покрывайте ваш код тестами, не пренебрегайте локальными переменными и логированием, они помогут вам не кормить свой синдром самозванца.</p><h3>А какие очевидные баги вы долго не могли обнаружить в коде?</h3>]]></content:encoded>
    </item>
    <item>
      <title>Загадочные сообщения об ошибках: вносим ясность</title>
      <link>https://tproger.ru/articles/zagadochnye-soobshhenija-ob-oshibkah-vnosim-jasnost</link>
      <comments>https://tproger.ru/articles/zagadochnye-soobshhenija-ob-oshibkah-vnosim-jasnost?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Клименков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/zagadochnye-soobshhenija-ob-oshibkah-vnosim-jasnost</guid>
      <description><![CDATA[<p>Технический писатель Александр Клименков рассказывает о пяти правилах, которые помогут сделать сообщения об ошибках более полезными и информативными.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/zagadochnye-soobshhenija-ob-oshibkah-vnosim-jasnost">Загадочные сообщения об ошибках: вносим ясность</a>»</p>]]></description>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 30 Jul 2021 06:47:23 GMT</pubDate>
      <content:encoded><![CDATA[<p>Попробуйте вспомнить, какие оригинальные и необычные сообщения об ошибках вам выдавали многочисленные программы и приложения, которыми вы пользуетесь. Наверняка у каждого из вас найдётся пара забавных примеров таких сообщений. В моём личном рейтинге на данный момент безусловный лидер — «<b>Метод вернул что-то не то</b>». Если существует шкала информативности сообщений, то приведённый пример по этой шкале стремится к нулю.</p><p>Здесь система, по сути, сообщает нам, что произошла ошибка. Больше никакой информации из этого текста мы не получим. Разве что узнаем, что где-то в недрах самой системы существует какой-то метод, который что-то не то вернул. Ценность этого знания для обычного пользователя нулевая.</p><p>Можно предположить, что этот текст — артефакт, оставшийся от процесса тестирования очередной функциональности. Некий флаг, оставленный разработчиком на этапе альфа-тестирования. Для программиста он явно имел какой-то смысл, но для обычного пользователя — совершенно бесполезен.</p><p>Давайте порассуждаем, каким должно быть идеальное сообщение об ошибке.</p><h2>5 основных правил</h2><p>В книге Сьюзан Уэйншенк «100 главных принципов дизайна» приведены основные правила написания информативного и полезного сообщения об ошибке:</p><ol><li>Расскажите пользователю, что он сделал.</li><li>Объясните проблему.</li><li>Объясните, как исправить ошибку.</li><li>Используйте активный, а не пассивный залог.</li><li>Приведите пример.</li></ol><p>В книге есть пример того, как не надо писать сообщения об ошибке:</p><p><b>#402: до того как счёт может быть оплачен, необходимо, чтобы дата платежа была позднее, чем дата создания счёта.</b></p><p>После варианта «Метод вернул что-то не то» даже такое сообщение может показаться довольно информативным. Однако в нём есть что улучшить. Но для начала давайте в порядке эксперимента попробуем его ухудшить — разберёмся, как не надо писать сообщения об ошибках.</p><h2>А ещё хуже можешь?</h2><p><i>«Вы хотите отправить разработчикам отчёт об ошибке в приложении?»«Ok»«Ну и ябеда!»</i></p><p>Нам не привыкать — у нас за плечами многолетний опыт общения с разномастными продуктами отечественного софтостроения. Давайте уберём конкретику вовсе:</p><p><b>Счёт не может быть оплачен. Введены неправильные данные.</b></p><p>Чтобы ещё больше запутать пользователя, скроем от него причину ошибки:</p><p><b>Счёт не может быть оплачен.</b></p><p>Теперь приблизимся вплотную к нашему почти нулевому варианту:</p><p><b>Ошибка выполнения метода bill_payment.</b></p><p>Дальше для достижения антиидеала нам остаётся только убрать название метода и творчески преобразовать текст:</p><p><b>Неизвестная ошибка.</b></p><p>Интересно, не возникло ли у вас чувство дежавю, когда вы читали примеры в этом подразделе?</p><h2>Добавляем информацию</h2><p><i>В случае возникновения критической ошибки обновления:1. Установите причину ошибки.2. Устраните причину ошибки.Документация Microsoft</i></p><p>Теперь давайте двигаться в другую сторону по нашей шкале — будем постепенно улучшать текст сообщения, руководствуясь правилами Сьюзан Уэйншенк.</p><figure><img src="https://media.tproger.ru/uploads/2021/07/errorreport.png" alt="" /></figure><p>Напомню исходный вариант сообщения из книги:</p><p><b>#402: до того как счёт может быть оплачен, необходимо, чтобы дата платежа была позднее, чем дата создания счёта.</b></p><p>Для начала преобразуем исходный текст, чтобы его легче было воспринимать:</p><p><b>#402: Для оплаты счёта необходимо, чтобы дата платежа была позднее, чем дата создания счёта.</b></p><p>Теперь расскажем пользователю, что он сделал не так:</p><p><b>#402: Вы ввели неправильную дату платежа — она раньше, чем дата создания счёта. Для оплаты счёта необходимо, чтобы дата платежа была позднее, чем дата создания счёта.</b></p><p>Выделим суть проблемы в отдельное предложение и явно объясним пользователю, что он должен сделать, чтобы её решить.</p><p><b>#402: Ошибка оплаты счёта. Вы ввели неправильную дату платежа — она раньше, чем дата создания счёта. Измените дату платежа так, чтобы она была позднее даты создания счёта.</b></p><p>Наконец, приведём конкретный пример. В данном случае нам известны даты и мы их можем использовать в тексте:</p><p><b>#402: Ошибка оплаты счёта. Вы ввели неправильную дату платежа 25.07.2021 — она раньше, чем дата создания счёта 29.07.2021. Измените дату платежа так, чтобы она была позднее даты создания счёта 29.07.2021. Например, 30.07.2021.</b></p><p>Мы существенно улучшили сообщение об ошибке — учли все правила и почти приблизились к идеалу. Но давайте поднимемся на уровень выше и подумаем, можно ли сделать так, чтобы ошибка не возникала вовсе.</p><h2>Лучшее сообщение об ошибке</h2><p><i>Ошибка выполнения метода: «Метод выполнился без ошибок».</i></p><p>Есть фразы, которые сразу хочется выписать на жёлтый стикер и приклеить где-то рядом с монитором. Одна из таких фраз: «Лучшее сообщение об ошибке — отсутствие сообщения об ошибке». Иными словами, часто бывает проще не допустить возможности возникновения ошибки, чем её описывать.</p><p>В нашем примере никто не мешает разработчику сделать две вещи:</p><ol><li>При смене даты счёта очистить поле с датой платежа.</li><li>В интерфейсе запретить ввод даты платежа позже заданной даты счёта.</li></ol><p>Эти незначительные изменения в коде позволят избежать ошибки вовсе. И не нужно будет ломать голову над текстом сообщения.</p><p>К сожалению, таким простым способом решить проблему удаётся не всегда. Мы не можем полностью обезопасить приложение от возникновения ошибок. Но в наших силах сделать сообщения об ошибках более информативными и полезными.</p><h3>А с какими загадочными сообщениями об ошибках сталкивались вы?</h3>]]></content:encoded>
    </item>
    <item>
      <title>ТОП-5 проблем программистов: как решать?</title>
      <link>https://tproger.ru/articles/top-5-problem-programmistov-kak-reshat</link>
      <comments>https://tproger.ru/articles/top-5-problem-programmistov-kak-reshat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[ICL Services]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/top-5-problem-programmistov-kak-reshat</guid>
      <description><![CDATA[<p>В работе программистов часто возникают ошибки и непредвиденные проблемы. Разберем, как решать пять самых распространённых.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/top-5-problem-programmistov-kak-reshat">ТОП-5 проблем программистов: как решать?</a>»</p>]]></description>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 30 Jun 2021 14:44:59 GMT</pubDate>
      <content:encoded><![CDATA[<p>В работе над проектами приходится сталкиваться с разными ситуациями, в том числе экстренными и непрогнозируемыми. Со временем они становятся привычными, а для их решения вырабатывается эффективный алгоритм действий. Но всё равно отнимают силы и. время. Рассмотрим самые распространенные ошибки и проблемы программистов:</p><h2>Проблема №1: Программист ошибся в оценке объема работ и просит расширения</h2><p>Такие ошибки традиционно возникают из-за неверной первоначальной оценки масштаба проекта — когда ориентация идет на лучший сценарий, без адекватной оценки потенциальных рисков и сложностей, объёма задач и взаимовлияния выполняемых функций.</p><p>Это может быть недочёт менеджеров проекта или разработчика или результат нелинейности процесса программирования. В итоге во время работы над проектом программисту то и дело поступают новые задачи от руководителя либо от заказчика.</p><h3>Решение</h3><p>Нужно допускать, что процессы могут протекать с затруднениями — и не забывать худший вариант. Вместо того, чтобы ставить жёсткие дедлайны, лучше оценивать процессы в баллах или условных единицах. И в дальнейшем планировать результаты, опираясь на количество баллов, полученных в последних спринтах.</p><h2>Проблема №2: Программист ошибся при выборе технологии, что негативно сказалось на проекте</h2><p>Технологии меняются так быстро, что зачастую разработчики не успевают их изучить. И тут возникают две крайности: либо программисты приступают к работе без достаточного знания технологии (и это вина менеджера по продукту, который положился на такого специалиста), либо, напротив, слишком сильно погружаются в её изучение, посещают многочисленные онлайн-курсы, читают книги.</p><h3>Решение</h3><p>Правильный выбор технологического набора инструментов и фреймворков, используемых при разработке программного обеспечения, может гарантировать стабильную базовую производительность вашего продукта и ПО. И позволит избежать выгорания и авралов. И стоит помнить, что оптимальный объём знаний тот, который отвечает конкретному составу работ.</p><h2>Проблема №3: Код, написанный одним программистом, непонятен другому и требует много времени на изучение</h2><p>Большая часть программирования – это работы по улучшению существующей кодовой базы или её полное переписывание. Самые успешные кодовые базы в мире были разработаны сотнями людей, которые никогда не встречались друг с другом. У многих из этих проектов было очень мало документации (или вообще не было), отсутствовали комментарии в кодовой базе, рекомендации или помощь.</p><p>Так что главная проблема здесь – боязнь чужого кода, которая отражает недостаток профессионализма и опыта.</p><h3>Решение</h3><p>Профессионалы должны принимать такие ситуации и вызовы, чтобы оправдывать звание настоящего программиста!</p><h2>Проблема №4: Новый программист на проекте критикует предыдущего и рекомендует переделать всё с нуля</h2><p>Работа на проектом носит командный характер, и новым участникам важно это понимать. Если критика неконструктивна и затратна, то она никак не скажется на прогрессе.</p><p><b>Решение</b></p><p>В данной ситуации нужны шаги с обеих сторон. Новички должны влиться в команду и помочь ей работать продуктивнее. А, рекрутёрам или менеджерам проекта — если они заметили в сотруднике такие черты — лучше изначально отказаться от услуг «критикана» и поменять его на другого специалиста.</p><h2>Проблема №5: программист просит выделить время на переработку своего кода и устранение дефектов, которые не видны пользователям системы</h2><p>Основная цель переработки или перепроектирования (рефакторинга) кода — сделать его более эффективным и удобным в обслуживании. Это помогает снизить затраты на будущее обслуживание и поможет предотвратить новые ошибки.</p><h3>Решение</h3><p>Делать переработку кода медленно, но неуклонно. И при планомерной работе вы увидите, что постепенно код становится более компактным и читаемым. Люди увидят изменения, но на это требуется время.</p><h3>Из опыта и рекомендаций:</h3><ul><li>не забывайте повторно факторизовать код, которого вы касаетесь;</li></ul><ul><li>удаляйте устаревший код;</li></ul><ul><li>просите перефакторинг каждый раз, когда вас просят проверить изменение кода.</li></ul><p>Несмотря на разнообразие ошибок, объединяющим их начало — профилактика. Эта задача стоит и перед программистами, и перед менеджерами проекта. Регламент внутренних коммуникаций в команде или единая стратегия работы технических специалистов помогут предотвратить непредвиденные потери.</p>]]></content:encoded>
    </item>
    <item>
      <title>«Сломался» Ubuntu-репозиторий Microsoft</title>
      <link>https://tproger.ru/news/slomalsja-ubuntu-repozitorij-microsoft</link>
      <comments>https://tproger.ru/news/slomalsja-ubuntu-repozitorij-microsoft?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/slomalsja-ubuntu-repozitorij-microsoft</guid>
      <description><![CDATA[<p>Microsoft пока никак не отреагировала на случившееся. Но в сети уже появилась информация о том, что это далеко не первый подобный случай на счету компании.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/slomalsja-ubuntu-repozitorij-microsoft">«Сломался» Ubuntu-репозиторий Microsoft</a>»</p>]]></description>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Microsoft]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Ошибки в ПО]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 17 Jun 2021 10:57:08 GMT</pubDate>
      <content:encoded><![CDATA[<p>На GitHub появился <a href="https://github.com/dotnet/core/issues/6381">тред</a> с обсуждением недоступности репозитория Ubuntu от Microsoft.</p><p>При попытке загрузить списки пакетов из репозитория и «обновить» их с помощью apt update, пользователи наблюдают следующую картину:</p><figure><img src="https://media.tproger.ru/uploads/2021/06/1_1-4.png" alt="" /></figure><p>404 ошибка преследует их и при попытке скачать эти пакеты заново. Сообщается, что проблема затронула вообще все версии Ubuntu, а не какие-то определённые.</p><p>В ответ на первое сообщение в треде, пользователь charliemenke отметил, что не может скачать не только Ubuntu, но и Debian-пакеты:</p><figure><img src="https://media.tproger.ru/uploads/2021/06/1_2-1.png" alt="" /></figure><p>Проблема оказалась настолько массовой, что её обсуждение началось даже на Hacker News. Пользователь ресурса <a href="https://news.ycombinator.com/item?id=27537696">заявил</a>, что проблема встречается не в первый раз — ранее пользователи также <a href="https://social.msdn.microsoft.com/Forums/en-US/5d090bb8-6c22-4e8d-a534-3e79b3cb3113/failed-to-fetch">не могли обновить пакеты</a> при использовании Ubuntu через Azure.</p><p>Microsoft пока никак не прокомментировала случившееся.</p>]]></content:encoded>
    </item>
    <item>
      <title>Топ-5 необычных и эпических багов в программах и устройствах</title>
      <link>https://tproger.ru/articles/top-5-neobychnyh-i-jepicheskih-bagov-v-programmah-i-ustrojstvah</link>
      <comments>https://tproger.ru/articles/top-5-neobychnyh-i-jepicheskih-bagov-v-programmah-i-ustrojstvah?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Клименков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/top-5-neobychnyh-i-jepicheskih-bagov-v-programmah-i-ustrojstvah</guid>
      <description><![CDATA[<p>Программ без ошибок не бывает, но некоторые баги невозможно просто взять и исправить. Подборка легендарных ошибок в софте и технике.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/top-5-neobychnyh-i-jepicheskih-bagov-v-programmah-i-ustrojstvah">Топ-5 необычных и эпических багов в программах и устройствах</a>»</p>]]></description>
      <category><![CDATA[История IT]]></category>
      <category><![CDATA[Пост пользователя]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 01 Jun 2021 16:28:10 GMT</pubDate>
      <content:encoded><![CDATA[<ol><li><a href="https://tproger.ru/#part1">Ошибка ошибке рознь</a></li><li><a href="https://tproger.ru/#part2">1900 год, всё ещё високосный</a></li><li><a href="https://tproger.ru/#part3">Рассеянный процессор</a></li><li><a href="https://tproger.ru/#part4">Купертино с нашими итальянскими товарищами</a></li><li><a href="https://tproger.ru/#part5">Очень умный ксерокс</a></li><li><a href="https://tproger.ru/#part6">Ядерный Ганди</a></li></ol><h2>Ошибка ошибке рознь</h2><p>Представьте себе, что вы работаете над довольно сложной программой. Например, с мудрёными математическими расчётами. Вы её компилируете, запускаете в первый раз, она выдаёт ожидаемый результат и работает без ошибок.</p><p>Скорее всего, в такой ситуации типичный программист сильно удивится и начнет перепроверять свой код: так не бывает, чтобы сложная программа сразу скомпилировалась, заработала, да ещё и не содержала багов.</p><p>Действительно, такое случается довольно редко. Как однажды сказал один очень хороший разработчик с моей прошлой работы: «Код хороший, жаль только, что не работает». Если у вас получается сразу писать сложные программы, которые не содержат ошибок, то вы — уникальный специалист.</p><p>Баги в программах — это любимая тема программистского фольклора. Они сопровождают разработчиков всю жизнь. Ошибки бывают разные: совсем глупые описки, которые отлавливаются при первой же компиляции; более серьёзные, которые можно найти при прогоне на тестовых примерах; системные, которые связаны со сложными аспектами взаимодействия разных систем. Самые неприятные — это плавающие ошибки, которые появляются неожиданно и так же неожиданно исчезают. Этакие привидения программной среды.</p><p>Для того, чтобы отловить все баги в программах, придумано множество различных методик и приёмов. Серьёзные программы проходят через несколько этапов тестирования: функциональное, регрессионное, интеграционное.</p><p>Есть множество систем автоматизации тестирования. Придуманы даже специальные методики разработки, нацеленные на то, чтобы минимизировать количество ошибок ещё на уровне создания программного кода.</p><p>Несмотря на все эти ухищрения, баги всё равно прорываются в релизные сборки. Тогда они становятся проблемой пользователей и службы технической поддержки. Такие ошибки часто бывают некритичными: кнопка не работает, картинка не на месте. Они доставляют пользователям много неприятных минут, мешают работать, но не приводят к серьёзным последствиям.</p><p>Но иногда возникают такие баги, которым удалось укорениться в базовых системах: в процессорах, ядре ОС. Их обнаруживают не сразу, и они имеют гораздо более критические последствия. О нескольких таких эпических багах я и хочу рассказать в этом топе ошибок.</p><figure><img src="https://media.tproger.ru/uploads/2021/05/First_Computer_Bug.jpg" alt="" /><figcaption>Самый первый компьютерный баг / Wikimedia Commons</figcaption></figure><h2>1. 1900 год, всё ещё високосный</h2><p><i>Самое неприятное — когда ты в своей программе случайно находишь явную ошибку, исправляешь её и программа после этого перестаёт работать.bash.im</i></p><p>В 1983 году компания Lotus Software выпустила для компьютеров IBM PC своё знаменитое приложение для работы с таблицами, которое называлось «Lotus 1-2-3». В своё время эта программа считалась самым надёжным и быстрым табличным процессором. К сожалению, она содержала маленькую, но очень серьёзную ошибку.</p><p>По какой-то причине 1900 год в ней считался високосным. Пользователи сообщили об этой ошибке, но её так никто и не исправил. Разработчики утверждали, что исправление этого бага приведёт к необходимости переписывать почти весь код программы. Почему — это вопрос, на который нет ответа. Календари и даты — это вообще <a href="https://tproger.ru/articles/postojannyj-kalendar-mechta-programmista/">больная тема</a> для многих разработчиков.</p><p>В 1995 году компанию Lotus Software приобрела фирма IBM, но к тому времени уже набирал свои обороты Microsoft Excel. Многие пользователи переходили с Lotus 1-2-3 на Microsoft Excel, а это значит, что нужно было обеспечить их совместимость. Именно для обеспечения совместимости разработчики Microsoft приняли осознанное взвешенное решение аккуратно транслировать эту ошибку в пакет Microsoft Office.</p><p>Как утверждали сотрудники Microsoft, недостатки, которые могут возникнуть при исправлении этой ошибки, значительно перевешивают преимущества. Её исправление привело бы к тому, что изменились бы данные в тысячах файлов пользователей Excel.</p><p>Вы можете спросить: «Кто же использует в своих таблицах даты в начале XX века?» Оказалось, что исправление ошибки затронет и другие даты. Как <a href="https://docs.microsoft.com/ru-ru/office/troubleshoot/excel/wrongly-assumes-1900-is-leap-year">написано</a> на сайте Microsoft, если ошибку исправить, то «почти все даты в текущих Microsoft Excel и других документах будут уменьшены на один день».</p><p>Считается, что исправление ошибки повлияет на даты после 1 марта 1900, поэтому Microsoft не хочет рисковать, ведь книг Excel с такими датами «<a href="https://inf.1sept.ru/view_article.php?ID=200802110">сотни тысяч</a>». Кроме того, «исправление этого поведения нарушает совместимость последовательной даты между Microsoft Excel и другими программами, которые используют даты». Это тоже может стать проблемой для пользователей.</p><p>В некоторых источниках утверждается, что эта ошибка была исправлена ещё в 2007 году, но в моём рабочем Excel 2016 она всё ещё воспроизводится. Проведём эксперимент: если у вас есть Excel, откройте новую таблицу и введите в ячейки две даты: 29.02.1900 и 29.02.1901. Первая тут же выравнивается по правому краю, а значит Excel воспринимает эту строку как валидную дату. Вторая дата так и останется строкой — такой даты не бывает даже по версии Lotus. Так что баг, о котором известно уже больше 30 лет, всё ещё с нами.</p><figure><img src="https://media.tproger.ru/uploads/2021/05/Excel_Date.png" alt="" /><figcaption>В Microsoft Excel 1900 год всё ещё високосный</figcaption></figure><h2>2. Рассеянный процессор</h2><p><i>Не печалься, Барт, все совершают ошибки. Просто твои публичные и дорого стоят.Гомер Симпсон</i></p><p>Мы все давно привыкли к тому, что сложные математические программы могут содержать ошибки. Это, конечно, неприятно, но этого можно ожидать. Где-то программист забыл перевести градусы в радианы, где-то ошибся в размерности. Такие баги бывают и их несложно исправить — главное вовремя их отловить.</p><p>Но когда речь заходит о процессоре, ошибок в расчётах мы никак не ожидаем. Мы твёрдо верим, что уж на аппаратном уровне всё должно быть чётко и надёжно. К сожалению, так бывает далеко не всегда. Довольно широкую известность получила ошибка, возникавшая при выполнении операций над числами с плавающей запятой в процессорах Intel Pentium. Этот баг обнаружили и обнародовали в 1994 году.</p><p>Процессор при использовании ассемблерной команды FDIV в некоторых случаях неправильно делил одно число на другое. Не всегда, только для определённых чисел, но, согласитесь, что это слабое утешение. Одно дело, если деление выполнено неправильно при расчёте домашнего бюджета. А если это расчёт траектории спутника?</p><p>Вот очень наглядный пример:</p><ul><li>4195835 / 3145727 * 3145727 = 4195835 — правильный расчёт;</li><li>4195835 / 3145727 * 3145727 = 4195579 — то, что выдавал процессор Intel.</li></ul><figure><img src="https://media.tproger.ru/uploads/2021/05/Intel_Pentium.png" alt="" /><figcaption>График, иллюстрирующий ошибку выполнения FDIV в процессорах Intel Pentium / Wikimedia Commons</figcaption></figure><p>Компания Intel в этой истории повела себя не лучшим образом. Её руководство знало об ошибке, но попросту скрывало её. Более того, когда о проблеме стало известно, Intel заявила, что для бесплатной замены процессора пользователи должны были доказать, что им эта замена необходима.</p><p>Это вызвало такую волну недовольства и критики, что представители компании пошли на попятную: принесли публичные извинения и заявили, что каждый может обменять процессор бесплатно. Всё это стоило компании 475 миллионов долларов. О подробностях этой истории можно почитать <a href="https://habr.com/ru/company/pvs-studio/blog/314534/">здесь</a>.</p><p>Замена процессора — дело хлопотное. Так что программисты исправили проблему самым простым доступным им способом: в компиляторах Delphi и Visual Basic была добавлена проверка наличия этой ошибки и её автоматическое исправление на уровне приложения. Если ошибка обнаруживается, то программа просто корректирует результат выполнения операции FDIV. Конечно, это несколько замедляет работу приложений, но правильный результат дороже.</p><p>После того, как весь этот кошмар закончился, руководство фирмы Intel раздало главным инженерам компании <a href="https://vilianov.com/raznoe/istoriya-odnoy-oshibki-intel-pentium-fdiv-bug/">брелоки</a> с впаянным злополучным процессором и надписью: «Кризисы разрушают плохие компании. Хорошие компании переживают их. Великие компании от кризисов становятся лучше».</p><h2>3. Купертино с нашими итальянскими товарищами</h2><p><i>— Тупой словарь: «калевалу» знает, а «коленвал» — нет.— Он не тупой, просто гуманитарный.bash.im</i></p><p>От дат и расчётов перейдём к проверке орфографии. Представьте себе, что словарь замены неправильных слов содержит ошибку. Хотя многим это даже не придётся представлять — почти все рано или поздно попадают в ловушку автозамены на телефоне. В сети есть множество примеров смешных автозамен, которые торопливые авторы сообщений не успевают заметить перед отправкой.</p><p>Здесь же речь пойдёт не о быстром обмене сообщениями в мессенджерах, а о государственных документах. Ошибка, о которой я хочу рассказать, оставила свой след во многих официальных бумагах.</p><p>В английском языке есть вполне обычное слово «cooperation» — «сотрудничество». По правилам английского языка его можно написать и так: «co-operation». В моём словаре Lingvo есть обе версии написания. Соответственно, и программы проверки орфографии английских текстов должны содержать оба варианта.</p><p>К сожалению, долгое время в некоторых приложениях (например, в Microsoft Word примерно с 1989 года) вариант «cooperation» в списке правильных слов отсутствовал. В общем-то, ничего страшного в этом бы не было, если бы умная система не предлагала заменить вариант «cooperation» на «Cupertino» — название города в Калифорнии.</p><figure><img src="https://media.tproger.ru/uploads/2021/05/Cupertino.png" alt="" /><figcaption>Эффект Купертино в действии / https://revealingerrors.com/cupertino_effect</figcaption></figure><p>Теперь представим, что у многих пользователей настроена автозамена неправильно набранных слов на правильный вариант. Стоит ли удивляться, что в архивах ООН, НАТО, Евросоюза сохранилось немалое количество официальных документов со словом «Купертино» вместо слова «сотрудничество».</p><p>Вот вам несколько примеров:</p><ul><li>Купертино с нашими итальянскими товарищами было очень плодотворным.</li><li>Азиатская ассоциация регионального Купертино.</li><li>Презентация афро-немецкого Купертино.</li></ul><p>Эта ошибка была настолько распространена, что даже получила своё собственное название: «эффект Купертино». Есть и другие примеры подобных автозамен в английском языке. Американский лингвист Бенжамин Зиммер даже коллекционирует такие случаи. В его коллекции есть пример публикации из газеты New York Times, в которой вместо «Voldemort» написано «Voltmeter».</p><p>По возможности всегда старайтесь следить за своими текстами, проверяйте автозамену.</p><h2>4. Очень умный ксерокс</h2><p><i>— Добрый день, а у нас ксерокс не печатает.— Он и не должен печатать, он делает копии.— Ой, да, спасибо!bash.im</i><i></i></p><p>Вернёмся обратно к аппаратному обеспечению. Как вам ксерокс, который при копировании заменяет цифры в оригинале? Вам кажется, что такое невозможно? К сожалению, в современной гонке за оптимизацией алгоритмов работы офисной техники такой фантастический вариант стал реальностью.</p><p>Мы привыкли к тому, что ксерокс, хоть и технологичное устройство, но в общем его функции достаточно банальны — сделать копию с оригинала. Интуитивно мы предполагаем, что ксерокс работает как связка сканера с принтером: отсканировали изображение, получили картинку и отправили её на принтер. Именно так, видимо, и работали ранние ксероксы.</p><p>Но устройства усложнялись, появились МФУ, в которых уже была встроена довольно сложная программная начинка. Видимо, в этом и заключалась причина ошибки, которую в 2013 году <a href="http://www.dkriesel.com/en/blog/2013/0802_xerox-workcentres_are_switching_written_numbers_when_scanning">обнаружил</a> немец Дэвид Крисель в МФУ фирмы Xerox.</p><p>Совершенно случайно Дэвид заметил, что при ксерокопировании некоторых документов с цифрами устройство периодически заменяло цифру «6» на «8», а цифру «2» на «1». Согласитесь, что это крайне странное поведение для ксерокса. Тем более, что в нём был отключён алгоритм распознавания текста и пользователь выполнял простое ксерокопирование.</p><p>Оказалось, что в устройстве использовался алгоритм компрессии графики JBIG2. Этот алгоритм фактически выполняет распознавание текста и заменяет графическое изображение на символы. При появлении схожих символов алгоритм подбирал им замену из библиотеки уже распознанных букв и цифр.</p><p>Понятно, что распознавание текста позволяет существенно сжать исходный файл. Другой вопрос, зачем этот алгоритм применялся при простом ксерокопировании. Интересно, что при недостаточном качестве сканирования алгоритм может заменить ещё и «2» на «7» или даже «1» на «3».</p><figure><img src="https://media.tproger.ru/uploads/2021/05/Xerox.png" alt="" /><figcaption>Ксерокс корректирует вашу зарплату / http://www.dkriesel.com</figcaption></figure><p>Компания Xerox повела себя аналогично Intel — <a href="https://www.computerra.ru/183468/jbig2/">не сразу</a> признала свою ошибку. Поупиравшись какое-то время, Xerox всё-таки выпустили обновлённую прошивку для своих устройств. Но многочисленные пользователи должны её скачать и установить, а это делают далеко не все.</p><p>Кроме того, этот же алгоритм используется не только в большинстве современных устройств фирмы Xerox, но и в устройствах и программах других фирм. Так что проверяйте свои ксерокопии. Ведь баг действительно серьёзный. Вдруг при очередном ксерокопировании устройство заменит, например, номер вашего паспорта или сумму вашей зарплаты в бухгалтерской ведомости.</p><h2>5. Ядерный Ганди</h2><p><i>Кстати, о «Цивилизации». Поначалу мне нравилась третья часть, до того самого момента, как рота элитных мушкетёров, окопавшихся в городе, сбила ракету с ядерной боеголовкой.bash.im</i></p><p>Последняя ошибка в нашем топе называется «Ядерный Ганди», конечно, она не столь критична, но очень уж необычная. Появилась она при весьма странных обстоятельствах. Сначала прошёл слух, что она есть. Потом оказалось, что её нет. Потом разработчики «для прикола» добавили этот баг в свою программу.</p><p>По легенде в ранних версиях популярной стратегии Sid Meier’s Civilization существовала ошибка, из-за которой один из самых миролюбивых лидеров цивилизаций Махатма Ганди при определённом стечении обстоятельств превращался в самого агрессивного персонажа и не задумываясь использовал ядерные боеголовки против других цивилизаций.</p><figure><img src="https://media.tproger.ru/uploads/2021/05/Gandhi-autoconverted.jpeg" alt="" /><figcaption>Пример интернет-мема про «Ядерного Ганди» / Wikimedia Commons</figcaption></figure><p>Обычно эту ошибку описывают так: для каждого лидера в игре использовалась переменная, которая хранила уровень его агрессии. Этот уровень мог меняться в зависимости от игровой ситуации и действий других лидеров и игрока.</p><p>Для Ганди, понятное дело, по умолчанию был установлен минимальный уровень агрессии — 1. При переходе к демократии уровень агрессии по игровому алгоритму снижался на два пункта. Уровень агрессии Ганди при этом должен был стать отрицательным, но для хранения этого показателя использовалась однобайтовая беззнаковая целочисленная переменная. А это означало, что после такой операции уровень агрессии становился равным 255. Из-за этого бедный Ганди становился в 25 раз более агрессивным, чем самые отрицательные лидеры в игре.</p><p>Разработчики игры утверждали, что всё это только миф. Сам Сид Мейер говорил, что этого бага в ранних версиях игры никогда не существовало, что в коде применялись знаковые целочисленные переменные, и что государственный строй по игровому алгоритму вообще не влиял на агрессивность лидеров.</p><p>Миф об этой ошибке появился примерно в 2012 году и постепенно стал <a href="https://ru.wikipedia.org/wiki/%D0%AF%D0%B4%D0%B5%D1%80%D0%BD%D1%8B%D0%B9_%D0%93%D0%B0%D0%BD%D0%B4%D0%B8">распространяться</a> в сети.<br />В итоге разработчики решили пошутить и специально добавили эту ошибку в качестве пасхалки в новую версию игры Sid Meier’s Civilization V. В ней Ганди остался таким же незлобливым и миролюбивым, как Нед Фландерс в сериале «Симпсоны», но при этом вероятность создания и применения ядерного оружия у него установлена максимальная — 12. Это самое больше из возможных значений.</p><p>Так ошибка под названием «Ядерный Ганди» из мифа стала реальностью. А ещё это источник множества мемов в сети. Сам же Махатма Ганди был очень мудрым человеком. Когда-то он сказал: «Ценность идеала в том, что он удаляется, по мере того как мы приближаемся к нему».</p><p>Программисты могут своим опытом подтвердить эти слова — идеальных программ не существует, в них всегда будут ошибки.</p>]]></content:encoded>
    </item>
    <item>
      <title>Сайт РЖД сломался после объявления Путиным непрерывных майских праздников</title>
      <link>https://tproger.ru/news/sajt-rzhd-slomalsja-posle-objavlenija-putinym-nepreryvnyh-majskih-prazdnikov</link>
      <comments>https://tproger.ru/news/sajt-rzhd-slomalsja-posle-objavlenija-putinym-nepreryvnyh-majskih-prazdnikov?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/sajt-rzhd-slomalsja-posle-objavlenija-putinym-nepreryvnyh-majskih-prazdnikov</guid>
      <description><![CDATA[<p>Сайты Кремля, ТАСС и РЖД перестали открываться сразу после заявления о праздничных днях с 1 по 10 мая; ресурсы РЖД восстанавливались дольше всех.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/sajt-rzhd-slomalsja-posle-objavlenija-putinym-nepreryvnyh-majskih-prazdnikov">Сайт РЖД сломался после объявления Путиным непрерывных майских праздников</a>»</p>]]></description>
      <category><![CDATA[Россия]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 23 Apr 2021 12:43:12 GMT</pubDate>
      <content:encoded><![CDATA[<p>Несколько часов назад Владимир Путин объявил период с 1 по 10 мая праздничными днями. Практически сразу после этого «упали» сайты Кремля, ТАСС и РЖД. Последний до сих пор не может оправиться от шока.</p><figure><img src="https://media.tproger.ru/uploads/2021/04/3-autoconverted.jpeg" alt="" /></figure><p>Так, при попытке зайти на официальную страницу Кремля, пользователи сталкивались с рядом проблем. Например, они видели ошибку 403, сообщает TJ:</p><figure><img src="https://media.tproger.ru/uploads/2021/04/1-38.png" alt="" /><figcaption>Источник: TJ</figcaption></figure><p>То же самое происходило и при открытии сайта ТАСС.</p><p>Но хуже всего пережили объявление президента ресурсы РЖД. Так, помимо самого сайта, проблемы наблюдались и в мобильном приложении госкомпании. Об этом написало РИА Новости.</p><figure><img src="https://media.tproger.ru/uploads/2021/04/2-autoconverted-6.jpeg" alt="" /><figcaption>Источник: TJ</figcaption></figure><p>И если сайты Кремля и информационного агентства уже восстановились, то Российские железные дороги продолжает «штормить». Вот так, например, выглядит главная страница сайта компании:</p><figure><img src="https://media.tproger.ru/uploads/2021/04/Screenshot-2021-04-23-at-15.29.16.png" alt="" /></figure><p>Источник: TJ, <a href="https://ria.ru/20210423/sboy-1729644965.html">РИА Новости</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Rockstar всё же ускорит загрузку ПК-версии GTA Online</title>
      <link>https://tproger.ru/news/rockstar-vsjo-zhe-uskorit-zagruzku-pk-versii-gta-online</link>
      <comments>https://tproger.ru/news/rockstar-vsjo-zhe-uskorit-zagruzku-pk-versii-gta-online?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/rockstar-vsjo-zhe-uskorit-zagruzku-pk-versii-gta-online</guid>
      <description><![CDATA[<p>Компания подтвердила находку игрока t0st о коде времени загрузки онлайн-режима и обещает выпустить исправление в ближайшем обновлении.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/rockstar-vsjo-zhe-uskorit-zagruzku-pk-versii-gta-online">Rockstar всё же ускорит загрузку ПК-версии GTA Online</a>»</p>]]></description>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[JSON]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Игра]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 16 Mar 2021 08:39:25 GMT</pubDate>
      <content:encoded><![CDATA[<p>Представители Rockstar Games отреагировали на появление любительской модификации для GTA V, ускоряющей загрузку онлайн-режима.</p><p>Внезапно, компания не стала подавать на разработчика в суд, а наоборот поблагодарила его и пообещала выпустить официальное обновление со схожими исправлениями в ближайшее время, <a href="https://www.theverge.com/2021/3/15/22332421/grand-theft-auto-online-slow-load-times-fan-made-patch">пишет</a> The Verge.</p><figure><img src="https://media.tproger.ru/uploads/2021/03/1-33.jpg" alt="" /></figure><p>После тщательного изучения [ситуации] мы можем подтвердить, что игрок t0st действительно раскрыл аспект игрового кода, связанный со временем загрузки ПК-версии GTA Online, который можно было бы улучшить. В результате мы внесли некоторые изменения, которые будут реализованы в предстоящем обновлении.</p><p>Напомним, что в конце февраля 2021 года пользователь под ником t0st рассказал об ускорении загрузки GTA Online.</p><p>Как оказалось, главной причиной замедления процесса было использование одного потока процессора для обработки 10 МБ JSON-файла. И всё это он выяснил и исправил, не имея доступа к исходному коду самой игры.</p><p>Источник: <a href="https://www.theverge.com/2021/3/15/22332421/grand-theft-auto-online-slow-load-times-fan-made-patch">The Verge</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Twitter банил пользователей на 12 часов за упоминание одного слова</title>
      <link>https://tproger.ru/news/twitter-banil-polzovatelej-na-12-chasov-za-upominanie-odnogo-slova</link>
      <comments>https://tproger.ru/news/twitter-banil-polzovatelej-na-12-chasov-za-upominanie-odnogo-slova?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/twitter-banil-polzovatelej-na-12-chasov-za-upominanie-odnogo-slova</guid>
      <description><![CDATA[<p>Twitter автоматически блокировал на 12 часов пользователей, написавших слово Memphis; ошибку исправили без объяснения причин.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/twitter-banil-polzovatelej-na-12-chasov-za-upominanie-odnogo-slova">Twitter банил пользователей на 12 часов за упоминание одного слова</a>»</p>]]></description>
      <category><![CDATA[Социальные сети]]></category>
      <category><![CDATA[Twitter]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 15 Mar 2021 08:16:29 GMT</pubDate>
      <content:encoded><![CDATA[<p>Пользователи Twitter обнаружили один «интересный» баг в работе соцсети. Каждый, кто написал твит с использованием слова «Memphis», получал бан на 12 часов, <a href="https://www.independent.co.uk/life-style/gadgets-and-tech/twitter-accounts-ban-memphis-b1817064.html">сообщает</a> The Independent.</p><figure><img src="https://media.tproger.ru/uploads/2021/03/1-30.jpg" alt="" /><figcaption>Источник: Unsplash</figcaption></figure><p>Издание отмечает, что блокировка происходила автоматически. Это может означать, что слово на букву «M» каким-то образом попало в «чёрный список» автоматической системы фильтрации контента Twitter.</p><p>Пользователи предположили, что блокировка аккаунтов связана с тем, что футболист Мемфис Депай (Memphis Depay) зарегистрировал своё имя как товарный знак. Но журналисты сразу же отметили, что будь оно так, то пользователи платформы не могли бы использовать и прочие популярные названия (например, Coca-Cola, Apple, Google).</p><p>Сама соцсеть никак не прокомментировала случившееся. Она лишь молча, без дополнительного шума, исправила баг. На запрос The Independent представители компании отвечать не стали.</p><p>Источник: <a href="https://www.independent.co.uk/life-style/gadgets-and-tech/twitter-accounts-ban-memphis-b1817064.html">The Independent</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Баги в играх, которые стали культовыми фичами и изменили геймдев</title>
      <link>https://tproger.ru/articles/game-changing-bugs</link>
      <comments>https://tproger.ru/articles/game-changing-bugs?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Максим Притула]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/game-changing-bugs</guid>
      <description><![CDATA[<p>9 сентября отмечают день бага: истории игровых ошибок, которые превратились в культовые особенности и повлияли на развитие геймдева.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/game-changing-bugs">Баги в играх, которые стали культовыми фичами и изменили геймдев</a>»</p>]]></description>
      <category><![CDATA[Разработка игр]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Игра]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 09 Sep 2020 12:20:13 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это одна из <a href="https://tproger.ru/articles/top-program-bugs/">двух статьей</a>, посвящённых дню бага (тестировщика). В этом материале оглядываемся на значимые баги в играх, благодаря которым изменился геймдев, а мы можем вспомнить моменты из беззаботного прошлого. Также мы рассказали о <a href="https://tproger.ru/articles/top-program-bugs/">программных багах, наделавших шумиху в мире</a>.</p><blockquote>9 сентября 1945 года тестировщики электронного компьютера Harvard Mark II обнаружили застрявшего в реле мотылька, который остановил работу машины. Грейс Хоппер внесла в журнал запись и прилепила к ней эту моль. С тех пор разработчики стали называть ошибки «багами» («жук» — англ.), а 9 сентября стал днём бага — или тестировщика.</blockquote><h2>Баг в игре, спасший GTA</h2><p>В 1995 году ребята из студии DMA Design потёрли руки и взялись за разработку игры с рабочим названием Race’n’Chase. По задумке, здесь можно было ходить пешком и выполнять задания за полицейского или грабителя. Они планировали выпустить её за 18 месяцев. Однако концепция оказалась настолько скучной, что тестировщики задыхались от зевоты. После этого разработка приостановилась. Разработчики то возвращались к ней, то снова оставляли. Проект не сдвигался с мёртвой точки, и издатель уже подумывал вовсе его закрыть.</p><p>Всё изменил один баг в игре. Сломалось просчитывание пути у полицейских: при погоне они больше не объезжали игрока, а пытались просто проехать через него. Полицейские-психопаты добавили игровому процессу драйва и очень понравились тестировщикам. Разработчики переименовали игру, дошлифовали поведение копов, и в октябре 1997 года вышла Grand Theft Auto. А в начале 2002 года студия DMA Design была куплена и переименована в Rockstar North.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/gta-1.jpg" alt="" /></figure><h2>Файтинги могли быть без комбо-ударов?</h2><p>Согласитесь, нет ничего лучше, чем отвесить сочное комбо на треть здоровья, играя в какой-нибудь Mortal Kombat с друзьями. Этим мы тоже обязаны игровому багу. На этот раз — в Street Fighter 2.</p><p>Его обнаружили во время теста бонусного уровня, в котором нужно разбить машину за отведённое время. Продюсер проекта Нориката Фунамидзу заметил, что если правильно подобрать момент, то можно прервать анимацию атак и нанести несколько ударов быстрее обычного. Разработчики не спешили это исправлять, так как подумали, что игроки не станут заморачиваться из-за сложности приёма.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/street-fighter-2.jpg" alt="" /></figure><p>Игра вышла в 1991 году, обрела всемирную популярность, а участники соревнований по Street Fighter 2, конечно же, обнаружили особенность анимации и активно её использовали. После этого разработчики создали специальную систему комбо-ударов. Так баг игры стал культовой фичей, определяющей жанр файтинга по сегодняшний день.</p><h2>Искусство распрыжки: как появился strafe-jumping</h2><p>Если вы играли в Counter-Strike или Quake, то скорее всего видели, как некоторые ребята перемещаются по карте прыжками и при этом постоянно ускоряются. Вы уже наверное догадались, что и здесь не обошлось без бага.</p><p>Снова возвращаемся в 1996 год: id Software работают над первым Quake и допускают ошибку в движке <a href="https://ru.wikipedia.org/wiki/Quake_engine">Quake engine</a>. При одновременном движении вперёд и в сторону (например при нажатии W+A), игра просто складывает векторы. В результате игрок бежит примерно в 1,4 раза быстрее по диагонали, чем по прямой. Вот как это выглядит:</p><figure><img src="https://media.tproger.ru/uploads/2020/09/Perpendicular_Vector_Addition-280x280.png" alt="" /></figure><p>За много лет движок использовался разными компаниями. Сами id Software решили не исправлять этот баг, и он перекочевал в другие игры (например Half-Life, и как следствие — Counter-Strike). С выходом Quake 3 в 1999 году игроки <a href="http://adrianb.io/2015/02/14/bunnyhop.html#:~:text=Bunnyhopping%20earned%20its%20name%20because,friction%20would%20reduce%20their%20speed.&amp;text=This%20means%20that%20the%20player,why%20bunnyhopping%20is%20so%20hard!">обнаружили</a>, что если добавить к диагональному бегу прыжок и правильное движение мышью, то можно разогнаться до больших скоростей. Это было возможно из-за того, что максимальная скорость ограничена значением не самого вектора скорости, а его проекции.</p><p>Благодаря такому математическому просчёту разработчиков появился специальный мод и целая дисциплина соревнований: <a href="https://ru.wikipedia.org/wiki/DeFRaG">DeFRaG</a>. Для игроков это повод оттачивать распрыжку и рокет-джампы, чтобы проходить препятствия как можно быстрее. Для творческих людей — площадка, где они могут дать свободу своим идеям в картостроении. Предлагаем оценить это слегка гипнотизирующее зрелище.</p><h2>Баг в игре Team Fortress, когда друг оказался вдруг…</h2><p>Хороший игрок прекрасно знает, что просто облачиться в шкуру врага недостаточно для обмана. Чтобы оставаться незамеченным как можно дольше, нужно имитировать его поведение. Так вот, шпионство в мультиплеере началось с того, что трое австралийских разработчиков в 1996 году создали мод для Quake, который назвали Team Fortress.</p><p>Они обнаружили баг в игре, из-за которого имя игрока, отображаемое при наведении прицела, окрашивалось в цвет вражеской команды. Благо, разработчики решили не просто пофиксить его (хотя запросто могли). Они развили идею, которую подкинул им баг, и создали целый класс — шпиона. Он мог превращаться во врагов и устраивать диверсии в их тылу. Дальше только история: TF стал сначала модом для Half-Life, а в 2007 вышла полноценная игра Team Fortress 2, которая обрела широкую популярность.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/spy-models-280x276.png" alt="" /></figure><p>Какие ещё игровые баги вы знаете или видели сами? Расскажите в комментариях! Это могут быть как значимые для геймдева ошибки, так и просто смешные нелепости.</p>]]></content:encoded>
    </item>
    <item>
      <title>Топ-8 программных багов, наделавших шумиху в мире</title>
      <link>https://tproger.ru/articles/top-program-bugs</link>
      <comments>https://tproger.ru/articles/top-program-bugs?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/top-program-bugs</guid>
      <description><![CDATA[<p>От мотылька в реле компьютера Mark II до проблемы Y2K: программные ошибки, которые нарушали работу машин и вызывали сбои в системах.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/top-program-bugs">Топ-8 программных багов, наделавших шумиху в мире</a>»</p>]]></description>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 09 Sep 2020 12:19:58 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сегодня, 9 сентября, в мире отмечают день бага и день тестировщика. По этому случаю мы собрали топ интересных багов в программах, наделавших много шума вокруг себя, а также собрали <a href="https://tproger.ru/articles/game-changing-bugs/">культовые баги в играх</a>, которые стали фичами и изменили игровой мир.</p><h2>Самый настоящий баг</h2><p>Топ багов откроет случай 9 сентября 1945 г. 75 лет назад учёные Гарвардского университета, тестировавшие электромеханический компьютер Mark II Aiken Relay Calculator, нашли мотылька, застрявшего между контактами реле. Он вызывал короткое замыкание, что приводило к неправильной работе машины.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/DfmoWcMXUAENb7Z.jpg" alt="" /></figure><p>Насекомое было извлечено и вклеено в дневник с сопроводительной подписью «Первый реальный случай обнаружения бага». От этого случая и пошло слово bug, а этот день стал днём бага (и заодно днём тестировщика).</p><h2>Y2K — проблемы тысячелетия</h2><p>Проблема Y2K ещё известна как «проблема 2000 года». Разработчики ПО в XX веке часто использовали для обозначения года в датах две последние цифры вместо привычных четырёх. Например, 7 апреля 1994 года представлялось как «07.04.94».</p><p>Так информация хранилась более эффективно — ведь в то время каждый байт был на счету, а базы данных могли хранить тысячи дат. В таком случае сокращение было разумным решением. Или нет?</p><p>1 января 2000 по всему миру начали происходить локальные конфликты и сбои в системах. В некоторых городах даже начало пропадать электричество, отопление, часть вычислительных центров просто зависала. Всё это произошло из-за сокращения формата года. После 1999 года наступил 2000, но для машин этот год воспринимался как 1900 (у некоторых даже как 19100). Такая ошибка послужила причиной зависаний во многих системах.</p><p>img class=”wp-image-133888 size-full” src=”https://media.tproger.ru/uploads/2020/09/Bug_de_lan_2000.jpg” alt=”Электронное табло, неправильно отображающее год ” width=”640″ height=”480″ /&gt;</p><p>Однако никакой масштабной мировой катастрофы не произошло благодаря тому, что разработчики начали задумываться об этой проблеме заранее. По некоторым оценкам, на подготовку к 2000 году общие мировые затраты составили 300 млрд. долларов.</p><h2>Ping of Death</h2><p>Так назывался тип сетевой атаки, распространённый в середине 1990-х годов. Её суть заключалась в том, что компьютер-жертва получал особый эхо-запрос (<a href="https://ru.wikipedia.org/wiki/Ping">ping</a>), после которого машина намертво зависала. Эта уязвимость встречалась на многих ОС, включая Windows, Unix, Linux, Mac.</p><p><a href="https://media.tproger.ru/uploads/2020/09/hck2.1.png"></a></p><p>По стандарту протокола IPv4 объём передаваемого пакета не может быть больше 65 535 байт. Превышение этого размера вызывало у принимающего компьютера переполнение сетевого стека.</p><p>В целях защиты пакет большего размера нельзя было отправлять. Однако из-за ошибки в системе осталась лазейка: пакет искажался и разбивался на части, а после уже отправлялся. Когда атакуемая система пыталась собрать повреждённые пакеты вместе, у неё происходило переполнение стека.</p><p>Эта уязвимость в большинстве систем была исправлена в конце 90-х.</p><h2>Торговля — трудное ремесло</h2><p>В самом начале на Amazon можно было заказать отрицательное количество товара. В таком случае при покупке деньги не списывались, а зачислялись на карту клиента. Такая глупая ошибка случилась из-за того, что разработчик магазина Джефф Безос старался как можно чаще выпускать обновления для своего сервиса и у него не хватало времени на его отладку.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/25626069_730308427175348_9161887678370383863_o.jpg" alt="" /></figure><p>Как отмечает сам разработчик, это одна из его любимых ошибок: «Мы переводили покупателям деньги и, соответственно, ждали, когда этот товар доставят нам».</p><p>Примитивные баги в программах случались и в других интернет-магазинах. Иногда можно было выставить в поле «Количество товара» значение 0,01 и тем самым получить скидку на товар в 99%. А в некоторых сервисах персональные данные хранились в файлах cookie. Можно было изменять ID покупателя и совершать покупки от другого лица.</p><h2>Падение телефонной линии AT&amp;T</h2><p>Один из 114 коммутаторов AT&amp;T был механически повреждён. Он отправил сообщение о поломке другим АТС, а те, в свою очередь, соседним. Запустилась цепная реакция, которая положила телефонную сеть на 9 часов, из-за чего 60 тыс. человек остались без связи.</p><p><a href="https://media.tproger.ru/uploads/2020/09/shutterstock_1035743311.jpg"></a></p><p>Причина была в недавнем обновлении ПО для АТС. Теперь, если ломался один из коммутаторов, он посылал об этом сообщения соседним коммутаторам, чтобы те перехватывали его трафик. Тогда другие коммутаторы должны были перезагружаться в новый режим работы и периодически проверять, не заработал ли первый.</p><p>Однако из-за бага в коде неисправный коммутатор высылал по два сообщения. Второе сообщение достигало других коммутаторов прямо во время их перезагрузки. Из-за этого они начинали думать, что сами неисправны и рассылали сообщение дальше по цепи. Таким образом, словно домино, упала целая сеть. Общие потери компании составили примерно 60 млн. долларов.</p><h2>Взрыв ракеты Ariane 5 из-за переполнения переменной</h2><p>Авария ракеты-носителя «Ариан-5» произошла 4 июня 1996 г. Ракета разрушилась на 40-й секунде полёта из-за неверной работы бортового ПО. Этот баг в системе — самый дорогостоящий в нашем топе багов и вообще в мире — его ущерб оценивается от 360 до 500 млн. долларов. В результате аварии были также потеряны 4 спутника Cluster.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/screen-shot-2016-04-02-at-10-09-27-pm-autoconverted.jpeg" alt="" /></figure><p>Во время разработки бортового программного обеспечения некоторые фрагменты кода были взяты из ПО предыдущей удачной Ariane 4. Однако софт так и не протестировали в новом окружении. После старта ракеты её модуль попытался просчитать определённое значение, исходя из горизонтальной скорости ракеты. Так как эта скорость у Ariane 5 была намного выше, чем у предыдущей версии, это вызвало переполнение переменной, что, в свою очередь, повлекло ошибки. На борту была запасная система управления, но т. к. в ней было идентичное ПО, ошибка повторилась.</p><p>Работы над строительством ракеты велись на протяжении 10 лет, а на разработку было потрачено 7 млрд. долларов.</p><h2>Податливый Microsoft Bob</h2><p>Существовала такая ОС — Microsoft Bob. Это была система с упрощённой графической оболочкой. Представляла она из себя виртуальный дом с вполне обычными комнатами: гостиная или кухня. Некоторые предметы, размещённые в комнатах, соответствовали аналогичным приложениям: ручка с бумагой открывала текстовый редактор, а настенные часы вызывали соответствующее приложение. Одним словом — уют и комфорт.</p><figure><img src="https://media.tproger.ru/uploads/2020/09/content_8c7923c0cfbeac.jpg" alt="" /></figure><p>К сожалению, этот комфорт не распространялся на систему безопасности. После трёх неудачных попыток авторизации система интересовалась, не забыл ли пользователь свой пароль. Если пользователь соглашался, то система попросту предлагала установить новый пароль. Таким образом любой злоумышленник с лёгкостью мог войти в защищённую паролем систему.</p><p>Microsoft Bob достаточно сильно критиковался СМИ. В журнале<a href="https://www.pcworld.com/"> PC World</a> эта ОС получила 7-е место в списке «25 худших продуктов всех времён», а в списке худших продуктов десятилетия от <a href="https://www.cnet.com/">CNET.com</a> она заняла 1-е место.</p><h2>Шерстяной баг</h2><p>Топ багов закроет случай с котиком. Однажды один пользователь собрался пообедать в рабочее время. Он заблокировал экран системы и отправился на обед. Но когда он вернулся, то увидел, что его кошка сидела на клавиатуре, а сама система зависла.</p><p><a href="https://media.tproger.ru/uploads/2020/09/Screenshot_9-1.png"></a></p><p>На компьютере стоял LightDM — менеджер входа в систему. Именно он появляется при запуске ОС, где предлагает выбрать пользователя и ввести его пароль. Кошка, сев на клавиатуру, зажала какие-то из клавиш. Это и привело к зависанию системы.</p><p>Это происшествие не стало бы таким популярным, если бы разработчики не <a href="https://bugs.launchpad.net/unity/+bug/1463112">признали этот баг</a> из-за выявления нескольких таких же случаев.</p>]]></content:encoded>
    </item>
    <item>
      <title>Недоработка в Python-коде поставила под сомнение результаты 100 исследований по химии</title>
      <link>https://tproger.ru/news/python-research-script-problem</link>
      <comments>https://tproger.ru/news/python-research-script-problem?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[karpov]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/python-research-script-problem</guid>
      <description><![CDATA[<p>Из-за неё результаты вычислений менялись в зависимости от операционной системы.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/python-research-script-problem">Недоработка в Python-коде поставила под сомнение результаты 100 исследований по химии</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 15 Oct 2019 11:23:26 GMT</pubDate>
      <content:encoded><![CDATA[<p>Из-за сбоя в Python-скрипте могли пострадать результаты 100 научных исследований в области химии. Информацию о недоработке выложили в журнале Organic Letters.</p><p>Сбой обнаружил аспирант Гавайского университета в Маноа Юхэн Луо, он проверял исследование Филиппа Уильямса по цианобактериям. Как и Уильямс, он использовал скрипт на Python, чтобы вычислить химический сдвиг. Однако контрольные значения не сошлись. Юхэн попросил учеников запустить код на своих компьютерах, и они обнаружили, что на разных ОС выходят разные данные.</p><p>Во всём виновата функция glob(). Авторы скрипта думали, что она возвращает файлы, отсортированные по имени, однако в документации указано, что сортировка не гарантируется. Проблему исправили добавлением метода list_of_files.sort().</p><p>Учёные используют этот скрипт с 2014 года, на него опирались авторы более 100 опубликованных работ. И это не считая исследователей, которые в своих статьях на результаты этих работ ссылались.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как искать и исправлять ошибки в коде</title>
      <link>https://tproger.ru/translations/how-to-fix-bugs</link>
      <comments>https://tproger.ru/translations/how-to-fix-bugs?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/how-to-fix-bugs</guid>
      <description><![CDATA[<p>Пошаговый поиск ошибок начинается с записи проблемы в трекер и проверки сообщения в сети, затем помогает найти строку кода с проявлением бага.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/how-to-fix-bugs">Как искать и исправлять ошибки в коде</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 26 Mar 2018 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Искать ошибки в программах — непростая задача. Здесь нет никаких готовых методик или рецептов успеха. Можно даже сказать, что это — искусство. Тем не менее есть общие советы, которые помогут вам при поиске. В статье описаны основные шаги, которые стоит предпринять, если ваша программа работает некорректно.</p><h3>Шаг 1: Занесите ошибку в трекер</h3><p>После выполнения всех описанных ниже шагов может так случиться, что вы будете рвать на себе волосы от безысходности, все еще сидя на работе, когда поймете, что:</p><ol><li>Вы забыли какую-то важную деталь об ошибке, например, <i>в чем она заключалась.</i></li><li>Вы могли делегировать ее кому-то более опытному.</li></ol><p><a href="https://ru.wikipedia.org/wiki/Система_отслеживания_ошибок">Трекер</a> поможет вам не потерять нить размышлений и о текущей проблеме, и о той, которую вы временно отложили. А если вы работаете в команде, это поможет делегировать исправление коллеге и держать все обсуждение в одном месте.</p><p>Вы должны записать в трекер следующую информацию:</p><ol><li>Что делал пользователь.</li><li>Что он ожидал увидеть.</li><li>Что случилось на самом деле.</li></ol><p>Это должно подсказать, как воспроизвести ошибку. Если вы не сможете воспроизвести ее в любое время, ваши шансы исправить ошибку стремятся к нулю.</p><h3>Шаг 2: Поищите сообщение об ошибке в сети</h3><p>Если у вас есть сообщение об ошибке, то вам повезло. Или оно будет достаточно информативным, чтобы вы поняли, где и в чем заключается ошибка, или у вас будет готовый запрос для поиска в сети. Не повезло? Тогда переходите к следующему шагу.</p><h3>Шаг 3: Найдите строку, в которой проявляется ошибка</h3><p>Если ошибка вызывает падение программы, попробуйте запустить её в IDE под отладчиком и посмотрите, на какой строчке кода она остановится. Совершенно необязательно, что ошибка будет именно в этой строке (см. следующий шаг), но, по крайней мере, это может дать вам информацию о природе бага.</p><h3>Шаг 4: Найдите точную строку, в которой появилась ошибка</h3><p>Как только вы найдете строку, в которой проявляется ошибка, вы можете пройти назад по коду, чтобы найти, где она содержится. Иногда это может быть одна и та же строка. Но чаще всего вы обнаружите, что строка, на которой упала программа, ни при чем, а причина ошибки — в неправильных данных, которые появились ранее.</p><p>Если вы отслеживаете выполнение программы в отладчике, то вы можете пройтись назад по стектрейсу, чтобы найти ошибку. Если вы находитесь внутри функции, вызванной внутри другой функции, вызванной внутри другой функции, то стектрейс покажет список функций до самой точки входа в программу (функции main()). Если ошибка случилась где-то в подключаемой библиотеке, предположите, что ошибка все-таки в вашей программе — это случается гораздо чаще. Найдите по стектрейсу, откуда в вашем коде вызывается библиотечная функция, и продолжайте искать.</p><h3>Шаг 5: Выясните природу ошибки</h3><p>Ошибки могут проявлять себя по-разному, но большинство из них можно отнести к той или иной категории. Вот наиболее частые.</p><ol><li><a href="https://ru.wikipedia.org/wiki/Ошибка_на_единицу">Ошибка на единицу</a><br />Вы начали цикл for с единицы вместо нуля или наоборот. Или, например, подумали, что метод .count() или .length() вернул индекс последнего элемента. Проверьте документацию к языку, чтобы убедиться, что нумерация массивов начинается с нуля или с единицы. Эта ошибка иногда проявляется в виде исключения Index out of range.</li><li><a href="https://ru.wikipedia.org/wiki/Состояние_гонки">Состояние гонки</a><br />Ваш процесс или поток пытается использовать результат выполнения дочернего до того, как тот завершил свою работу. Ищите использование sleep() в коде. Возможно, на мощной машине дочерний поток выполняется за миллисекунду, а на менее производительной системе происходят задержки. Используйте правильные способы синхронизации многопоточного кода: мьютексы, семафоры, события и т. д.</li><li><b>Неправильные настройки или константы</b><br />Проверьте ваши конфигурационные файлы и константы. Я однажды потратил ужасные 16 часов, пытаясь понять, почему корзина на сайте с покупками виснет на стадии отправки заказа. Причина оказалась в неправильном значении в /etc/hosts, которое не позволяло приложению найти ip-адрес почтового сервера, что вызывало бесконечный цикл в попытке отправить счет заказчику.</li><li><b>Неожиданный null</b><br />Бьюсь об заклад, вы не раз получали ошибку с неинициализированной переменной. Убедитесь, что вы проверяете ссылки на null, особенно при обращении к свойствам по цепочке. Также проверьте случаи, когда возвращаемое из базы данных значение NULL представлено особым типом.</li><li><b>Некорректные входные данные</b><br />Вы проверяете вводимые данные? Вы точно не пытаетесь провести арифметические операции с введенными пользователем строками?</li><li><b>Присваивание вместо сравнения</b><br />Убедитесь, что вы не написали = вместо ==, особенно в C-подобных языках.</li><li><b>Ошибка округления</b><br />Это случается, когда вы используете целое вместо Decimal, или float для денежных сумм, или слишком короткое целое (например, пытаетесь записать число большее, чем 2147483647, в 32-битное целое). Кроме того, может случиться так, что ошибка округления проявляется не сразу, а накапливается со временем (т. н. <a href="https://ru.wikipedia.org/wiki/%D0%AD%D1%84%D1%84%D0%B5%D0%BA%D1%82_%D0%B1%D0%B0%D0%B1%D0%BE%D1%87%D0%BA%D0%B8">Эффект бабочки</a>).</li><li><b>Переполнение буфера и выход за пределы массива</b><br />Проблема номер один в компьютерной безопасности. Вы выделяете память меньшего объема, чем записываемые туда данные. Или пытаетесь обратиться к элементу за пределами массива.</li><li><b>Программисты не умеют считать</b><br />Вы используете некорректную формулу. Проверьте, что вы не используете целочисленное деление вместо взятия остатка, или знаете, как перевести рациональную дробь в десятичную и т. д.</li><li><b>Конкатенация строки и числа</b><br />Вы ожидаете конкатенации двух строк, но одно из значений — число, и компилятор пытается произвести арифметические вычисления. Попробуйте явно приводить каждое значение к строке.</li><li><b>33 символа в varchar(32)</b><br />Проверяйте данные, передаваемые в INSERT, на совпадение типов. Некоторые БД выбрасывают исключения (как и должны делать), некоторые просто обрезают строку (как MySQL). Недавно я столкнулся с такой ошибкой: программист забыл убрать кавычки из строки перед вставкой в базу данных, и длина строки превысила допустимую как раз на два символа. На поиск бага ушло много времени, потому что заметить две маленькие кавычки было сложно.</li><li><b>Некорректное состояние</b><br />Вы пытаетесь выполнить запрос при закрытом соединении или пытаетесь вставить запись в таблицу прежде, чем обновили таблицы, от которых она зависит.</li><li><b>Особенности вашей системы, которых нет у пользователя</b><br />Например: в тестовой БД между ID заказа и адресом отношение 1:1, и вы программировали, исходя из этого предположения. Но в работе выясняется, что заказы могут отправляться на один и тот же адрес, и, таким образом, у вас отношение 1:многим.</li></ol><p>Если ваша ошибка не похожа на описанные выше, или вы не можете найти строку, в которой она появилась, переходите к следующему шагу.</p><h3>Шаг 6: Метод исключения</h3><p>Если вы не можете найти строку с ошибкой, попробуйте или отключать (комментировать) блоки кода до тех пор, пока ошибка не пропадет, или, используя фреймворк для юнит-тестов, изолируйте отдельные методы и вызывайте их с теми же параметрами, что и в реальном коде.</p><p>Попробуйте отключать компоненты системы один за другим, пока не найдете минимальную конфигурацию, которая будет работать. Затем подключайте их обратно по одному, пока ошибка не вернется. Таким образом вы вернетесь на шаг 3.</p><h3>Шаг 7: Логгируйте все подряд и анализируйте журнал</h3><p>Пройдитесь по каждому модулю или компоненту и добавьте больше сообщений. Начинайте постепенно, по одному модулю. Анализируйте лог до тех пор, пока не проявится неисправность. Если этого не случилось, добавьте еще сообщений.</p><p>Ваша задача состоит в том, чтобы вернуться к шагу 3, обнаружив, где проявляется ошибка. Также это именно тот случай, когда стоит использовать сторонние библиотеки для более тщательного логгирования.</p><h3>Шаг 8: Исключите влияние железа или платформы</h3><p>Замените оперативную память, жесткие диски, поменяйте сервер или рабочую станцию. Установите обновления, удалите обновления. Если ошибка пропадет, то причиной было железо, ОС или среда. Вы можете по желанию попробовать этот шаг раньше, так как неполадки в железе часто маскируют ошибки в ПО.</p><p>Если ваша программа работает по сети, проверьте свитч, замените кабель или запустите программу в другой сети.</p><p>Ради интереса, переключите кабель питания в другую розетку или к другому ИБП. Безумно? Почему бы не попробовать?</p><p>Если у вас возникает одна и та же ошибка вне зависимости от среды, то она в вашем коде.</p><h3>Шаг 9: Обратите внимание на совпадения</h3><ol><li>Ошибка появляется всегда в одно и то же время? Проверьте задачи, выполняющиеся по расписанию.</li><li>Ошибка всегда проявляется вместе с чем-то еще, насколько абсурдной ни была бы эта связь? Обращайте внимание на каждую деталь. На каждую. Например, проявляется ли ошибка, когда включен кондиционер? Возможно, из-за этого падает напряжение в сети, что вызывает странные эффекты в железе.</li><li>Есть ли что-то общее у пользователей программы, даже не связанное с ПО? Например, географическое положение (так был найден легендарный баг с <a href="http://www.ibiblio.org/harris/500milemail.html">письмом за 500 миль</a>).</li><li>Ошибка проявляется, когда другой процесс забирает достаточно большое количество памяти или ресурсов процессора? (Я однажды нашел в этом причину раздражающей проблемы «no trusted connection» с SQL-сервером).</li></ol><h3>Шаг 10: Обратитесь в техподдержку</h3><p>Наконец, пора попросить помощи у того, кто знает больше, чем вы. Для этого у вас должно быть хотя бы примерное понимание того, где находится ошибка — в железе, базе данных, компиляторе. Прежде чем писать письмо разработчикам, попробуйте задать вопрос на профильном форуме.</p><p>Ошибки есть в операционных системах, компиляторах, фреймворках и библиотеках, и ваша программа может быть действительно корректна. Но шансы привлечь внимание разработчика к этим ошибкам невелики, если вы не сможете предоставить подробный алгоритм их воспроизведения. Дружелюбный разработчик может помочь вам в этом, но чаще всего, если проблему сложно воспроизвести вас просто проигнорируют. К сожалению, это значит, что нужно приложить больше усилий при составлении багрепорта.</p><h3>Полезные советы (когда ничего не помогает)</h3><ol><li><b>Позовите кого-нибудь еще.</b><br />Попросите коллегу поискать ошибку вместе с вами. Возможно, он заметит что-то, что вы упустили. Это можно сделать на любом этапе.</li><li><b>Внимательно просмотрите код.</b><br />Я часто нахожу ошибку, просто спокойно просматривая код с начала и прокручивая его в голове.</li><li><b>Рассмотрите случаи, когда код работает, и сравните их с неработающими.</b><br />Недавно я обнаружил ошибку, заключавшуюся в том, что когда вводимые данные в XML-формате содержали строку xsi:type='xs:string', все ломалось, но если этой строки не было, все работало корректно. Оказалось, что дополнительный атрибут ломал механизм десериализации.</li><li><b>Идите спать.</b><br />Не бойтесь идти домой до того, как исправите ошибку. Ваши способности обратно пропорциональны вашей усталости. Вы просто потратите время и измотаете себя.</li><li><b>Сделайте творческий перерыв.</b><br />Творческий перерыв — это когда вы отвлекаетесь от задачи и переключаете внимание на другие вещи. Вы, возможно, замечали, что лучшие идеи приходят в голову в душе или по пути домой. Смена контекста иногда помогает. Сходите пообедать, посмотрите фильм, полистайте интернет или займитесь другой проблемой.</li><li><b>Закройте глаза на некоторые симптомы и сообщения и попробуйте сначала.</b><br />Некоторые баги могут влиять друг на друга. Драйвер для dial-up соединения в Windows 95 мог сообщать, что канал занят, при том что вы могли отчетливо слышать звук соединяющегося модема. Если вам приходится держать в голове слишком много симптомов, попробуйте сконцентрироваться только на одном. Исправьте или найдите его причину и переходите к следующему.</li><li><b>Поиграйте в доктора Хауса (только без Викодина).</b><br />Соберите всех коллег, ходите по кабинету с тростью, пишите симптомы на доске и бросайте язвительные комментарии. Раз это работает в сериалах, почему бы не попробовать?</li></ol><h3>Что вам точно не поможет</h3><ol><li><b>Паника</b><br />Не надо сразу палить из пушки по воробьям. Некоторые менеджеры начинают паниковать и сразу откатываться, перезагружать сервера и т. п. в надежде, что что-нибудь из этого исправит проблему. Это никогда не работает. Кроме того, это создает еще больше хаоса и увеличивает время, необходимое для поиска ошибки. Делайте только один шаг за раз. Изучите результат. Обдумайте его, а затем переходите к следующей гипотезе.</li><li><b>«Хелп, плиииз!»</b><br />Когда вы обращаетесь на форум за советом, вы как минимум должны уже выполнить шаг 3. Никто не захочет или не сможет вам помочь, если вы не предоставите подробное описание проблемы, включая информацию об ОС, железе и участок проблемного кода. Создавайте тему только тогда, когда можете все подробно описать, и придумайте информативное название для нее.</li><li><b>Переход на личности</b><br />Если вы думаете, что в ошибке виноват кто-то другой, постарайтесь по крайней мере говорить с ним вежливо. Оскорбления, крики и паника не помогут человеку решить проблему. Даже если у вас в команде не в почете демократия, крики и применение грубой силы не заставят исправления магическим образом появиться.</li></ol><h3>Ошибка, которую я недавно исправил</h3><p>Это была загадочная проблема с дублирующимися именами генерируемых файлов. Дальнейшая проверка показала, что у файлов различное содержание. Это было странно, поскольку имена файлов включали дату и время создания в формате yyMMddhhmmss. <b>Шаг 9, совпадения</b>: первый файл был создан в полпятого утра, дубликат генерировался в полпятого вечера того же дня. Совпадение? Нет, поскольку hh в <a href="http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx">строке формата</a> — это 12-часовой формат времени. Вот оно что! Поменял формат на yyMMddHHmmss, и ошибка исчезла.</p><p>Перевод статьи «How to fix bugs, step by step»</p>]]></content:encoded>
    </item>
    <item>
      <title>Проприетарное ПО может быть опасно для вашего здоровья: например, если оно в кардиостимуляторе</title>
      <link>https://tproger.ru/news/proprietary-is-death</link>
      <comments>https://tproger.ru/news/proprietary-is-death?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/proprietary-is-death</guid>
      <description><![CDATA[<p>Кэт МакГоуан собрала истории людей, пострадавших из-за багов в программах кардиостимуляторов: устройство ещё и несложно взломать без ведома владельца.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/proprietary-is-death">Проприетарное ПО может быть опасно для вашего здоровья: например, если оно в кардиостимуляторе</a>»</p>]]></description>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 10 Aug 2016 19:47:04 GMT</pubDate>
      <content:encoded><![CDATA[<h4>Что случилось?</h4><p>Журналистка Кэт МакГоуан <a href="https://web.archive.org/web/20170205075024/https://backchannel.com/i-want-to-know-what-code-is-running-inside-my-body-ff9a159da34b">опубликовала</a> истории нескольких людей, которые сталкивались с угрозой своему здоровью из-за ошибок в программах их кардиостимуляторов. Сделала она это ещё в феврале, но <a href="https://www.reddit.com/r/programming/comments/4wu0nj/i_want_to_know_what_code_is_running_inside_my_body/">внимание</a> пост привлёк только недавно.</p><h4>И о чём рассказывает Кэт?</h4><p>В её статье собрано несколько рассказов. Один из них о девушке, которой стало плохо, когда она спускалась в метро. Это случилось из-за того, что частота ударов у кардиостимулятора была выставлена неверно. Виной всему был баг в программном обеспечении. Кроме этого, выяснилось, что устройство не так сложно взломать и записать туда неверную программу без ведома владельца. Возможность теоретическая, ставить эксперимент на себе девушка не стала.</p><p>Другую историю рассказывает open source активист Карен Сандлер. У неё установлен не кардиостимулятор, а небольшой дефибриллятор, которой в случае аритмии или других ситуаций, когда для восстановления работы сердца необходим разряд, должен его произвести. У неё доверие к проприетарному софту стало пропадать после того, как устройство дважды сработало без всякой необходимости, в том числе один раз во время беременности (во время беременности сердцебиение меняется, и устройство ошибочно посчитало, что начался приступ аритмии).</p><h4>Это вообще реальные истории?</h4><p>Да, например, первый же комментарий на Reddit <a href="https://www.reddit.com/r/programming/comments/4wu0nj/i_want_to_know_what_code_is_running_inside_my_body/d6a36qc">рассказывает</a> о том, что некий(-ая) SaraMG обнаружил(-а) баг в своей инсулиновой помпе. Обнаружила, наблюдая за его работой, разумеется, исходников взять неоткуда.</p><h4>Что с этим делать?</h4><p>Бороться за повышение прозрачности работы корпораций, которые занимаются подобным оборудованием. Кстати, уже есть инициатива Private Play Accord, которая направлена на то, чтобы сделать более открытой работу различных интимных устройств, в том числе вибраторов и кардиостимуляторов.</p>]]></content:encoded>
    </item>
    <item>
      <title>Самый сложный баг Дэйва Баджета</title>
      <link>https://tproger.ru/translations/the-hardest-bug-by-dave-baggett</link>
      <comments>https://tproger.ru/translations/the-hardest-bug-by-dave-baggett?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/the-hardest-bug-by-dave-baggett</guid>
      <description><![CDATA[<p>Разработчик Crash Bandicoot вспоминает, как искал ошибку в коде сохранения на карту памяти и в итоге дошёл до аппаратной причины сбоя.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/the-hardest-bug-by-dave-baggett">Самый сложный баг Дэйва Баджета</a>»</p>]]></description>
      <category><![CDATA[Hardware]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 24 Jan 2016 19:23:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>Перевод <a href="https://www.quora.com/Whats-the-hardest-bug-youve-debugged">интервью</a> с Дэйвом Баджетом (Dave Baggett). Разработчик отвечает на вопрос: Какой самый сложный баг вам когда-либо приходилось отлавливать?</p><p>Довольно неприятно переживать все это снова. Будучи программистом, вы учитесь в первую очередь искать ошибку в коде, во вторую тоже, и в третью… И только в десятитысячную – в компиляторе. И вот когда спустя еще несколько тысяч попыток вы не найдете ошибки и в компиляторе, у вас остается последнее место, где еще что-то может быть не так – это железо.</p><p>Итак, вот моя история борьбы с хардварным багом.</p><p>Одна из моих задач в процессе разработки Crash Bandicoot’a заключалась в написании кода для сохранения/загрузки игры на карту памяти. Как для любого чванливого кодера, такой кодинг похож на прогулку в парке: я был уверен, что эта работа займет всего несколько дней. Но все закончилось спустя шесть недель. Нет, конечно же, я не тратил время только на этот баг, но над проблемным, как мне казалось, кодом, я проводил по несколько часов в два—три дня. Это были настоящие мучения.</p><h3>И вот когда спустя еще несколько тысяч попыток вы не найдете ошибки и в компиляторе, у вас остается последнее место, где еще что-то может быть не так – это железо.</h3><p>Проблема заключалась в том, что при попытках сохранить игру, т.е. получить доступ к карте памяти, которые в абсолютном большинстве случаев были успешны, время от времени завершались неудачно из-за тайм-аута… причина которого была совершенно неизвестна! Короткая запись и та могла повредить все данные на карте. Игрок мог попытаться сохранить прогресс, но не только не сохранил бы его, но и уничтожил бы все имевшиеся до того на носителе данные.</p><p>Через некоторое время Кони Бут, наш продюсер из Sony, начала волноваться. Мы не могли выпустить игру с таким багом, а спустя шесть недель у меня не было ни единой мысли, откуда могла эта ошибка появится. Мы спросили наших коллег по работе с PlayStation, не сталкивались ли они с подобной проблемой. Нет. Ни у кого никогда не было подобных проблем с системой обработки данных карт памяти.</p><p>В случае, когда у вас больше нет идей, как справиться с возникшим багом, остается последнее средство: принцип «разделяй и властвуй» – вы начинаете делить код, отсекая от него куски, пока ошибка проявляет себя, а затем исправлять ее уже в меньшем, чем целый проект, коде.</p><p>Но не забывайте, что работа шла над проектом видеоигры, а стало быть, отсекать от проекта куски было делом непростым. Как вы можете продолжать запускать игру, если отбросили симулятор гравитации? или код рендеринга персонажей?</p><p>В таком случае приходится не совсем отбрасывать отдельный кусок кода, а заменять его более простым, достаточно тривиальным для того, чтобы быть уверенным, что ошибка возникает не в нем. А подобный рефакторинг, фактически, пересоздание игры — процесс болезненный и долгий.</p><p>Вкратце история такова: я избавился от ошибки. Я продолжал отсекать и отсекать куски кода, пока не остался только код запуска игры, который только подготавливал систему к запуску, инициализировал оборудование рендеринга и т.п. Естественно, я не мог поместить сюда меню сохранения/загрузки, потому что отсутствовал графический интерфейс. И поэтому это происходило все как бы в невидимом режиме.</p><p>В конце концов у меня остался совсем небольшой фрагмент кода, который все еще порождал ошибку – и все так же случайно! Большую часть времени все происходило, как нужно, но время от времени баг снова проявлял себя. Уже почти ничего не осталось от кода Crash’a, но запись данных по-прежнему периодически завершалась с тайм-аутом. Дальнейшие действия предпринимать было затруднительно: оставшийся код, фактически, не делал вообще ничего.</p><h3>Когда у вас больше нет идей, как справится с возникшим багом, остается последнее средство: принцип «разделяй и властвуй».</h3><p>И в один момент – около 3 часов ночи – мне в голову пришла одна мысль. Чтение и запись имеют точную привязку ко времени. C чем бы вы ни работали – флешкой, Bluetooth-устройством – низкоуровневый код чтения/записи имеет строгую привязку ко времени.</p><p>Часы CPU позволяют синхронизировать действия оборудования, не подключенного напрямую к процессору, с самим процессором. CPU clock задает скорость изменения информационного параметра (baud rate), т.е., фактически, скорость чтения и записи информации, и если часы сбиваются, то сбиваются и программное, и аппаратное обеспечение. Это очень, очень плохо, и обычно приводит к порче данных на носителе.</p><p>Так что же в нашем коде запуска приводило к сбою часов? Я изучил игру на предмет влияющего на часы кода и заметил, что программный таймер PS1 был установлен на частоту 1 КГц, что значительно превышало 100 Гц, на которых по умолчанию начинала работать приставка при запуске. Частота программных часов большинства игр составляла те же 100 Гц.</p><p>Энди, ведущий и единственный разработчик, участвовавший в проекте, кроме меня, установил таймер на 1000 Гц, чтобы движения были более плавными. Энди не прочь использовать ресурсы системы в полном объеме, и для хорошей симуляции гравитации нам было необходимо производить вычисления с максимальной возможной точностью и частотой.</p><p>А что если повышение частоты программных вычислений так накладывалось на общую частоту вычислений, что как-то сбивало частоту процессора PS1, нарушая обмен данными с картой памяти?</p><p>Я закомментировал проблемный код. Но это еще не означало, что баг исправлен: ведь ошибка возникала не всегда. Может быть, мне просто везло?</p><p>Прошло несколько дней, я игрался с тестирующей системой, и проблемы больше не возникало. Тогда я вернулся к полному коду Crash’а, исправив код так, чтобы перед записью или чтением данных с карты памяти частота устанавливалась на 100 Гц, а затем возвращалась к исходному значению в 1 КГц. И больше никогда эта проблема с сохранением/загрузкой игры не возникала.</p><p>Но почему?</p><p>Я снова и снова возвращался к тестирующей программе, чтобы выявить хоть какие-то причины появления ошибки при установлении таймера на частоту 1КГц. Наконец я заметил, что ошибки появлялись тогда, когда кто-то играл с джойстиком PS1. В силу того, что я сам не пользовался контроллером – к чему мне это в процессе тестирования чтения/записи? – я этого не замечал. Но вот однажды меня позвал к себе один из наших художников – тут я был уверен, что меня прокляли – при моем появлении он нервно вертел в руках джойстик приставки, и сказал, что сохранить игру вновь не удалось. «Что? Эй, повтори-ка, что сделал сейчас!»</p><p>Таким образом совпали две такие вещи: начало записи на карту памяти, игра с джойстиком – и результатом всего этого становилось повреждение данных на носителе. Для меня это однозначно выглядело как аппаратный баг.</p><p>Я пришел к Кони и рассказал, что обнаружил. Она связалась с одним из инженеров, разрабатывавших PS1. «Невозможно,» – ответили нам, – «это не аппаратная ошибка». Я попросил ее дать мне связаться с ним.</p><p>Он позвонил мне, и мы пытались отстоять свою точку зрения в споре: он – на ломаном английском, я – на еще более ломаном японском. Наконец я сказал, что готов выслать ему тестирующую программу в 30 строчек, при выполнении которой возникнет ошибка, если в это время манипулировать контроллером. Он согласился, рассказав, как он сейчас сильно занят, и соглашается на эту трату времени только потому, что мы были очень важным партнером для Sony. Я послал ему фрагмент кода тестирующей программы, демонстрирующей проявление ошибки.</p><p>Следующим вечером (мы были в Лос-Анжелесе, а он в Токио, так что для меня начало его рабочего дня было уже вечером) он позвонил мне и извинился. Это действительно была проблема с оборудованием.</p><p>В последствии я так и не узнал, в чем же именно была проблема, но общее представление после услышанного от инженеров Sony у меня сложилось примерно следующее: установление достаточно высокой частоты вычислений имело пагубное влияние на находящееся рядом с часами материнской платы оборудование. В частности, там же находился контроллер скорости записи/чтения данных с карты памяти, который задавал и скорость приема и обработки сигналов с контроллеров. Я не слишком-то разбираюсь в оборудовании, поэтому в этом вопросе плаваю.</p><p>Но суть проблемы заключалась в том, что при одновременной обработке данных с карты памяти и контроллеров, работающих обособленно друг от друга, если частота программы установлена на 1 КГц, некоторые биты пропадут, и потому данные будут потеряны, а карта памяти повреждена.<br />Это единственный случай в моей практике, когда суть бага объяснялась квантовой механикой.</p><h3>Наставления потомкам</h3><p>Некоторые утверждают, что этот баг совершенно не имеет отношения к квантовой механике, ровно, как и любой другой баг. Естественно, я с преувеличением сказал о квантмехе, но этот баг был действительно чем-то новым для меня: своей природой, способностью проявляться не систематически, а случайным образом.</p><p>Кое-кто сказал, что мне не мешало бы набраться знаний в электронике. Полностью согласен: я считаю себя программистом «с полным набором», но граница этого набора находится где-то в области программирования на языке ассмеблера, а не игры с транзисторами. Вероятно, когда-нибудь я узнаю больше о «бренном железе».</p>]]></content:encoded>
    </item>
    <item>
      <title>5 худших багов в истории</title>
      <link>https://tproger.ru/articles/5-worst-bugs-in-history</link>
      <comments>https://tproger.ru/articles/5-worst-bugs-in-history?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Денис Глаз]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/5-worst-bugs-in-history</guid>
      <description><![CDATA[<p>Слово «баг» пошло от мотылька, найденного 9 сентября 1945 года в Mark II Aiken Relay Calculator; в подборке — падение сети AT&amp;T и другие сбои.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/5-worst-bugs-in-history">5 худших багов в истории</a>»</p>]]></description>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Nov 2015 14:23:28 GMT</pubDate>
      <content:encoded><![CDATA[<p>Баг — ошибка в программе или системе, из-за которой программа выдает неожиданное и непредсказуемое поведение и, как следствие, результат. Первый баг был зафиксирован 9 сентября 1945 года: в вычислительной машине Mark II Aiken Relay Calculator нашли мотылька, застрявшего между контактами электромеханического реле, что приводило к ошибкам. Извлеченное насекомое было вклеено в технический дневник с сопроводительной надписью: «First actual case of bug being found». Этот забавный факт и положил начало использованию слова «баг» в современном значении.</p><h3>Падение телефонной сети AT&amp;T</h3><p>15 января 1990 года ошибка в новой версии прошивки междугородних коммутаторов привела к тому, что в один прекрасный день 114 соседних коммутаторов перестали работать, и в следствие этого более 60 тысяч человек остались без междугородней связи на 9 часов. Согласно программе, коммутатор должен перезагрузиться, если получит соответствующий сигнал, однако он по ошибке рассылался каждый раз при восстановлении после сбоя какого-либо коммутатора. Все началось с падения и перезагрузки коммутатора в Нью-Йорке, который вызвал масштабную цепную реакцию, в результате которой 114 коммутаторов перезагружались непрерывно каждые 6 секунд. Сначала компания обвиняла хакеров, однако позже выяснилось, что причиной стала одна ошибочная строка кода, в которой реализовывалась отправка неверного сигнала.</p><h3>Запуск ракеты Mariner I</h3><p>В 1962 году ракета с космическим зондом Mariner-1, который должен был отправиться к Венере, вскоре после запуска отклонилась от намеченной траектории полета. Через 293 секунды был отдан приказ уничтожить ракету над Атлантическим океаном. Никто не погиб, однако экономические потери составили 18.3 миллиона долларов. Программист сделал ошибку, когда переводил рукописные математические формулы в код. Символ логического отрицания он принял за минус, и это привело к тому, что ракета воспринимала нормальные скорости как критические и из-за этого сбилась с курса. На будущее совет: внимательно переписывайте рукописный текст.</p><h3>Аппарат лучевой терапии Therac-25</h3><p>В 1985 году эта модель аппарата лишила жизни двух человек, и еще четверо получили серьезное облучение. Ошибка крылась в программном управлении защитой от облучения, которая считалась более надежной, чем электромеханическая. Известно, что неисправность была вызвана тем, что в проекте использовались библиотеки с ошибками, входящие в состав ПО аппарата Therac-20, что и привело к фатальным последствиям. В коде была найдена довольно распространенная ошибка многопоточности, называемое состоянием гонки. Тем не менее ошибку не заметили, так как Therac-20 работал исправно из-за дополнительных мер предосторожности. Не зря вопрос про состояние гонки — один из самых задаваемых на собеседовании вопросов по многопоточности, ведь он приносит так много хлопот. Рекомендуем почитать подробнее про эту <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%B3%D0%BE%D0%BD%D0%BA%D0%B8">ошибку</a>.</p><h3>Взрыв ракеты Ariane 5</h3><p>В 1996 году ракета Ariane 5, на борту которой были 4 научных спутника, взорвалась через 36.7 секунд после взлета. Это тот случай, когда банальная ошибка переполнения переменной нанесла ущерб в 7 миллиардов долларов. При разработке системы управления ракетой были использованы некоторые части кода от ПО Ariane 4, однако у Ariane 5 показатели скорости были несколько больше, что и вызвало переполнение переменной. Ошибки можно было избежать, если бы при использовании ПО от другого оборудования не забыли подправить код. Видео, запечатлевшее аварию:</p><h3>Массовое отключение электроэнергии в США и Канаде</h3><p>В 2003 году <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%B3%D0%BE%D0%BD%D0%BA%D0%B8">состояние гонки</a> в ПО системы управления электростанцией вызвала прекращение подачи электроэнергии и 55 миллионов человек лишились электроснабжения. Деревья, которые находятся под линиями электропередач, регулярно подстригают, однако в Кливленде этого не сделали. Вследствие жары и увеличения энергопотребления провода провисли ниже обычного и коснулись деревьев: случилось замыкание, и электростанция в Кливленде вышла из строя. Аварийная система не предупредила персонал о перегрузках в сети, поэтому произошло каскадное отключение еще около сотни электростанций. В ходе расследования установили, как такое произошло: состояние гонки вызывало подвисание сигнализации в комнате персонала на целый час, поэтому сотрудники электростанции не получали никаких сигналов о том, что произошла беда.</p><p>Баги хоть и являются вполне обычным делом в жизни программиста, однако некоторые из них способны нанести огромное количество ущерба и даже унести жизни. Так что учитесь на чужих ошибках и будьте очень внимательны!</p>]]></content:encoded>
    </item>
    <item>
      <title>Как правильно составлять сообщения об ошибках</title>
      <link>https://tproger.ru/translations/the-art-of-error-message</link>
      <comments>https://tproger.ru/translations/the-art-of-error-message?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/the-art-of-error-message</guid>
      <description><![CDATA[<p>Плохие тексты ошибок усложняют отладку в разы, особенно внутри обёрток над библиотеками: что с ними не так и как сделать их полезными.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/the-art-of-error-message">Как правильно составлять сообщения об ошибках</a>»</p>]]></description>
      <category><![CDATA[Лучшая практика]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 17 Jul 2015 17:29:24 GMT</pubDate>
      <content:encoded><![CDATA[<p>Что не так с этими сообщениями об ошибках (и тысячами других, на них похожих)?</p><p>Что в них такого, что усложняет отладку приложения в несколько раз, особенно, если эти ошибки возникают в классе, который оборачивает какая-то используемая вами библиотека? Конечно, если стек вызовов уводит вас на двадцать уровней глубже вашего кода, то вы можете просмотреть все двадцать и таки найти ошибку, но ведь было бы гораздо лучше, если бы вам не пришлось этого делать?</p><p>Почему <a href="http://stackoverflow.com/questions/14269529/coffeescript-unmatched-outdent-error">эта ошибка</a> в CoffeeScript <a href="http://stackoverflow.com/questions/16325872/coffeescript-unmatched-outdent">так часто</a> <a href="http://stackoverflow.com/questions/20343452/coffeescript-unmatched-outdent-error">появлялась</a> в вопросах на StackOverflow?</p><p>Что, если бы каждая ошибка, которую вы встречали, будучи разработчиком, точно говорила вам, в чем проблема? Не просто почему что-то не работает, а что вы сделали такого, что это что-то перестало работать?</p><h3>Контекст — наше все</h3><p>Исправить проблему легко: достаточно лишь сделать так, чтобы в сообщении упоминался контекст, в котором произошла ошибка. Например:</p><p>Может, это и отнимет у вас немного времени при разработке продукта, но оно окупится — <a href="http://stackoverflow.com/questions/25115017/error-when-assigning-attributes-you-must-pass-a-hash-as-an-argument">каждый</a> <a href="http://stackoverflow.com/questions/26398020/when-assigning-attributes-you-must-pass-a-hash-as-an-argument">раз</a>, <a href="http://stackoverflow.com/questions/27325985/rails-4-when-assigning-attributes-you-must-pass-a-hash-as-an-argument">когда</a> кто-то совершит ошибку при написании кода, ему не надо будет тратить минуты и часы, чтобы разобраться в ней и исправить ее.</p><p>Особенно это важно для тех, кто только начинает использовать вашу библиотеку или фреймворк — порой сообщения об ошибках могут быть довольно пугающими, даже если они хорошо спроектированы, так что очень важно сделать их настолько понятными, насколько вы можете.</p><h3>Для Clojure-истов: используйте ex-info</h3><p>Следующая информация предназначена только для тех, кто пишет на Clojure. Используйте функцию ex-info при дизайне исключений и указании контекста:</p><p>Если инструмент, который вы используете в продакшене для трассировки исключений, понимает Clojure, то он автоматически выведет ex-data, т.е. весь нужный контекст. Кроме того, вы можете манипулировать ex-data как любыми другими данными, что особенно полезно при отладке сложных ошибок.</p><h3>Как это касается меня?</h3><p>Всякий раз, когда вы создаете исключение, или видите код, который делает это, включайте в сообщение об ошибке все локальные переменные, которые могут быть хоть как-то с ней связаны. Тем самым вы сэкономите другим разработчикам кучу времени и нервов, сами практически их не потратив.</p><p>Потратьте немного своего времени сейчас, чтобы сэкономить его себе и другим в будущем, и не повторяйте чужих ошибок.</p><p>Перевод статьи «The art of the good error message»</p>]]></content:encoded>
    </item>
    <item>
      <title>10 причин, почему ошибки надо исправлять сразу</title>
      <link>https://tproger.ru/translations/ten-reasons-why-you-fix-bugs-as-soon-as-you-find-them</link>
      <comments>https://tproger.ru/translations/ten-reasons-why-you-fix-bugs-as-soon-as-you-find-them?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/ten-reasons-why-you-fix-bugs-as-soon-as-you-find-them</guid>
      <description><![CDATA[<p>Некритичный баг легко отложить и забыть о нём, но неисправленные ошибки скрывают новые — десять доводов в пользу немедленного исправления.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/ten-reasons-why-you-fix-bugs-as-soon-as-you-find-them">10 причин, почему ошибки надо исправлять сразу</a>»</p>]]></description>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Баги и ошибки]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 15 Jul 2015 11:54:58 GMT</pubDate>
      <content:encoded><![CDATA[<p>Многие новички, а иногда и опытные программисты, сталкиваются с соблазном отложить исправление некритичного бага на потом. Приложение какое-то время работает, к наличию ошибки привыкают и постепенно про нее забывают. В конце концов, встает вопрос: «А стоит ли вообще тратить на нее время?». Конечно стоит! Вот десять причин, почему ошибки в программах следует исправлять сразу же.</p><h3>1. Неисправленные ошибки скрывают новые</h3><p>Часто можно встретить ситуацию, когда только что исправленный участок кода работает корректно и проходит тесты, но при этом всплывает новая ошибка. Возможно, вам повезло, и новых ошибок не будет. Но если они все же всплывут, не стоит откладывать их исправление. Вполне возможно, что игнорируемая вами ошибка с низким приоритетом скрывает более серьезную. Или, что еще хуже, потребует переписывания значительной части кода. Если у вас в коде есть такая проблема, будет тем лучше, чем раньше вы ее обнаружите и устраните. Не откладывайте — исправляйте ошибки сразу.</p><h3>2. Неисправленные ошибки подразумевают, что качество кода неважно</h3><p>Все мы стремимся, чтобы наши программы были высокого качества. Но иногда руки опускаются, и качество когда стремительно снижается. Если программист работает с кодом, в котором есть плохо написанные функции, без юнит-тестов, новый код, который он пишет, скорее всего будет таким же. Аналогично, тестировщик, видя десятки незакрытых багрепортов, вряд ли захочет добавлять новый. Если вы работаете в команде, такое настроение передается и коллегам. Рано или поздно они начнут задавать себе вопрос: «А есть ли смысл стараться? Зачем стремиться к лучшему, если оно и так работает?». Не позволяйте себе расслабляться — исправляйте ошибки сразу.</p><h3>3. Обсуждение ошибок приводит к бесполезной трате времени</h3><p>Обсуждение старых неисправленных ошибок — это пустая трата времени, вне зависимости, планируете ли вы архитектуру, тестируете приложение или просто собрались с коллегами. Есть только один важный вопрос: «Должна ли это ошибка быть исправлена?». Все остальное — просто шум. Какой приоритет ей присвоить? Сколько это займет времени? Следует ли сначала исправить баг №114 или №113? Всего этого можно избежать (как и долгих дискуссий), если исправлять ошибки сразу. Безусловно, в каждом проекте могут быть ошибки, которые требуют повышенного внимания, но это не должно быть нормой. Не тратьте время на пустую болтовню — исправляйте ошибки сразу.</p><h3>4. Неисправленные ошибки ведут к дополнительной работе</h3><p>Чем больше неисправленных ошибок в проекте, тем труднее понять, есть ли найденная в списке известных. Представьте, что у вас 5 неисправленных ошибок. При обнаружении новой легко увидеть, сообщал ли кто уже о ней. А если их будет 50? В этом случае или потребуется дополнительное время на просмотр всех отчетов (которое можно потратить с пользой), или отчет будет отправлен повторно. Повторный отчет потребует дополнительного исследования и тестов. Не делайте лишней работы — исправляйте ошибки сразу.</p><h3>5. Неисправленные ошибки портят статистику</h3><p>Разные команды по-своему отслеживают ошибки. Кто-то просто учитывает количество оставшихся, а кто-то записывает все: от плотности до времени жизни. Вне зависимости от способа расчета, любая статистика полагается на точность данных. По мере того, как количество неисправленных ошибок растет, получать точную информацию об их состоянии становится все сложнее. Чем дольше ошибка остается неисправленной, тем больше отклонение от действительности. В результате это дезинформирует всю команду и ведет к неправильным решениям. Не портьте статистику — исправляейте ошибки сразу.</p><h3>6. Неисправленные ошибки отвлекают</h3><p>Когда кто-то встречает неисправленный баг, у него в голове возникает много отвлекающих вопросов. Возьмем, например, программиста, который пишет новую функцию и замечает старую ошибку. Должен ли он ее исправить? Может, кто-то другой уже работает над этим, но еще не успел сообщить? Можно ли полагаться на некорректный код? Точно так же и тестировщик, обнаружив старый баг при тестировании нового кода, столкнется с вопросами: «Продолжать ли тестировать новый код? Сообщили ли уже об этой ошибке? Повлияет ли она (положительно или отрицательно) на результаты тестов?» Не позволяйте себе отвлекаться — исправляйте ошибки сразу.</p><h3>7. Неисправленные ошибки могут срывать релизы</h3><p>Время от времени возникает ситуация, когда необходимо выпустить новую версию приложения <i>прямо сейчас</i>. Экстренный патч, например, или заказчик хочет увидеть последнюю версию на своем ноутбуке. Такие события вызывают стресс сами по себе, а при наличии ошибок ситуация становится еще хуже. Выпустить свежую версию само по себе обычно не занимает много времени, но сколько потребуется для исправления ошибок — неизвестно никому. Даже если быстро исправить критичные, нужно будет протестировать приложение заново. Как результат — или задержка релиза, или выпуск версии, в которой исправлены только критические ошибки. Не позволяйте ошибкам срывать релиз — исправляйте их сразу.</p><h3>8. Неисправленные ошибки срывают сроки</h3><p>Нет двух одинаковых багов. Некоторые находятся за считанные секунды, на другие уходят часы. Некоторые исправляются за минуту, другие могут отнять несколько дней. Исправление некоторых можно проверить автоматическими тестами. Некоторые требуют ручной верификации. Все вместе сильно усложняет оценку сроков выполнения задачи. Очень легко предположить, что исправление ошибок и повторное тестирование займет незначительное время, по сравнению с остальным проектом, и его можно отложить. Такое случается редко. Даже при наличии всей необходимой информации сложно сказать, сколько на самом деле времени уйдет на исправление ошибки. Не срывайте сроки — исправляйте ошибки вовремя.</p><h3>9. Исправлять знакомый код легче</h3><p>Человеческий мозг может решать множество сложных задач. Но долговременное хранение информации — явно не одна из них. Информация, которую мы запоминаем, забывается со временем. Даже то, что мы делали ранее сами, становится размытым и незнакомым. Код, который пишется в команде — не исключение. Поэтому проще исправить ошибку, пока память еще свежая, а не через одну-две недели. Можно несколько уменьшить забываемость кода, придерживаясь определённых правил, но это только даст время и не поможет запомнить код навсегда. Не заставляйте себя вспоминать, как работает код, который вы писали давно — исправляйте ошибки сразу.</p><h3>10. Исправить ошибку сразу легче</h3><p>Исходя из всех вышеописанных причин, исправить ошибку сразу выходит дешевле. Если не исправлять ее, а искать обходные пути, она может повлиять на остальной код, демотивировать и вас, и команду, стать причиной бесполезных споров, лишней работы, некорректной метрики. Она будет отвлекать вас, срывать сроки и релизы и мозолить глаза. И чем дольше вы ее игнорируете, тем сложнее ее исправить и тем больше негативные эффекты. Не рискуйте своими деньгами и нервами — исправляйте ошибки сразу.</p><p>Перевод статьи «Ten reasons why you fix bugs as soon as you find them»</p>]]></content:encoded>
    </item>
  </channel>
</rss>