ЧАСТЬ II: ФУНДАМЕНТ МАСТЕРСТВА

Навык #4: Формулирование задач

Глава 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 может упустить важные детали.

Разбиваем на подзадачи:

  1. Регистрация пользователя:

    • Принимает: email, password
    • Проверяет: email уникален, password сильный
    • Хеширует password
    • Сохраняет в БД
  2. Логин пользователя:

    • Принимает: email, password
    • Проверяет: пользователь существует
    • Проверяет: password правильный
    • Генерирует JWT токен
    • Возвращает токен
  3. Проверка токена:

    • Принимает: JWT токен
    • Проверяет: токен валиден, не истёк
    • Возвращает: данные пользователя
  4. Логаут:

    • Инвалидирует токен

Как разбивать

Задайте вопросы:

  1. Какие шаги нужно выполнить?

    • Перечислите последовательно
  2. Какие данные нужны на входе/выходе каждого шага?

    • Input → Process → Output
  3. Какие проверки нужны?

    • Валидация, ошибки, edge cases
  4. Какие зависимости между шагами?

    • Что должно быть выполнено сначала

Пример: TODO-приложение

Большая задача: "Создай TODO-приложение"

Разбиваем:

Функциональность:

  1. Создать задачу (title, description)
  2. Получить список всех задач
  3. Отметить задачу как выполненную
  4. Удалить задачу
  5. Фильтровать: все / активные / выполненные

Для каждой функции — подзадачи:

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, блоги).

Для каждого:

  1. Оцените качество (чёткость, полнота)
  2. Улучшите промпт
  3. Сравните результаты AI

Задание 2: Декомпозиция

Выберите сложную задачу (например, "Создай интернет-магазин").

Разбейте на:

  1. Модули (3-5)
  2. Функции в каждом модуле (3-5)
  3. Детальные промпты для каждой функции

Задание 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 к правильным решениям