Отладка. Step-by-step к эффективному выявлению ошибок

Каждый разработчик сталкивается с необходимостью исправления дефектов/багов/ошибок в разрабатываемых программах. И к сожалению, согласно результатам анализа, приведенным в книге Стива Макконнела «Совершенный код», статистика показывает, что мы затрачиваем на это довольно много своего рабочего времени. Даже стопроцентное покрытие тестами кода не дает гарантии отсутствия ошибок. Сократив время отладки, исправления и предупредив возникновение новых дефектов, мы сохраним время на то, что нам нравится больше всего, — на решение сложных задач. К тому же это существенно уменьшит затраты на разработку продукта.

Я много взаимодействую с разработчиками в команде: менторинг, консультирование, обучение, парное программирование, код-ревью. В процессе становится понятно: ошибки допускают все — от новичка, только ступившего на карьерную тропу, до матерого разраба с многолетним опытом.

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

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

Причины возникновения ошибок

Обычно возникновение ошибок связано с написанием или изменением кода. Стоит понимать, что 99% ошибок — это ответственность разработчика. А вот с причинами их возникновения будем разбираться дальше.

Позволю себе выделить такие категории причин (по приоритету сложности обнаружения):

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

Рассмотрим каждую категорию на примерах. Язык программирования практически не имеет значения. Примеры будут на JavaScript, однако такой же принцип применим и к другим языкам.

Опечатки

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

Кейс из жизни. Около часа не могли понять, почему падает тест, проверяющий в поле значение Сontact. При проверке кода теста у себя (с включенным spellchecker) была выявлена кириллическая С.


Анализаторы кода (Roslyn, ReSharper, SonarQube, ESLint и другие), code review и парное программирование помогут избавиться от этих ошибок.

Непонимание логики

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

  1. Найдите автора кода. Разузнайте детали, задавайте много вопросов, чтобы убедиться, что поняли все верно. Если автор вы или его уже нет, идем дальше.
  2. Обратитесь к тестам. Хорошие тесты доступно описывают поведение кода.
  3. Расспросите QA, владельца продукта, аналитика, PM (или того, кто у вас занимает роль хранителя знаний о поведении этого продукта).

Незнание тонкостей языка разработки

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

Для входного параметра value = 10 условие тоже будет выполняться. Нужно добавить строгое сравнение ===.

Операция + для чисел выполняет сложение, а если один из параметров представлен строковым значением, то конкатенацию:

Свойство count было изменено по ссылке.
Кейс из жизни. При связывании контекста через bind аргументы в метод приходят в таком порядке: сначала параметры при связывании, потом параметры вызова. На это не обратили внимания при разработке, в итоге метод работал лишь по счастливой случайности при удачном порядке входных аргументов.
Валидаторы кода и статические анализаторы укажут на эти проблемы.

Невнимательность

К этой категории можно отнести следующие ошибки:

  • неучитывание граничного значения, к примеру < вместо <= (о граничных значениях и эквивалентных классах можно почитать здесь);
  • вызов метода с другой перегрузкой;
  • неверный порядок операций, например забыли () для выражения 1 + 2 * 3;
  • использование не того свойства/параметра.

Кейс из жизни. Был цикл динамического наполнения коллекции для combo box на 10 элементов. Позже первым элементом добавили статическое значение, не изменив цикл. В итоге последний элемент коллекции не отображался.

С такими ошибками хорошо справляется unit-тестирование.

Ложные тесты

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

К примеру, этот тест будет проходить даже в том случае, если у currentUser не будет заполнено свойство Id.

Писать более «правдивые» тесты поможет подход TDD. Рекомендую к прочтению книгу адепта TDD Roy Osherove.

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

В случае если мы вызываем методы, которые могут генерировать исключения, без какой-либо обработки, то это уже является ошибкой на нашей стороне. Нужно уметь обезопасить свой код от внешнего воздействия: проверять входные параметры (утверждения, граничные проверки, проверки типов), обрабатывать исключения, применять паттерн Null-object.

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

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

Паттерн Null-object сокращает количество проверок типа:

Он может быть реализован таким образом:

Копирование чужих ошибок

Копирование чужого кода может сократить время разработки в два раза и вчетверо увеличить время отладки. В интернете очень много готовых решений на шаблонные/простые задачи. И нередко возникает искушение «позаимствовать» этот код для своих нужд. Однако помните: в системе контроля версий будет ваше имя, а не имя автора первоначального кода.

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

Предотвращение багов

Лучшая борьба с багами — это предотвращение их на этапе разработки. Для этого существует множество практик, методологий, инструментов. Парное программирование + TDD + анализаторы кода = стена, уверенно защищающая от «ходячих» багов. Вот некоторые из подходов:

