# Отладка A: что такое баг, виды ошибок и чтение Stack Trace<no value>

## Что мы изучим?

- Что такое **баг** (Bug).
- Три вида ошибок: **Syntax Error**, **Runtime Error**, **Logical Error**.
- Как читать **Stack Trace** (Traceback) — сообщение об ошибке в Python.
- Самые распространённые ошибки и что говорит каждая из них.

## Зачем это нужно?

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

## Объяснение с основ

### Что такое баг?

**Баг** (Bug) — **разрыв между тем, что программа должна делать, и тем, что она делает на самом деле.** Иногда программа падает, а иногда «работает», но возвращает неверный результат.

{{< box "tip" "Почему это называется Bug?" >}}
В 1947 году инженеры компьютера Mark II нашли мотылька (bug — «жук», «насекомое»), застрявшего в одном из реле, и вклеили его в рабочий журнал с подписью «first actual case of bug being found». Слово использовалось для технических неисправностей и раньше, но именно эта история сделала его знаменитым.
{{< /box >}}

### Три вида ошибок

| Вид | Когда обнаруживается | Сообщает ли о ней Python? | Пример |
|---|---|---|---|
| **Syntax Error** | **До** запуска (при проверке синтаксиса) | Да, и не выполняется ни одна строка | Пропущенный `:` или скобка |
| **Runtime Error** (исключение, Exception) | **Во время** выполнения, на проблемной строке | Да, а строки перед ней уже выполнились | Деление на 0, несуществующая переменная |
| **Logical Error** | **Никогда** автоматически | **Нет!** Код выполняется «успешно» | `+` вместо `-`, перевёрнутое условие |

Помните тему о компьютере, операционной системе и сети? Syntax Error обнаруживается, когда Python переводит код в байт-код (Bytecode), до выполнения. Runtime Error возникает, когда PVM выполняет определённую строку и терпит неудачу.

**Logical Error — самая опасная:** ни сообщения, ни красного цвета, просто неверный результат. Её находят, только если **проверяют**, что результат правильный.

### Stack Trace — карта ошибки

Когда возникает Runtime Error, Python печатает **Traceback** (его также называют Stack Trace): путь, который прошёл код до ошибки.

```text
Traceback (most recent call last):
  File "shop.py", line 9, in <module>
    total = checkout(cart)
  File "shop.py", line 5, in checkout
    return subtotal / count
ZeroDivisionError: division by zero
```

**Читайте снизу вверх:**

1. **Последняя строка** — **тип** ошибки (`ZeroDivisionError`) и **сообщение** (`division by zero`). Всегда начинайте отсюда.
2. **Строка над ней** — **где** именно: файл `shop.py`, строка 5, внутри функции `checkout`, и сам код.
3. **Ещё выше** — **кто вызвал** эту функцию: строка 9, на верхнем уровне файла (`<module>`).

Почему «Stack»? Потому что каждый вызов функции кладётся в **стек** вызовов (Call Stack). Traceback показывает стопку в момент падения — от первого вызова (сверху) до последнего (снизу).

### Распространённые ошибки в Python

| Ошибка | Значение | Частая причина |
|---|---|---|
| `SyntaxError` | Код — недопустимый синтаксис | Пропущенный `:`, скобки, кавычки |
| `IndentationError` | Неправильный отступ | Смешение пробелов, непоследовательный отступ |
| `NameError` | Имя не определено | Опечатка, использование до определения |
| `TypeError` | Операция над неподходящим типом | `"5" + 3`, вызов функции с неверным числом аргументов |
| `ValueError` | Правильный тип, недопустимое значение | `int("hello")` |
| `ZeroDivisionError` | Деление на ноль | Знаменатель, который не проверили |
| `IndexError` | Индекс за пределами списка | `items[5]`, когда элементов 3 |
| `KeyError` | Ключ, которого нет в словаре | (изучим в теме структур данных и функций) |
| `AttributeError` | У объекта нет такого атрибута/метода | `"abc".push()`, опечатка в имени метода |
| `FileNotFoundError` | Файл не существует | Неверный путь, рабочая папка (Working Directory) |

## Синтаксис

Нового синтаксиса нет. Инструмент — **чтение**. Шаблон любого Traceback:

```text
Traceback (most recent call last):
  File "<file>", line <N>, in <function or <module>>
    <the code on that line>
  ...                                  ← more frames, each a function call
<ErrorType>: <message>                 ← START READING HERE
```

