共计 2269 个字符,预计需要花费 6 分钟才能阅读完成。
为什么我们需要封装 Skill?
刚开始接触 Skill 开发时,很多同学会直接调用原始 API。这样做短期内看起来简单,但随着项目复杂度上升,很快就会遇到这些问题:

- 重复代码泛滥:每个调用点都要写相同的参数校验和错误处理
- 维护困难:API 变更时需要修改所有调用处的代码
- 缺乏统一控制:日志记录、性能监控等横切关注点难以统一管理
- 错误处理不完整:容易遗漏某些异常情况的处理
举个典型例子,假设我们要调用一个天气查询 API,未经封装的代码可能是这样的:
# 糟糕的示例:直接调用 API
response = requests.get('https://weather.api/latest',
params={'city': city},
headers={'Authorization': f'Bearer {token}'})
if response.status_code != 200:
print('查询失败')
data = response.json()
这段代码至少有 3 个问题:
- 没有验证输入参数的有效性
- 错误处理过于简单
- 相同的代码会在项目中重复出现
封装设计原则
遵循 SOLID 原则可以帮我们设计出更好的 Skill 封装:
- 单一职责原则:每个 Skill 类 / 函数只做一件事
- 开闭原则:通过扩展而非修改来增加新功能
- 里氏替换原则:子类应该可以替换父类而不破坏程序
- 接口隔离原则:客户端不应依赖它不需要的接口
- 依赖倒置原则:依赖抽象而非具体实现
其中最重要的是 单一职责 和接口隔离。比如天气查询 Skill 应该:
- 只负责天气数据获取
- 不处理用户认证
- 不负责数据持久化
实战代码示例
下面用 Python 演示一个完整的天气 Skill 封装(TypeScript 版本思路类似):
from typing import Optional, Dict
import requests
import logging
from dataclasses import dataclass
# 首先定义数据模型
@dataclass
class WeatherData:
temperature: float
humidity: float
description: str
class WeatherSkill:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://weather.api/v1"
self.logger = logging.getLogger(__name__)
def get_weather(self, city: str) -> Optional[WeatherData]:
"""
获取指定城市的天气数据
:param city: 城市名称
:return: WeatherData 对象或 None(查询失败时)
"""
# 1. 输入验证
if not city or not isinstance(city, str):
self.logger.error("Invalid city parameter")
return None
try:
# 2. 构造请求
response = requests.get(f"{self.base_url}/current",
params={"city": city},
headers={"Authorization": f"Bearer {self.api_key}"},
timeout=5
)
# 3. 处理响应
response.raise_for_status()
data = response.json()
# 4. 数据转换
return WeatherData(temperature=data["temp"],
humidity=data["humidity"],
description=data["weather"][0]["description"]
)
except requests.exceptions.RequestException as e:
self.logger.error(f"Weather API error: {str(e)}")
return None
这个实现包含了:
- 类型注解(Python 3.6+)
- 输入验证
- 完善的错误处理
- 日志记录
- 超时控制
- 清晰的数据模型
进阶优化技巧
1. 性能优化
对于频繁调用的 Skill,可以考虑增加缓存:
from functools import lru_cache
class WeatherSkill:
@lru_cache(maxsize=100)
def get_weather(self, city: str) -> Optional[WeatherData]:
# 原有实现...
2. 线程安全
如果 Skill 会被多线程调用,需要注意:
- 避免共享可变状态
- 使用线程安全的数据结构
- 考虑为每个线程创建 Skill 实例
常见错误与解决方案
- 错误:忽略超时设置
- 现象:请求卡死导致程序无响应
-
修复:为所有网络请求添加合理的 timeout
-
错误:过度宽泛的异常捕获
- 现象:catch Exception 会掩盖真正的问题
-
修复:只捕获预期的异常类型
-
错误:硬编码配置
- 现象:API 密钥等写在代码中
- 修复:通过构造函数或环境变量注入
重构路线图
如果你已经有一些未经封装的 Skill 代码,可以按这个步骤重构:
- 识别重复的 API 调用代码
- 提取公共参数和逻辑
- 定义清晰的数据模型
- 实现统一的错误处理
- 添加必要的日志和监控
- 编写单元测试验证
思考题
- 如何设计支持热插拔的 Skill 组合系统?
- 当 Skill 需要访问多个数据源时,如何避免依赖耦合?
- 如何实现 Skill 的版本兼容和灰度发布?
希望这篇指南能帮你构建更健壮的 Skill 系统。记住:好的封装不是一次性工作,而是随着需求变化不断演进的过程。
正文完