Code review

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

Парное программирование

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

Тестирование

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

Ниже приведены unit-тесты, которые покажут ошибки в малейшем изменении поведения.

TDD

Хорошие тесты — правдивые тесты. Чтобы доверять своим тестам, рекомендуем применять разработку через тестирование. С таким подходом ваши тесты будут проверять лишь то, что нужно, и будет повышаться уверенность в их качестве. К тому же сокращается количество ненужных строк кода. Подробнее можно прочесть тут.

Анализаторы кода

Множество орфографических, структурных ошибок, опечаток могут помешать правильной настройке IDE с анализаторами кода. Статические анализаторы по типу SonarQube указывают на проблемы и подробно описывают, как их устранить.

Реализация атомарными частями

Коммитить чаще, релизить чаще — это привычки, которые потенциально сократят ошибки. Частые и маленькие коммиты — быстрое прохождение pipeline-тестирования и ранний feedback по качеству коммита. Этот подход поможет сократить время влияния ошибок на продукт. Чем дольше ошибка в коде, тем дороже будет ее устранение.

Делайте перерывы

Возможно, не самый технический совет, однако один из действенных. Регулярные перерывы снижают усталость, помогают сфокусироваться, удерживают в тонусе. Это приводит к более вдумчивой разработке, тем самым сокращая вероятность ошибки. Можете воспользоваться одной из проверенных техник — Pomodoro.

Поиск и устранение

Ну что ж, о том, чем баги страшны и как они зарождаются, мы поговорили. Самое время найти и «пофиксить» их. В идеале у вас должен быть полный список перечисленных инструментов (а может, и больше). Если же отсутствуют тесты, нет журналирования или VCS, то самое время задуматься об их внедрении. Затраты на внедрение с лихвой окупятся в дальнейшей перспективе разработки.

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

Воспроизвести

Получив issue от QA, первым делом нужно убедиться: а есть ли проблема? Нужно воспроизвести ошибку и удостовериться в стабильности ее возникновения.

Проверить актуальность изменений

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

Понять бизнес-логику

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

Свериться со списком частых ошибок

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

Свериться с тестами

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

Проанализировать журнал

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

Исследовать систему контроля версий

Нередко момент появления бага можно определить с точностью до часа. «Еще вчера работало» — одна из избитых фраз разработчика. И VCS будет лучшим хранителем истории изменений. Посмотрев в log репозитория (blame файла), можно с уверенностью определить роковой commit. В этом случае осмысленные комментарии в коммите и связь с ticket-системой значительно облегчат понимание намерений автора.

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

Самое время воспользоваться мощными инструментами для анализа и выявления ошибок.

Средства профилирования (dotTrace, ANTS, SQL Profiler) — для поиска плавающих багов, утечек памяти, влияния на систему.

Дебагеры (ChromeTools, OzCode) — для отслеживания выполнения программы строка за строкой. Хорошее владение этими инструментами значительно упрощает поиск и исправление ошибок.

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

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

Сделать гипотезу

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

  • метод откатов — откатывать изменения из VCS до тех пор, пока ошибка не исчезнет, тем самым определив нужный коммит;
  • сравнение версий — сравнить поведение и результат с предыдущей рабочей версией.

Эффект утенка

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

Заключение

Мы рассмотрели способы, которые помогут сократить время поиска. Системный и структурированный подход к поиску и фиксации ошибок значительно сокращает затраты времени на их исправление. Даже 10% выигранного времени каждого разработчика принесут хорошую выгоду. И вы сможете уделять больше времени созданию, а не исправлению. Формат чек-листа помогает ничего не забыть и наладить системный процесс выявления ошибок. Возможно, вы используете другие подходы, которые помогают вам. Буду рад обсудить их в комментариях.

И помните: качество — синоним успеха!

Похожие статьи:
В рубрике DOU Проектор все желающие могут презентовать свой продукт (как стартап, так и ламповый pet-проект). Если вам есть о чем...
Это статья для тех, кто хочет стать Android-разработчиком, и в целом для всех, кто желает работать программистом. Несмотря на то,...
У випуску: Python отримує нову модель „уряду”, AWS Lambda підтримує Python 3.7, PyCharm 2018.3. Новини Super Potato Bruh, написана на Pygame, доступна...
Роботизовані комплекси дають змогу зменшити втрати серед українських військових на 30%, повідомили DOU у Генштабі....
У застосунку «Резерв+» став доступним електронний військово-обліковий документ з QR-кодом. Він має таку ж...
Яндекс.Метрика