Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Помилки й налагодження»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

3 питання

try - блок коду, у якому може статися помилка. catch - що робити, якщо вона сталася. finally - що виконати в будь-якому разі.

function loadSettings() {
  try {
    const raw = localStorage.getItem('settings');
    return JSON.parse(raw) ?? {};
  } catch (error) {
    console.warn('Пошкоджені налаштування, використовуємо типові', error);
    return {};
  } finally {
    hideLoader();   // виконається і при успіху, і при помилці
  }
}

Як це працює:

  • коли в try кидається виняток, решта блоку пропускається і керування переходить у catch;
  • у catch доступний об'єкт помилки: error.name, error.message, error.stack;
  • якщо помилки не було, catch не виконується;
  • finally виконується завжди - навіть якщо в try чи catch був return чи нова помилка.

Змінна в catch необов'язкова:

try {
  JSON.parse(input);
} catch {
  showError('Некоректний JSON');
}

Кинути власну помилку - throw:

if (!email.includes('@')) {
  throw new Error('Некоректна електронна адреса');
}

Кидати варто об'єкти Error (або нащадки), а не рядки: throw 'помилка' не має стеку викликів, і налагоджувати таке набагато важче.

Типові помилки новачків:

  • ловити все й мовчати: catch (e) {} - помилка зникла, а програма працює з неправильними даними. Як мінімум - записати в лог;
  • занадто великий try: обгорнути всю функцію, а потім не розуміти, який рядок кинув виняток. Краще обгортати лише те, що справді може впасти і що ви знаєте, як обробити;
  • очікувати, що try зловить помилку в колбеку setTimeout чи обробнику події - не зловить, бо колбек виконується пізніше, коли try уже завершено;
  • асинхронний код без await: try { fetchData() } catch не зловить відхилений Promise. Потрібно try { await fetchData() } catch.

finally для прибирання: зняти індикатор завантаження, розблокувати кнопку, закрити з'єднання - те, що має статися незалежно від результату.

Докладніше в документації: try...catch

Усі вбудовані помилки - нащадки Error. За типом зазвичай одразу зрозуміло, що пішло не так.

TypeError - операція над значенням не того типу. Найчастіша помилка у фронтенді:

const user = null;
user.name;              // TypeError: Cannot read properties of null (reading 'name')
undefined();            // TypeError: undefined is not a function
const PI = 3.14; PI = 3; // TypeError: Assignment to constant variable

Зазвичай означає, що дані прийшли не такі, як очікувалося: API повернув null, елемент не знайдено на сторінці, змінну не ініціалізовано.

ReferenceError - звернення до змінної, якої не існує або яка ще не ініціалізована:

console.log(totla);     // ReferenceError: totla is not defined (друкарська помилка)
console.log(x); let x = 1;   // ReferenceError: Cannot access 'x' before initialization

SyntaxError - код чи дані неможливо розібрати:

JSON.parse('{ name: "Оля" }');   // SyntaxError: ключі в JSON мають бути в лапках

Синтаксична помилка в самому файлі скрипта не дає йому виконатися взагалі - жоден рядок файлу не спрацює.

RangeError - значення поза допустимими межами:

new Array(-1);              // RangeError: Invalid array length
(1.5).toFixed(200);         // RangeError: toFixed() digits argument must be between 0 and 100
function f() { f(); } f();  // RangeError: Maximum call stack size exceeded

URIError - некоректний URI: decodeURIComponent('%').

AggregateError - кілька помилок разом, наприклад від Promise.any, коли відхилилися всі Promise (error.errors - масив причин).

Як розрізняти в коді:

try {
  data = JSON.parse(text);
} catch (error) {
  if (error instanceof SyntaxError) {
    showError('Сервер повернув некоректні дані');
  } else {
    throw error;   // невідому помилку - далі, а не ковтати
  }
}

Читати повідомлення уважно: Cannot read properties of undefined (reading 'map') каже не про map, а про те, що об'єкт перед .map - undefined. Шукати треба, звідки взялося це undefined.

Стек викликів (error.stack) показує, через які функції пройшов виконання до помилки, - з номерами рядків. У DevTools по ньому можна клікнути й перейти до коду.

Докладніше в документації: Error

console.log - найпростіший спосіб, але для складних помилок значно ефективніше зупинити виконання й подивитися на стан програми зсередини.

Оператор debugger у коді зупиняє виконання в цьому місці, якщо відкрито DevTools:

function calculateTotal(items) {
  debugger;   // виконання зупиниться тут
  return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}

Без відкритих DevTools оператор нічого не робить. Але в репозиторій його не комітять - лінтери на це скаржаться.

Точки зупинки в DevTools (вкладка Sources) - те саме без зміни коду: клік по номеру рядка. Види:

  • умовна - зупиняється лише якщо вираз істинний (item.price < 0). Незамінно в циклі на тисячу ітерацій;
  • logpoint - нічого не зупиняє, а друкує вираз у консоль. Як console.log, але без зміни коду й перезбирання;
  • на подію - зупинитися на будь-якому click, submit, таймері (панель Event Listener Breakpoints);
  • на зміну DOM - зупинитися, коли елемент змінюється чи видаляється (контекстне меню елемента у вкладці Elements). Відповідає на питання «хто змінює цей елемент?»;
  • на запит - коли URL запиту містить рядок (XHR/fetch Breakpoints);
  • на винятки - «Pause on exceptions», включно з перехопленими.

Після зупинки:

  • Scope - значення всіх змінних у поточній функції й замиканнях;
  • Call Stack - як ми сюди потрапили; клік на попередню функцію показує її стан;
  • Watch - вирази, що перераховуються на кожному кроці;
  • консоль працює в контексті зупиненої функції - можна викликати функції й змінювати змінні;
  • кроки: Step over (наступний рядок), Step into (увійти у функцію), Step out (вийти з неї), Resume.

Корисні дрібниці:

  • $0 у консолі - поточний виділений у Elements елемент;
  • «Blackbox script» / «Add to ignore list» - не заходити в код бібліотек (Vue, Alpine) при покроковому виконанні;
  • вкладка Network - запити, відповіді, статуси; «Copy as fetch» для повторення запиту в консолі;
  • у Livewire-проєктах корисно дивитися запити livewire/update у Network - там видно, що компонент відправив і що отримав.

Мініфікований код налагоджувати неможливо - для цього потрібні source maps, які у Vite в режимі розробки є завжди.

Докладніше в документації: debugger