Виды ошибок в программировании: баги, дефекты и сбои

Виды ошибок в программировании: баги, дефекты и сбои Полезное

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

Баг, дефект, ошибка, сбой — в чем разница

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

  • Ошибка (error, mistake) — действие человека, приведшее к неверному результату. Например, разработчик перепутал знак в условии или неправильно понял требование.
  • Дефект (defect) — изъян в коде или в продукте, который появился из-за ошибки. Баг — это разговорный синоним дефекта.
  • Сбой (отказ, failure) — проявление дефекта во время работы: программа повела себя не так, как ожидал пользователь.

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

Как классифицируют ошибки: основные виды

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

Синтаксические

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

Ошибки компиляции

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

Логические

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

Ошибки времени выполнения (runtime)

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

Интеграционные

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

Ресурсные ошибки (переполнение буфера, утечки памяти) и арифметические (переполнение типа, потеря точности) на практике чаще относят к частным случаям runtime-ошибок.

Серьезность (severity) и приоритет (priority)

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

  • Severity (серьезность) — насколько сильно дефект влияет на работу продукта. Это техническая, объективная характеристика. Типичные уровни: blocker (работать дальше нельзя), critical, major, minor, trivial.
  • Priority (приоритет) — насколько срочно баг нужно чинить. Это уже решение команды с учетом бизнеса и планов релиза: high, medium, low.

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

Жизненный цикл бага

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

  1. New — баг заведен тестировщиком, но еще не разобран.
  2. Assigned / In progress — назначен на разработчика, идет работа.
  3. Fixed — разработчик считает, что исправил, и передал на проверку.
  4. Retest / Verified — тестировщик перепроверяет исправление.
  5. Closed — баг подтвержденно исправлен, либо Reopened, если проблема осталась.

Есть и «тупиковые» статусы: Rejected (не признан багом), Duplicate (дубликат уже заведенного), Deferred (отложен на будущее). Понимание этого цикла помогает разработчику и тестировщику говорить на одном языке.

Примеры кода: неверно, результат, исправление

Разберу три частых случая на Python. Для каждого — неверный код, его фактический результат и исправленный вариант.

1. Синтаксическая ошибка

Неверно (пропущено двоеточие после условия):

x = 10
if x > 5
    print("больше пяти")

Результат — код даже не запустится:

  File "example.py", line 2
    if x > 5
            ^
SyntaxError: expected ':'

Исправление:

x = 10
if x > 5:
    print("больше пяти")   # больше пяти

2. Логическая ошибка

Неверно — хотели среднее, но взяли целочисленное деление, и дробная часть потерялась:

total = 7
people = 2
share = total // people
print(share)   # 3   (а ожидали 3.5)

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

Исправление:

total = 7
people = 2
share = total / people
print(share)   # 3.5

3. Ошибка времени выполнения (runtime)

Неверно — функция падает на пустом списке из-за деления на ноль:

def average(nums):
    return sum(nums) / len(nums)

print(average([]))   # ZeroDivisionError: division by zero

Синтаксис и логика для непустого списка верны, но на граничном значении (пустой ввод) программа аварийно завершается. Исправление — проверить условие заранее:

def average(nums):
    if not nums:
        return 0
    return sum(nums) / len(nums)

print(average([]))   # 0

Таблица: где искать каждый вид ошибки

Тип ошибки Когда проявляется Как ловят
Синтаксическая до запуска, при разборе кода компилятор или интерпретатор, линтер, подсветка в редакторе
Компиляции на этапе сборки в машинный или байт-код компилятор, сообщения сборки
Логическая во время работы, результат неверный тесты, ревью кода, отладчик, сверка с ожиданием
Времени выполнения (runtime) во время выполнения при определенных данных обработка исключений, логи, мониторинг, тесты граничных значений
Интеграционная при взаимодействии модулей, сервисов, API интеграционные и контрактные тесты, тестовые стенды

Выводы

  • Баг — это дефект в коде или поведении программы; он не всегда виден, пока не сложатся условия для сбоя.
  • Ошибка, дефект (баг) и сбой — разные звенья одной цепочки: ошибка человека порождает дефект, а дефект проявляется как сбой.
  • По моменту проявления ошибки делятся на синтаксические, компиляции, логические, времени выполнения и интеграционные. Компилятор ловит первые два вида, но логику и runtime не проверяет.
  • Severity и priority — независимые шкалы: серьезный технически баг может иметь низкий приоритет, а мелкий — высокий.
  • Самые дорогие в отладке — логические и runtime-ошибки, потому что код при них выглядит рабочим; ловят их тестами и отладчиком, а не компилятором.
  • Чем раньше найден баг (на разработке или тестировании, а не после релиза), тем дешевле и быстрее его исправить.

Где применяется / связь с практикой

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

Если хочется разобраться в тестировании системно — от классификации багов до работы с трекерами и автотестами, посмотрите курс QA-инженер по автоматизации на JavaScript. Чтобы сначала присмотреться к профессии без обязательств, удобно сходить на бесплатные вебинары — там разбирают реальные задачи и отвечают на вопросы вживую.

Смежные темы: Тестирование ПО или как стать тестировщиком, Чем занимается QA-инженер?, Топ 7 мифов в работе QA-тестировщика.

FAQ

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

Можно ли выпускать продукт с известными багами?
Да, так делают часто: баги с низкой severity и низким приоритетом заносят в список известных проблем (known issues) и чинят в следующих версиях. Блокеры и критические дефекты релиз обычно останавливают.

Все ли ошибки находит компилятор?
Нет. Компилятор и интерпретатор ловят синтаксические ошибки и ошибки типов, но логические и runtime-ошибки для него невидимы — код формально корректен. Их выявляют тестами, ревью и отладкой.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)