Паттерны проектирования и лучшие практики для навыков

Паттерны проектирования навыков, отточенные обширной практикой, помогают быстро принимать верные решения при появлении новых требований.

В этой статье собраны 7 самых часто используемых паттернов проектирования и сценарии их применения.


Паттерн 1: Навык с единственной ответственностью

Каждый навык делает одно дело и делает его хорошо.

Чем более сфокусирована функциональность, тем точнее описание, тем выше точность срабатывания и тем проще сопровождение.

ПодходПример
Правильно: единственная ответственностьpdf-to-text (занимается только извлечением текста)
Неправильно: смешанные обязанностиpdf-tools (чтение, конвертация, объединение, нанесение водяных знаков... всё в одном месте)

Если вы обнаружите, что описание навыка должно быть очень длинным, чтобы прояснить, чего он «не делает», это обычно означает, что он взял на себя слишком много обязанностей и его следует разделить.


Паттерн 2: Защитная проверка входных данных

Выполняйте всю проверку входных данных централизованно в точке входа скрипта навыка и переходите к бизнес-логике только после успешной проверки.

Пример

# Путь к файлу: scripts/defensive_entry.py
# Шаблон: Защитная проверка входных данных — централизованная валидация в точке входа, пропуск после успешного прохождения проверки

import sys
import os
import json

def validate(args) -> list:
    """Централизованно проверить все входные данные и вернуть список ошибок (пустой список означает, что все проверки пройдены)"""
    errors = []

    if not args.file:
        errors.append("Отсутствует аргумент --file")
    elif not os.path.exists(args.файл):
        errors.append(f"Файл не существует: {args.file}")
    elif os.path.getsize(args.file) == 0:
        errors.append("Содержимое файла пусто")

    if args.limit < 1:
        errors.append("--limit должен быть больше 0")

    return errors

def run(args):
    """Точка входа в бизнес-логику, вызывается только после успешной валидации"""
    # Выполнить фактическую обработку...
    return {"status": "success", "file": args.file}

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--file",  required=False, default="")
    parser.add_argument("--limit", type=int, default=100)
    args = parser.parse_args()

    # 1. Централизованная валидация
    errors = validate(args)
    if errors:
        result = {"status": "input_error", "errors": errors}
        print(json.dumps(result, ensure_ascii=False))
        sys.exit(1)

    # 2. Выполнить бизнес-логику после успешной валидации
    result = run(args)
    print(json.dumps(result, ensure_ascii=False))

Шаблон 3: Постепенный вывод

Для навыков, выполнение которых занимает более 5 секунд, прогресс должен выводиться поэтапно, а не оставлять пользователя в молчаливом ожидании.

## 执行流程(渐进式输出示例)

### 步骤一:读取文件
运行读取脚本后,立即告知用户:
> 已读取文件:example_data.csv(1,024 行,8 列)正在进行数据清洗...

### 步骤二:数据清洗
清洗完成后,告知用户:
> 数据清洗完成:删除重复行 3 条,空值填充 12 处。正在生成报告...

### 步骤三:生成报告
报告生成后,展示下载链接并说明内容:
> 报告已生成,包含统计摘要和数据质量评估。

Шаблон 4: Идемпотентные операции

Результаты выполнения навыка должны быть идемпотентными: при одном и том же входе, сколько бы раз он ни выполнялся, вывод должен быть одинаковым.

Неидемпотентные операции (например, добавление содержимого при каждом выполнении) могут вызвать путаницу при повторных попытках пользователей.

Пример

# Путь к файлу: scripts/idempotent_output.py
import os
from datetime import datetime

OUTPUT_DIR = "/mnt/user-data/outputs"

def get_output_path(base_name: str, suffix: str = ".xlsx") -> str:
    """
Сгенерировать детерминированный путь вывода

Идемпотентный подход: перезаписывать одноимённый файл при каждом выполнении (вместо добавления или создания нового файла)
Обеспечить, чтобы один и тот же вход всегда соответствовал одному и тому же выходному файлу
    """

    # Метод A: Фиксированное имя файла (один и тот же вход всегда перезаписывает)
    return os.path.join(OUTPUT_DIR, f"{base_name}_report{suffix}")

    # Метод B (отметка времени): генерировать новый файл каждый раз (неидемпотентно, но сохраняет историю)
    # ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    # return os.path.join(OUTPUT_DIR, f"{base_name}_{ts}{suffix}")

Шаблон 5: Явный сигнал завершения

После завершения выполнения Skill он должен подать чёткий "сигнал завершения", чтобы пользователь знал, что задача завершена и не нужно продолжать ждать.

## 任务完成后的输出规范

无论成功还是失败,最后一步必须输出一个明确的状态行:

成功时:
> 任务完成:报告已生成,共处理 1,024 行数据,耗时约 3 秒。

失败时:
> 任务中断:在"数据清洗"步骤遇到错误(原因:文件编码不是 UTF-8)。
> 建议:将文件另存为 UTF-8 格式后重新上传。

Шаблон 6: Декларация совместимости версий

Объявляйте совместимые версии модели в frontmatter файла SKILL.md, чтобы предотвратить неожиданное поведение в неподдерживаемых средах.

---
name: data-analyzer
version: 1.2.0
description: 分析 CSV/Excel 数据,生成统计报告。
compatibility:
  claude_models:
    - claude-sonnet-4-20250514   # 经过验证的模型版本
    - claude-opus-4              # 同样支持
  python: ">=3.8"
  platforms:
    - linux    # Claude 计算机使用环境
---

Шаблон 7: Быстрый отказ

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

Пример

# Путь к файлу: scripts/fail_fast.py
import sys
import json

def assert_condition(condition: bool, message: str, hint: str = ""):
    """Если условия не выполнены, немедленно вывести ошибку и выйти (шаблон Fail Fast)"""
    if not condition:
        result = {
            "status":  "error",
            "message": message,
            "hint": hint
        }
        print(json.dumps(result, ensure_ascii=False))
        sys.exit(1)

# Выполнить все предварительные проверки централизованно в начале скрипта
import os
file_path = sys.argv[1] if len(sys.argv) > 1 else ""

assert_condition(bool(file_path),
                 "Отсутствует аргумент пути к файлу",
                 "Использование: python script.py <путь к файлу>")

assert_condition(os.path.exists(file_path),
                 f"Файл не существует: {file_path}",
                 "Пожалуйста, проверьте, правильно ли указан путь, или загрузите файл заново")

assert_condition(file_path.endswith(".csv"),
                 "Формат файла не поддерживается, ожидается .csv",
                 f"Фактический файл: {file_path}")

# Все проверки пройдены, выполните фактическую логику
print(json.dumps({"status": "ok", "file": file_path}))

Краткий справочник по шаблонам

ПаттернОсновной принципРешаемая проблема
Единственная ответственностьОдин Skill делает одну вещьНеточное срабатывание, сложно поддерживать
Защитная проверка входных данныхЦентрализованная валидация в точке входаОшибки сообщаются только на середине выполнения
Постепенный выводСообщать о прогрессе поэтапноПользователи не знают, выполняется ли процесс
Идемпотентные операцииТот же вход — тот же выходПовторные попытки дают запутанные результаты
Явный сигнал завершенияПредоставлять уведомление о конечном состоянии по завершенииПользователи не знают, завершена ли задача
Декларация совместимости версийОбъявите поддерживаемые среды выполненияАномальное поведение при несовпадении среды
Быстрый отказНемедленно останавливаться при ошибкеПродолжение работы при ошибках приводит к некорректному выводу
Другие расширения