Как называется программа для поиска ошибок в других программах это

Дебаг и поиск ошибок

Время на прочтение

Как называется программа для поиска ошибок в других программах это

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

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

Как обнаружить ошибку

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

В каждом языке есть свои способы уведомления об исключениях. Например в JavaScript для обработки ошибок связанных с Web Api существует DOMException. Для пользовательских сценариев есть базовый тип Error. В обоих случаях в них содержится информация о наименовании и описании ошибки.

Для .NET существует класс Exception и каждое исключение в приложении унаследовано от данного класса, который представляет ошибки происходящие во время выполнения программы. В свойстве Message читаем текст ошибки. Это даёт общее понимание происходящего. В свойстве Source смотрим в каком объекте произошла ошибка. В InnerException смотрим, нет ли внутреннего исключения и если было, то разворачиваем его и смотрим информацию уже в нём. В свойстве StackTrace хранится строковое представление информации о стеке вызова в момент появления ошибки.

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

Всю полученную информацию читаем вдумчиво и внимательно. Любая деталь важна при поиске ошибки. Иногда начинающие разработчики не придают значения этому описанию. Например в .NET при возникновении ошибки NRE с описанием параметра, который разработчик задаёт выше по коду. Из-за этого думает, что параметр не может быть NRE, а значит ошибка в другом месте. На деле оказывается, что ошибки транслируют ту картину, которую видит среда выполнения и первым делом за гипотезу стоит взять утверждение, что этот параметр равен null. Поэтому разберитесь при каких условиях параметр стал null, даже если он определялся выше по коду.

Пример неявного переопределения параметров — использование интерцептора, который изменяет этот параметр в запросе и о котором вы не знаете.

Разверните стек

Когда выбрасывается исключение, помимо самого описания ошибки полезно изучить стек выполнения. Для .NET его можно посмотреть в свойстве исключения StackTrace. Для JavaScript аналогично смотрим в Error.prototype.stack (свойство не входит в стандарт) или можно вывести в консоль выполнив console.trace(). В стеке выводятся названия методов в том порядке в котором они вызывались. Если то место, где падает ошибка зависит от аргументов которые пришли из вызывающего метода, то если развернуть стек, мы проследим где эти аргументы формировались.

Загуглите текст ошибки

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

Прочитайте документацию

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

Проведите исследовательское тестирование

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

Бинарный поиск

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

Где обитают ошибки

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

Ошибки в чужом коде

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

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

Ошибки в библиотеках

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

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

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

Ошибки не воспроизводимые локально

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

Проверьте версию приложения

На стенде и локально версии приложения должны совпадать. Возможно на стенде приложение развёрнуто из другой ветки.

Проверьте данные

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

Проверьте соответствие окружений

Если проект на стенде развёрнут в контейнере, то в некоторых IDE (JB RIder) можно дебажить в контейнере. Если проект развёрнут не в контейнере, то воспроизводимость ошибки может зависеть от окружения. Хотя .Net Core мультиплатформенный фреймворк, не всё что работает под Windows так же работает под Linux. В этом случае либо найти рабочую машину с таким же окружением, либо воспроизвести окружение через контейнеры или виртуальную машину.

Коварные ошибки

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

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

Дополнительные материалы

Подробнее ознакомиться с ним можно в докладе Сергея Щегриковича «Отладка как процесс».

Чем искать ошибки, лучше не допускать ошибки. Прочитайте статью «Качество вместо контроля качества», чтобы узнать как это делать.

Итого

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

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

Занесите ошибку в трекер

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

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

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

Вы должны записать в трекер следующую информацию:

  • Что делал пользователь.
  • Что он ожидал увидеть.
  • Что случилось на самом деле.

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

Поищите сообщение об ошибке в сети

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

Найдите строку, в которой проявляется ошибка

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

Найдите точную строку, в которой появилась ошибка

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

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

