Логическая ошибка — это дефект, при котором программа запускается и завершается без сбоя, но выдает неверный результат, потому что сам алгоритм описан неправильно. В отличие от синтаксической ошибки (код не компилируется) и ошибки времени выполнения (программа падает с исключением), логическая ошибка не дает ни красной строки в редакторе, ни трейсбека: интерпретатор считает код корректным. Ниже — основные виды логических ошибок в программировании и примеры с разбором: неверные границы диапазона, ошибки в условиях, приоритет операций, сравнение вещественных чисел, путаница копии и ссылки. Каждый пример дан тройкой: неверный код, фактический результат, исправление.
Содержание
- Виды ошибок в программировании: где место логической ошибки
- Off-by-one: ошибка на единицу в границах
- Неверное логическое условие: and вместо or
- Приоритет операций: где не хватило скобок
- Сравнение вещественных чисел: почему 0.1 + 0.2 не равно 0.3
- Копия против ссылки: изменили одно, поменялось все
- Изменение коллекции во время перебора
- Как ловить логические ошибки
- Выводы
- Где применяется / связь с практикой
- FAQ
Виды ошибок в программировании: где место логической ошибки
Прежде чем разбирать примеры, стоит развести три категории ошибок. Они проявляются на разных этапах и ловятся разными средствами.
| Вид ошибки | Когда проявляется | Кто ловит | Пример |
|---|---|---|---|
| Синтаксическая | до запуска, при разборе кода | компилятор / интерпретатор | пропущено двоеточие, скобка |
| Времени выполнения (runtime) | во время работы | среда выполнения, исключение | деление на ноль, обращение за границу списка |
| Логическая | во время работы, молча | только тесты и человек | неверная формула, перепутанное условие |
Ключевое отличие логической ошибки: программа не жалуется. Она честно делает то, что вы написали, — просто вы написали не то, что задумали. Поэтому такую ошибку не найти по сообщению об ошибке, ее ищут сравнением ожидаемого и фактического результата. О синтаксических ошибках подробнее в отдельном разборе (ссылка в конце), а здесь сосредоточимся именно на логических.
Off-by-one: ошибка на единицу в границах
Самая частая логическая ошибка — неверная граница диапазона или индекса. В Python функция range(a, b) не включает правую границу b, и об этом легко забыть.
# Нужно сложить числа от 1 до 10 включительно
total = 0
for i in range(1, 10):
total += i
print(total)
Фактический результат: 45. Ожидали 55, но range(1, 10) перебирает числа с 1 по 9, десятка не попала в цикл.
# Исправление: правая граница на единицу больше
total = 0
for i in range(1, 11):
total += i
print(total) # 55
Тот же класс ошибок — обращение по индексу len(arr) (последний допустимый индекс len(arr) - 1), проверка <= вместо < в границе цикла. Правило простое: всегда проверяйте крайние значения диапазона руками, на первом и последнем элементе.
Неверное логическое условие: and вместо or
Условие, которое читается «правильно» по-русски, легко записать неверно в коде. Классика — перепутать and и or.
# Пропускаем только пользователей от 18 до 65 лет
age = 70
if age > 18 or age < 65:
print("доступ разрешен")
Фактический результат: строка печатается даже для 70 лет. Любое число удовлетворяет хотя бы одному из условий (70 > 18 истинно), а or требует только одного истинного операнда, поэтому проверка всегда проходит.
# Исправление: оба условия должны выполняться одновременно
age = 70
if age > 18 and age < 65:
print("доступ разрешен") # для 70 не печатается
Сюда же относятся инвертированные условия (if not is_valid там, где нужно if is_valid) и сравнение не с той переменной. Такие места стоит покрывать тестом хотя бы на две точки: внутри диапазона и за его пределом.
Приоритет операций: где не хватило скобок
Языки вычисляют выражения по приоритету операторов, а не слева направо. Умножение и деление выполняются раньше сложения, и без скобок формула считает не то.
# Среднее двух чисел
a, b = 4, 6
avg = a + b / 2
print(avg)
Фактический результат: 7.0. Сначала выполнилось b / 2 (это 3.0), потом прибавилось a. Ожидали 5.0.
# Исправление: скобки задают нужный порядок
a, b = 4, 6
avg = (a + b) / 2
print(avg) # 5.0
Тот же подвох в логических выражениях: and связывает сильнее, чем or, поэтому a or b and c — это a or (b and c). Если сомневаетесь в приоритете, ставьте скобки: они ничего не ломают, зато делают намерение явным.
Сравнение вещественных чисел: почему 0.1 + 0.2 не равно 0.3
Дробные числа хранятся в двоичном формате с плавающей точкой (стандарт IEEE 754), и многие десятичные дроби в нем не представимы точно. Прямое сравнение таких чисел на равенство — логическая ошибка.
x = 0.1 + 0.2
print(x == 0.3)
print(x)
Фактический результат: False и 0.30000000000000004. Сумма получилась чуть больше 0.3 из-за погрешности представления, и строгое равенство не выполнилось.
# Исправление: сравнивать с допуском
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
Функция math.isclose (доступна с Python 3.5) сравнивает числа с относительным допуском. Для денежных расчетов вместо float берут тип Decimal, где 0.1 + 0.2 дает ровно 0.3. Правило: вещественные числа не сравнивают через ==.
Копия против ссылки: изменили одно, поменялось все
В Python переменная хранит не сам объект, а ссылку на него. Если присвоить один список двум именам, они указывают на одни и те же данные, и правка через одно имя видна через другое.
# Хотим две независимые строки таблицы
row = [0, 0, 0]
grid = [row, row]
grid[0][1] = 5
print(grid)
Фактический результат: [[0, 5, 0], [0, 5, 0]]. Обе «строки» — это один и тот же список, поэтому пятерка появилась в обеих.
# Исправление: создать отдельный список для каждой строки
grid = [[0, 0, 0] for _ in range(2)]
grid[0][1] = 5
print(grid) # [[0, 5, 0], [0, 0, 0]]
Когда нужна именно независимая копия вложенных структур, применяют copy.deepcopy. Поверхностное копирование (list(x) или срез x[:]) дублирует только верхний уровень, а вложенные списки останутся общими — это отдельная ловушка того же рода.
Изменение коллекции во время перебора
Еще одна тихая ошибка — удалять или добавлять элементы списка прямо в цикле по этому же списку. Индексы сдвигаются на ходу, и часть элементов пропускается.
nums = [2, 4, 6]
# Хотим удалить все четные, ожидаем пустой список
for n in nums:
if n % 2 == 0:
nums.remove(n)
print(nums)
Фактический результат: [4]. После удаления двойки список стал короче, перебор «перескочил» четверку и не проверил ее.
# Исправление: строим новый список, а старый не трогаем
nums = [2, 4, 6]
nums = [n for n in nums if n % 2 != 0]
print(nums) # []
Можно перебирать копию (for n in nums[:]), но генератор списка обычно чище и быстрее. Общее правило: не меняйте коллекцию, по которой прямо сейчас идет цикл.
Как ловить логические ошибки
Раз компилятор молчит, нужен внешний источник истины — ожидаемый результат. Ниже сводка типовых приемов и того, какие ошибки они помогают поймать.
| Прием | Что дает | Против каких ошибок |
|---|---|---|
| Юнит-тесты | сравнение факта с эталоном | все виды логических |
| Отладчик, точки останова | пошаговый просмотр значений | неверные условия, границы |
print и логирование |
видно фактические данные | формулы, приоритет операций |
| Проверка граничных значений | первый и последний элемент | off-by-one |
| Ревью кода коллегой | свежий взгляд на логику | перепутанные условия, знаки |
| Аннотации типов и линтер | часть ошибок до запуска | типы, очевидные опечатки |
Ни один прием не заменяет остальные. Тесты фиксируют ожидаемое поведение и не дают ошибке вернуться, отладчик показывает, где значение разошлось с ожиданием, а ревью ловит то, что автор просто не заметил.
Выводы
- Логическая ошибка не вызывает ни ошибки компиляции, ни исключения: программа отрабатывает и выдает неверный результат, поэтому ловится только сравнением факта с ожиданием.
- Самые частые виды — off-by-one в границах, перепутанные
and/or, нехватка скобок из-за приоритета операций, сравнение float через==, путаница копии и ссылки, изменение коллекции во время перебора. - Границы диапазонов и циклов всегда проверяются на крайних значениях: первый и последний элемент, пустой вход.
- Вещественные числа сравнивают с допуском (
math.isclose) или считают вDecimal, а не через строгое равенство. - В Python присваивание копирует ссылку, а не данные: для независимых структур нужен явный новый объект или
deepcopy. - Юнит-тесты, отладчик, проверка границ и ревью дополняют друг друга; надежнее всего они работают вместе.
Где применяется / связь с практикой
Освойте тему на практике
Логические ошибки — повседневная часть работы любого разработчика, и умение их находить важнее, чем знание синтаксиса: язык можно подсмотреть, а вот отличить «код работает» от «код дает верный результат» приходится самому. Начинающим удобнее всего тренировать этот навык на Python: понятный синтаксис не отвлекает, и внимание остается на самой логике. Разобраться с основами языка и приемами отладки помогает курс Python Basic, где разбор ошибок и тестирование идут вместе с практикой. Посмотреть формат занятий и задать вопрос преподавателю можно на бесплатных вебинарах — там же разбирают типовые ошибки новичков.
Смежные темы: Python и синтаксические ошибки, Какими бывают ошибки в приложениях, Ошибки в PHP: виды и логирование.
FAQ
Почему логическую ошибку найти труднее, чем синтаксическую?
Синтаксическую сразу подсвечивает редактор или интерпретатор, а логическая не дает никакого сигнала: программа выполняется до конца и просто возвращает неправильное значение. Ее замечают только по расхождению с ожидаемым результатом.
Какие инструменты помогают находить логические ошибки?
Юнит-тесты фиксируют ожидаемый ответ, отладчик показывает значения переменных по шагам, логирование выводит фактические данные, а линтер и аннотации типов отсекают часть ошибок еще до запуска. Ревью кода коллегой добавляет свежий взгляд.
Считается ли логической ошибкой неверный результат из-за плохих входных данных?
Нет, если сам алгоритм верен, а проблема во входе, — это вопрос валидации данных, а не логики программы. Но отсутствие проверки входных данных там, где она нужна, — уже логическая ошибка в самой программе.



