Глава 4. Навык #1: Понимание как работает код
"Код, который вы не понимаете, — это код, которому вы не можете доверять."
4.1. Почему понимание критично в эпоху AI
Представьте ситуацию:
Вы попросили AI создать функцию для обработки платежей. AI сгенерировал 50 строк кода. Код работает. Тесты проходят. Вы деплоите на продакшн.
Через неделю — утечка данных пользователей.
Что пошло не так?
AI сгенерировал код с уязвимостью SQL injection. Вы не заметили, потому что не понимали, что делает каждая строка.
Парадокс AI-разработки:
Чем проще генерировать код, тем важнее его понимать.
AI может написать код быстрее вас. Но AI:
- ❌ Не понимает контекст вашего бизнеса
- ❌ Может сгенерировать уязвимый код
- ❌ Может использовать неоптимальный подход
- ❌ Может не учесть edge cases
Вы — последняя линия защиты.
Ваша роль изменилась:
Раньше: Писатель кода Сейчас: Архитектор + Ревьюер + Отладчик
Но для всех этих ролей нужно понимать код.
4.2. Базовые концепции программирования
Программа — это инструкции для компьютера
Код — это способ сказать компьютеру: "Сделай вот так, шаг за шагом."
Пример (на человеческом языке):
1. Возьми два числа
2. Сложи их
3. Покажи результат
Пример (на Python):
def add(a, b):
result = a + b
return result
print(add(2, 3)) # Вывод: 5
Что здесь происходит:
- Мы определяем функцию
add, которая принимает два параметра:aиb - Внутри функции мы складываем
aиb, результат сохраняем в переменнуюresult - Возвращаем
result - Вызываем функцию с аргументами 2 и 3, печатаем результат
Важно понимать КАЖДОЕ слово:
def— объявление функцииadd— имя функции(a, b)— параметры (входные данные):— начало блока кодаresult = a + b— операция сложения и присваиваниеreturn— возвращаемое значениеprint()— вывод на экран
С AI: AI может сгенерировать этот код. Ваша задача — понять каждую строку.
4.3. Переменные и типы данных
Переменные — это контейнеры для данных
Аналогия: Коробка с наклейкой (имя переменной) и содержимым (значение).
name = "Шахруз"
age = 40
is_developer = True
name— строка (string)age— целое число (integer)is_developer— булево значение (boolean: True/False)
Типы данных
Примитивные типы:
Числа:
integer = 42 # Целое число
floating = 3.14 # Число с плавающей точкой
Строки:
text = "Growing Software"
char = 'A'
Булевы:
is_active = True
is_deleted = False
Составные типы:
Списки (массивы):
numbers = [1, 2, 3, 4, 5]
names = ["Ева", "Адам", "Софья"]
Словари (объекты):
user = {
"name": "Шахруз",
"age": 40,
"role": "сеньор"
}
Почему типы важны?
Разные типы — разные операции:
a = 5
b = 3
print(a + b) # 8 (сложение чисел)
x = "Hello"
y = "World"
print(x + y) # HelloWorld (конкатенация строк)
# Ошибка!
z = "5" + 3 # TypeError: cannot concatenate str and int
С AI: AI знает типы, но может сгенерировать код, где типы конфликтуют. Вы должны заметить.
4.4. Память: стек и куча
Как компьютер хранит данные
Когда программа работает, она использует два вида памяти:
Стек (Stack):
- Быстрая память
- Хранит локальные переменные и вызовы функций
- Автоматически освобождается
def calculate(x):
result = x * 2 # result хранится в стеке
return result
answer = calculate(5) # answer хранится в стеке
Куча (Heap):
- Медленная память
- Хранит объекты и большие данные
- Нужно освобождать вручную (в низкоуровневых языках) или через сборщик мусора
# Большой список хранится в куче
big_list = [i for i in range(1000000)]
Почему это важно?
Утечки памяти:
Если программа создаёт объекты в куче и не освобождает их, память заканчивается → программа падает.
# Плохо: утечка памяти
cache = []
while True:
cache.append(large_data) # Память растёт бесконечно
С AI: AI может сгенерировать код с утечкой памяти. Вы должны понимать, как работает память.
4.5. Циклы, условия, функции
Условия (if/else)
Выбор: сделать A или B в зависимости от условия.
age = 17
if age >= 18:
print("Совершеннолетний")
else:
print("Несовершеннолетний")
Вложенные условия:
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
Циклы (loops)
Повторение действия несколько раз.
For loop:
# Вывести числа от 1 до 5
for i in range(1, 6):
print(i)
While loop:
# Вывести числа, пока они меньше 5
i = 1
while i <= 5:
print(i)
i += 1
Опасность бесконечного цикла:
# Плохо: бесконечный цикл
while True:
print("Навсегда!") # Программа зависнет
С AI: AI может сгенерировать бесконечный цикл по ошибке. Вы должны заметить.
Функции
Переиспользуемый блок кода.
def greet(name):
return f"Привет, {name}!"
print(greet("Шахруз")) # Привет, Шахруз!
print(greet("Ева")) # Привет, Ева!
Параметры и аргументы:
def multiply(a, b): # a, b — параметры
return a * b
result = multiply(3, 4) # 3, 4 — аргументы
Возвращаемое значение:
def add(x, y):
return x + y # Возвращает результат
# Без return
def print_sum(x, y):
print(x + y) # Только печатает, не возвращает
result1 = add(2, 3) # result1 = 5
result2 = print_sum(2, 3) # result2 = None (печатает 5)
4.6. Асинхронность и параллелизм
Синхронное выполнение (обычное)
Код выполняется последовательно, строка за строкой.
print("1")
print("2")
print("3")
# Вывод:
# 1
# 2
# 3
Асинхронное выполнение
Код может выполняться не по порядку.
Зачем? Чтобы не ждать медленных операций (запрос к API, чтение файла).
import asyncio
async def fetch_data():
print("Начало запроса...")
await asyncio.sleep(2) # Симуляция медленного запроса
print("Данные получены!")
return "data"
async def main():
print("Запускаем...")
data = await fetch_data()
print(f"Результат: {data}")
asyncio.run(main())
# Вывод:
# Запускаем...
# Начало запроса...
# (ждём 2 секунды)
# Данные получены!
# Результат: data
Параллелизм
Несколько задач выполняются одновременно.
import asyncio
async def task1():
await asyncio.sleep(1)
print("Task 1 done")
async def task2():
await asyncio.sleep(1)
print("Task 2 done")
async def main():
await asyncio.gather(task1(), task2())
asyncio.run(main())
# Обе задачи выполнятся параллельно за 1 секунду (а не 2)
С AI: Асинхронный код сложнее. AI может ошибиться с await. Вы должны понимать, как работает асинхронность.
4.7. Структуры данных (массивы, объекты, списки)
Массивы/Списки
Упорядоченная коллекция элементов.
fruits = ["apple", "banana", "orange"]
# Доступ по индексу (начинается с 0)
print(fruits[0]) # apple
print(fruits[1]) # banana
# Добавление элемента
fruits.append("grape")
# Длина списка
print(len(fruits)) # 4
# Итерация
for fruit in fruits:
print(fruit)
Словари (хеш-таблицы, объекты)
Коллекция пар ключ-значение.
user = {
"name": "Шахруз",
"age": 40,
"city": "Ташкент"
}
# Доступ по ключу
print(user["name"]) # Шахруз
# Добавление/изменение
user["role"] = "сеньор"
user["age"] = 41
# Итерация
for key, value in user.items():
print(f"{key}: {value}")
Множества (Sets)
Неупорядоченная коллекция уникальных элементов.
numbers = {1, 2, 3, 3, 4}
print(numbers) # {1, 2, 3, 4} — дубликаты удалены
Когда использовать что:
- Список — когда важен порядок
- Словарь — когда нужен быстрый доступ по ключу
- Множество — когда нужны уникальные элементы
4.8. Практика: читаем и понимаем AI-код
Упражнение 1: Анализ AI-кода
AI сгенерировал этот код. Что он делает? Есть ли проблемы?
def process_data(data):
result = []
for item in data:
if item > 0:
result.append(item * 2)
return result
numbers = [1, -2, 3, 0, 5]
output = process_data(numbers)
print(output)
Ваш анализ:
- Что делает функция?
- Какой будет
output? - Есть ли edge cases, которые не учтены?
- Можно ли улучшить?
Ответ
- Что делает: Фильтрует положительные числа и умножает на 2
- Output:
[2, 6, 10] - Edge cases: Что если
dataпустой? Что если элементы не числа? - Улучшение:
def process_data(data):
if not data:
return []
result = []
for item in data:
if isinstance(item, (int, float)) and item > 0:
result.append(item * 2)
return result
Упражнение 2: Найди ошибку
AI сгенерировал код для подсчёта среднего:
def calculate_average(numbers):
total = 0
for num in numbers:
total += num
return total / len(numbers)
scores = [80, 90, 70]
avg = calculate_average(scores)
print(f"Средний балл: {avg}")
Проблема: Что будет, если передать пустой список?
calculate_average([]) # ZeroDivisionError!
Исправление:
def calculate_average(numbers):
if not numbers:
return 0
total = sum(numbers)
return total / len(numbers)
Упражнение 3: Оптимизация
AI сгенерировал код для проверки, есть ли дубликаты:
def has_duplicates(items):
for i in range(len(items)):
for j in range(i + 1, len(items)):
if items[i] == items[j]:
return True
return False
# Тестируем
print(has_duplicates([1, 2, 3, 4])) # False
print(has_duplicates([1, 2, 3, 1])) # True
Вопрос: Какая сложность алгоритма? (O(?))
Ответ
Сложность: O(n²) — два вложенных цикла
Проблема: Медленно для больших списков
Оптимизация: Использовать множество (set) — O(n)
def has_duplicates(items):
return len(items) != len(set(items))
# Или более явно:
def has_duplicates(items):
seen = set()
for item in items:
if item in seen:
return True
seen.add(item)
return False
4.9. Упражнения
Задание 1: Понимание кода
Попросите AI сгенерировать функцию для проверки, является ли строка палиндромом.
Затем:
- Прочитайте код построчно
- Объясните, что делает каждая строка
- Найдите edge cases
- Улучшите код
Задание 2: Поиск ошибок
Попросите AI создать функцию для расчёта факториала.
Затем:
- Проверьте на edge cases: factorial(0), factorial(-5)
- Есть ли риск переполнения для больших чисел?
- Есть ли риск бесконечной рекурсии?
Задание 3: Оптимизация
Попросите AI создать функцию для поиска наибольшего элемента в списке.
Затем:
- Проверьте сложность алгоритма
- Можно ли сделать быстрее?
- Что будет, если список пустой?
Ключевые выводы главы
✅ Понимание важнее генерации: AI пишет код, вы понимаете
✅ Каждая строка имеет значение: Читайте код построчно
✅ Типы данных критичны: Разные типы → разные операции
✅ Память имеет значение: Стек и куча, утечки памяти
✅ Асинхронность сложна: AI может ошибиться
✅ Edge cases важны: Пустые списки, null, отрицательные числа
✅ Всегда проверяйте AI-код: Ошибки, уязвимости, оптимизация
Следующая глава: Архитектурное мышление — проектирование систем с AI