Как искать ошибки в программах
Искать ошибки в программах — непростая задача. Здесь нет никаких готовых методик или рецептов успеха. Можно даже сказать, что это — искусство. Тем не менее есть общие советы, которые помогут вам при поиске. В статье описаны основные шаги, которые стоит предпринять, если ваша программа работает некорректно.
Шаг 1: Занесите ошибку в трекер
После выполнения всех описанных ниже шагов может так случиться, что вы будете рвать на себе волосы от безысходности, все еще сидя на работе, когда поймете, что:
- Вы забыли какую-то важную деталь об ошибке, например, в чем она заключалась.
- Вы могли делегировать ее кому-то более опытному.
Трекер поможет вам не потерять нить размышлений и о текущей проблеме, и о той, которую вы временно отложили. А если вы работаете в команде, это поможет делегировать исправление коллеге и держать все обсуждение в одном месте.
Вы должны записать в трекер следующую информацию:
- Что делал пользователь.
- Что он ожидал увидеть.
- Что случилось на самом деле.
Шаг 3: Найдите строку, в которой проявляется ошибка
Если ошибка вызывает падение программы, попробуйте запустить её в IDE под отладчиком и посмотрите, на какой строчке кода она остановится. Совершенно необязательно, что ошибка будет именно в этой строке (см. следующий шаг), но, по крайней мере, это может дать вам информацию о природе бага.
Шаг 4: Найдите точную строку, в которой появилась ошибка
Как только вы найдете строку, в которой проявляется ошибка, вы можете пройти назад по коду, чтобы найти, где она содержится. Иногда это может быть одна и та же строка. Но чаще всего вы обнаружите, что строка, на которой упала программа, ни при чем, а причина ошибки — в неправильных данных, которые появились ранее.
Если вы отслеживаете выполнение программы в отладчике, то вы можете пройтись назад по стектрейсу, чтобы найти ошибку. Если вы находитесь внутри функции, вызванной внутри другой функции, вызванной внутри другой функции, то стектрейс покажет список функций до самой точки входа в программу (функции main() ). Если ошибка случилась где-то в подключаемой библиотеке, предположите, что ошибка все-таки в вашей программе — это случается гораздо чаще. Найдите по стектрейсу, откуда в вашем коде вызывается библиотечная функция, и продолжайте искать.
Шаг 5: Выясните природу ошибки
Ошибки могут проявлять себя по-разному, но большинство из них можно отнести к той или иной категории. Вот наиболее частые.
Если ваша ошибка не похожа на описанные выше, или вы не можете найти строку, в которой она появилась, переходите к следующему шагу.
Шаг 6: Метод исключения
Если вы не можете найти строку с ошибкой, попробуйте или отключать (комментировать) блоки кода до тех пор, пока ошибка не пропадет, или, используя фреймворк для юнит-тестов, изолируйте отдельные методы и вызывайте их с теми же параметрами, что и в реальном коде.
Попробуйте отключать компоненты системы один за другим, пока не найдете минимальную конфигурацию, которая будет работать. Затем подключайте их обратно по одному, пока ошибка не вернется. Таким образом вы вернетесь на шаг 3.
Шаг 7: Логгируйте все подряд и анализируйте журнал
Ваша задача состоит в том, чтобы вернуться к шагу 3, обнаружив, где проявляется ошибка. Также это именно тот случай, когда стоит использовать сторонние библиотеки для более тщательного логгирования.
Шаг 8: Исключите влияние железа или платформы
Замените оперативную память, жесткие диски, поменяйте сервер или рабочую станцию. Установите обновления, удалите обновления. Если ошибка пропадет, то причиной было железо, ОС или среда. Вы можете по желанию попробовать этот шаг раньше, так как неполадки в железе часто маскируют ошибки в ПО.
Если ваша программа работает по сети, проверьте свитч, замените кабель или запустите программу в другой сети.
Ради интереса, переключите кабель питания в другую розетку или к другому ИБП. Безумно? Почему бы не попробовать?
Если у вас возникает одна и та же ошибка вне зависимости от среды, то она в вашем коде.
Шаг 9: Обратите внимание на совпадения
- Ошибка появляется всегда в одно и то же время? Проверьте задачи, выполняющиеся по расписанию.
- Ошибка всегда проявляется вместе с чем-то еще, насколько абсурдной ни была бы эта связь? Обращайте внимание на каждую деталь. На каждую. Например, проявляется ли ошибка, когда включен кондиционер? Возможно, из-за этого падает напряжение в сети, что вызывает странные эффекты в железе.
- Есть ли что-то общее у пользователей программы, даже не связанное с ПО? Например, географическое положение (так был найден легендарный баг с письмом за 500 миль).
- Ошибка проявляется, когда другой процесс забирает достаточно большое количество памяти или ресурсов процессора? (Я однажды нашел в этом причину раздражающей проблемы «no trusted connection» с SQL-сервером).
Шаг 10: Обратитесь в техподдержку
Наконец, пора попросить помощи у того, кто знает больше, чем вы. Для этого у вас должно быть хотя бы примерное понимание того, где находится ошибка — в железе, базе данных, компиляторе. Прежде чем писать письмо разработчикам, попробуйте задать вопрос на профильном форуме.
Ошибки есть в операционных системах, компиляторах, фреймворках и библиотеках, и ваша программа может быть действительно корректна. Но шансы привлечь внимание разработчика к этим ошибкам невелики, если вы не сможете предоставить подробный алгоритм их воспроизведения. Дружелюбный разработчик может помочь вам в этом, но чаще всего, если проблему сложно воспроизвести вас просто проигнорируют. К сожалению, это значит, что нужно приложить больше усилий при составлении багрепорта.
Полезные советы (когда ничего не помогает)
Что вам точно не поможет
- Паника
Не надо сразу палить из пушки по воробьям. Некоторые менеджеры начинают паниковать и сразу откатываться, перезагружать сервера и т. п. в надежде, что что-нибудь из этого исправит проблему. Это никогда не работает. Кроме того, это создает еще больше хаоса и увеличивает время, необходимое для поиска ошибки. Делайте только один шаг за раз. Изучите результат. Обдумайте его, а затем переходите к следующей гипотезе. - «Хелп, плиииз!»
Когда вы обращаетесь на форум за советом, вы как минимум должны уже выполнить шаг 3. Никто не захочет или не сможет вам помочь, если вы не предоставите подробное описание проблемы, включая информацию об ОС, железе и участок проблемного кода. Создавайте тему только тогда, когда можете все подробно описать, и придумайте информативное название для нее. - Переход на личности
Если вы думаете, что в ошибке виноват кто-то другой, постарайтесь по крайней мере говорить с ним вежливо. Оскорбления, крики и паника не помогут человеку решить проблему. Даже если у вас в команде не в почете демократия, крики и применение грубой силы не заставят исправления магическим образом появиться.
Ошибка, которую я недавно исправил
Это была загадочная проблема с дублирующимися именами генерируемых файлов. Дальнейшая проверка показала, что у файлов различное содержание. Это было странно, поскольку имена файлов включали дату и время создания в формате yyMMddhhmmss . Шаг 9, совпадения: первый файл был создан в полпятого утра, дубликат генерировался в полпятого вечера того же дня. Совпадение? Нет, поскольку hh в строке формата — это 12-часовой формат времени. Вот оно что! Поменял формат на yyMMddHHmmss , и ошибка исчезла.
Нам в редакцию Tproger пришел вопрос от подписчика, которым мы хотим поделиться с вами:
«Какие типичные ошибки в программировании совершают новички?»
Мы обратились за разъяснениями к нашим экспертам, а полученные ответы предоставляем вашему вниманию.
Неправильный подбор источников для обучения. Профессия программиста — занятие, которое подразумевает ежедневное обучение и освоение огромного количества информации. Иногда приходится и переучиваться, потому что старый опыт и старые знания могут больше мешать, чем помогать. Поэтому две из трех ключевых ошибок связаны именно с обучением.
Сегодня существует большое количество источников информации различной степени качества: начиная от классики Computer Science, написанной 10-15 лет назад, до современных школ обучения, где преподают практики. Я бы рекомендовал с особой осторожностью относиться к современным инфо-бизнесменам, за неделю обещающих из вас сделать программиста.
Типичная ошибка новичка — скакать от источника к источнику, пытаясь охватить все. Такая практика приводит к забавному эффекту: представьте, что вы пытаетесь научится танцевать, посещая занятия в разных школах — скорее всего вы так и не научитесь танцевать. Но, такой подход вполне применим с точки зрения выбора самой школы танцев. Поэтому важно выбрать правильный источник и работать с ним. Ключевые критерии отбора источников: актуальность информации, уровень ее подачи и наполненность практическими задачами (умение решать конкретные задачи).
Отсутствие практики. Я очень часто сталкивался с тем, что на первых порах некоторым начинающим все понятно и они просто не делают практические задания, потому что там все просто. Через какое-то время они банально ничего не могут написать из того, что в их понимании уже не так просто, так как нет практического навыка.
Ключевые критерии: наличие практики по изучаемому материалу и приближенность практики к реальной жизни.
Непонимание контекста задачи. К сожалению, с детства нас учат тому, что в задаче сформулированы все первоначальные данные и условия, а также четко определено, что нужно сделать.
В разработке программных продуктов нужно же больше анализировать конкурентов, т.к. они уже проделали работы по решению задачи до вас. Приведу очень простой пример — если вы поставите начинающему программисту задачу сделать сервис по подбору дебетовых карт, допустим по % на остаток, то он сделает вам сервис, в котором процент будет одним числом. Если же посмотреть на реальные продукты, которые решают подобную задачу, то процент может быть не только одним числом. Поэтому учиться этому нужно сразу, а для этого решать не игрушечные задачи, а задачи, которые встречаются в реальном мире (пусть и в упрощенном варианте).
Главная ошибка новичка заключается в том, что он спешит. Вместо того, чтобы изучать язык, платформу или фреймворк, он вводит вопрос в Google/Stack Overflow и просто копирует результат. Это плохо не только потому что без системных знаний невозможно понять новые технологии «изнутри», но и просто потому что в скопированных ответах могут быть ошибки. Если не разобраться досконально, вместо кода получится «лоскутное одеяло» из кусков Stack Overflow, в котором сам автор разбирается крайне поверхностно. Как следствие, развивать это решение дальше будет крайне сложно.
Я бы задал этот вопрос иначе: «Какие опасности подстерегают новичка в программировании?». По моему мнению, самая главная из них — самоуверенность. Если новичок решил задачу с первого раза, то у него может сложиться впечатление, что все легко и можно расслабиться. Но это не так — наоборот, если задача была решена, надо работать еще больше, чтобы выйти на тот уровень, на котором возникают ошибки, или ты не можешь предложить решение. Иначе тебя обойдут те, кто не был столь уверен в своих знаниях, но много и усердно работал. С другой стороны, решение задачи может быть не самым оптимальным, но без стремления найти другие решения и «поиграть» с ними новичок может запомнить свое первое решение и предлагать его всегда, даже если оно больше не уместно.
Типичная ошибка новичков в программировании — это желание самостоятельно исправлять то, что им кажется неидеальным кодом или несовершенным решением. Это происходит по разным причинам, кто-то просто боится обратиться лишний раз с вопросом к более опытным коллегам, кто-то по-юношески амбициозен, кто-то — перфекционист. Новички склонны чаще выбирать наиболее сложные и универсальные решения (и как следствие, более трудоемкие) в тех случаях, когда у задачи есть простое.
Какие же основные ошибки могут совершать начинающие программисты?
1. Не пытаться искать информацию. Если вы начинающий программист, наверняка кто-то уже сталкивался с вашим вопросом, и решение есть в открытом доступе, мануалах, FAQ и т/ д. Поиск ответа также может показать выбранное решение проблемы с другой стороны. И возможно, это решение только ухудшит ваш проект. Очень полезно использовать опыт других программистов.
2. Не бросать неверное решение. Если Вы поняли, что выбранное решение – не самое лучше, смело отбрасывайте его и начинайте заново. Плохой код и много “костылей” в программе не сделают её удобной для эксплуатации, даже если в итоге она заработает.
3. Ошибка вытекает из пункта два – ухудшать, а не улучшать код. Всегда нужно стараться, пусть и понемногу, улучшать код, но не ухудшать его временными решениями.
4. Не планировать. В этом вопросе важно найти оптимальный вариант, т.к. чересчур подробное планирование может только затруднить подготовку проекта. Нет идеальных планов, но, особенно для крупных проектов, планирование необходимо.
Также начинающему программисту стоит обратить внимание на некоторые моменты, касающиеся уже непосредственно написания кода: инкапсуляция, правильно подобранные инструменты, тестирование (а лучше автотестирование) и т. п. Как говорится, все приходит с опытом, но если заранее учесть возможные ошибки – опыт вы получите намного быстрее.
Статус новичков в программировании наши разработчики перешагнули уже давно. Однако даже тем, кто стал гуру, есть что вспомнить. Ведь все они тоже были когда-то на заре своей карьеры и совершали ошибки. И вот какие: использование в работе непроверенной информации, нежелание углубляться в проблему, поверхностное ее изучение, выбор некорректного источника для обучения (что потом плохо сказывалось на результатах работы), разработка новой фичи ради фичи (увидел что-то новенькое, и принялся делать, забывая о нюансах и подводных камнях) и лишние, бесполезные вложенности в HTML.
Какие типичные ошибки в программировании совершают новички:
1. Изобретают велосипед. Начинающие программисты, в силу небольшого кругозора и юношеского максимализма, склонны использовать свои решения, а не существующие модули. Избежать такой ошибки новички могут благодаря общению с более опытными коллегами и жестким ограничениям по срокам сдачи работ.
2. Склонность к «красивым» решениям. Если есть возможность решить задачу сложно и красиво или просто и утилитарно, новичок выберет «красоту». Естественно, такие «решения» сложнее поддерживать, масштабировать и использовать вторично. Избавиться от такой склонности поможет работа в команде и получение регулярной обратной связи от коллег, которые вынуждены мучиться с такой «красотой».
4. Пренебрежение стилю написания кода, форматированию, названию переменных и объектов. Конечно же, коллегам не приносит радости читать такой код. Работа в команде и обратная связь с куратором поможет искоренить у новичка это пренебрежение.
5. Недостаточное или некорректное комментирование кода. Не секрет, что по прошествии месяца чтение не комментированного или плохо комментированного кода превращается в квест. Это, безусловно, увлекательно, но занимает массу времени. Что уже говорить про попытки использовать такой код коллегами.
Резюме. Нужно понимать, что все эти ошибки относятся к программированию за деньги. Если вы программируете для удовольствия, то это другое. Ошибок в программировании, которые относятся к работе, довольно много. Выше перечислены только основные. Можно упомянуть еще пренебрежение к «юзабилити», недостаточное тестирование и отрицательное отношение к оценке кода коллегами, да и многое другое. Но новичку не стоит унывать, так как работа в команде позволит ему избавиться от всех этих недостатков.
Говоря о типичных ошибках новичков в программировании, как правило, смотрят на профессиональные знания и навыки и упускают из внимания возможные ошибки в самом рабочем процессе. Основываясь на своем опыте могу сказать, чего делать новичку точно не стоит:
Бояться подойти с вопросом, когда что-то непонятно. Всем людям свойственен страх выглядеть глупо, задав очевидный вопрос. Мы надеемся, что сами разберёмся во всем. Но один простой вопрос на stackoverflow или коллеге может сэкономить пару дней. Это не значит, что не надо разбираться в проблеме самому, конечно надо, и полезно. Но объять необъятное крайне сложно, а вокруг вас много разносторонних людей и многие задачи удастся решить быстрее с их помощью.
Считать, что раз ты новичок, то все вокруг умнее тебя. Это круто (и так должно быть), когда в коллективе есть люди гораздо более опытные и знающие, готовые помочь и подсказать. Но не всегда стоит стопроцентно полагаться на их слова. Скорее – как на мнение. Например, у тебя возникла проблема и тебе сказали: «а, ну это здесь не так». Ты идешь разбираться, не можешь найти ошибку в указанном месте и считаешь, что просто недостаточно изучил тему, ты же джуниор. Но может случиться, что сам сеньор не очень понимает, о чем идет речь. Полностью погрузиться в контекст, не упуская ни одной детали, бывает сложно. Все мы люди. Поэтому полагаться на сеньора как на «истину в последней инстанции» не нужно, руководствуйтесь еще и фактами, доказательствами.
2. Копипаст.
Некоторые начинающие программисты копируют чужой общедоступный код – это нормально. Плохо, если код берется целиком, без разбора составляющих и чтения документации о них. Такой подход чреват поздними ошибками при эксплуатации кода, которые сложно диагностировать и устранить. Во-первых, разработчик уже успел «забыть» проблемный код. Во-вторых, исправление может затронуть не только проблемный, но и окружающий код, который его использует.
3. Непрозрачные названия переменных или функций.
Чтение кода занимает в 10 раз больше времени, чем написание, поэтому важно, чтобы он хорошо читался и программист быстро понимал, что именно этот код делает. Возьмем для примера язык Python.
Неопытные программисты могут назвать переменные так:
Но лучше написать так:
4. Преждевременная оптимизация.
Многие разработчики стремятся сделать код быстрым, однако начинающие программисты часто торопятся и не разбираются в причинах медленной работы. Зачастую такой подход приводит к тому, что код плохо читается, а приложение не становится быстрее. Это вызвано тем, что оптимизации подвергается код, выполнение которого и так происходило быстро по сравнению с другими частями приложения. Поэтому оптимизировать код надо в момент, когда видно, что он работает медленно. А саму оптимизацию начинать с определения того места, которое занимает больше всего времени.
Например, такой код:
Можно потратить много времени на оптимизацию функции list_tree , но, скорее всего, ускорение не будет заметно на фоне отправки множества запросов к базе данных на следующем этапе. Лучше сосредоточиться на подготовке единого запроса к базе данных с последующей итерацией по полученным данным:
5. Погоня за модными технологиями.
Как правило, новые технологии имеют превосходство в одних областях за счет проигрыша в других. До тех пор, пока у начинающего программиста нет понимания этих особенностей, лучше использовать проверенные временем универсальные решения. Например, если программист разрабатывает приложение, которое хранит какие-то данные, не стоит спешить использовать самое последнее NoSQL-решение, только потому, что это модно. В большинстве случаев подойдет обычная SQL база-данных (MySQL, PostrgreSQL, SQLite) с большим объемом документации, стандартизированными подходами, описанными и давно решенными проблемами.
Напоследок:
• Всегда тестируйте свой код, в идеале, пишите автоматизированные тесты;
• Делайте бэкапы данных, в том числе, систему контроля версий кода;
• Не выкладывайте новый код на продакшн-сервер в пятницу вечером, потому что есть вероятность, что придется провести выходные, исправляя ошибки.
Так же часто встречается ошибка, связанная с написанием собственных библиотек, реализацией функционала (велосипедов), когда есть готовые или общепринятые. Кроме очевидной потери времени на написание кода и последующую его поддержку, код одного человека заведомо не может быть лучше (исключения конечно есть) кода, который досконально протестирован и уже несколько лет используется многими разработчиками во всем мире.
Многие начинающие разработчики используют технику copy-paste. Мы живем в прекрасное время, где любая проблема может решиться за несколько секунд с помощью поисковика. Но не стоит этим злоупотреблять. Бездумное копирование решений со stackoverflow не только не добавит в вашу копилку опыта, но и может что-то сломать в проекте. После того, как вы нашли проблему, постарайтесь ее решить с помощью документации, если это не получается сделать за вменяемый срок, то уже можно обращаться за помощью к великому Google. После того, как было найдено решение, необходимо понять, как оно работает, и только после этого, можно использовать его в коде.
Бывают ошибки с использованием неподходящих инструментов. Многие джуниоры, не успев дописать свой первый hello world, уже бегут осваивать новые технологии и прорывы в мире ПО,при этом не желая изучать фундаментальные вещи, которые являются основой всего современного стека технологий и кардинально не меняются десятилетиями. Как следствие появляется несколько значительных проблем:
Использование избыточных для задачи технологий, о которых где-то прочитал или услышал, только ради самого использования технологии. Это затрудняет поддержку кода другими разработчиками, кроме того не все технологии подходят для продакшна.
Непонимание, где, как, а главное зачем использовать ту или иную технологию или инструмент, негативно сказывается на качестве кода и проекта в целом.
Приверженность к одному конкретному языку/технологии/OS/IDE и попытки навязать их другим (евангелизм), тоже часто говорит о том, что человек не понимает или не может объективно сравнить тот или иной инструмент для решении конкретной задачи.
Знать C или Ассемблер в совершенстве не обязательно, но знать, какие бывают протоколы, как они работают, иметь базовые представления о фундаментальных технологиях необходимо любому разработчику. Уже отталкиваясь от этих знаний, можно понять, в чем именно состоит проблема и как и чем ее можно решить.
Еще есть зацикливание на проблеме и боязнь критики. Всем нам тяжело признавать свои ошибки и провалы. А начинающему разработчику тяжелее вдвойне: он хочет максимально проявить себя, доказать, что может самостоятельно работать. Частой ошибкой является негативная реакция на комментарии коллег по поводу качества кода новичка. Нужно понимать, что каждая найденная ваша ошибка, делает вас сильнее, и что коллеги не издеваются над вами, а хотят помочь.
Или начинающий программист не всегда может решить проблему в короткие сроки, и начинает зарываться в неё, пишет велосипеды или вовсе ничего не пишет и очень грустит. Главная ошибка тут не в том, что разработчик не знает как решить проблему — все начинали с этого уровня, а в том, что он не обратился за помощью после первых 3-4 неудачных попыток. Стоит акцентировать внимание, что сразу бежать к ментору или на форум не стоит, ошибки — это опыт.
Для опытных разработчиков информация статьи может быть очевидной и если вы себя таковым считаете, то лучше добавьте в комментариях полезных советов.
Как обнаружить ошибку
Прочитай информацию об исключении
Если выполнение программы прерывается исключением, то это первое место откуда стоит начинать поиск.
В каждом языке есть свои способы уведомления об исключениях. Например в JavaScript для обработки ошибок связанных с Web Api существует DOMException. Для пользовательских сценариев есть базовый тип Error. В обоих случаях в них содержится информация о наименовании и описании ошибки.
Каким бы языком вы не пользовались, не поленитесь изучить каким образом язык предоставляет информацию об исключениях и что эта информация означает.
Пример неявного переопределения параметров - использование интерцептора, который изменяет этот параметр в запросе и о котором вы не знаете.
Разверните стек
Загуглите текст ошибки
Очевидное правило, которым не все пользуются. Применимо к не типовым ошибкам, например связанным с конкретной библиотекой или со специфическим типом исключения. Поиск по тексту ошибки помогает найти аналогичные случаи, которые даже если не дадут конкретного решения, то помогут понять контекст её возникновения.
Прочитайте документацию
Если ошибка связана с использованием внешней библиотеки, убедитесь что понимаете как она работает и как правильно с ней взаимодействовать. Типичные ошибки, когда подключив новую библиотеку после прочтения Getting Started она не работает как ожидалось или выбрасывает исключение. Проблема может быть в том, что базовый шаблон подключения библиотеки не применим к текущему приложению и требуются дополнительные настройки или библиотека не совместима с текущим окружением. Разобраться в этом поможет прочтение документации.
Проведите исследовательское тестирование
Если используете библиотеку которая не работает как ожидалось, а нормальная документация отсутствует, то создайте тесты которые покроют интересующий функционал. В ассертах опишите ожидаемое поведение. Если тесты не проходят, то подбирая различные вариации входных данных выясните рабочую конфигурацию. Цель исследовательских тестов помочь разобраться без документации, какое ожидаемое поведение у изучаемой библиотеки в разных сценариях работы. Получив эти знания будет легче понять как правильно использовать библиотеку в проекте.
Бинарный поиск
Где обитают ошибки
Ошибки в своём коде
Ошибки в чужом коде
Если над проектом работает больше одного разработчика, чей код взаимодействует друг с другом, возможна ситуация, когда ошибка происходит в чужом коде. Может сложиться впечатление, что если программа раньше работала, а сломалась только после того, как вы добавили свой код, то проблема в этом коде. На деле может быть, что ваш код обращается к уже существующему чужому коду, но передаёт туда граничные значения данных, работу с которыми забыли протестировать и обработать такие случаи.
В зависимости от соглашений на проекте исправляйте такие ошибки как свои собственные, либо сообщайте о них автору и ждите внесения правок.
Ошибки в библиотеках
Ошибки могут падать во внешних библиотеках к которым нет доступа и в таком случае непонятно что делать. Такие ошибки можно разделить на два типа. Первый- это ошибки в коде библиотеки. Второй- это ошибки связанные с невалидными данными или окружением, которые приводят к внутреннему исключению.
Первый случай хотя и редкий, но не стоит о нём забывать. В этом случае можно откатиться на другую версию библиотеки и создать Issue с описанием проблемы. Если это open-source и нет времени ждать обновления, можно собрать свою версию исправив баг самостоятельно, с последующей заменой на официальную исправленную версию.
Во втором случае определите откуда из вашего кода пришли невалидные данные. Для этого смотрим стек выполнения и по цепочке прослеживаем место в котором библиотека вызывается из нашего кода. Далее с этого места начинаем анализ, как туда попали невалидные данные.
Ошибки не воспроизводимые локально
Ошибка воспроизводится на develop стенде или в production, но не воспроизводится локально. Такие ошибки сложнее отлавливать потому что не всегда есть возможность запустить дебаг на удалённой машине. Поэтому убеждаемся, что ваше окружение соответствует внешнему.
Проверьте версию приложения
На стенде и локально версии приложения должны совпадать. Возможно на стенде приложение развёрнуто из другой ветки.
Проверьте данные
Проблема может быть в невалидных данных, а локальная и тестовая база данных рассинхронизированы. В этом случае поиск ошибки воспроизводим локально подключившись к тестовой БД, либо сняв с неё актуальный дамп.
Проверьте соответствие окружений
Коварные ошибки
Похожая ситуация и с одинаково названными типами. Если сборка включает несколько проектов в которых присутствуют одинаково названные классы, то можно по ошибке обращаться не к тому который требуется. Чтобы избежать обоих случаев, убедитесь, что в месте возникновения ошибки идёт обращение к правильным типам и методам.
Дополнительные материалы
Алгоритм отладки
Проверь гипотезу - если гипотеза проверку не прошла то п.3.
Убедись что исправлено - если не исправлено, то п.3.
Подробнее ознакомиться с ним можно в докладе Сергея Щегриковича «Отладка как процесс».
Чем искать ошибки, лучше не допускать ошибки. Прочитайте статью «Качество вместо контроля качества», чтобы узнать как это делать.
Итого
При появлении ошибки в которой сложно разобраться сперва внимательно и вдумчиво читаем текст ошибки.
Смотрим стек выполнения и проверяем, не находится ли причина возникновения выше по стеку.
Если по прежнему непонятно, гуглим текст и ищем похожие случаи.
Если проблема при взаимодействии с внешней библиотекой, читаем документацию.
Если нет документации проводим исследовательское тестирование.
Если не удается локализовать причину ошибки, применяем метод Бинарного поиска.
Искать ошибки в программах — непростая задача. Здесь нет никаких готовых методик или рецептов успеха. Можно даже сказать, что это — искусство. Тем не менее есть общие советы, которые помогут вам при поиске. В статье описаны основные шаги, которые стоит предпринять, если ваша программа работает некорректно.
Шаг 1: Занесите ошибку в трекер
После выполнения всех описанных ниже шагов может так случиться, что вы будете рвать на себе волосы от безысходности, все еще сидя на работе, когда поймете, что:
- Вы забыли какую-то важную деталь об ошибке, например, в чем она заключалась.
- Вы могли делегировать ее кому-то более опытному.
Трекер поможет вам не потерять нить размышлений и о текущей проблеме, и о той, которую вы временно отложили. А если вы работаете в команде, это поможет делегировать исправление коллеге и держать все обсуждение в одном месте.
Вы должны записать в трекер следующую информацию:
- Что делал пользователь.
- Что он ожидал увидеть.
- Что случилось на самом деле.
Шаг 3: Найдите строку, в которой проявляется ошибка
Если ошибка вызывает падение программы, попробуйте запустить её в IDE под отладчиком и посмотрите, на какой строчке кода она остановится. Совершенно необязательно, что ошибка будет именно в этой строке (см. следующий шаг), но, по крайней мере, это может дать вам информацию о природе бага.
Шаг 4: Найдите точную строку, в которой появилась ошибка
Как только вы найдете строку, в которой проявляется ошибка, вы можете пройти назад по коду, чтобы найти, где она содержится. Иногда это может быть одна и та же строка. Но чаще всего вы обнаружите, что строка, на которой упала программа, ни при чем, а причина ошибки — в неправильных данных, которые появились ранее.
Если вы отслеживаете выполнение программы в отладчике, то вы можете пройтись назад по стектрейсу, чтобы найти ошибку. Если вы находитесь внутри функции, вызванной внутри другой функции, вызванной внутри другой функции, то стектрейс покажет список функций до самой точки входа в программу (функции main() ). Если ошибка случилась где-то в подключаемой библиотеке, предположите, что ошибка все-таки в вашей программе — это случается гораздо чаще. Найдите по стектрейсу, откуда в вашем коде вызывается библиотечная функция, и продолжайте искать.
Шаг 5: Выясните природу ошибки
Ошибки могут проявлять себя по-разному, но большинство из них можно отнести к той или иной категории. Вот наиболее частые.
Если ваша ошибка не похожа на описанные выше, или вы не можете найти строку, в которой она появилась, переходите к следующему шагу.
Шаг 6: Метод исключения
Если вы не можете найти строку с ошибкой, попробуйте или отключать (комментировать) блоки кода до тех пор, пока ошибка не пропадет, или, используя фреймворк для юнит-тестов, изолируйте отдельные методы и вызывайте их с теми же параметрами, что и в реальном коде.
Попробуйте отключать компоненты системы один за другим, пока не найдете минимальную конфигурацию, которая будет работать. Затем подключайте их обратно по одному, пока ошибка не вернется. Таким образом вы вернетесь на шаг 3.
Шаг 7: Логгируйте все подряд и анализируйте журнал
Ваша задача состоит в том, чтобы вернуться к шагу 3, обнаружив, где проявляется ошибка. Также это именно тот случай, когда стоит использовать сторонние библиотеки для более тщательного логгирования.
Шаг 8: Исключите влияние железа или платформы
Замените оперативную память, жесткие диски, поменяйте сервер или рабочую станцию. Установите обновления, удалите обновления. Если ошибка пропадет, то причиной было железо, ОС или среда. Вы можете по желанию попробовать этот шаг раньше, так как неполадки в железе часто маскируют ошибки в ПО.
Если ваша программа работает по сети, проверьте свитч, замените кабель или запустите программу в другой сети.
Ради интереса, переключите кабель питания в другую розетку или к другому ИБП. Безумно? Почему бы не попробовать?
Если у вас возникает одна и та же ошибка вне зависимости от среды, то она в вашем коде.
Шаг 9: Обратите внимание на совпадения
- Ошибка появляется всегда в одно и то же время? Проверьте задачи, выполняющиеся по расписанию.
- Ошибка всегда проявляется вместе с чем-то еще, насколько абсурдной ни была бы эта связь? Обращайте внимание на каждую деталь. На каждую. Например, проявляется ли ошибка, когда включен кондиционер? Возможно, из-за этого падает напряжение в сети, что вызывает странные эффекты в железе.
- Есть ли что-то общее у пользователей программы, даже не связанное с ПО? Например, географическое положение (так был найден легендарный баг с письмом за 500 миль).
- Ошибка проявляется, когда другой процесс забирает достаточно большое количество памяти или ресурсов процессора? (Я однажды нашел в этом причину раздражающей проблемы «no trusted connection» с SQL-сервером).
Шаг 10: Обратитесь в техподдержку
Наконец, пора попросить помощи у того, кто знает больше, чем вы. Для этого у вас должно быть хотя бы примерное понимание того, где находится ошибка — в железе, базе данных, компиляторе. Прежде чем писать письмо разработчикам, попробуйте задать вопрос на профильном форуме.
Ошибки есть в операционных системах, компиляторах, фреймворках и библиотеках, и ваша программа может быть действительно корректна. Но шансы привлечь внимание разработчика к этим ошибкам невелики, если вы не сможете предоставить подробный алгоритм их воспроизведения. Дружелюбный разработчик может помочь вам в этом, но чаще всего, если проблему сложно воспроизвести вас просто проигнорируют. К сожалению, это значит, что нужно приложить больше усилий при составлении багрепорта.
Полезные советы (когда ничего не помогает)
Что вам точно не поможет
- Паника
Не надо сразу палить из пушки по воробьям. Некоторые менеджеры начинают паниковать и сразу откатываться, перезагружать сервера и т. п. в надежде, что что-нибудь из этого исправит проблему. Это никогда не работает. Кроме того, это создает еще больше хаоса и увеличивает время, необходимое для поиска ошибки. Делайте только один шаг за раз. Изучите результат. Обдумайте его, а затем переходите к следующей гипотезе. - «Хелп, плиииз!»
Когда вы обращаетесь на форум за советом, вы как минимум должны уже выполнить шаг 3. Никто не захочет или не сможет вам помочь, если вы не предоставите подробное описание проблемы, включая информацию об ОС, железе и участок проблемного кода. Создавайте тему только тогда, когда можете все подробно описать, и придумайте информативное название для нее. - Переход на личности
Если вы думаете, что в ошибке виноват кто-то другой, постарайтесь по крайней мере говорить с ним вежливо. Оскорбления, крики и паника не помогут человеку решить проблему. Даже если у вас в команде не в почете демократия, крики и применение грубой силы не заставят исправления магическим образом появиться.
Ошибка, которую я недавно исправил
Это была загадочная проблема с дублирующимися именами генерируемых файлов. Дальнейшая проверка показала, что у файлов различное содержание. Это было странно, поскольку имена файлов включали дату и время создания в формате yyMMddhhmmss . Шаг 9, совпадения: первый файл был создан в полпятого утра, дубликат генерировался в полпятого вечера того же дня. Совпадение? Нет, поскольку hh в строке формата — это 12-часовой формат времени. Вот оно что! Поменял формат на yyMMddHHmmss , и ошибка исчезла.
Отладка изначально вдвое сложнее написания кода. Поэтому, если вы пишете код настолько заумный, насколько можете, то по определению вы не способны отлаживать его.
Брайан Керниган и П.Ж.Плауэр, «Основы программного стиля»
Юан-Ма написал небольшую программу, использующую много глобальных переменных и ужасных хаков. Ученик, читая программу, спросил его: «Вы предупреждали нас о подобных техниках, но при этом я нахожу их в вашей же программе. Как это возможно?» Мастер ответил: «Не нужно бежать за поливальным шлангом, если дом не горит».
Мастер Юан-Ма, «Книга программирования».
Программа – это кристаллизованная мысль. Иногда мысли путаются. Иногда при превращении мыслей в программу в код вкрадываются ошибки. В обоих случаях получается повреждённая программа.
Недостатки в программах обычно называют ошибками. Это могут быть ошибки программиста или проблемы в системах, с которыми программа взаимодействует. Некоторые ошибки очевидны, другие – трудноуловимы, и могут скрываться в системах годами.
Часто проблема возникает в тех ситуациях, возникновение которых программист изначально не предвидел. Иногда этих ситуаций нельзя избежать. Когда пользователя просят ввести его возраст, а он вводит «апельсин», это ставит программу в непростую ситуацию. Эти ситуации необходимо предвидеть и как-то обрабатывать.
Ошибки программистов
Разные языки по-разному могут помогать вам в поиске ошибок. К сожалению, JavaScript находится на конце этой шкалы, обозначенном как «вообще почти не помогает». Некоторым языкам надо точно знать типы всех переменных и выражений ещё до запуска программы, и они сразу сообщат вам, если типы использованы некорректно. JavaScript рассматривает типы только во время исполнения программ, и даже тогда он разрешает делать не очень осмысленные вещи без всяких жалоб, например
На некоторые вещи JavaScript всё-таки жалуется. Написание синтаксически неправильной программы сразу вызовет ошибку. Другие ошибки, например вызов чего-либо, не являющегося функцией, или обращение к свойству неопределённой переменной, возникнут при выполнении программы, когда она сталкивается с такой бессмысленной ситуацией.
Процесс поиска ошибок (bugs) в программах называется отладкой (debugging).
Строгий режим (strict mode)
JavaScript можно заставить быть построже, переведя его в строгий режим. Для этого наверху файла или тела функции пишется «use strict». Пример:
Обычно, когда ты забываешь написать var перед переменной, как в примере перед counter, JavaScript по-тихому создаёт глобальную переменную и использует её. В строгом режиме выдаётся ошибка. Это очень удобно. Однако, ошибка не выдаётся, когда глобальная переменная уже существует – только тогда, когда присваивание создаёт новую переменную.
Ещё одно изменение – привязка this содержит undefined в тех функциях, которые вызывали не как методы. Когда мы вызываем функцию не в строгом режиме, this ссылается на объект глобальной области видимости. Поэтому если вы случайно неправильно вызовете метод в строгом режиме, JavaScript выдаст ошибку, если попытается прочесть что-то из this, а не будет радостно работать с глобальным объектом.
К примеру, рассмотрим код, вызывающий конструктор без ключевого слова new, в случае чего this не будет ссылаться на создаваемый объект.
Некорректный вызов Person успешно происходит, но возвращается как undefined и создаёт глобальную переменную name. В строгом режиме всё по-другому:
Нам сразу сообщают об ошибке. Очень удобно.
Строгий режим умеет ещё кое-что. Он запрещает вызывать функцию с несколькими параметрами с одним и тем же именем, и удаляет некоторые потенциально проблемные свойства языка (например, инструкцию with, которая настолько ужасна, что даже не обсуждается в этой книге).
Короче говоря, надпись «use strict» перед текстом программы редко причиняет проблемы, зато помогает вам видеть их.
Тестирование
Если язык не собирается помогать нам в поиске ошибок, приходится искать их сложным способом: запуская программу и наблюдая, делает ли она что-то так, как надо.
Делать это вручную, снова и снова – верный способ сойти с ума. К счастью, часто возможно написать другую программу, которая автоматизирует проверку вашей основной программы.
Для примера вновь обратимся к типу Vector.
Мы напишем программу, которая проверит, что наша реализация Vector работает, как нужно. Затем после каждого изменения реализации мы будем запускать проверочную программу, чтобы убедиться, что мы ничего не сломали. Когда мы добавим функциональности (к примеру, новый метод) к типу Vector, мы добавим проверки к этой новой функциональности.
Написание таких проверок приводит к появлению повторяющегося кода. К счастью, есть программные продукты, помогающие писать наборы проверок при помощи специального языка, приспособленного именно для написания проверок. Их называют testing frameworks.
Отладка (debugging)
Когда вы заметили проблему в программе,– она ведёт себя неправильно и выдаёт ошибки,- самое время выяснить, в чём проблема.
Следующий пример пробует преобразовать целое число в строку в заданной системы счисления (десятичной, двоичной или другой), отнимая последнюю ((цифру)) и совершая деление, чтобы избавиться от этой цифры.
Даже если вы нашли проблему – притворитесь, что ещё не нашли. Мы знаем, что программа сбоит, и нам нужно узнать, почему.
Здесь вам надо преодолеть желание начать вносить случайные изменения в код. Вместо этого подумайте. Проанализируйте результат и придумайте теорию, по которой это происходит. Проведите дополнительные наблюдения для проверки теории – а если теории нет, проведите наблюдения, которые бы помогли вам изобрести её.
Размещение нескольких вызовов console.log в стратегических местах – хороший способ получить дополнительную информацию о том, что программа делает. В нашем случае нам нужно, чтобы n принимала значения 13, 1, затем 0. Давайте выведем значения в начале цикла:
Н-да. Деление 13 на 10 выдаёт не целое число. Вместо n /= base нам нужно n = Math.floor(n / base), тогда число будет корректно «сдвинуто» вправо.
Кроме console.log можно воспользоваться отладчиком в браузере. Современные браузеры умеют ставить точку остановки на выбранной строчке кода. Это приведёт к приостановке выполнения программы каждый раз, когда будет достигнута выбранная строчка, и тогда вы сможете просмотреть содержимое переменных. Не буду подробно расписывать процесс, поскольку у разных браузеров он организован по-разному – поищите в вашем браузере “developer tools”, инструменты разработчика. Ещё один способ установить точку остановки – включить в код инструкцию для отладчика, состоящую из ключевого слова debugger. Если инструменты разработчика активны, исполнение программы будет приостановлено на этой инструкции, и вы сможете изучить состояние программы.
Распространение ошибок
К сожалению, программист может предотвратить появление не всех проблем. Если ваша программа общается с внешним миром, она может получить неправильные входные данные, или же системы, с которыми она пытается взаимодействовать, окажутся сломанными или недоступными.
Простые программы, или программы, работающие под вашим надзором, могут просто «сдаваться» в такой момент. Вы можете изучить проблему и попробовать снова. «Настоящие» приложения не должны просто «падать». Иногда приходится принимать неправильные входные данные и как-то с ними работать. В других случаях, нужно сообщить пользователю, что что-то пошло не так – и потом уже сдаваться. В любом случае программа должна что-то сделать в ответ на возникновение проблемы.
Допустим, у вас есть функция promptInteger, которая запрашивает целое число и возвращает его. Что она должна сделать, если пользователь введёт «апельсин»?
Один из вариантов – вернуть особое значение. Обычно для этих целей используют null и undefined.
Это надёжная стратегия. Теперь любой код, вызывающий promptNumber, должен проверять, было ли возвращено число, и если нет, как-то выйти из ситуации – спросить снова, или задать значение по-умолчанию. Или вернуть специальное значение уже тому, кто его вызвал, сообщая о неудаче.
Во многих таких случаях, когда ошибки возникают часто и вызывающий функцию код должен принимать их во внимание, совершенно допустимо возвращать специальное значение как индикатор ошибки. Но есть и минусы. Во-первых, что, если функция и так может вернуть любой тип значения? Для неё сложно найти специальное значение, которое будет отличаться от допустимого результата.
Вторая проблема – работа со специальными значениями может замусорить код. Если функция promptNumber вызывается 10 раз, то надо 10 раз проверить, не вернула ли она null. Если реакция на null заключается в возврате null на уровень выше, тогда там, где вызывался этот код, тоже нужно встраивать проверку на null, и так далее.
Исключения
Когда функция не может работать нормально, мы бы хотели остановить работу и перепрыгнуть туда, где такая ошибка может быть обработана. Этим занимается обработка исключений.
Код, встретивший проблему в момент выполнения, может поднять (или выкинуть) исключение (raise exception, throw exception), которое представляет из себя некое значение. Возврат исключения напоминает некий «прокачанный» возврат из функции – он выпрыгивает не только из самой функции, но и из всех вызывавших её функций, до того места, с которого началось выполнение. Это называется развёртыванием стека (unwinding the stack). Может быть, вы помните стек функций из главы 3… Исключение быстро проматывает стек вниз, выкидывая все контексты вызовов, которые встречает.
Если бы исключения сразу доходили до самого низа стека, пользы от них было бы немного. Они бы просто предоставляли интересный способ взорвать программу. Их сила в том, что на их пути в стеке можно поставить «препятствия», которые будут ловить исключения, мчащиеся по стеку. И тогда с этим можно сделать что-то полезное, после чего программа продолжает выполняться с той точки, где было поймано исключение.
Ключевое слово throw используется для выбрасывания исключения. Ловлей занимается кусок кода, обёрнутый в блок try, за которым следует catch. Когда код в блоке try выкидывает исключение, выполняется блок catch. Переменная, указанная в скобках, будет привязана к значению исключения. После завершения выполнения блока catch, или же если блок try выполняется без проблем, выполнение переходит к коду, лежащему после инструкции try/catch.
В данном случае для создания исключения мы использовали конструктор Error. Это стандартный конструктор, создающий объект со свойством message. В современных окружениях JavaScript экземпляры этого конструктора также собирают информацию о стеке вызовов, который был накоплен в момент выкидывания исключения – так называемое отслеживание стека (stack trace). Эта информация сохраняется в свойстве stack, и может помочь при разборе проблемы – она сообщает, в какой функции случилась проблема и какие другие функции привели к данному вызову.
Обратите внимание, что функция look полностью игнорирует возможность возникновения проблем в promptDirection. Это преимущество исключений – код, обрабатывающий ошибки, нужен только в том месте, где происходит ошибка, и там, где она обрабатывается. Промежуточные функции просто не обращают на это внимания.
Подчищаем за исключениями
Представьте следующую ситуацию: функция withContext желает удостовериться, что во время её выполнения переменная верхнего уровня context получит специальное значение контекста. После завершения выполнения, она восстанавливает её старое значение.
Что, если функция body выбросит исключение? В таком случае вызов withContext будет выброшен исключением из стека, и переменной context никогда не будет возвращено первоначальное значение.
Но у инструкции try есть ещё одна особенность. За ней может следовать блок finally, либо вместо catch, либо вместе с catch. Блок finally означает «что бы ни произошло, выполнить код в любом случае после выполнения блока try». Если функции надо что-то подчистить, то подчищающий код нужно включать в блок finally.
Заметьте, что нам больше не нужно сохранять результат вызова body в отдельной переменной, чтобы вернуть его. Даже если мы возвращаемся из блока try, блок finally всё равно будет выполнен. Теперь мы можем безопасно сделать так:
Несмотря на то, что вызываемая из withContext функция «сломалась», сам по себе withContext по-прежнему подчищает значение переменной context.
Выборочный отлов исключений
Когда исключение доходит до низа стека и его никто не поймал — его обрабатывает окружение. Как именно – зависит от конкретного окружения. В браузерах описание ошибки выдаётся в консоль (она обычно доступна в меню «Инструменты» или «Разработка»).
Если речь идёт об ошибках или проблемах, которые программа не может обработать в принципе, допустимо просто пропустить такую ошибку. Необработанное исключение – разумный способ сообщить о проблеме в программе, и консоль в современных браузерах выдаст вам необходимую информацию о том, какие вызовы функций были в стеке в момент возникновения проблемы.
Если возникновение проблемы предсказуемо, программа не должна падать с необработанным исключением — это не очень дружественно по отношению к пользователю.
Недопустимое использование языка – ссылки на несуществующую переменную, запрос свойств у переменной, равной null, или вызов чего-то, что не является функцией, тоже приводит к выбрасыванию исключений. Такие исключения можно отлавливать точно так же, как свои собственные.
При входе в блок catch мы знаем только, что что-то внутри блока try привело к исключению. Мы не знаем, что именно, и какое исключение произошло.
JavaScript (что является вопиющим упущением) не предоставляет непосредственной поддержки выборочного отлова исключений: либо ловим все, либо никакие. Из-за этого люди часто предполагают, что случившееся исключение – именно то, ради которого и писался блок catch.
Но может быть и по-другому. Нарушение произошло где-то ещё, или в программу вкралась ошибка. Вот пример, где мы пробуем вызывать promptDirection до тех пор, пока не получим допустимый ответ:
Значит, нам надо поймать определённое исключение. Мы можем в блоке catch проверять, является ли случившееся исключение интересующим нас исключением, а в противном случае заново выбрасывать его. Но как нам распознать исключение?
Давайте лучше определим новый тип ошибки и используем instanceof для его распознавания.
Прототип наследуется от Error.prototype, поэтому instanceof Error тоже будет выполняться для объектов типа InputError. И ему назначено свойство name, как и другим стандартным типам ошибок (Error, SyntaxError, ReferenceError, и т.п.)
Присвоение свойству stack пытается передать этому объекту отслеживание стека, на тех платформах, которые это поддерживают, путём создания объекта Error и использования его стека.
Теперь promptDirection может сотворить такую ошибку.
А в цикле её будет ловить сподручнее.
Утверждения (Assertions)
Утверждения – инструмент для простой проверки ошибок. Рассмотрим вспомогательную функцию assert:
Это – компактный способ ужесточения требований к значениям, который выбрасывает исключение в случае, когда заданное условие не выполняется. К примеру, функция lastElement, добывающая последний элемент массива, вернула бы undefined для пустых массивов, если б мы не использовали assertion. Извлечение последнего элемента пустого массива не имеет смысла, и это явно было бы ошибкой программиста.
Утверждения – способ убедиться в том, что ошибки провоцируют прерывание программы в том месте, где они совершены, а не просто выдают странные величины, которые передаются по системе и вызывают проблемы в каких-то других, не связанных с этим, местах.
Ошибки и недопустимые входные данные случаются в жизни. Ошибки в программах надо искать и исправлять. Их легче найти, используя автоматические системы проверок и добавляя утверждения в ваши программы.
Проблемы, вызванные чем-то, что неподвластно вашей программе, нужно обрабатывать достойно. Иногда, когда проблему можно решить локально, допустимо возвращать специальные значения для отслеживания таких случаев. В других случаях предпочтительно использовать исключения.
Выброс исключения приводит к разматыванию стека до тех пор, пока не будет встречен блок try/catch или пока мы не дойдём до дна стека. Значение исключения будет передано в блок catch, который сможет удостовериться в том, что это исключение действительно то, которое он ждёт, и обработать его. Для работы с непредсказуемыми событиями в потоке программы можно использовать блоки finally, чтобы определённые части кода были выполнены в любом случае.
Упражнения
Повтор
Допустим, у вас есть функция primitiveMultiply, которая в 50% случаев перемножает 2 числа, а в остальных случаях выбрасывает исключение типа MultiplicatorUnitFailure. Напишите функцию, обёртывающую эту, и просто вызывающую её до тех пор, пока не будет получен успешный результат.
Убедитесь, что вы обрабатываете только нужные вам исключения.
Запертая коробка
Рассмотрим такой, достаточно надуманный, объект:
Это коробочка с замком. Внутри лежит массив, но до него можно добраться только, когда коробка не заперта. Напрямую обращаться к свойству _content нельзя.
Напишите функцию withBoxUnlocked, принимающую в качестве аргумента функцию, которая отпирает коробку, выполняет функцию, и затем обязательно запирает коробку снова перед выходом – неважно, выполнилась ли переданная функция правильно, или она выбросила исключение.
В качестве призовой игры убедитесь, что при вызове withBoxUnlocked, когда коробка не заперта, коробка остаётся незапертой.
Читайте также: