Отладка программы призвана выискивать «вредителей» кода и устранять их. За это отвечают отладчик и журналирование для вывода сведений о программе.
В предыдущей части мы рассмотрели исходный код и его составляющие.
После того, как вы начнете проверять фрагменты кода или попытаетесь решить связанные с ним проблемы, вы очень скоро поймете, что существуют моменты, когда программа крашится, прерывается и прекращает работу.
Это часто вызвано ошибками, известными как дефекты или исключительные ситуации во время выполнения. Акт обнаружения и удаления ошибок из нашего кода – это отладка программы. Вы лучше разберетесь в отладке на практике, используя ее как можно чаще. Мы не только отлаживаем собственный код, но и порой дебажим написанное другими программистами.
Для начала необходимо рассортировать общие ошибки, которые могут возникнуть в исходном коде.
Синтаксические ошибки
Эти эрроры не позволяют скомпилировать исходный код на компилируемых языках программирования. Они обнаруживаются во время компиляции или интерпретации исходного кода. Они также могут быть легко обнаружены статическими анализаторами (линтами). Подробнее о линтах мы узнаем немного позже.
Синтаксические ошибки в основном вызваны нарушением ожидаемой формы или структуры языка, на котором пишется программа. Как пример, это может быть отсутствующая закрывающая скобка в уравнении.
Семантические ошибки
Отладка программы может потребоваться и по причине семантических ошибок, также известных как логические. Они являются наиболее сложными из всех, потому что не могут быть легко обнаружены. Признак того, что существует семантическая ошибка, – это когда программа запускается, отрабатывает, но не дает желаемого результата.
Рассмотрим данный пример:
3 + 5 * 6
По порядку приоритета, называемому старшинством операции, с учетом математических правил мы ожидаем, что сначала будет оценена часть умножения, и окончательный результат будет равен 33. Если программист хотел, чтобы сначала происходило добавление двух чисел, следовало поступить иначе. Для этого используются круглые скобки, которые отвечают за смещение приоритетов в математической формуле. Исправленный пример должен выглядеть так:
(3 + 5) * 6
3 + 5, заключенные в скобки, дадут желаемый результат, а именно 48.
Ошибки в процессе выполнения
Как и семантические, ошибки во время выполнения никогда не обнаруживаются при компиляции. В отличие от семантических ошибок, эти прерывают программу и препятствуют ее дальнейшему выполнению. Они обычно вызваны неожиданным результатом некоторых вычислений в исходном коде.
Вот хороший пример:
input = 25
x = 0.8/(Math.sqrt(input) — 5)
Фрагмент кода выше будет скомпилирован успешно, но input 25 приведет к ZeroDivisionError. Это ошибка во время выполнения. Другим популярным примером является StackOverflowError или IndexOutofBoundError. Важно то, что вы идентифицируете эти ошибки и узнаете, как с ними бороться.
Существуют ошибки, связанные с тем, как ваш исходный код использует память и пространство на платформе или в среде, в которой он запущен. Они также являются ошибками во время выполнения. Такие ошибки, как OutOfMemoryErrorand и HeapError обычно вызваны тем, что ваш исходный код использует слишком много ресурсов. Хорошее знание алгоритмов поможет написать код, который лучше использует ресурсы. В этом и заключается отладка программы.
Процесс перезаписи кода для повышения производительности называется оптимизацией. Менее популярное наименование процесса – рефакторинг. Поскольку вы тратите больше времени на кодинг, то должны иметь это в виду.
Отладка программы
Вот несколько советов о том, как правильно выполнять отладку:
Двигаемся дальше
Поздравляем! Слово «ошибка» уже привычно для вас, равно как и «отладка программы». В качестве новичка вы можете изучать кодинг по книгам, онлайн-урокам или видео. И даже чужой код вам теперь не страшен 🙂
В процессе кодинга измените что-нибудь, чтобы понять, как он работает. Но будьте уверены в том, что сами написали.
Викторина
input = Hippo’
if input == ‘Hippo’:
print ‘Hello, Hippo’
Ответы на вопросы
2. Синтаксическая ошибка: Отсутствует стартовая кавычка в первой строке.
Данная статья является переводом. Ссылка на оригинал.
В статье рассмотрим:
Представьте, как разрабатываете RESTful web API на Node.js.
Первое, с чего стоит начать изучение –
Разберем на примере:
throw new Error(‘database failed to connect’);
Здесь происходят две вещи: создается объект Error и выбрасывается исключение.
Начнем с рассмотрения объекта Error, и того, как он работает. К ключевому слову вернемся чуть позже.
Объект Error представляет из себя реализацию , которая использует набор инструкций (аргументы и само тело конструктора) для создания объекта.
Тем не менее, что же такое объекты ошибок? Почему они должны быть однородными? Это важные вопросы, поэтому давайте перейдем к ним.
Первым аргументом для объекта Error является его описание.
Описание – это понятная человеку строка объекта ошибки. Также эта строка появляется в консоли, когда что-то пошло не так.
Объекты ошибок также имеют свойство name, которое рассказывает о типе ошибки. Когда создается нативный объект ошибки, то свойство name по умолчанию содержит Error. Вы также можете создать собственный тип ошибки, расширив нативный объект ошибки следующим образом:
Обработка ошибок становится проще, когда у нас есть согласованность в объектах.
Ранее мы упоминали, что хотим, чтобы объекты ошибок были однородными. Это поможет в объекте ошибки.
Теперь давайте поговорим о следующей части головоломки –
Ключевое слово Throw
Создание объектов ошибок – это не конец истории, а только подготовка ошибки к отправке. Отправка ошибки заключается в том, чтобы выбросить исключение. Но что значит выбросить? И что это значит для нашей программы?
Throw делает две вещи: останавливает выполнение программы и находит зацепку, которая мешает выполнению программы.
Давайте рассмотрим эти идеи одну за другой:
Бросаем исключения на примере
Мы рассмотрели теорию, а теперь давайте изучим пример:
Здесь в функции инициализации init() предусмотрена обработка ошибок, поскольку она содержит try/catch блок.
init() вызывает функцию doAthing(), которая вызывает функцию byDoingSomethingElse(), где выбрасывается исключение. Именно в этот момент ошибки, программа останавливается и начинает отслеживать функцию, вызвавшую ошибку. Далее в функции init() и выполняет оператор catch. С помощью оператора catch мы решаем что делать: подавить ошибку или даже выдать другую ошибку (для распространения вверх).
Стек вызовов
То, что показано в приведенном выше примере – это проработанный пример стека вызовов. Как и большинство языков, JavaScript использует концепцию, известную как стек вызовов.
Но как работает стек вызовов?
Всякий раз, когда вызывается функция, она помещается в стек, а при завершении удаляется из стека. Именно от этого стека мы получили название «трассировки стека».
Трассировка стека – это список функций, которые были вызваны до момента, когда в программе произошло исключение.
часто выглядит так:
На этом этапе вам может быть интересно, как стек вызовов помогает нам с обработкой ошибок Node.js. Давайте поговорим о важности стеков вызовов.
Стек вызовов предоставляет «хлебные крошки», помогая проследить путь, который привел к исключению(ошибке).
Почему у нас должны быть функции без имен? Иногда в наших программах мы хотим определить маленькие одноразовые функции, которые выполняют небольшую задачу. Мы не хотим утруждать себя задачей давать им имена, но именно эти анонимные функции могут вызвать у нас всевозможные головные боли. Анонимная функция удаляет имя функции из нашего стека вызовов, что делает наш стек вызовов значительно более сложным в использовании.
Обратите внимание, что присвоить имена функциям в JavaScript не так просто. Итак, давайте кратко рассмотрим различные способы определения функций, и рассмотрим некоторые ловушки в именовании функций.
Как называть функции
Чтобы понять, как называть функции, давайте рассмотрим несколько примеров:
Вот три примера функций.
Первая – это лямбда (или стрелочная функция). Лямбда функции по своей природе анонимны. Не запутайтесь. Имя переменной one не является именем функции. Имя функции следующее за ключевым словом function необязательно. Но в этом примере мы вообще ничего не передаем, поэтому наша функция анонимна.
Не помогает и то, что некоторые среды выполнения JavaScript, такие как V8, могут иногда угадывать имя вашей функции. Это происходит, даже если вы его не даете.
Во втором примере мы получили функциональное выражение. Это очень похоже на первый пример. Это анонимная функция, но просто объявленная с помощью ключевого слова function вместо синтаксиса жирной стрелки.
В последнем примере объявление переменной с подходящим именем explicitFunction. Это показывает, что это единственная функция, у которой соответствующее имя.
Как правило, рекомендуется указывать это имя везде, где это возможно, чтобы иметь более удобочитаемую трассировку стека.
Обработка асинхронных исключений
Мы познакомились с объектом ошибок, ключевым словом throw, стеком вызовов и наименованием функций. Итак, давайте обратим наше внимание на любопытный случай обработки асинхронных ошибок. Почему? Потому что асинхронный код ведет себя не так, как ожидаем. Асинхронное программирование необходимо каждому программисту на Node.js.
Javascript – это однопоточный язык программирования, а это значит, что Javascript запускается с использованием одного процессора. Из этого следует, что у нас есть блокирующий и неблокирующий код. Блокирующий код относится к тому, будет ли ваша программа ожидать завершения асинхронной задачи, прежде чем делать что-либо еще. В то время как неблокирующий код относится к тому, где вы регистрируете обратный вызов (callback) для выполнения после завершения задачи.
Стоит упомянуть, что есть два основных способа обработки асинхронности в JavaScript: promises (обещания или промисы) и callback (функция обратного вызова). Мы намеренно игнорируем async/wait, чтобы избежать путаницы, потому что это просто сахар поверх промисов.
В статье мы сфокусируемся на промисах. Существует консенсус в отношении того, что для приложений промисы превосходят обратные вызовы с точки зрения стиля программирования и эффективности. Поэтому в этой статье проигнорируем подход с callback-ами, и предположим, что вместо него вы выберете promises.
Существует множество способов конвертировать код на основе callback-ов в promises. Например, вы можете использовать такую утилиту, как promisify, или обернуть свои обратные вызовы в промисы, например, так:
Мы разберемся с этой ошибкой, обещаю!
Давайте взглянем на анатомию обещаний.
Промисы в JavaScript – это объект, представляющий будущее значение. Promise API позволяют нам моделировать асинхронный код так же, как и синхронный. Также стоит отметить, что обещание обычно идет в цепочке, где выполняется одно действие, затем другое и так далее.
Но что все это значит для обработки ошибок Node.js?
Промисы элегантно обрабатывают ошибки и перехватывают любые ошибки, которые им предшествовали в цепочке. С помощью одного обработчика обрабатывается множество ошибок во многих функциях.
Изучим код ниже:
Здесь видно, как объединить обработку ошибок для трех различных функций в один обработчик, т. е. код ведет себя так же, как если бы три функции заключались в синхронный блок try/catch.
Отлавливать или не отлавливать?
На данном этапе стоит спросить себя, повсеместно ли добавляется .catch к промисам, поскольку это опционально. Из-за проблем с сетью, аппаратного сбоя или истекшего времени ожидания в асинхронных вызовах возникает исключение. По этим причинам указывайте программе, что делать в случаях невыполнения промиса.
Запомните «Золотое правило» – каждый раз обрабатывать исключения в обещаниях.
Риски асинхронного try/catch
Мы приближаемся к концу в нашем путешествии по обработке ошибок в Node.js. Пришло время поговорить о ловушках асинхронного кода и оператора try/catch.
Вам может быть интересно, почему промис предоставляет метод catch, и почему мы не можем просто обернуть нашу реализацию промиса в try/catch. Если бы вы сделали это, то результаты были бы не такими, как вы ожидаете.
Рассмотрим на примере:
try/catch по умолчанию синхронны, что означает, что если асинхронная функция выдает ошибку в синхронном блоке try/catch, ошибка не будет брошена.
Однозначно это не то, что ожидаем.
Подведем итог! Необходимо использовать обработчик промисов, когда мы имеем дело с асинхронным кодом, а в случае с синхронным кодом подойдет try/catch.
Заключение
Из этой статьи мы узнали:
Материалы по теме
В истории программирования не всегда все было легко и безоблачно. Ведь любому программисту, вне зависимости от опыта и технического бэкграунда, трудно уберечься от ошибок и порой даже небольшого количества плохого кода хватало, чтобы вызвать серьезную проблему. «Библиотека программиста» немного полистала ИТ-летописи и нашла для вас 10 самых худших ошибок в истории кодинга. Поехали!
Ошибка 2000 года
Более двух десятилетий назад, в канун нового 2000 года, весь развитый мир охватила паника из-за проблемы способной привести к сбоям в работе компьютеров, падению самолетов, прекращению работы медицинского оборудования и разрушению глобальной финансовой системы.
Суть проблемы заключалась в том, что большинство устаревших информационных систем, созданных еще в 70-х и 80-х, использовали только две цифры для исчисления года. Это значит, что часы внутри микропроцессоров различного аппаратного ПО регистрировали 1999 год как «99», основываясь на ошибочном предположении разработчиков прошлого, что мы всегда будем жить в 20-м веке и цифра «19» в обозначении года никогда не изменится.
Тогда все полагали, что в 2000 году у всех нас наверняка будут реактивные ранцы, еда в форме таблеток, футуристическая серебристая униформа и человечество к этому времени откажется от старых компьютерных систем середины века. Но чуда не случилось!
И в канун всеми любимого праздника мир охватила полномасштабная паника. Кто-то усердно работал над внедрением обходных путей, выделяя на эти цели колоссальные средства. Другие же вообще не чинили, не исправляли и не обновляли свои системы. Кто из них был прав, сейчас сказать сложно, но когда наступил рубеж тысячелетий, глобальной катастрофы не произошло. Однако у всей этой шумихи, все же был положительный эффект. До общественности наконец начало доходить, как много жизненно важных систем зависит от компьютеров и микропроцессоров и как важно следить за их ПО и своевременно его обслуживать.
Терак-25
Плохой код на самом деле может убить. Такая катастрофа произошла с аппаратом лучевой терапии Therac-25, произведенным компанией Atomic Energy of Canada, ставшего причиной гибели не менее шести пациентов. Расследование выявило недоработку системы, вызвавшую передозировку радиацией. Связано это было с трудностью проведения автоматизированных тестов такого специфического программного обеспечения. И поэтому машина, призванная помочь людям, стала машиной для убийств из научной фантастики. Этот случай заставил разработчиков ПО медицинской отрасли крайне ответственно подходить к тестированию такого оборудования.
Сеть AT&T выходит из строя
15 января 1990 года около 50 процентов мобильной сети AT&T вышло из строя. За девять часов простоя более 75 миллионов звонков остались без ответа. И хотя в первоначальных отчетах следствия по этому делу значилось хакерская атака, на самом деле, виновником сего происшествия стало стандартное обновление ПО. Ошибка всего в одной строке кода стоила компании огромных денег. Все организации, целиком зависящие от наличия и качества связи, выставили AT&T иски с внушительными суммами. К примеру, крупнейший авиаперевозчик American Airlines, понес колоссальные финансовые убытки из-за того, что получил наполовину меньше звонков своих клиентов из-за сбоя. Авария 1990 года до сих пор служит прекрасным примером важности тестирования программного обеспечения и служит напоминанием о неразрывной связи между технологиями и экономической деятельностью большинства компаний.
Досрочное освобождение заключенных
В 2005 году в США штате Мичиган произошел сбой тюремной программы, отвечающей за расчет срока наказания заключенных, в результате чего более 20 заключенных досрочно вышли на свободу. Программа ошибочно посчитала смягчающий коэффициент и снизила срок пребывания отбывающих наказание людей в несколько раз. Ошибку в коде заметили не сразу, а лишь по прошествии некоторого времени. И поэтому меру пресечения счастливчикам никто не пересчитывал. Среди них, к слову, не было матерых гангстеров и маньяков. В основном это были мелкие правонарушители, отбывавшие срок за неуплату алиментов, махинации с налогами и незаконное хранение психотропов.
Произошедшее показывает, что в погоне за автоматизацией процессов и сокращением количества ручных операций программисты зачастую забывают о грамотной настройке разработанной системы и ее тестировании. А мы с вами в очередной раз убеждаемся, что даже незначительная ошибка может привести к серьезным последствиям.
Взрыв Ariane 5
Случай произошел 4 июня 1996 года при первом запуске Ariane 5 — одной из самых надежных беспилотных ракетных установок, целью которой было изучение взаимодействия между солнечным ветром и магнитосферой Земли. Через 37 секунд после старта ракета, вылетевшая с космодрома, находящегося на берегах Французской Гвианы, развернулась на 90 градусов и всего через несколько секунд превратилась в огромный огненный шар.
Специальная группа прочесала место происшествия, собирая обломки, чтобы попытаться выяснить, что произошло. Через некоторое время техническая комиссия установила, что авария была вызвана программным сбоем.
Отказ системы произошел из-за полной потери информации о точности наведения ракеты и ориентации ее в пространстве, поступающей в центральный процессор. Потеря — следствие ошибки проектировщиков ПО и вызвана она была некорректным преобразованием 64-битного числа с плавающей запятой в 16-битное целое число.
К слову, целочисленное переполнение является широко распространенной ошибкой в компьютерном программировании.
Ошибка Paypal
Что бы вы сделали, если бы PayPal случайно зачислил на ваш счет 92 квадриллиона долларов? Крису Рейнольдсу, 56-летнему американцу, продающему автозапчасти на eBay, не пришлось долго об этом думать. Ведь он даже не успел ощутить себя первым в мире квадриллионером и самым богатым человеком в мире, так как ошибка была устранена в течение нескольких минут. Поэтому, прежде чем мужчина начал мечтать о новом кадиллаке и золотой карте члена королевского яхт-клуба, сумма на его счету вернулась к привычному балансу. Конечно, стоило бы потребовать с компании хотя бы часть этой суммы за моральный ущерб, но, видимо, шок от увиденного не позволил ему сделать это.
Калькулятор Windows
Эта ошибка, существующая в большинстве версий Windows (кроме Windows 10), которую вы сможете проверить самостоятельно.
Для этого нужно:
- Открыть калькулятор Windows и ввести 4.
- Извлечь из этого числа квадратный корень и получите 2.
Причиной ошибки является недоработка, вызванная некорректным обращением калькулятора с операциями sqrt. В итоге результаты сохраняются как числа с плавающей запятой, а не целые числа, вызывая ошибку точности при дальнейших вычислениях, которую вы можете видеть.
Microsoft признала эту ошибку в приложении калькулятора и исправила ее в Windows 10.
Проблема 2038 года
Ошибка 2038 будет вызвана использованием 32-разрядных процессоров в 32-разрядных системах. Проще говоря, 19 января 2038 года наступит в 03:14:07. Компьютеры, которые все еще используют 32-разрядные системы для управления датой и временем, не смогут справиться с этим изменением. Как и в случае с ошибкой 2000 года, компьютеры не смогут отличить 2038 год от 1970 года.
Однако волноваться не стоит: почти все современные процессоры в настольных ПК имеют 64-битные системы с 64-битным программным обеспечением и в 2038 году само существование 32-битных систем будет под вопросом.
Видео Gangnam Style «сломало» YouTube
Однако корейская поп-группа PSY, выложившая на сервис свой клип с зажигательной песней Gangnam Style благополучно его сломала, превысив допустимую планку.
«Когда мы его делали, никогда не думали, что какое-нибудь видео посмотрят столько раз, но это было до Gangman Style», — написал на своей странице в сети один из разработчиков портала.
Клип PSY опубликовали 15 июля 2012 года и к концу мая 2014 года он стал единственным видеороликом, которой просмотрели больше 2 млрд раз.
BSOD или «Синий экран смерти» — жаргонное название фатальной системной ошибки Windows, показывающей системный сбой, при котором операционка достигала состояния, в котором она больше не могла надежно работать. Как правило, вызывалась она в Windows 95-98 после неожиданного завершения важного процесса или общего сбоя оборудования. Старожилы наверняка помнят этот баг, который довольно часто возникал на заре становления ИТ-культуры.
16 марта 2000 года Microsoft выпустила обновление для системы безопасности, устраняющее эту неполадку, однако и сейчас на форумах изредка можно встретить сообщение от пользователей об этой проблеме.
Людям свойственно совершать ошибки. Однако, будьте внимательны — всегда нужно помнить, что даже одна плохо написанная строчка кода может привести к печальным последствиям. Удачи!
Существует три
основных типа ошибок в программах:
— ошибки этапа
компиляции (или синтаксические ошибки);
— ошибки этапа
выполнения или семантические ошибки);
Cинтаксические
ошибки происходят из-за нарушений
правил синтаксиса
языка программирования.
Когда компилятор обнаруживает
синтаксическую
ошибку, то отмечает
место (позицию или строку) ошибки и
выводт сообщение
Наиболее
распространенными синтаксическими
ошибками являются:
— ошибки набора
(опечатки);
— пропущенные
точки с запятой;
— ссылки на
неописанные переменные;
— передача
неверного числа (или типа) параметров
процедуры или
— присваивание
переменной значений неверного типа.
После исправления
cинтаксической ошибки компиляцию можно
выполнить
заново. После
устранения всех синтаксических ошибок
и успешной компиля-
ции программа готова
к выполнению и поиску ошибок этапа
выполнения и ло-
Семантические
ошибки происходят, когда программа
компилируется, но
при выполнении
операторов что-то происходит неверно.
Например, программа
пытается открыть
для ввода несуществующий файл или
выполнить деление на
ноль. При обнаружении
семантических ошибок выполнение
программы заверша-
ется и выводится
сообщение об ошибке. Например, в системе
Turbo Pascal
выводится сообщение
следующего вида:
Run-time error ## at seg:ofs
По номеру
ошибки (##) можно установить причину ее
возникновения.
Логические ошибки
— это ошибки проектирования и реализации
програм-
мы. Логические
ошибки приводят к некорректному или
непредвиденному зна-
чению переменных,
неправильному виду графических
изображений или невы-
полнению кода, когда
это ожидается. Эти ошибки часто трудно
отслежива-
ются, поскольку ни
компилятор, ни исполняющая система не
обнаруживают их
автоматически, как
синтаксические и семантические ошибки.
Обычно системы
программирования
включает в себя средства отладки,
помогающие найти ло-
Цели и задачи отладки и тестирования.
Многие программисты
путают отладку программ с тестированием,
пред-
назначенным для
проверки их работоспособности. Отладка
имеет место тог-
да, когда программа
со всей очевидностью работает неправильно.
Поэтому
отладка начинается
всегда в предположении отказа программы.
Если же ока-
зывается, что
программа работает верно, то она
тестируется. Часто случа-
ется так, что после
прогона тестов программа вновь должна
быть подверг-
нута отладке. Таким
образом, тестирование устанавливает
факт наличия
ошибки, а отладка
выявляет ее причину, и эти два этапа
разработки прог-
Основные возможности интегрированного отладчика системы
Основной смысл
использования встроенного отладчика
состоит в управ-
ляемом выполнении
программы. Отслеживая выполнение
каждой инструкции,
можно легко определить,
какая часть программы вызывает проблемы.
В от-
ладчике предусмотрено
шесть основных механизмов управления
выполнением
— выполнять
программу до заданной точки (Toggle
Breakpoint или
— выполнять сброс
программы (Run¦Reset Program или Ctrl+F2).
Выполнение
программы по шагам (команда Step Over меню
выполнения
Run) и трассировка
программы (команда Trace Into меню выполнения
Run)
дают возможность
построчного выполнения программы.
Единственное отличие
выполнения по шагам
и трассировки состоит в том, как они
работают с вы-
зовами процедур и
функций. Выполнение по шагам вызова
процедуры или
функции интерпретирует
вызов как простой оператор и после
завершения
подпрограммы
возвращает управление на следующую
строку. Трассировка
подпрограммы
загружает код этой подпрограммы и
продолжает ее построчное
Выполнение
программы до заданной точки (команда
Toggle Breakpoint
локального меню
редактора) — более гибкий механизм
отладки, чем исполь-
зование метода
выполнения до позиции курсора (команда
Go to Cursor меню
выполнения Run),
поскольку в программе можно установить
несколько точек
Интегрированная
среда разработки программы предусматривает
несколь-
ко способов поиска
в программе заданного места. Простейший
способ пре-
процедуры или
функции, затем находит соответствующую
строку в файле, где
определяется эта
подпрограмма. Этот подход полезно
использовать при ре-
дактировании, но
его можно комбинировать с возможностью
выполнения прог-
раммы до определенной
точки, чтобы пройти программу до той
части кода,
которую надо отладить.
Чтобы сбрасить
все ранее задействованные отладочные
средства и
reset или нажать клавиши
Ctrl+F2.
При выполнении
программы по шагам можно наблюдать ее
вывод несколь-
— открытие окна
вывода (Debug¦Output);
— использование
второго монитора;
Выполнение
программы по шагам или ее трассировка
могут помочь найти
ошибки в алгоритме
программы, но обычно желательно также
знать, что про-
исходит на каждом
шаге со значениями отдельных переменных.
Например, при
Встроенный отладчик
имеет два инструментальных средства
для проверки со-
— окно Watches
(Просмотр);
— диалоговое окно
Evaluate and Modify (Вычисление и модификация).
Чтобы открыть
окно Watches, необходимо выполнить
команду
окно Watches является
активным окном, то можно добавить
выражение
просмотра, нажав
клавишу Ins. Отладчик открывает диалоговое
окно Add
Watch, запрашивающее
тип просматриваемого выражения. По
умолчанию выра-
жением считается
слово в позиции курсора в текущем окне
редактирования.
Просматриваемые
выражения, которые отслеживались ранее,
сохраняются в
списке протокола.
Последнее добавленное или модифицированное
просматри-
ваемое выражение
является текущим просматриваемым
выражением, которое
указывается выводимым
слева от него символом жирной левой
точки. Если
окно Watches активно,
можно удалить текущее выражение, нажав
клавишу Del
или Ctrl+Y. Чтобы
удалить все просматриваемые выражения,
необходимо вы-
полнить команду
Clear All локального меню активного окна
Watches. Чтобы
отредактировать
просматриваемое выражение, нужно
выполнить команду
Watches. Отладчик
открывает диалоговое окно Edit Watch,
аналогичное то-
му, которое
используется для добавления просматриваемого
выражения, ко-
торое позволяет
отредактировать текущее выражение.
Чтобы вычислить
выражение, необходимо выполнить
команду
диалоговое окно
Evaluate and Modify. По умолчанию слово в позиции
курсо-
ра в текущем окне
редактирования выводится подсвеченным
в поле
Expression. Можно
отредактировать это выражение, набрать
другое выраже-
ние или выбрать
вычисленное ранее выражение из списка
протокола.
Даже если не
установлены точки останова, можно выйти
в отладчик при
выполнении программы,
нажав клавиши Ctrl+Break. Отладчик находит
позицию
в исходном коде, где
прервалась программа. Затем, как и в
случае обычной
точки останова,
можно выполнить программу по шагам,
трассировать ее,
отследить или
вычислить выражения.
Иногда в ходе
отладки полезно узнать, как вы попали
в данную часть
кода. Окно Call Stack
показывает последовательность вызовов
процедур или
функций, которые
привели к текущему состоянию (глубиной
до 128 уровней).
Для вывода окна Call
Stack необходимо выполнить команду
Debug¦Call Stack
или нажать клавиши
Ctrl+F3.
Соседние файлы в папке 13_3xN
Что такое баги, ворнинги и исключения в программировании
Разбираемся, какие бывают типы ошибок в программировании и как с ними справляться.
vlada_maestro / shutterstock
Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Многим известно слово баг (англ. bug — жук), которым называют ошибки в программах. Однако баг — это не совсем ошибка, а скорее неожиданный результат работы. Также есть и другие термины: ворнинг, исключение, утечка.
В этой статье мы на примере C++ разберём, что же значат все эти слова и как эти проблемы влияют на эффективность программы.
Словом «ошибка» (англ. error) можно описать любую проблему, но чаще всего под ним подразумевают синтаксическую ошибку — некорректно написанный код, который даже не скомпилируется:
//В конце команды забыли поставить точку с запятой (;)
int =
Компилятор тут же скажет, что в коде ошибка и скорее всего не хватает запятой или точки с запятой.
Также существуют ворнинги (англ. warning — предупреждение). Они не являются ошибками, поэтому программа всё равно будет собрана. Вот пример:
Мы можем попросить компилятор показать нам все предупреждения с помощью флага -Wall:
Предупреждения не являются чем-то критичным, но могут иметь негативные последствия. Например, ваша программа будет использовать больше памяти, чем должна. Так как C++ нужен в том числе и для разработки высоконагруженных систем, этого допускать нельзя.
После восклицательного знака в треугольнике — количество предупреждений
Третий вид ошибок — ошибки сегментации (англ. segmentation fault, сокр. segfault, жарг. сегфолт). Они возникают, если программа пытается записать что-то в ячейку, недоступную для записи. Например:
//Создаём константный массив символов
* s = ;
//Если мы попытаемся перезаписать значение константы, компилятор выдаст ошибку
//Но с помощью указателей мы можем обойти её, поэтому программа успешно скомпилируется
//Однако во время работы она будет выдавать ошибку сегментации
* ( *) s = ;
Вот результат работы такого кода:
Мы выяснили, что баг — это не совсем ошибка, а скорее неожиданное поведение программы или результат такого поведения. Баги могут быть чем-то забавным или неприятным. Например, как в играх:
Но они могут привести и к более серьёзным последствиям. Если неправильно спроектировать работу многопоточного приложения, то потоки будут постоянно опережать друг друга. Например, сообщение об ошибке из одного потока может опоздать на миллисекунду, из-за чего второй поток подумает, что никакой ошибки не было, и продолжит работу.
Если ваш код приводит в действие какое-нибудь потенциально опасное устройство, то ценой такой ошибки может быть чья-нибудь жизнь. Такое случилось с кодом для аппарата лучевой терапии Therac-25 — как минимум два человека умерло и ещё больше пострадали из-за превышения дозы радиации.
Также во время работы программы могут возникать ситуации, которые мешают корректной работе программы. Например, если вы просите пользователя ввести число, а он вводит строку.
Конвертировать введённое значение не всегда возможно, поэтому функция, которая занимается преобразованием, «выбрасывает» исключение (англ. exception). Это специальное сообщение говорит о том, что что-то идёт не так.
Если разработчик не описывает логику работы программы при вы выбрасывании исключения, то программа аварийно закрывается. Подробнее мы рассказали об этом в статье про ввод и конвертацию в C++.
Одно из самых известных исключений — переполнение стека (англ. stack overflow). В честь него даже назвали сайт, на котором программисты ищут помощь в решении своих проблем.
Компилятор C++ при этом может выдать ошибку сегментации, а не сообщение о переполнении стека:
Вот аналогичный код на языке C#:
Однако сообщение в этот раз более конкретное:
В обоих случаях программа завершается, потому что не может дальше корректно работать.
Похожая ситуация — переполнение буфера (англ. buffer overflow). Она происходит, когда записываемое значение больше выделенной области в памяти.
//Пробуем записать в переменную типа int значение, которое превышает лимит
//Константа INT_MAX находится в библиотеке climits
a = INT_MAX + ;
Обратите внимание, что мы получили предупреждение об арифметическом переполнении (англ. integer overflow):
Тем не менее программа скомпилировалась. Если же такая ситуация возникнет во время вычислений, то мы можем не получить предупреждения.
Арифметическое переполнение стало причиной одной из самых дорогих аварий, произошедших из-за ошибки в коде. В 1996 году ракета-носитель «Ариан-5» взорвалась на 40-й секунде полёта — потери оценивают в 360–500 миллионов долларов.
К сожалению, вручную всё это заметить и исправить не получится. Однако существуют различные инструменты и технологии, которые могут помочь.
Один из таких инструментов — отладчик. Он помогает контролировать ход работы программы, чтобы отслеживать разные показатели.
Второй, более эффективный метод — unit-тесты. Они представляют из себя набор описанных ситуаций для каждого компонента программы с указанием ожидаемого поведения.
Например, у вас есть функция sum (int a, int b), которая возвращает сумму двух чисел. Вы можете написать unit-тесты, чтобы проверять следующие ситуации:
Если какой-то из этих тестов не пройден, вы узнаете об этом и сможете всё исправить. Это намного быстрее, чем проверять всё вручную.
Ошибок существует слишком много. При этом самые опасные тяжелее обнаружить, что только усугубляет ситуацию.
Научитесь: Профессия Разработчик на C++ с нуля
в будущем» и «когда-то в будущем». Заметим, что второй оператор может быть выражен с помощью первого и отрицания — то, что некоторое свойство когда-то будет выполнено, эквивалентно тому, что отрицание этого свойства не будет выполнено всегда. Свойства безопасности легко записываются в виде «всегда будет выполнено отрицание нежелательного свойства», а свойства живости — в виде «когда-то обязательно будет выполнено желаемое».
Проверяемая программа в классическом подходе моделируется при помощи конечного автомата. Проверка, выполняемая автоматически, состоит в том, что для всех достижимых при работе системы состояний этого автомата проверяется нужное свойство. Если оно оказывается выполненным, выдается сообщение об успешности проверки, если нет — выдается трасса, последовательность выполнения отдельных шагов программы, моделируемых переходами автомата, приводящая из начального состояния в такое, в котором нужное свойство нарушается. Эта трасса используется для анализа происходящего и исправления либо программы, либо модели, если ошибка находится в ней.
Основная проблема этого подхода — огромное, а часто и бесконечное, количество состояний в моделях, достаточно хорошо отражающих поведение реальных программ. Для борьбы с комбинаторным взрывом состояний применяются различные методы оптимизации представления автомата, выделения и поиска состояний, существенных для выполнения проверяемого свойства.
Ошибки в программах
Ошибками в ПО, вообще говоря, являются все возможные несоответствия между демонстрируемыми характеристиками его качества и сформулированными или подразумеваемыми требованиями и ожиданиями пользователей.
В англоязычной литературе используется несколько терминов, часто одинаково переводящихся как «ошибка» на русский язык.
— самое общее нарушение каких-либо требований или ожиданий, не обязательно проявляющееся вовне (к дефектам относятся нарушения стандартов кодирования, недостаточная гибкость системы и пр.).
— наблюдаемое нарушение требований, проявляющееся при каком-то реальном сценарии работы ПО. Это можно назвать проявлением ошибки.
— ошибка в коде программы, вызывающая нарушения требований при работе (failures), то место, которое надо исправить. Хотя это понятие используется довольно часто, оно, вообще говоря, не вполне четкое, поскольку для устранения нарушения можно исправить программу в нескольких местах. Что именно надо исправлять, зависит от дополнительных условий, выполнение которых мы хотим при этом обеспечить, хотя в некоторых ситуациях наложение дополнительных ограничений не устраняет неоднозначность.
— используется в двух смыслах.
Первый — это ошибка в ментальной модели программиста, ошибка в его рассуждениях о программе, которая заставляет его делать ошибки в коде (faults). Это, собственно, ошибка, которую сделал человек в своем понимании свойств программы.
Второй смысл — это некорректные значения данных (выходных или внутренних), которые возникают при ошибках в работе программы.
Эти понятия некоторым образом связаны с основными источниками ошибок. Поскольку при разработке программ необходимо сначала понять задачу, затем придумать ее решение и закодировать его в виде программы, то, соответственно, основных источников ошибок три.
Неправильное понимание задач.
Очень часто люди не понимают, что им пытаются сказать другие. Так же и разработчики ПО не всегда понимают, что именно нужно сделать. Другим источником непонимания служит отсутствие его у самих пользователей и заказчиков — достаточно часто они могут просить сделать несколько не то, что им действительно нужно.
Для предотвращения неправильного понимания задач программной системы служит анализ предметной области.
Неправильное решение задач.
Зачастую, даже правильно поняв, что именно нужно сделать, разработчики выбирают неправильный подход к тому, как это делать. Выбираемые решения могут обеспечивать лишь некоторые из требуемых свойств, они могут хорошо подходить для данной задачи в теории, но плохо работать на практике, в конкретных обстоятельствах, в которых должно будет работать ПО.
Помочь в выборе правильного решения может сопоставление альтернативных решений и тщательный анализ их на предмет соответствия всем требованиям, поддержание постоянной связи с пользователями и заказчиками, предоставление им необходимой информации о выбранных решениях, демонстрация прототипов, анализ пригодности выбираемых решений для работы в том контексте, в котором они будут использоваться.
Неправильный перенос решений в код.
Имея правильное решение правильно понятой задачи, люди, тем не менее, способны сделать достаточно много ошибок при воплощении этих решений. Корректному представлению решений в коде могут помешать как обычные опечатки, так и забывчивость программиста или его нежелание отказаться от привычных приемов, которые не дают возможности аккуратно записать принятое решение.
С ошибками такого рода можно справиться при помощи инспектирования кода, взаимного контроля, при котором разработчики внимательно читают код друг друга, опережающей разработки модульных тестов и тестирования.
отдать спровоцировавшей его ошибке в системе управления электростанцией. Широко известны также примеры ошибок в системах управления космическими аппаратами, приведшие к их потере или разрушению. Менее известны, но не менее трагичны, ошибки в ПО, управлявшем медицинским и военным оборудованием, некоторые из которых привели к гибели людей.
Стоит отметить, что в большинстве примеров ошибок, имевших тяжелые последствия, нельзя однозначно приписать всю вину за случившееся ровно одному недочету, одному месту в коде. Ошибки очень часто «охотятся стаями». К тяжелым последствиям чаще всего приводят ошибки системного характера, затрагивающие многие аспекты и элементы системы в целом. Это значит, что при анализе такого происшествия обычно выявляется множество частных ошибок, нарушений действующих правил, недочетов в инструкциях и требованиях, которые совместно привели к создавшейся ситуации.
Даже если ограничиться рассмотрением только ПО, часто одно проявление ошибки (failure) может выявить несколько дефектов, находящихся в разных местах. Такие ошибки возникают, как показывает практика, в тех ситуациях, поведение в рамках которых неоднозначно или недостаточно четко определяется требованиями (а иногда и вообще никак не определяется — признак неполного понимания задачи). Поэтому разработчики различных модулей ПО имеют возможность по-разному интерпретировать те части требований, которые относятся непосредственно к их модулям, а также иметь разные мнения по поводу области ответственности каждого из взаимодействующих модулей в данной ситуации. Если различия в их понимании не выявляются достаточно рано, при разработке системы, то становятся «минами замедленного действия» в ее коде.
была способна летать при более высоких значениях ускорений и скоростей, чем это могла делать ракета предыдущей серии, Ариан-4.
Однако большое количество процедур контроля и управления движением по траектории в
коде управляющей системы было унаследовано от Ариан-4. Большинство таких процедур не были специально проверены на работоспособность в новой ситуации, как в силу большого размера кода, который надо было проанализировать, так и потому, что этот код раньше не вызывал проблем, а соотнести его со специфическими характеристиками полета ракет вовремя никто не сумел.
В одной из таких процедур производилась обработка горизонтальной скорости ракеты. При выходе этой величины за границы, допустимые для Ариан-4, создавалась исключительная ситуация переполнения.
Надо отметить, что обработка нескольких достаточно однородных величин производилась по-разному — семь переменных могли вызвать исключительную ситуацию данного вида, обработка четырех из них была защищена от этого, а три оставшихся, включая горизонтальную скорость, оставлены без защиты. Аргументом для этого послужило выдвинутое при разработке требование поддерживать загрузку процессора не выше 80%. «Нагружающие» процессор защитные действия для этих переменных не были использованы, поскольку предполагалось, что эти величины будут находиться в нужных пределах в силу физических ограничений на параметры движения ракеты. Обоснований для поддержки именно такой загрузки процессора и того, что отсутствие обработки переполнения выбранных величин будет способствовать этому, найдено не было.
Когда такая ситуация действительно случилась, т.е. горизонтальная скорость ракеты превысила определенное значение, она не была обработана соответствующим образом, и в результате ею вынужден был заняться модуль, обеспечивающим отказоустойчивость программной системы в целом.
Этот модуль, в силу отсутствия у него какой-либо возможности обрабатывать такие ошибки специальным образом, применил обычный прием — остановил процесс, в котором возникла ошибка, и запустил другой процесс с теми же исходными данными. Как легко догадаться, эта же ошибка повторилась и во втором процессе.
Не в силах получить какие-либо осмысленные данные о текущем состоянии полета, система управления использовала ранее полученные, которые уже не соответствовали действительности. При этом были ошибочно включены боковые двигатели «для корректировки траектории», ракета начала болтаться, угол между нею и траекторией движения стал увеличиваться и достиг 20 градусов. В результате она стала испытывать чрезмерные аэродинамические нагрузки и была автоматически уничтожена.
Литература к Лекции 5
М.: Мир, 1991.
Отладка
программы является итеративным процессом
обнаружения и исправления ошибок и
обычно требует последовательного
выполнения четырех этапов:
Некоторые
ошибки проявляются после первого же
запуска программы на выполнение, и для
их обнаружения не надо прибегать ни к
каким специальным средствам. Некоторые
ошибки проявляются в случайные моменты
работы программы. С такими ошибками
справиться труднее всего – зафиксировать
условия возникновения ошибки, понять
причину ошибки и устранить ее. С целью
обнаружения подобных ошибок осуществляется
тестирование
программы–
ее выполнение для специально подобранных
представительных контрольных примеров
– тестов. Тест
– это такой
набор исходных данных, для которого
вручную или другим способом просчитаны
промежуточные и конечные результаты и
который может быть использован для
получения информации о надежности
проверяемой программы.
Тестирование
программы должно включать в себя прогон
трех видов контрольных примеров:
нормальных ситуаций, граничных ситуаций
и случаев неправильных данных.
Нормальные
случаи –
это примеры с правильными входными
данными. Если программа не работает в
подобных случаях, она требует серьезных
переделок. Граничные контрольные примеры
помогают установить, способна ли
программа нормально реагировать на
особые случаи во входных данных. Граничные
примеры
представляют собой данные, которые,
будучи математически корректными,
приводят программу к необходимости
работать особым образом. Неправильными
являются
такие данные,
которые расположены вне допустимого
диапазона. Примеры с неправильными
данными должны быть обработаны
соответствующим образом, поскольку в
повседневной эксплуатации программе
придется иметь дело и с неверными
входными данными.
После того как
ошибка обнаружена, необходимо найти в
исходном тексте программы то место, в
котором она возникала, – локализовать
ошибку. Можно
использовать ряд различных методов
отладки, позволяющих обнаружить
расположение ошибки; выбор существенно
зависит от особенностей ситуации.
Большинство программистов начинают с
неформального метода, известного под
названием проверка
за столом. Используя
контрольный пример, который привел к
ошибке в программе, программист
аналитически трассирует листинг
программы в надежде локализовать ошибку.
Проверка за столом – это хороший метод,
поскольку он заставляет программиста
детально понять работу программы. Если
применение метода проверки за столом
оказалось бесплодным, нужно использовать
специальные методы и способы отладки,
позволяющие наблюдать за передачей
управления в программе и за изменением
значений наиболее важных переменных.
Полученная отладочная информация
позволит локализовать подозрительные
ситуации, провести анализ и выявить
причину ошибки, устранить ее, а затем
продолжить поиск других ошибок.
В общем случае
ошибки могут возникать на любом этапе
разработки программы, причина ошибок
может быть связана с недопониманием
сути задачи, недостатками проектирования
алгоритма, неправильным использованием
языковых средств. При выполнении
программы ошибки разного типа проявляют
себя различным образом, и их принято
подразделять на следующие группы:
Синтаксические
ошибки –
это ошибки, проявляющиеся на этапе
компиляции программы и возникающие в
связи с нарушением синтаксических
правил написания предложений используемого
языка программирования (к таким ошибкам
относятся
пропущенные точки с запятой, ссылки на
неописанные переменные, присваивание
переменной значений неверного типа и
т. д.). Если компилятор встречает в
тексте программы оператор или описание,
которые
он не может интерпретировать, то он
позиционирует курсор на место обнаруженной
ошибки и
в строку статуса выводит сообщение,
содержащее номер ошибки и ее краткое
описание.
Семантические
ошибки – это
ошибки, проявляющиеся на этапе
выполнения программы при ее попытке
вычислить недопустимые значения
параметров или выполнить недопустимые
действия. Причина возникновения ошибок
данного типа связана с нарушением
семантических правил написания программ
(примером являются ситуации
попытки
открыть несуществующий файл или выполнить
деление на нуль). Если программа
обнаруживает ошибку такого типа, то она
завершает свое выполнение
и
выводит
соответствующее сообщение в окне Build,
содержащее номер строки с ошибкой и ее
возможный характер. Список сообщений
можно просмотреть с помощью команды
меню View/Debug
Windows/Event
Log.
При выполнении программы из среды Delphi
автоматически выбирается соответствующий
исходный файл и в нем находится
местоположение ошибки. Если же программа
выполнялась вне среды и в ней появилась
ошибка данного типа, то необходимо
запустить
среду и найти вызвавший ошибку оператор.
Логические
(смысловые) ошибки – самые
сложные и трудноуловимые, связанные с
неправильным применением тех или иных
алгоритмических конструкций. Эти ошибки
при выполнении программы могут проявиться
явно (выдано сообщение об ошибке, нет
результата или выдан неверный результат,
программа «зацикливается»), но чаще
они проявляют себя только при определенных
сочетаниях параметров или вообще не
вызывают нарушения работы программы,
которая в этом случае выдает правдоподобные,
но неверные результаты.
Ошибки первого
типа легко выявляются самим компилятором.
Обычно устранение синтаксических ошибок
не вызывает особых трудностей. Более
сложно выявить ошибки второго и особенно
третьего типа. Для обнаружения и
устранения ошибок второго и третьего
типа обычно применяют специальные
способы и средства отладки программ.
Выявлению ошибок второго типа часто
помогает использование контролирующих
режимов компиляции с проверкой допустимых
значений тех или иных параметров (границ
индексов элементов массивов, значений
переменных типа диапазона, ситуаций
переполнения, ошибок ввода-вывода).
Устанавливаются эти режимы с помощью
ключей
компилятора,
задаваемых либо в программе, либо в меню
Project/Options/Compiler
среды
Delphi, либо
в
меню
Options/Compiler Турбо-среды.
Соседние файлы в папке крутые билеты по инфе