Глава 7. Навык #4: Формулирование задач
"Чем чётче вы сформулируете задачу, тем лучше результат от AI. Формулирование — это новый core skill."
7.1. Core skill для работы с AI
Парадигма изменилась
Раньше: Вы пишете код → Компьютер выполняет
Сейчас: Вы формулируете задачу → AI генерирует код → Вы проверяете
Новый критический навык: формулирование задачи.
Почему формулирование важнее написания
Сравните два промпта:
Плохо:
Сделай функцию
Результат AI:
def func():
pass # Что делать?
Хорошо:
Создай функцию calculate_discount, которая:
- Принимает: цену товара (price) и процент скидки (discount_percent)
- Возвращает: цену со скидкой
- Округляет до 2 знаков после запятой
- Проверяет: discount_percent должен быть от 0 до 100
- Если невалидный discount_percent, вызывает ValueError
Результат AI:
def calculate_discount(price: float, discount_percent: float) -> float:
"""
Вычисляет цену со скидкой.
Args:
price: Исходная цена товара
discount_percent: Процент скидки (0-100)
Returns:
Цена со скидкой, округлённая до 2 знаков
Raises:
ValueError: Если discount_percent вне диапазона 0-100
"""
if not 0 <= discount_percent <= 100:
raise ValueError("Скидка должна быть от 0 до 100")
discount_amount = price * (discount_percent / 100)
final_price = price - discount_amount
return round(final_price, 2)
Разница: Вторая формулировка дала готовое, рабочее решение!
7.2. Разбиение задачи на подзадачи
Принцип: большую задачу нельзя решить за раз
Большая задача: "Создай систему аутентификации"
Слишком широко! AI может упустить важные детали.
Разбиваем на подзадачи:
-
Регистрация пользователя:
- Принимает: email, password
- Проверяет: email уникален, password сильный
- Хеширует password
- Сохраняет в БД
-
Логин пользователя:
- Принимает: email, password
- Проверяет: пользователь существует
- Проверяет: password правильный
- Генерирует JWT токен
- Возвращает токен
-
Проверка токена:
- Принимает: JWT токен
- Проверяет: токен валиден, не истёк
- Возвращает: данные пользователя
-
Логаут:
- Инвалидирует токен
Как разбивать
Задайте вопросы:
-
Какие шаги нужно выполнить?
- Перечислите последовательно
-
Какие данные нужны на входе/выходе каждого шага?
- Input → Process → Output
-
Какие проверки нужны?
- Валидация, ошибки, edge cases
-
Какие зависимости между шагами?
- Что должно быть выполнено сначала
Пример: TODO-приложение
Большая задача: "Создай TODO-приложение"
Разбиваем:
Функциональность:
- Создать задачу (title, description)
- Получить список всех задач
- Отметить задачу как выполненную
- Удалить задачу
- Фильтровать: все / активные / выполненные
Для каждой функции — подзадачи:
1. Создать задачу:
- Input: title (обязательно), description (опционально)
- Валидация: title не пустой
- Действие: Сохранить в БД с id, created_at, completed = False
- Output: Созданная задача (объект)
2. Получить список:
- Input: filter (опционально: 'all', 'active', 'completed')
- Действие: Запрос к БД с фильтром
- Output: Массив задач
И так далее.
С AI
Плохо:
AI, создай TODO-приложение
Хорошо:
AI, создай TODO-приложение с функциями:
1. create_task(title, description=None)
- Валидация: title не пустой
- Возвращает: объект Task
2. get_tasks(filter='all')
- filter: 'all' | 'active' | 'completed'
- Возвращает: список Task
3. complete_task(task_id)
- Отмечает задачу как выполненную
- Возвращает: обновлённый Task
4. delete_task(task_id)
- Удаляет задачу
- Возвращает: True/False
Используй SQLAlchemy для БД.
AI сгенерирует структурированное решение.
7.3. Чёткое описание требований
Чек-лист требований
При формулировании задачи опишите:
✅ Что делает функция (краткое описание) ✅ Входные параметры (типы, обязательные/опционные) ✅ Выходной результат (тип, формат) ✅ Валидация (какие проверки нужны) ✅ Ошибки (что делать при ошибке) ✅ Примеры использования (input → output)
Пример: функция отправки email
Плохо:
Напиши функцию для отправки email
Хорошо:
Создай функцию send_email:
Что делает:
- Отправляет email через SMTP
Параметры:
- to: str - адрес получателя (обязательно)
- subject: str - тема письма (обязательно)
- body: str - текст письма (обязательно)
- from_email: str - адрес отправителя (опционально, default: config.DEFAULT_FROM)
- cc: list[str] - копия (опционально)
- attachments: list[str] - пути к файлам (опционально)
Валидация:
- to должен быть валидный email
- subject и body не пустые
Ошибки:
- Если SMTP недоступен, вызвать ConnectionError
- Если невалидный email, вызвать ValueError
Возвращает:
- True если успешно, иначе False
Пример:
send_email(
to="user@example.com",
subject="Welcome",
body="Hello, welcome to our platform!"
)
→ True
AI сгенерирует полную, рабочую функцию!
7.4. Примеры: input/output
Почему примеры критичны
AI лучше понимает задачу через конкретные примеры.
Техника: Few-shot learning
Дайте AI несколько примеров input → output, и он поймёт паттерн.
Пример: парсинг дат
Без примеров:
Создай функцию parse_date, которая парсит строку в дату
AI может неправильно понять формат.
С примерами:
Создай функцию parse_date(date_string: str) -> datetime
Примеры:
parse_date("2025-01-15") → datetime(2025, 1, 15)
parse_date("15/01/2025") → datetime(2025, 1, 15)
parse_date("Jan 15, 2025") → datetime(2025, 1, 15)
parse_date("invalid") → ValueError
AI сгенерирует функцию, которая обрабатывает все эти случаи.
Пример: валидация пароля
Создай функцию is_strong_password(password: str) -> bool
Правила сильного пароля:
- Минимум 8 символов
- Содержит хотя бы одну цифру
- Содержит хотя бы одну заглавную букву
- Содержит хотя бы один спецсимвол (!@#$%^&*)
Примеры:
is_strong_password("Abc123!@") → True
is_strong_password("abc123") → False (нет заглавной)
is_strong_password("Abcdefg!") → False (нет цифры)
is_strong_password("Ab1!") → False (короткий)
AI сгенерирует точную валидацию.
7.5. Edge cases — крайние случаи
Что такое edge cases
Edge cases — это граничные, нестандартные ситуации:
- Пустые данные
- Null/None
- Отрицательные числа
- Очень большие числа
- Специальные символы
Почему важно указывать
AI может не предусмотреть edge cases, если вы их не укажете.
Пример: деление чисел
Без edge cases:
Создай функцию divide(a, b)
AI сгенерирует:
def divide(a, b):
return a / b
Проблема:
divide(10, 0) # ZeroDivisionError!
С edge cases:
Создай функцию divide(a: float, b: float) -> float
Edge cases:
- Если b == 0, вызвать ValueError("Cannot divide by zero")
- Если результат > 10^6, вернуть float('inf')
Примеры:
divide(10, 2) → 5.0
divide(10, 0) → ValueError
divide(10, 0.0001) → 100000.0
divide(10, 0.00000001) → inf
AI сгенерирует:
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
result = a / b
if result > 1e6:
return float('inf')
return result
Типичные edge cases
Для строк:
- Пустая строка:
"" - Только пробелы:
" " - Специальные символы:
"@#$%" - Очень длинная строка
Для чисел:
- Ноль:
0 - Отрицательные:
-5 - Очень большие:
10**100 - Дробные:
0.000001
Для массивов:
- Пустой массив:
[] - Один элемент:
[1] - Все одинаковые:
[5, 5, 5, 5] - Очень большой:
[1, 2, ..., 1000000]
7.6. Критерии успеха
Определите: когда задача решена
Плохо:
Создай функцию поиска
Что искать? Где? Как определить, что найдено?
Хорошо:
Создай функцию search_users(query: str) -> list[User]
Критерии успеха:
- Ищет по имени и email (частичное совпадение, case-insensitive)
- Возвращает максимум 10 результатов
- Сортирует по релевантности (полное совпадение → частичное)
- Время выполнения < 100ms для БД с 10,000 пользователей
Примеры:
БД: [
User(name="John Doe", email="john@example.com"),
User(name="Jane Doe", email="jane@example.com"),
User(name="Bob Smith", email="bob@test.com")
]
search_users("john") → [User(name="John Doe", ...)]
search_users("doe") → [User(name="John Doe", ...), User(name="Jane Doe", ...)]
search_users("xyz") → []
AI понимает точно, что нужно сделать.
7.7. Промпт-инжиниринг базируется на формулировании
Промпт-инжиниринг = формулирование задач
Промпт-инжиниринг — это искусство писать эффективные промпты для AI.
Но это не новый навык!
Это формулирование задач — навык, который всегда был важен:
- Для технических спецификаций
- Для постановки задач команде
- Для написания требований
С AI это стало критичным.
Структура хорошего промпта
[Роль] — кто AI
[Задача] — что нужно сделать
[Контекст] — дополнительная информация
[Требования] — детальные требования
[Примеры] — input/output
[Edge cases] — крайние случаи
[Формат вывода] — как представить результат
Пример промпта
[Роль]
Ты опытный Python разработчик, специализирующийся на веб-разработке.
[Задача]
Создай функцию для валидации номера телефона.
[Контекст]
Приложение для международных пользователей.
Поддерживаются форматы: США, Великобритания, Россия.
[Требования]
- Функция: validate_phone(phone: str, country: str) -> bool
- Поддерживаемые страны: 'US', 'UK', 'RU'
- Удаляет пробелы, дефисы, скобки перед проверкой
- Проверяет длину и формат для каждой страны
[Примеры]
validate_phone("+1 (555) 123-4567", "US") → True
validate_phone("555-1234", "US") → False (короткий)
validate_phone("+7 999 123-45-67", "RU") → True
validate_phone("+44 20 1234 5678", "UK") → True
[Edge cases]
- Пустая строка → False
- Неподдерживаемая страна → ValueError
- Только цифры, без + → пытаться определить страну по длине
[Формат вывода]
Функция с docstring и type hints.
Добавь юнит-тесты для всех примеров.
AI сгенерирует идеальное решение.
7.8. Практика: пишем промпты
Упражнение 1: Улучшите промпт
Плохой промпт:
Сделай функцию для сортировки
Ваша задача: Улучшите промпт, добавив:
- Что сортировать (тип данных)
- По какому критерию
- Порядок (возрастание/убывание)
- Примеры
Хороший промпт
Создай функцию sort_products(products: list[dict], key: str, reverse: bool = False) -> list[dict]
Что делает:
- Сортирует список товаров по указанному полю
Параметры:
- products: список словарей с полями: name, price, rating
- key: поле для сортировки ('price' | 'rating' | 'name')
- reverse: True для убывания, False для возрастания (default)
Примеры:
products = [
{"name": "Laptop", "price": 1000, "rating": 4.5},
{"name": "Mouse", "price": 20, "rating": 4.8},
{"name": "Keyboard", "price": 50, "rating": 4.2}
]
sort_products(products, "price")
→ [Mouse, Keyboard, Laptop] (по возрастанию цены)
sort_products(products, "rating", reverse=True)
→ [Mouse, Laptop, Keyboard] (по убыванию рейтинга)
Edge cases:
- Пустой список → вернуть []
- Невалидный key → ValueError
Упражнение 2: Формулирование с нуля
Задача: Нужна функция для расчёта стоимости доставки.
Правила бизнес-логики:
- Базовая стоимость: $5
- Если вес > 5 кг, добавить $2 за каждый кг сверх
- Если расстояние > 100 км, добавить $0.1 за км сверх
- Если срочная доставка, умножить на 1.5
- Бесплатная доставка при заказе > $100
Ваша задача: Напишите детальный промпт для AI.
Пример промпта
Создай функцию calculate_shipping(weight: float, distance: float, order_total: float, express: bool = False) -> float
Бизнес-логика:
1. Базовая стоимость: $5
2. Если вес > 5 кг: + $2 за каждый кг сверх 5
3. Если расстояние > 100 км: + $0.1 за каждый км сверх 100
4. Если express=True: умножить итог на 1.5
5. Если order_total > $100: стоимость доставки = $0
Параметры:
- weight: вес в кг (float, > 0)
- distance: расстояние в км (float, > 0)
- order_total: стоимость заказа в $ (float, >= 0)
- express: срочная доставка (bool, default False)
Возвращает:
- стоимость доставки (float, округлено до 2 знаков)
Примеры:
calculate_shipping(3, 50, 30) → 5.0
calculate_shipping(8, 50, 30) → 11.0 (5 + 3*2)
calculate_shipping(3, 150, 30) → 10.0 (5 + 50*0.1)
calculate_shipping(3, 50, 30, express=True) → 7.5 (5 * 1.5)
calculate_shipping(3, 50, 150) → 0.0 (бесплатная)
calculate_shipping(8, 150, 30, express=True) → 24.0 ((5 + 6 + 5) * 1.5)
Edge cases:
- weight <= 0 → ValueError
- distance <= 0 → ValueError
- order_total < 0 → ValueError
7.9. Упражнения
Задание 1: Анализ промптов
Найдите 3 примера промптов в интернете (Reddit, Twitter, блоги).
Для каждого:
- Оцените качество (чёткость, полнота)
- Улучшите промпт
- Сравните результаты AI
Задание 2: Декомпозиция
Выберите сложную задачу (например, "Создай интернет-магазин").
Разбейте на:
- Модули (3-5)
- Функции в каждом модуле (3-5)
- Детальные промпты для каждой функции
Задание 3: Edge cases
Для функции calculate_age(birth_date: str) -> int:
Перечислите все edge cases, которые нужно обработать.
Примеры
- Невалидный формат даты
- Дата в будущем
- Очень старая дата (> 150 лет)
- 29 февраля (високосный год)
- Разные форматы: "2000-01-15", "15/01/2000", "Jan 15, 2000"
Ключевые выводы главы
✅ Формулирование — новый core skill: Чем чётче промпт, тем лучше результат
✅ Разбивайте задачу: Большая задача → подзадачи
✅ Чёткие требования: Входы, выходы, валидация, ошибки
✅ Примеры критичны: Few-shot learning через input/output
✅ Edge cases обязательны: AI не догадается сам
✅ Критерии успеха: Определите, когда задача решена
✅ Промпт-инжиниринг = формулирование: Навык, который был важен всегда
✅ С AI: Время на формулирование < время на написание (но качество выше!)
Следующая глава: Паттерны проектирования — направляем AI к правильным решениям