Выясните природу ошибки

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

  • Ошибка на единицу
    Вы начали цикл for с единицы вместо нуля или наоборот. Или, например, подумали, что метод .count() или .length() вернул индекс последнего элемента. Проверьте документацию к языку, чтобы убедиться, что нумерация массивов начинается с нуля или с единицы. Эта ошибка иногда проявляется в виде исключения Index out of range.
  • Состояние гонки
    Ваш процесс или поток пытается использовать результат выполнения дочернего до того, как тот завершил свою работу. Ищите использование sleep() в коде. Возможно, на мощной машине дочерний поток выполняется за миллисекунду, а на менее производительной системе происходят задержки. Используйте правильные способы синхронизации многопоточного кода: мьютексы, семафоры, события и т. д.
  • Неожиданный null
    Бьюсь об заклад, вы не раз получали ошибку с неинициализированной переменной. Убедитесь, что вы проверяете ссылки на null, особенно при обращении к свойствам по цепочке. Также проверьте случаи, когда возвращаемое из базы данных значение NULL представлено особым типом.
  • Некорректные входные данные
    Вы проверяете вводимые данные? Вы точно не пытаетесь провести арифметические операции с введенными пользователем строками?
  • Присваивание вместо сравнения
    Убедитесь, что вы не написали = вместо ==, особенно в C-подобных языках.
  • Ошибка округления
    Это случается, когда вы используете целое вместо Decimal, или float для денежных сумм, или слишком короткое целое (например, пытаетесь записать число большее, чем 2147483647, в 32-битное целое). Кроме того, может случиться так, что ошибка округления проявляется не сразу, а накапливается со временем (т. н. Эффект бабочки).
  • Переполнение буфера и выход за пределы массива
    Проблема номер один в компьютерной безопасности. Вы выделяете память меньшего объема, чем записываемые туда данные. Или пытаетесь обратиться к элементу за пределами массива.
  • Программисты не умеют считать
    Вы используете некорректную формулу. Проверьте, что вы не используете целочисленное деление вместо взятия остатка, или знаете, как перевести рациональную дробь в десятичную и т. д.
  • Конкатенация строки и числа
    Вы ожидаете конкатенации двух строк, но одно из значений — число, и компилятор пытается произвести арифметические вычисления. Попробуйте явно приводить каждое значение к строке.
  • 33 символа в varchar(32)
    Проверяйте данные, передаваемые в INSERT, на совпадение типов. Некоторые БД выбрасывают исключения (как и должны делать), некоторые просто обрезают строку (как MySQL). Недавно я столкнулся с такой ошибкой: программист забыл убрать кавычки из строки перед вставкой в базу данных, и длина строки превысила допустимую как раз на два символа. На поиск бага ушло много времени, потому что заметить две маленькие кавычки было сложно.
  • Некорректное состояние
    Вы пытаетесь выполнить запрос при закрытом соединении или пытаетесь вставить запись в таблицу прежде, чем обновили таблицы, от которых она зависит.
  • Особенности вашей системы, которых нет у пользователя
    Например: в тестовой БД между ID заказа и адресом отношение 1:1, и вы программировали, исходя из этого предположения. Но в работе выясняется, что заказы могут отправляться на один и тот же адрес, и, таким образом, у вас отношение 1:многим.

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

Метод исключения

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

Логгируйте все подряд и анализируйте журнал

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

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

Исключите влияние железа или платформы

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

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

Ради интереса, переключите кабель питания в другую розетку или к другому ИБП. Безумно? Почему бы не попробовать?

Если у вас возникает одна и та же ошибка вне зависимости от среды, то она в вашем коде.

Обратите внимание на совпадения

  • Ошибка появляется всегда в одно и то же время? Проверьте задачи, выполняющиеся по расписанию.
  • Ошибка всегда проявляется вместе с чем-то еще, насколько абсурдной ни была бы эта связь? Обращайте внимание на каждую деталь. На каждую. Например, проявляется ли ошибка, когда включен кондиционер? Возможно, из-за этого падает напряжение в сети, что вызывает странные эффекты в железе.
  • Есть ли что-то общее у пользователей программы, даже не связанное с ПО? Например, географическое положение (так был найден легендарный баг с письмом за 500 миль).
  • Ошибка проявляется, когда другой процесс забирает достаточно большое количество памяти или ресурсов процессора? (Я однажды нашел в этом причину раздражающей проблемы «no trusted connection» с SQL-сервером).

Обратитесь в техподдержку

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

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

