Многие баги, на первый взгляд, зарыты в коде. Но что, если код — это просто зеркало нашего мышления, а баг — результат когнитивной ошибки, которую мы даже не осознали? Эта статья — ретроспектива инженерных провалов, где причина — не баг в логике, а баг в голове. Разбираемся, почему мы думаем криво, как это ломает код, и можно ли «дебажить» собственное мышление.

Введение: баг, которого не было
Были ли у вас баги, которые исчезли, когда вы начали их кому-то объяснять? Или, наоборот, баги, которые вы не видели часами, пока не посмотрели на них с совершенно другой стороны? Я хочу поговорить не о коде. И даже не о компиляторах, алгоритмах или архитектуре. А о той странной штуке, в которой всё это происходит — о мозге разработчика.
Большинство багов начинается не с if
, а с «я точно всё понял». Не с null
, а с «не может такого быть». И не с segfault
, а с «я же так делал сто раз». То есть — с багов мышления.
1. Раздел первый: баг как симптом мышления
Код — это манифестация наших мыслей. Баг — это симптом ошибочной логики, прошедшей через компилятор. Компиляторы не догадываются, что вы имели в виду. Они работают строго по инструкции. А инструкция — это и есть следствие вашего мышления.
Пример
# Я уверен, что переменная всегда инициализирована до вызова
def calculate_discount(price):
if has_discount:
return price * 0.9
return price
Ошибка очевидна? Только если вы привыкли думать о переменных как о сущностях, которым надо дать значение до использования. А если вы переучились с языка, где has_discount
глобальная или имеет дефолт — вы можете и не заметить.
Это не ошибка кода. Это ошибка в предположениях.
3. Мышление — не поток, а сеть
Мы любим представлять мышление как поток: получил задачу → подумал → написал код → протестировал. Но реальность — это сеть взаимосвязей, ожиданий, эвристик, привычек и когнитивных искажений.
Мы редко задумываемся, что значат слова «всё работает». Работает как? В каких условиях? А почему ты считаешь, что оно работает? Потому что прошёл тест? А тест написан в том же багнутом контексте мышления.
4. Когнитивные искажения: чем багат наш мозг
Некоторые классические баги в коде — это просто отражения стандартных когнитивных искажений:
Иллюзия прозрачности — «ясно же, как работает этот метод»;
Предвзятость подтверждения — мы читаем код, чтобы доказать, что он работает, а не искать, где он не работает;
Эффект Даннинга-Крюгера — чем меньше мы понимаем, тем увереннее пишем код;
Слепое пятно предвзятости — «ну я-то объективен».
Каждое из этих искажений находит свой способ сломать ваш прод.
5. Язык как ловушка мышления
Языки программирования задают каркас мышления. Например:
let isAdmin = false;
if (isAdmin = true) {
// always runs
}
JavaScript позволяет такую конструкцию, потому что =
возвращает значение. Вы думаете, что проверяете, а на самом деле присваиваете. И это баг не в языке. Это баг в том, как мозг ожидает работу конструкции. Язык формирует эти ожидания. А потом ломает их.
6. Архитектура «невидимого бага»
Самые сложные баги — это не сломанный for
и не неправильный index
. Это баги, встроенные в архитектуру, в саму модель системы. Они сидят на фундаментальном предположении. На чём-то вроде:
«Событие не может произойти дважды подряд»;
«Состояние X всегда следует за состоянием Y»;
«Пользователь не нажмёт эту кнопку три раза за секунду».
Ни одна из этих предпосылок не проверена. Все они — просто убеждения разработчика. То есть баги мышления.
7. Диалог как способ дебага головы
Когда объясняешь кому-то свой баг, часто ловишь себя: «блин, а что если тут…» — и ты находишь ошибку. Это называется «эффект резиновой уточки». Но он работает не из-за уточки, а потому, что ты пересобираешь свою модель мышления, когда вербализуешь её.
8. Как баги проходят ревью и тесты
Классика: «пропустили баг на ревью». Почему?
Читали глазами, а не умом;
Имеют ту же ментальную модель, что и автор кода;
Проверяют «соответствие стандартам», а не «подразумеваемую логику».
Ревью, в сущности, не проверка кода. Это попытка сравнить мышление автора и ревьюера. Если оно одинаково кривое — баг пройдет.
9. Можно ли тестировать мышление?
Да. Это называется «деструктивное мышление». В программировании мы тестируем код. А вот в инженерии высокого уровня тестируют гипотезы и предпосылки.
Пример:
# Python: проверим предположение о порядке вызовов
calls = []
def a():
calls.append('a')
def b():
calls.append('b')
def c():
a()
b()
return calls
print(c()) # ['a', 'b']
Теперь предположим, что a()
вызывает b()
внутри. Если ты об этом не знаешь, поведение сломается. Проблема не в коде. Проблема в том, что ты думал иначе.
10. Как не попасть в мышленческий баг
Никак. Мы все люди. Но можно:
Обсуждать логику с людьми, которые мыслят по-другому;
Вводить практики тестирования предпосылок;
Добавлять в код комментарии не только «что делает», но и «почему так»;
Развивать паранойю: «а что если всё не так, как я думаю?»;
Писать код, будто тебе будет его объяснять враг на суде.
11. (Немного личного) Почему я пишу об этом
Потому что у меня был баг. Странный, непонятный, абсолютно неуловимый. И когда я его нашёл — я понял, что всё это время думал не так. Не кодил, не проектировал, а просто думал криво. Я уверен, у каждого был такой баг.
И может быть, если мы научимся замечать свои баги мышления, багов в коде станет чуть меньше.
Если ты дочитал до этого момента — возможно, у нас с тобой была похожая архитектура мышления. Или баг в ней. В любом случае, спасибо, что заглянул в глубину кода, где баги начинают свой путь — в нашей голове))