И для Syntax Error (без Traceback, потому что ничего не выполнялось):

```text
  File "<file>", line <N>
    <the code>
        ^                              ← where Python noticed the problem
SyntaxError: <message>
```

## Первый пример кода

`week-2/bug_report.py`:

```python
def average(numbers):
    total = sum(numbers)
    return total / len(numbers)

scores = []
print("Average score:", average(scores))
```

**Вывод:**

```text
Traceback (most recent call last):
  File "C:\dev\kodkod6\week-2\bug_report.py", line 7, in <module>
    print("Average score:", average(scores))
                            ^^^^^^^^^^^^^^^
  File "C:\dev\kodkod6\week-2\bug_report.py", line 3, in average
    return total / len(numbers)
           ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero
```

## Разбор кода построчно

**Читаем Traceback снизу вверх:**

| Строка вывода | Что она нам говорит |
|---|---|
| `ZeroDivisionError: division by zero` | **Что** произошло: деление на ноль. |
| `line 3, in average` + `return total / len(numbers)` | **Где**: строка 3, внутри `average`. `~~~^~~~` точно отмечает операцию деления. |
| `line 7, in <module>` + `average(scores)` | **Кто вызвал**: строка 7, на верхнем уровне. `<module>` = «не внутри функции». |

**Делаем вывод:** `len(numbers)` равно 0, то есть список пуст. А кто передал пустой список? Строка 7 передала `scores`, который равен `[]`. **Настоящий баг** в том, что мы не обработали случай пустого списка. Строка, которая упала (3), — это **симптом**, а решение, что делать с пустым списком, — **исправление**:

```python
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)
```

## Ещё один пример

**Три вида ошибок в одной и той же функции.** Функция должна вычислять цену после процентной скидки.

**Syntax Error:**

```python
def price_after_discount(price, percent)
    return price - price * percent / 100
```

```text
  File "discount.py", line 1
    def price_after_discount(price, percent)
                                            ^
SyntaxError: expected ':'
```

Не хватает двоеточия. Python не выполнил **ни одной строки**.

**Runtime Error:**

```python
def price_after_discount(price, percent):
    return price - price * percent / 100

print(price_after_discount("100", 10))
```

```text
Traceback (most recent call last):
  File "discount.py", line 4, in <module>
    print(price_after_discount("100", 10))
  File "discount.py", line 2, in price_after_discount
    return price - price * percent / 100
                   ~~~~~~~~~~~~~~~~^~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'
```

Синтаксис допустим, но во время выполнения `price` — текст. `"100" * 10` «работает» (повторяет текст 10 раз!), но затем деление на 100 падает. `^` указывает на `/`.

**Logical Error:**

```python
def price_after_discount(price, percent):
    return price - percent / 100

print(price_after_discount(100, 10))
```

```text
99.9
```

**Никакой ошибки!** Но скидка 10% на 100 должна вернуть `90.0`. Мы забыли умножить на `price`. Python не может знать, что мы **имели в виду**. Только проверка по ожидаемому результату это выявит.

## Типичная ошибка

**Ошибка 1: читать Traceback сверху и паниковать.** Всегда начинайте **с последней строки**: тип и сообщение. Затем строка над ней. Чаще всего этого достаточно.

**Ошибка 2: исправлять строку, которая упала, а не причину.** В первом примере «исправление» вроде `return total / (len(numbers) + 1)` убирает ошибку, но возвращает **неверный** результат во всех случаях! Это превратило Runtime Error в Logical Error. Ещё хуже.

**Ошибка 3: искать Syntax Error только в строке, указанной в сообщении.** Иногда проблема в **предыдущей** строке:

```python
prices = [10, 20, 30
total = sum(prices)
```

```text
  File "test.py", line 1
    prices = [10, 20, 30
             ^
SyntaxError: '[' was never closed
```

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

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Практика

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

### Упражнение 1: Какой вид ошибки?

Для каждого фрагмента — это SyntaxError, Runtime Error или Logical Error? И в чём проблема?

```text
# A
if age > 18
    print("adult")

# B
print(total_price)        # total_price was never defined

# C
def area(width, height):
    return width + height   # should be the area of a rectangle
```