Полезные советы (когда ничего не помогает)

  • Позовите кого-нибудь еще.
    Попросите коллегу поискать ошибку вместе с вами. Возможно, он заметит что-то, что вы упустили. Это можно сделать на любом этапе.
  • Внимательно просмотрите код.
    Я часто нахожу ошибку, просто спокойно просматривая код с начала и прокручивая его в голове.
  • Рассмотрите случаи, когда код работает, и сравните их с неработающими.
    Недавно я обнаружил ошибку, заключавшуюся в том, что когда вводимые данные в XML-формате содержали строку xsi:type=’xs:string’, все ломалось, но если этой строки не было, все работало корректно. Оказалось, что дополнительный атрибут ломал механизм десериализации.
  • Идите спать.
    Не бойтесь идти домой до того, как исправите ошибку. Ваши способности обратно пропорциональны вашей усталости. Вы просто потратите время и измотаете себя.
  • Сделайте творческий перерыв.
    Творческий перерыв — это когда вы отвлекаетесь от задачи и переключаете внимание на другие вещи. Вы, возможно, замечали, что лучшие идеи приходят в голову в душе или по пути домой. Смена контекста иногда помогает. Сходите пообедать, посмотрите фильм, полистайте интернет или займитесь другой проблемой.
  • Закройте глаза на некоторые симптомы и сообщения и попробуйте сначала.
    Некоторые баги могут влиять друг на друга. Драйвер для dial-up соединения в Windows 95 мог сообщать, что канал занят, при том что вы могли отчетливо слышать звук соединяющегося модема. Если вам приходится держать в голове слишком много симптомов, попробуйте сконцентрироваться только на одном. Исправьте или найдите его причину и переходите к следующему.

Что вам точно не поможет

  • Паника
    Не надо сразу палить из пушки по воробьям. Некоторые менеджеры начинают паниковать и сразу откатываться, перезагружать сервера и т. п. в надежде, что что-нибудь из этого исправит проблему. Это никогда не работает. Кроме того, это создает еще больше хаоса и увеличивает время, необходимое для поиска ошибки. Делайте только один шаг за раз. Изучите результат. Обдумайте его, а затем переходите к следующей гипотезе.
  • «Хелп, плиииз!»
    Когда вы обращаетесь на форум за советом, вы как минимум должны уже выполнить шаг 3. Никто не захочет или не сможет вам помочь, если вы не предоставите подробное описание проблемы, включая информацию об ОС, железе и участок проблемного кода. Создавайте тему только тогда, когда можете все подробно описать, и придумайте информативное название для нее.
  • Переход на личности
    Если вы думаете, что в ошибке виноват кто-то другой, постарайтесь по крайней мере говорить с ним вежливо. Оскорбления, крики и паника не помогут человеку решить проблему. Даже если у вас в команде не в почете демократия, крики и применение грубой силы не заставят исправления магическим образом появиться.

Ошибка, которую я недавно исправил

Это была загадочная проблема с дублирующимися именами генерируемых файлов. Дальнейшая проверка показала, что у файлов различное содержание. Это было странно, поскольку имена файлов включали дату и время создания в формате yyMMddhhmmss. Шаг 9, совпадения: первый файл был создан в полпятого утра, дубликат генерировался в полпятого вечера того же дня. Совпадение? Нет, поскольку hh в строке формата — это 12-часовой формат времени. Вот оно что! Поменял формат на yyMMddHHmmss, и ошибка исчезла.

Перевод статьи «How to fix bugs, step by step»

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

Юан-Ма написал небольшую программу, использующую много глобальных переменных и ужасных хаков. Ученик, читая программу, спросил его: «Вы предупреждали нас о подобных техниках, но при этом я нахожу их в вашей же программе. Как это возможно?» Мастер ответил: «Не нужно бежать за поливальным шлангом, если дом не горит».

Мастер Юан-Ма, «Книга программирования».

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

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

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

Ошибки программистов

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

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

x = true * «обезьяна»

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

Но часто ваши бессмысленные вычисления просто породят NaN (not a number) или undefined. Программа радостно продолжит, будучи уверенной в том, что она делает что-то осмысленное. Ошибка проявит себя позже, когда такое фиктивное значение уже пройдёт через несколько функций. Она может вообще не вызвать сообщение об ошибке, а просто привести к неправильному результату выполнения. Поиск источника таких проблем – сложная задача.

Процесс поиска ошибок (bugs) в программах называется отладкой (debugging).

Строгий режим (strict mode)

JavaScript можно заставить быть построже, переведя его в строгий режим. Для этого наверху файла или тела функции пишется «use strict». Пример:

