Глава 20 Налагоджуємо код
⏱️ Час на опанування теми: 15 хвилин
🤷 Для чого ми це вивчаємо:
🔑 Результати навчання:
- Розуміння що таке програма, додаток та програмне забезпечення
- Розуміння що таке алгоритм, кодування та програмування
🎈 Увага: Наразі ця глава знаходиться у стані активної розробки і ймовірно буде змінюватись і доповнюватись!
Ви колись замислювались, як ви навчились ходити? Напевно, це був процес навчання, спочатку ви навчились повзати, потім ви почали по тихеньку підводитись, зробили свій перший крок, через якісь час другий. І сто відсотків за весь час вашого навчання ви падали величезну кількість разів. Якщо вам цікава статистика, то малюки від 12 до 19 років падають 17 разів. На годину!
Так і в програмуванні, якщо ви робили будь-які вправи, то ви напевно робили десь помилку, чи у певний помент ваш код не працював. І це абсолютно нормальний процес. Помилятися – невідємна частина начання. Але по-перше, такі помилки у коді можуть дуже засмучувати та демотивувати, а по-друге, навчання здійснюється саме тоді, коли ці помилки виправляються. Ну і звичайно, коли ви будете працювати програмістом, вам треба буде навчитись виправляти помилки. Коли ви знаєте що у вас в коді є помилка і намагаєтесь її виправити – це називається debugging або налагодження.
Перед тим як поглибитись у техніки та стратегії налагодження коду, давайте спершу погоримо про різницю між баґами та помилками.
Під помилками ми будемо розуміти ті ситуції, коли ви порушите правила Python коду. В таких ситуаціях Python не буде розуміти як інтерпретувати ваш код, виконання програми зупиниться і Python надрукує повідомлення про помилку. Зазвичай повідомлення буде друкуватись червоним коліром. Наприклад, Python не знає як додати об’єкт int
та об’єкт str
:
"1" + 1
## can only concatenate str (not "int") to str
Це все одно що сказати таку фарзу:
Якщо чайник закипів, її треба вимкнути.
У цьому реченні є граматична помилка, а саме замість її повинно стояти його (чайнк – чоловічого роду). Ми, люди, зможемо зрозуміти це речення з конексту, навіть якщо побачимо таку помилку. А ось Python не зможе, тому що розуміє тільки точні інструкції.
Баґ – трохи інший тип дефекту програми. Код може бути абсолютно валідним і зрозумілим для Python, але він буде містити в собі хибну логіку або просто поводитись не так, як того хоче розробник. Тобто ми :) Давайте розглянемо наступний випадок:
= -1
x if x < 0:
print("x є додатнім числом")
## x є додатнім числом
Код спрацював і не “викинув” помилку, проте він містить хибне твердження. Такі баґи не завжди очевидні, тому нам спершу треба їх знайти, і тільки потім “фіксити” (тобто виправляти). Якщо ми повернемось до нашого прикладу з чайником, це буде виглядати якось так:
Якщо чайник закипів, його треба увімкнути.
Граматично це речення побудовано корректно, але сенсу в ному “нуль цілих нуль десятих кілобайт”13, тобто він зовсім відсутній. Чайник який закипів, скоріш за все вже був увімкнений до того, як закипів.
У цій главі ми сфокусуємось саме на помилках, але якщо ви знайшли баґ у вашому коді, ви можете використати такі самі принципи.
20.1 Звужаємо пошук
Скоріш за все не весь ваш код не правильний, а тільки його маленька частина. Нам треба сфокусуватись саме на тій частині, яка друкує помилку. Коли ви локалізували помилку, тобто знайшли яка клітинка друкує помилку, ми зразу ж можемо забути про все, що йде після неї.
Якщо така клітанка має велику кількість рядків коду, не погана ідея розбити її на декілька клітинок, і подивитись у якій саме клітинци виникає помилка. Це також дозволяє виконувати код радок за рядком, і спостерігати значення змінних, які використовуються у хибному коді.
У коді внизу ми хочемо розрахувати процент хлопчиків у класі:
= 0
boys = 0
girls = boys / (boys + girls) share_boys
## division by zero
print(share_boys)
## name 'share_boys' is not defined
Якщо ми його розіб’ємо на частини, то побачимо що проблема у рядку share_boys = boys / (boys + girls)
, тому що попередні рядки виконуються без помилок.
= 0 boys
= 0 girls
= boys / (boys + girls) share_boys
## division by zero
Після того, як ми знайшли у якому рядку виникає помилка, ми можемо далі розібратися що саме скоїлось, а саме – ми розділили на нуль, тому що обидві змінні boys
та girls
дорівнюють нулю.
20.2 Спрощуємо код
Коли рядок коду складається з декількох викликів функцій чи операторів, ми можемо розгортати та розділяти на прості компоненти. Потім, ми можемо дивитись який саме компонент спровокував помилку.
У прикладі внизу ми намагаємось порахувати площу кола з радіусом R = 1
:
= 1
R = "3*14"
pi int(pi.replace("*", ",")) * R ** 2
## invalid literal for int() with base 10: '3,14'
Давайте видалемо * R ** 2
з останнього рядку і подивимось чи є ще помилка. Якщо її нема – то помилка саме в * R ** 2
.
= 1
R = "3*14"
pi int(pi.replace("*", ","))
## invalid literal for int() with base 10: '3,14'
Далі ми можемо ще спростити цей код, позбавившись int()
:
= 1
R = "3*14"
pi "*", ",") pi.replace(
## '3,14'
Тепер код не повертає помилку, тому помилка утворюється саме коли ми передаємо 3,14
в int()
. А, так Python не розуміє кому ,
як десятковий розділювач, а тільки крапку .
. Тобто нам треба замінити ","
на "."
:
= 1
R = "3*14"
pi int(pi.replace("*", ".")) * R ** 2
## invalid literal for int() with base 10: '3.14'
А, так 3.14
– це float
, а не int
. Тому треба використати фунцкію float()
замість int()
:
= 1
R = "3*14"
pi float(pi.replace("*", ".")) * R ** 2
## 3.14
20.3 Розгортаємо цикли та фунції
Якщо код, який повертає помилку, міститься у фунції або циклі, ми не можемо розділити його на клітинки. В цьому випадку, ми можемо розгорнути цикли та функції. Розгорнути цикл означає явно прописати кожну його ітерацію – те що ми робили, коли намагались зрозуміти як працюють цикли for
у Главі ??.
Цикл унизу повинен взяти логаріфм кожного елемента у списку:
from math import log
= [3, -2, 0]
temperature
for x in temperature:
log(x)
## math domain error
Давайте тепер розгорнимо цей цикл:
from math import log
= [3, -2, 0]
temperature
# перша ітерація
0]) log(temperature[
## 1.0986122886681098
# друга ітерація
1]) log(temperature[
## math domain error
# третя ітерація
2]) log(temperature[
## 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)
"Boris", "Johnson", 58, "London") print_person(
## can only concatenate str (not "int") to str
Ця функція друкує ім’я, прізвище та вік у гарному форматі. Проте ця функція повертає помилку. Код який ми написали зверху еквівалентний коду унизу. Ми створимо змінні з іменами параметрів фунцкії і присвоїмо їм значення аргументів, які ми використовували при виклику функції. Далі, ми просто скопіюємо тіло функції без змін.
= "Boris"
name = "Johnson"
surname = 58
age = "London"
city
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
= list(range(1, 101))
temperature
32] = -1 # тридцять третій елемент буде проблемним
temperature[
for x in temperature:
log(x)
## math domain error
Для того щоб знайти проблему у коді, нам треба буде прописати прйнані 33 ітерації. А тепер ми скористаємось функцією print()
:
from math import log
= list(range(1, 100))
temperature
32] = -1 # тридцять третій елемент буде проблемним
temperature[
for i, x in enumerate(temperature):
print(i)
log(x)
## math domain error
Останній індекс був 32, тому це саме те місце де треба шукати помилку.
У цій главі ми показали тільки декілька технік. Ці техніки більш неформальні і використовують здоровий глузд ніж більш наукові підходи чи наворочені інструменти. Але для початкового програміста це повинно бути більш ніж достатнім.
🤸 Вправи
x = "10"
y = 5
z = x + y
print(z)
a = 5
b = 0
c = a / b
print(c)
рядок з пісні групи ТНМК – Мушу йти↩︎