{{< solution >}}
- **A — SyntaxError**: в конце строки `if` не хватает двоеточия `:`. Не выполняется ни одна строка.
- **B — Runtime Error** (`NameError`): синтаксис допустим, но во время выполнения Python не может найти переменную с именем `total_price`.
- **C — Logical Error**: ошибки нет вообще, но результат неверный — площадь вычисляется умножением, `width * height`. Такую ошибку находят, только если проверяют результат.
{{< /solution >}}

### Упражнение 2: Читаем Stack Trace

```text
Traceback (most recent call last):
  File "shop.py", line 12, in <module>
    print(checkout([20, 30], 0))
  File "shop.py", line 7, in checkout
    return average_price(prices, count)
  File "shop.py", line 3, in average_price
    return sum(prices) / count
ZeroDivisionError: division by zero
```

1. Какой тип ошибки и какое сообщение?
2. В каком файле, на какой строке и в какой функции она произошла?
3. Откуда пришло проблемное значение?

{{< solution >}}
1. **`ZeroDivisionError: division by zero`** — деление на ноль. Сначала читайте **последнюю** строку.
2. В файле `shop.py`, **строка 3**, в функции `average_price` — это самая нижняя строка в списке.
3. Читаем вверх: `average_price` вызвана из строки 7 в `checkout`, а `checkout` вызвана из строки 12 с `count = 0`. Значит, проблема началась **в вызове на строке 12**, который передал 0.
{{< /solution >}}

### Упражнение 3: Логический баг в переводе температуры

```python
def to_celsius(fahrenheit):
    return fahrenheit - 32 * 5 / 9

print(to_celsius(212))
print(to_celsius(32))
```

Результаты должны быть 100 и 0, но получается что-то другое. Найдите баг и исправьте его.

**Ожидаемый вывод:**

```text
100.0
0.0
```

{{< solution >}}
```python
def to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5 / 9


print(to_celsius(212))
print(to_celsius(32))
```

```text
100.0
0.0
```

Порядок операций: умножение и деление выполняются раньше вычитания, поэтому исходный код вычислял `fahrenheit - (32 * 5 / 9)`. Скобки заставляют вычитание выполниться первым. Это **Logical Error** — никакого сообщения об ошибке, просто неверный результат.
{{< /solution >}}

### Упражнение 4: TypeError

```python
age = 25
print("Age: " + age)
```

1. Какую ошибку вы получите и почему?
2. Исправьте её двумя разными способами.

**Ожидаемый вывод:**

```text
Age: 25
```

{{< solution >}}
1. **`TypeError: can only concatenate str (not "int") to str`** — нельзя соединять текст и число через `+`.
2. Два способа:

```python
age = 25
print("Age: " + str(age))
```

```text
Age: 25
```

```python
age = 25
print("Age:", age)
```

```text
Age: 25
```

Первый переводит число в текст. Второй передаёт `print` два значения, и он добавляет между ними пробел.
{{< /solution >}}

### Упражнение 5: Среднее пустого списка

```python
def average(numbers):
    return sum(numbers) / len(numbers)

print(average([80, 90]))
print(average([]))
```

1. Что произойдёт при втором вызове и какую ошибку вы получите?
2. Исправьте функцию так, чтобы для пустого списка она возвращала 0.

**Ожидаемый вывод:**

```text
85.0
0
```

{{< solution >}}
1. `len([])` равно 0, поэтому вы получите **`ZeroDivisionError: division by zero`** — ошибку времени выполнения.
2. Проверьте этот случай **до** деления:

```python
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)


print(average([80, 90]))
print(average([]))
```

```text
85.0
0
```
{{< /solution >}}

<!-- exercises:end -->

## Итоги

- **Баг** = разрыв между желаемым и фактическим поведением.
- **Syntax Error** — до запуска, не выполняется ни одна строка. **Runtime Error** — посреди выполнения, с Traceback. **Logical Error** — без сообщения, просто неверный результат.
- **Читайте Traceback снизу вверх:** тип + сообщение → строка и функция → кто вызвал.
- Строка, которая упала, иногда лишь **симптом**. Ищите **причину**.
- Logical Error обнаруживается, только когда вы сравниваете результат с **ожидаемым значением**.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Официальная документация

- [Python Tutorial — Syntax Errors](https://docs.python.org/3/tutorial/errors.html#syntax-errors)
- [Python Tutorial — Exceptions](https://docs.python.org/3/tutorial/errors.html#exceptions)
- [Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions)

<!-- docs:end -->
