Винит Менон
Технический архитектор
@WiproDigital

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

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

Существует множество разработчиков, которые могут просматривать новые функции, писать код и реализовывать бизнес-требования, как их обучал сам великий Деннис Ритчи, но задумывались ли вы когда-нибудь, кто устраняет колоссальное количество ошибок, которые они оставляют после себя?

Одно дело — уметь писать красивый элегантный код. Другое дело знать, как отлаживать самый неприятный код, который вы когда-либо видели в своей жизни, написанный ранее упомянутым мифическим человеком, который в одиночку сумел собрать все приложение за 48 часов.

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

Настоящий секрет отладки

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

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

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

Отладка связана с:

  1. Поиск причины проблемы в кодовой базе
  2. Распознавание возможных причин этого
  3. Проверка гипотез до тех пор, пока не будет обнаружена возможная первопричина
  4. Затем, в надлежащее время, устранить эту причину и гарантировать, что это никогда не повторится.

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

Теперь давайте пройдемся по типичному процессу отладки программиста, и вы поймете, что именно я имею в виду.

Единственное, что вы НЕ должны делать во время отладки

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

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

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

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

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

Одна вещь, которую вы абсолютно ДОЛЖНЫ делать во время отладки

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

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

Четыре шага к эффективной отладке

1. Воспроизведите ошибку

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

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

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

2. Сядь и подумай

Сразу после того, как вы воспроизведете свой сбой, следующий за ним этап является наиболее важным, которого разработчики программного обеспечения избегают по той же причине, по которой они так торопятся расшифровать сбой. Ваш следующий шаг — сесть и подумать. Да, это правильно. Подумайте о сбое и о том, каковы могут быть его мыслимые корни. Подумайте, как работает система, и возможные объяснения странного поведения, которое вы наблюдаете.

Вы будете в спешке прыгать в код и в отладчик и начинать «смотреть на вещи», но прежде чем вы начнете это делать, жизненно важно понять, что вы ищете и на что именно смотреть. Скорее всего, вы создадите несколько концепций или гипотез о том, что может быть причиной сбоя. У вас должно быть как минимум два или три, с которыми вы можете поэкспериментировать, прежде чем двигаться дальше.

3. Проверьте свои концепции

В большинстве случаев ваши гипотезы не сработают. Это просто жизнь. Если это так, то лучшее, что вы можете сделать, — это проверить свои предположения о том, как все работает. Мы, естественно, предполагаем, что код работает определенным образом или что некоторые входные или выходные данные должны иметь какое-то значение. Снова и снова мы думаем: «Этого не может быть!» и часто мы оказываемся неправы. Это случается с лучшими из нас. Лучшее, что вы можете сделать с этими предположениями, — это проверить их.

Вы делаете это, написав Unit Test Cases. Напишите несколько модульных тестов, которые проверяют очевидные вещи, которые должны работать в рабочем процессе проблемы, которую вы пытаетесь отладить. Большинство этих тестов должно пройти легко, но время от времени вы будете писать модульный тест для проверки какого-то очевидного предположения, и результаты будут не чем иным, как шокирующими. Всегда помните, что если бы ответ на ваш сбой был очевиден, это был бы вовсе не сбой.

4. Поймите, как вы это исправили

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

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

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

Эффективная отладка на практике

Некоторое время назад я исправил неприятную ошибку для клиента. Это было связано со случайным выпуском соединений Secure Socket Layer (SSL) с системой стороннего клиента. Сторонний клиент использовал древнее программное обеспечение, а у моего клиента был специальный код, который подключался к системе стороннего клиента через SSL.

Были предприняты многочисленные попытки устранить этот сбой, которые основывались не более чем на слепых выводах. Один из этих выводов (который, казалось, работал в течение некоторого времени) заключался в том, чтобы поместить всю информацию в буфер, и в то же время выполнить единственный вызов функции write() для функции OutputStream SSLSocket, ожидая, что она все они должны передаваться как один SSL-пакет. Некоторое время создавалось впечатление, что это работает, но ошибка время от времени появлялась снова после этого так называемого «исправления».

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

Теперь загадкой было то, почему наш код передавал первый байт в отдельной записи SSL. Чтобы выяснить, почему это произошло, я запустил код в режиме отладки и вошел в исходный код JDK. Через некоторое время я наткнулся на класс sun.security.ssl.SSLSocketImpl. Функция getOutputStream() этого класса возвращает sun.security.ssl.AppOutputStream. Класс AppOutputStream реализует функцию write(). Предусмотрительно наблюдая за этим, я увидел нечто, что выглядело немного подозрительно. Было несколько строк, чтобы решить, сколько байтов данных поместить в запись SSL:

При определенных обстоятельствах, если isFirstRecordOfThePayload и c.needToSplitPayload() имеют значение true, в SSL-запись помещается не более 1 байта. Это именно то, что видел клиент и что вызывало проблему. Но оказалось, что это было сделано намеренно и не было ошибкой в ​​JDK. Но почему? А что в методе needToSplitPayload()? Это метод класса SSLSocketImpl. Прочитав об этом подробно, я понял, что это оказалось обходным путем для проблемы безопасности с TLSv1.0 и старше. К сожалению, исходный код стороннего клиента не мог справиться с этим обходным путем.

К счастью, сотрудники Sun (теперь называемой Oracle) предвидели, что обходной путь может привести к проблемам с совместимостью, поэтому они предоставили способ деактивировать его, установив для системной переменной jsse.enableCBCProtection значение false. У нас был выбор: отключить обходной путь, установив для системного свойства jsse.enableCBCProtection значение false, что сделало бы программное обеспечение уязвимым к уязвимости безопасности, или убедиться, что мы использовали TLSv1.1 или новее, что в конце концов решило руководство. .

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