Обычно, когда ты забываешь написать var перед переменной, как в примере перед counter, JavaScript по-тихому создаёт глобальную переменную и использует её. В строгом режиме выдаётся ошибка. Это очень удобно. Однако, ошибка не выдаётся, когда глобальная переменная уже существует – только тогда, когда присваивание создаёт новую переменную.

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

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

Некорректный вызов Person успешно происходит, но возвращается как undefined и создаёт глобальную переменную name. В строгом режиме всё по-другому:

Нам сразу сообщают об ошибке. Очень удобно.

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

Короче говоря, надпись «use strict» перед текстом программы редко причиняет проблемы, зато помогает вам видеть их.

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

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

Для примера вновь обратимся к типу Vector.

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

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

Отладка (debugging)

Когда вы заметили проблему в программе,– она ведёт себя неправильно и выдаёт ошибки,- самое время выяснить, в чём проблема.

Иногда это очевидно. Сообщение об ошибке наводит вас на конкретную строку программы, и если вы прочтёте описание ошибки и эту строку, вы часто сможете найти проблему.

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

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

Даже если вы нашли проблему – притворитесь, что ещё не нашли. Мы знаем, что программа сбоит, и нам нужно узнать, почему.

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

Размещение нескольких вызовов console.log в стратегических местах – хороший способ получить дополнительную информацию о том, что программа делает. В нашем случае нам нужно, чтобы n принимала значения 13, 1, затем 0. Давайте выведем значения в начале цикла:

Н-да. Деление 13 на 10 выдаёт не целое число. Вместо n /= base нам нужно n = Math.floor(n / base), тогда число будет корректно «сдвинуто» вправо.

Кроме console.log можно воспользоваться отладчиком в браузере. Современные браузеры умеют ставить точку остановки на выбранной строчке кода. Это приведёт к приостановке выполнения программы каждый раз, когда будет достигнута выбранная строчка, и тогда вы сможете просмотреть содержимое переменных. Не буду подробно расписывать процесс, поскольку у разных браузеров он организован по-разному – поищите в вашем браузере “developer tools”, инструменты разработчика. Ещё один способ установить точку остановки – включить в код инструкцию для отладчика, состоящую из ключевого слова debugger. Если инструменты разработчика активны, исполнение программы будет приостановлено на этой инструкции, и вы сможете изучить состояние программы.

Распространение ошибок

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

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

Допустим, у вас есть функция promptInteger, которая запрашивает целое число и возвращает его. Что она должна сделать, если пользователь введёт «апельсин»?

Один из вариантов – вернуть особое значение. Обычно для этих целей используют null и undefined.

Это надёжная стратегия. Теперь любой код, вызывающий promptNumber, должен проверять, было ли возвращено число, и если нет, как-то выйти из ситуации – спросить снова, или задать значение по-умолчанию. Или вернуть специальное значение уже тому, кто его вызвал, сообщая о неудаче.

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

Вторая проблема – работа со специальными значениями может замусорить код. Если функция promptNumber вызывается 10 раз, то надо 10 раз проверить, не вернула ли она null. Если реакция на null заключается в возврате null на уровень выше, тогда там, где вызывался этот код, тоже нужно встраивать проверку на null, и так далее.

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

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

Ключевое слово throw используется для выбрасывания исключения. Ловлей занимается кусок кода, обёрнутый в блок try, за которым следует catch. Когда код в блоке try выкидывает исключение, выполняется блок catch. Переменная, указанная в скобках, будет привязана к значению исключения. После завершения выполнения блока catch, или же если блок try выполняется без проблем, выполнение переходит к коду, лежащему после инструкции try/catch.

В данном случае для создания исключения мы использовали конструктор Error. Это стандартный конструктор, создающий объект со свойством message. В современных окружениях JavaScript экземпляры этого конструктора также собирают информацию о стеке вызовов, который был накоплен в момент выкидывания исключения – так называемое отслеживание стека (stack trace). Эта информация сохраняется в свойстве stack, и может помочь при разборе проблемы – она сообщает, в какой функции случилась проблема и какие другие функции привели к данному вызову.

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

Подчищаем за исключениями

Представьте следующую ситуацию: функция withContext желает удостовериться, что во время её выполнения переменная верхнего уровня context получит специальное значение контекста. После завершения выполнения, она восстанавливает её старое значение.

Что, если функция body выбросит исключение? В таком случае вызов withContext будет выброшен исключением из стека, и переменной context никогда не будет возвращено первоначальное значение.

Но у инструкции try есть ещё одна особенность. За ней может следовать блок finally, либо вместо catch, либо вместе с catch. Блок finally означает «что бы ни произошло, выполнить код в любом случае после выполнения блока try». Если функции надо что-то подчистить, то подчищающий код нужно включать в блок finally.

Заметьте, что нам больше не нужно сохранять результат вызова body в отдельной переменной, чтобы вернуть его. Даже если мы возвращаемся из блока try, блок finally всё равно будет выполнен. Теперь мы можем безопасно сделать так:

Несмотря на то, что вызываемая из withContext функция «сломалась», сам по себе withContext по-прежнему подчищает значение переменной context.

Выборочный отлов исключений

Когда исключение доходит до низа стека и его никто не поймал — его обрабатывает окружение. Как именно – зависит от конкретного окружения. В браузерах описание ошибки выдаётся в консоль (она обычно доступна в меню «Инструменты» или «Разработка»).

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

Если возникновение проблемы предсказуемо, программа не должна падать с необработанным исключением — это не очень дружественно по отношению к пользователю.

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

При входе в блок catch мы знаем только, что что-то внутри блока try привело к исключению. Мы не знаем, что именно, и какое исключение произошло.

JavaScript (что является вопиющим упущением) не предоставляет непосредственной поддержки выборочного отлова исключений: либо ловим все, либо никакие. Из-за этого люди часто предполагают, что случившееся исключение – именно то, ради которого и писался блок catch.

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

Конструкция for (;;) – способ устроить бесконечный цикл. Мы вываливаемся из него, только когда получаем допустимое направление. Но мы неправильно написали название promptDirection, что приводит к ошибке “undefined variable”. А так как блок catch игнорирует значение исключения e, предполагая, что он разбирается с другой проблемой, он считает, что выброшенное исключение является результатом неправильных входных данных. Это приводит к бесконечному циклу и скрывает полезное сообщение об ошибке насчёт неправильного имени переменной.

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

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

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

Давайте лучше определим новый тип ошибки и используем instanceof для его распознавания.

Прототип наследуется от Error.prototype, поэтому instanceof Error тоже будет выполняться для объектов типа InputError. И ему назначено свойство name, как и другим стандартным типам ошибок (Error, SyntaxError, ReferenceError, и т.п.)

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

Теперь promptDirection может сотворить такую ошибку.

А в цикле её будет ловить сподручнее.

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

Утверждения (Assertions)

Утверждения – инструмент для простой проверки ошибок. Рассмотрим вспомогательную функцию assert:

Это – компактный способ ужесточения требований к значениям, который выбрасывает исключение в случае, когда заданное условие не выполняется. К примеру, функция lastElement, добывающая последний элемент массива, вернула бы undefined для пустых массивов, если б мы не использовали assertion. Извлечение последнего элемента пустого массива не имеет смысла, и это явно было бы ошибкой программиста.

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

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

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

Выброс исключения приводит к разматыванию стека до тех пор, пока не будет встречен блок try/catch или пока мы не дойдём до дна стека. Значение исключения будет передано в блок catch, который сможет удостовериться в том, что это исключение действительно то, которое он ждёт, и обработать его. Для работы с непредсказуемыми событиями в потоке программы можно использовать блоки finally, чтобы определённые части кода были выполнены в любом случае.

Упражнения

Допустим, у вас есть функция primitiveMultiply, которая в 50% случаев перемножает 2 числа, а в остальных случаях выбрасывает исключение типа MultiplicatorUnitFailure. Напишите функцию, обёртывающую эту, и просто вызывающую её до тех пор, пока не будет получен успешный результат.

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

Запертая коробка

Рассмотрим такой, достаточно надуманный, объект:

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

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

В качестве призовой игры убедитесь, что при вызове withBoxUnlocked, когда коробка не заперта, коробка остаётся незапертой.

Поиск ответов на кроссворды и сканворды

Ответ на вопрос «Поиск ошибок в программе «, 7 (семь) букв: отладка

Альтернативные вопросы в кроссвордах для слова отладка

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

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

Ему была поручена разработка элементов, макетирование и отладка главной части МЭСМ — арифметического устройства, с чем он отлично справился.

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

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

Отладка — это тяжелая и долгая часть системного программирования, и медленная оборачиваемость является проклятием отладки.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *