что значит дебажить код
10 консольных команд, которые помогут дебажить JavaScript-код like a PRO
Перевели статью Амита Соланки по отладке JavaScript-кода при помощи консольных команд. По словам автора, эти команды помогут значительно повысить производительность труда программиста при поиске багов и сэкономят кучу времени.
Давайте рассмотрим команды, которые действительно способны упростить жизнь любому программисту.
Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».
Группировка логов при помощи console.group(‘name’) и console.groupEnd(‘name’)
Консольные команды console.group(‘name’) и console.groupEnd(‘name’) обеспечивают группировку нескольких разрозненных логов в единое раскрывающееся дерево, которое дает быстрый доступ к любому из логов. Более того, эти команды позволяют формировать вложенные деревья для последующей группировки.
Всего здесь три метода. Первый, console.group(‘name’), отмечает начало группировки, второй, console.groupEnd(‘name’), отмечает окончание, а console.groupCollapsed() формирует группу в режиме свернутого дерева.
Трассировка console.trace()
Если программисту необходим полный стек вызова функции, то стоит воспользоваться командой console.trace(). Пример работы с ней:
Считаем вызовы с console.count()
Команда console.count() позволяет показать количество раз, которое ее вызывали. Стоит помнить: если изменить строку лога, которая отдается команде, то отсчет пойдет по новой. При желании можно сбросить счетчик командой console.countReset().
Запуск и остановка таймера с console.time() и console.timeEnd()
Здесь все просто. Обе команды управляют таймером, позволяя запустить или остановить его. Обычно они используются для проверки производительности. Кроме того, при желании можно создать специфический таймер — в этом случае необходимо передать строку любой из команд.
Логические выражения и console.assert()
Для работы с логическими выражениями незаменима функция console.assert(). Она позволяет проверить, приняло ли какое-либо выражение значение false. Результат записывается в лог. В принципе, можно использовать if, но консоль более удобна. Пример работы с командой:
Профилирование с console.profile()
Команда console.profile() позволяет без проблем запустить профилирование. Работа руками в этом случае не нужна, поскольку команда все делает сама.
Таймлайн и console.timeStamp()
Еще одна полезная функция, console.timeStamp(), добавляет метку времени для определенных событий. Ее можно использовать для фиксации момента возвращения вызова API или записи времени завершения процесса обработки данных. Собственно, кейсов здесь много.
Очистка консоли console.clear()
Здесь все просто. Если хотите очистить консоль, используйте console.clear().
Свойство console.memory
Позволяет отображать размер буфера. Использовать его стоит, если не слишком понятна статистика производительности, а знакомиться с графиками времени нет.
Вывод таблицы с console.table()
Функция console.table() позволяет выводить небольшую таблицу, с которой разработчик может взаимодействовать. В качестве аргумента здесь используется массив, его необходимо передать для вызова.
Собственно, на этом сегодня все. Если у вас собственные лайфхаки отладки, делитесь ими в комментариях, — мы будем вам благодарны.
Отладка Javascript
(Примечание: наверное эта статья больше для новичков. Так что не судите строго)
Казалось бы — да что тут рассказывать? Всё же очевидно. Но вопрос этот мне задают с завидной частотой. Да и мне есть, что рассказать.
Приведу конкретные примеры и расскажу, как я их решаю.
Видим цель, не видим препятствий
JavaScript вывалил ошибку? Замечательно! Нет, это конечно плохо, но гораздо лучше, чем если бы он промолчал (да, бывает и такое!) в случае ошибки.
Наша цель — понять, что же, чёрт побери, произошло? Но сначала — рекламная пауза лирическое отступление: средства JavaScript Debug’а в основных браузерах.
Debuggers
Как «тормознуть» поток
А вот вариант с условной остановкой:
Мне так нравится гораздо больше, чем ставить «бряку»: так я пишу код и дебажу его по сути в одном месте, а не в двух.
Debug через alert()
Это наименее информативный debug, который к тому же останавливает поток JavaScript. Да к тому же модальный по отношению к браузеру. Забудьте, что он вообще существует.
Особенность breakpoint’ов
Рассмотренные варианты все, как один, тормозят поток JavaScript. Это плохо!
Почему? Если в момент остановки скрипта у вас был запущен AJAX-запрос или Timeout, и ответ не успел вернутся — он может уже не вернутся никогда. Согласитесь, в современных web-проектах этого добра хватает. Поэтому в момент «экстренной остановки» скрипта мы уже не сможем адекватно debug’ать дальше — часть логики будет безвозвратно утеряна.
Поэтому я стараюсь избегать на практике debug с остановкой.
«Debugging JavaScript with breakpoints is bad, mmkay?» © Mr. Mackey, South Park
Однако: breakpoint есть breakpoint, и если вы исследуете ну очень запущенный баг — тут без остановки не обойтись (надо будет сделать watch текущим переменным и т.д.)
«Правильный» debug
Пример №1
JavaScript показал ошибку. Надо понять — что к чему.
Включаем в debugger’е режим «Break On Error»:
Воспроизводим ошибку снова. JavaScript останавливается. Видим место ошибки, делаем watch и точно определяем, в чём же дело.
Пример №2
CASE:
JavaScript не показал ошибку. Но вы знаете, что она есть (как суслик). Да, такое иногда бывает.
CASE:
Надо просто продебажить некий код. Скажем, посмотреть, что происходит по нажатию кнопки или после AJAX-загрузки данных.
Тут сложней — надо найти, с чего начать.
Немного искусства
// условная остановка
if (allowBreakpoints == true )
debugger;
Конечно данный способ не идеален. Бывает, что даёт промашки. Но это хороший способ, мне он сильно помагает в работе.
Так, значит место в коде нашли, бряку поставили. Если не хочется (или просто нельзя) изменять исходный код — можно вместо ключевого слова debugger поставить brakepoint в средстве отладки.
Пример №3
Тот же случай: надо продебажить некий код. Скажем, посмотреть, что происходит по нажатию кнопки или после AJAX-загрузки данных. Но в этот раз мы не можем тормозить поток JavaScript по описанным мной причинам.
CASE UNO
variable_to_watch — объект, который изменился с момента вывода в консоль. А хочется увидить его состояние именно на момент вызова.
CASE DUO
Нет консоли? Пишем в адресной строке: javascript:alert(temp_var.objMethod()); void 0;
Пример №4
Ещё один пример. Возможно, немного странный. Хочется продебажить метод 3d-party-framework’а (например, ExtJS), но вот беда — нельзя тормозить JavaScript и нет доступа к исходному коду (правда странный пример? 🙂
Что же делать? Я делаю так:
Создаём файл с патчем: my-ext-patch.js, и подключаем его после ext-all.js
Внутри пишем что-то вроде:
Ext.form.Form.render = function (container) < // Wrap'им метод
// А вот и дебаг
console.log(container);
// Возможны варианты:
// console.dir(container);
// console.log(window.t = container);
// debugger;
Извращение? Возможно. Но мне нравится >:)
Эпилог
Как дебажить программу
Введение
Обычный алгоритм решения задачи выглядит так:
Написать код решения (или лучше части решения).
Тестировать решение на своем компьютере (или лучше потестировать часть решения)
3+) Если не работает, то дебажить, пока не заработает на всех тестах.
4+++) Спросить преподавателя. Впрочем, этим методом можно и нужно пользоваться на более ранних стадиях, если вы только начинаете заниматься программированием
Здесь будут собраны советы о том, что делать в пунктах, начиная с 2.
Про первый пункт советов не будет 🙁 Разве что совет, что придумать решение необходимо ДО того, как писать код.
Что делать, пока пишешь код
Проследите путь каждой переменной и каждого цикла
Нужно для каждой переменной понимать * чему она равна изначально * как она меняется в какой части программы * какие значения она может понимать, а какие нет.
Также и для каждого цикла нужно понимать * какая будет первая итерация * в какой момент он закончится * сколько всего будет итераций * нет ли где-то выхода за границы массива
Пишите по кодстайлу
Зачем? * увидеть ошибку в структурированном коде гораздо проще, чем в каше * чужому человеку и вам в будущем читать ваш непонятный код будет гораздо сложнее и неприятнее * в промышленном программировании именно так все и пишут, полезно привыкнуть
Делите код на функции как можно более сильно
Почему? * в длинном сплошном коде легко не заметить ошибку * отдельные функции очень просто тестировать * если функция применяется несколько раз, то не нужно копировать код, что хорошо, так как при копировании кода можно скопировать ошибку, увидеть ее, исправить в одном месте, а в другом она * в промышленном программировании именно так все и пишут, полезно привыкнуть
Вот пример, когда в программе без функций найти ошибку тяжело: (код Сережи Карпышева)
Если все это сделать, то код станет понятным, в функции из одной строчки ошибиться очень тяжело. А еще каждую функцию можно легко потестировать отдельно, разобрать крайние случаи для каждой отдельной функции.
Как дебажить, если знаешь тест, на котором не работает
TODO описать процесс и виды дебага
Самые частые ошибки, если у вас все работает, а в тестирующей системе нет
Выход за границы массива
Поэтому: * C++ чаще всего не видит эту ошибку и работает дальше. * Ваша программа может работать по-разному у вас и на сервере.
Слишком маленький тип данных
Нужно знать, что * int содержит числа примерно от \(-2 * 10^9\) до \(2 * 10^9\) * unsigned int содержит числа примерно от \(0\) до \(4 * 10^9\) * long long содержит числа примерно от \(-9 * 10^18\) до \(9 * 10^18\) * unsigned long long содержит числа примерно от \(0\) до \(18 * 10^18\)
И всегда проверять, что когда вы * складываете * умножаете * вычитаете
числа, они все еще помещаются в выбранный вами тип.
Например, в задаче про быстрое возведение в степень, по условию гарантируется, что \(x, N, P \leq 2 * 10^9.\) Это значит, что в тип int они влезут.
Крайние случаи
Вы решаете не ту задачу
Перечитайте условие, возможно, вы просто праивльно решили не ту задачу, которую надо сдать, а немножко другую. Возможно, вы забыли про какой-то важный случай или принципиально делаете что-то не то.
Стресс-тестирование
Но если вы достаточно много тестировали свою задачу, то вам может помочь стресс-тестирование
Как дебажить запросы, используя только Spark UI
Егор Матешук (CDO AdTech-компании Квант и преподаватель в OTUS) приглашает Data Engineer’ов принять участие в бесплатном Demo-уроке «Spark 3.0: что нового?». Узнаете, за счет чего Spark 3.0 добивается высокой производительности, а также рассмотрите другие нововведения.
Также приглашаем посмотреть запись трансляции Demo-урока «Написание эффективных пользовательских функций в Spark» и пройти вступительное тестирование по курсу «Экосистема Hadoop, Spark, Hive»!
У вас уже есть все, что вам нужно для дебаггинга запросов
В этой статье я попытаюсь продемонстрировать, как дебажить задачу Spark, используя только Spark UI. Я запущу несколько задач Spark и покажу, как Spark UI отражает выполнение задачи. Также я поделюсь с вами несколькими советами и хитростями.
Вот как выглядит Spark UI.
Мы начнем с вкладки SQL, которая включает в себя достаточно много информации для первоначального обзора. При использовании RDD в некоторых случаях вкладки SQL может и не быть.
А вот запрос, который запускаю в качестве примера
Перевод разъяснений в правой части:
id = hash(125), count=1000
2. id = hash(124), count=900 …
Я запросил 40 исполнителей для сессии, однако при запуске вы можете увидеть, что он предоставил мне всего 10 активных исполнителей. Это может быть связано с тем, что не работают хосты или Spark не нуждается в таком большом количестве исполнителей. Это также может вызвать задержку в планировании задач, поскольку у вас всего 10 исполнителей, а вам нужно 40, что скажется на параллелизме.
Вкладка Environment
Вкладка Environment содержит подробную информацию обо всех параметрах конфигурации, которые в данный момент использует сессия spark.
Посмотрите, как здесь отражены параметры, отраженные мной ранее. Это полезно хотя бы просто для того, чтобы убедиться, что предоставленная вами конфигурация принята.
Вкладка Storage
Но перед этим давайте вернемся немного назад и потратим несколько минут на некоторые основы кэширования.
Есть два способа кэширования Dataframe:
→ df.persist
Для кэширования набора данных требуется несколько свойств.
→ df.cache
Под капотом это вызывает метод «persist». Обратимся к исходному коду
DISK_ONLY: хранить (persist) данные на диске только в сериализованном формате.
MEMORY_ONLY: [хранить данные в памяти только в десериализованном формате.
MEMORY_AND_DISK: хранить данные в памяти, а если памяти недостаточно, вытесненные блоки будут сохранены на диске.
MEMORY_ONLY_SER: этот уровень Spark хранит RDD как сериализованный объект Java (однобайтовый массив на партицию). Это более компактно по сравнению с десериализованными объектами. Но это увеличивает накладные расходы на CPU.
MEMORY_AND_DISK_SER: аналогично MEMORY_ONLY_SER, но с записью на диск, когда данные не помещаются в памяти.
Давайте воспользуемся df.cache в нашем примере и посмотрим, что произойдет
a.cache()
—> На вкладке Storage ничего не видно. Как вы можете догадаться, это из-за ленивого вычисления
Это потому, что данные в памяти десериализованы и несжаты. Это результирует в большем объеме памяти по сравнению с диском.
Так что, когда вы хотите принимаете решение о том, кэшировать или нет, помните об этом.
Далее мы рассмотрим вкладки Jobs и Stages, причины множества проблем можно отдебажить с помощью этих вкладок.
Я вижу, что для указанного выше запроса запускаются 3 задачи. Но 2 из них пропущены. Обычно это означает, что данные были извлечены из кэша и не было необходимости повторно выполнять данный этап. Кроме того, Spark выполняет множество фиктивных задач для оценки данных. Пропуск задач мог быть связан и с этим.
Давайте же глубоко погрузимся в задачу, которая не была пропущена. Это визуализация DAG для задачи
Мы ясно видим, что эта задача состоит из двух этапов, разделенных операцией перемешивания/обмена. Stages означают, что данные были записаны на диск для использования в следующем процессе.
Давайте углубимся во вкладку stages.
Вот несколько моментов, которые следует отметить:
→ Продолжительность (duration): В нашем примере минимальная и максимальная продолжительность составляет 0,4 и 4 секунды соответственно. Это может быть связано с несколькими причинами, и мы постараемся отдебажить их в пунктах ниже.
→ Время десериализации задачи (Task deserialization time):
В нашем примере в рамках десериализации задачи некоторое время тратится и на другие задачи. Одной из основных причин было выполнение процессов сборки мусора в исполнителях. У меня выполнялись другие процессы, в которых были кэшированы некоторые данные, что приводило к сборке мусора. Процессам сборки мусора предоставляется наивысший приоритет, и они останавливают все запущенные процессы в угоду обслуживания процесса сборки мусора. Если вы видите, что ваш процесс не потребляет много памяти, первым шагом для решения такой проблемы может быть разговор с администратором/OPS.
→ Задержка планировщика (Scheduler delay): максимальная задержка планировщика составляет 0,4 секунды. Это означает, что одна из задач должна была ждать отправки еще 0,4 секунды. Большое это значение или маленькое, зависит от вашего конкретного юзкейса.
* PROCESSLOCAL → Эта задача будет запущена в том же процессе, что и исходные данные
* NODELOCAL → Эта задача будет запущена на том же компьютере, что и исходные данные
* RACKLOCAL → Эта задача будет запущена в том же блоке, что и исходные данные
* NOPREF (Отображается как ANY) → Эта задача не может быть запущена в том же процессе, что и исходные данные, или это не имеет значения.
Я надеюсь, что эта статья послужит вам в качестве руководства по дебаггингу на Spark UI с целью устранения проблем с производительностью Spark. В Spark 3 есть много дополнительных функций, которые тоже стоит посмотреть.
Вы также можете связаться со мной в Linkedin.
Хотите узнать, как Apache Druid индексирует данные для сверхбыстрых запросов? Узнайте об этом здесь:
Интересно развиваться в данном направлении? Участвуйте в трансляции мастер-класса «Spark 3.0: что нового?» и оцените программу курса «Экосистема Hadoop, Spark, Hive»!