Глава 20 Налагоджуємо код

⏱️ Час на опанування теми: 15 хвилин

🤷 Для чого ми це вивчаємо:

🔑 Результати навчання:

  • Розуміння що таке програма, додаток та програмне забезпечення
  • Розуміння що таке алгоритм, кодування та програмування

🎈 Увага: Наразі ця глава знаходиться у стані активної розробки і ймовірно буде змінюватись і доповнюватись!


Ви колись замислювались, як ви навчились ходити? Напевно, це був процес навчання, спочатку ви навчились повзати, потім ви почали по тихеньку підводитись, зробили свій перший крок, через якісь час другий. І сто відсотків за весь час вашого навчання ви падали величезну кількість разів. Якщо вам цікава статистика, то малюки від 12 до 19 років падають 17 разів. На годину!

Так і в програмуванні, якщо ви робили будь-які вправи, то ви напевно робили десь помилку, чи у певний помент ваш код не працював. І це абсолютно нормальний процес. Помилятися – невідємна частина начання. Але по-перше, такі помилки у коді можуть дуже засмучувати та демотивувати, а по-друге, навчання здійснюється саме тоді, коли ці помилки виправляються. Ну і звичайно, коли ви будете працювати програмістом, вам треба буде навчитись виправляти помилки. Коли ви знаєте що у вас в коді є помилка і намагаєтесь її виправити – це називається debugging або налагодження.


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

Під помилками ми будемо розуміти ті ситуції, коли ви порушите правила Python коду. В таких ситуаціях Python не буде розуміти як інтерпретувати ваш код, виконання програми зупиниться і Python надрукує повідомлення про помилку. Зазвичай повідомлення буде друкуватись червоним коліром. Наприклад, Python не знає як додати об’єкт int та об’єкт str:

"1" + 1
## can only concatenate str (not "int") to str

Це все одно що сказати таку фарзу:

Якщо чайник закипів, її треба вимкнути.

У цьому реченні є граматична помилка, а саме замість її повинно стояти його (чайнк – чоловічого роду). Ми, люди, зможемо зрозуміти це речення з конексту, навіть якщо побачимо таку помилку. А ось Python не зможе, тому що розуміє тільки точні інструкції.

Баґ – трохи інший тип дефекту програми. Код може бути абсолютно валідним і зрозумілим для Python, але він буде містити в собі хибну логіку або просто поводитись не так, як того хоче розробник. Тобто ми :) Давайте розглянемо наступний випадок:

x = -1
if x < 0: 
    print("x є додатнім числом")
## x є додатнім числом

Код спрацював і не “викинув” помилку, проте він містить хибне твердження. Такі баґи не завжди очевидні, тому нам спершу треба їх знайти, і тільки потім “фіксити” (тобто виправляти). Якщо ми повернемось до нашого прикладу з чайником, це буде виглядати якось так:

Якщо чайник закипів, його треба увімкнути.

Граматично це речення побудовано корректно, але сенсу в ному “нуль цілих нуль десятих кілобайт”13, тобто він зовсім відсутній. Чайник який закипів, скоріш за все вже був увімкнений до того, як закипів.

У цій главі ми сфокусуємось саме на помилках, але якщо ви знайшли баґ у вашому коді, ви можете використати такі самі принципи.

20.1 Звужаємо пошук

Скоріш за все не весь ваш код не правильний, а тільки його маленька частина. Нам треба сфокусуватись саме на тій частині, яка друкує помилку. Коли ви локалізували помилку, тобто знайшли яка клітинка друкує помилку, ми зразу ж можемо забути про все, що йде після неї.

Якщо така клітанка має велику кількість рядків коду, не погана ідея розбити її на декілька клітинок, і подивитись у якій саме клітинци виникає помилка. Це також дозволяє виконувати код радок за рядком, і спостерігати значення змінних, які використовуються у хибному коді.

У коді внизу ми хочемо розрахувати процент хлопчиків у класі:

boys = 0
girls = 0
share_boys = boys / (boys + girls)
## division by zero
print(share_boys)
## name 'share_boys' is not defined

Якщо ми його розіб’ємо на частини, то побачимо що проблема у рядку share_boys = boys / (boys + girls), тому що попередні рядки виконуються без помилок.

boys = 0
girls = 0
share_boys = boys / (boys + girls)
## division by zero

Після того, як ми знайшли у якому рядку виникає помилка, ми можемо далі розібратися що саме скоїлось, а саме – ми розділили на нуль, тому що обидві змінні boys та girls дорівнюють нулю.

20.2 Спрощуємо код

Коли рядок коду складається з декількох викликів функцій чи операторів, ми можемо розгортати та розділяти на прості компоненти. Потім, ми можемо дивитись який саме компонент спровокував помилку.

У прикладі внизу ми намагаємось порахувати площу кола з радіусом R = 1:

R = 1
pi = "3*14"
int(pi.replace("*", ",")) * R ** 2
## invalid literal for int() with base 10: '3,14'

Давайте видалемо * R ** 2 з останнього рядку і подивимось чи є ще помилка. Якщо її нема – то помилка саме в * R ** 2.

R = 1
pi = "3*14"
int(pi.replace("*", ","))
## invalid literal for int() with base 10: '3,14'

Далі ми можемо ще спростити цей код, позбавившись int():

R = 1
pi = "3*14"
pi.replace("*", ",")
## '3,14'

Тепер код не повертає помилку, тому помилка утворюється саме коли ми передаємо 3,14 в int(). А, так Python не розуміє кому , як десятковий розділювач, а тільки крапку .. Тобто нам треба замінити "," на ".":

R = 1
pi = "3*14"
int(pi.replace("*", ".")) * R ** 2
## invalid literal for int() with base 10: '3.14'

А, так 3.14 – це float, а не int. Тому треба використати фунцкію float() замість int():

R = 1
pi = "3*14"
float(pi.replace("*", ".")) * R ** 2
## 3.14

20.3 Розгортаємо цикли та фунції

Якщо код, який повертає помилку, міститься у фунції або циклі, ми не можемо розділити його на клітинки. В цьому випадку, ми можемо розгорнути цикли та функції. Розгорнути цикл означає явно прописати кожну його ітерацію – те що ми робили, коли намагались зрозуміти як працюють цикли for у Главі ??.

Цикл унизу повинен взяти логаріфм кожного елемента у списку:

from math import log

temperature = [3, -2, 0]

for x in temperature: 
    log(x)
## math domain error

Давайте тепер розгорнимо цей цикл:

from math import log

temperature = [3, -2, 0]

# перша ітерація
log(temperature[0])
## 1.0986122886681098
# друга ітерація
log(temperature[1])
## math domain error
# третя ітерація
log(temperature[2])
## math domain error

А далі ми можемо розділити на клітини та виявити, що помилку гереує саме друга ітерація log(temperature[1]), тому що ми не можемо брати логаріфм від від’ємного числа.

Схожа історія трапляється з функціями. Але у випадку фунцкій ми будемо розгортати не на ітерації, а на виклик функції. Давайте подивимось на визначення та виклик функції унизу:

def print_person(name, surname, age, city):
    print("Name: " + name)
    print("Surname: " + surname)
    print("Age: " + age)
    print("City: " + city)

print_person("Boris", "Johnson", 58, "London")
## can only concatenate str (not "int") to str

Ця функція друкує ім’я, прізвище та вік у гарному форматі. Проте ця функція повертає помилку. Код який ми написали зверху еквівалентний коду унизу. Ми створимо змінні з іменами параметрів фунцкії і присвоїмо їм значення аргументів, які ми використовували при виклику функції. Далі, ми просто скопіюємо тіло функції без змін.

name = "Boris"
surname = "Johnson"
age = 58
city = "London"

print("Name: " + name)
## Name: Boris
print("Surname: " + surname)
## Surname: Johnson
print("Age: " + age)
## can only concatenate str (not "int") to str
print("City: " + city)
## City: London

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

20.4 Використовуємо функцію print()

Не завжди розгорнути цикл чи функцію – проста задача. Наприклад, якщо у нас більше десяти ітерацій, це може бути проблемою. Тоді ми можемо зробити хитрість – друкувати індкс на кожній ітерації. Таким чином, остній роздрукований індекс і буде індексом де трапляється проблема.

Ми трохи модифікуємо праклад з логаріфмом, але ідея буде та сама – в певний момент елемент буде від’єдним числом, логаріфм від якого ми не можемо взяти. Різниця буде в тому, що елементів буде 100:

from math import log

temperature = list(range(1, 101))

temperature[32] = -1 # тридцять третій елемент буде проблемним

for x in temperature: 
    log(x)
## math domain error

Для того щоб знайти проблему у коді, нам треба буде прописати прйнані 33 ітерації. А тепер ми скористаємось функцією print():

from math import log

temperature = list(range(1, 100))

temperature[32] = -1 # тридцять третій елемент буде проблемним

for i, x in enumerate(temperature): 
    print(i)
    log(x)
## math domain error

Останній індекс був 32, тому це саме те місце де треба шукати помилку.

У цій главі ми показали тільки декілька технік. Ці техніки більш неформальні і використовують здоровий глузд ніж більш наукові підходи чи наворочені інструменти. Але для початкового програміста це повинно бути більш ніж достатнім.



🤸 Вправи
1. Якщо ваша клітинка з кодом генерує помилку, то…:
2. Що буде результатом виконання наступного коду?
x = "10"
y = 5
z = x + y
print(z)
Оберіть правильну відповідь:
3. Що буде результатом виконання наступного коду?
a = 5
b = 0
c = a / b
print(c)
Оберіть правильну відповідь:
4. Як знайти індекс, на якому виникає помилка у циклі з більше, ніж 10 ітерацій?
5. Як знайти помилку у циклі, який містить 100 ітерацій?

  1. рядок з пісні групи ТНМК – Мушу йти↩︎