BEST_PRACTICES_REFACTORING.md
6.29 KB
最佳实践重构总结
重构日期: 2025-11-12
重构人员: AI Assistant
重构原则: 代码简洁、类型安全、单一职责
重构目标
- 移除兼容代码:不再支持多种数据格式,统一使用最佳实践
- 修复前端422错误:支持日期时间字符串的 range filter
- 保持代码简洁:单一数据流,清晰的类型定义
修改文件
1. /home/tw/SearchEngine/api/models.py
修改:RangeFilter 模型
之前:只支持数值 (float)
gte: Optional[float] = Field(None, description="大于等于 (>=)")
之后:支持数值和日期时间字符串
gte: Optional[Union[float, str]] = Field(None, description="大于等于 (>=)。数值或ISO日期时间字符串")
理由:
- 价格字段需要数值过滤:
{"gte": 50, "lte": 200} - 时间字段需要字符串过滤:
{"gte": "2023-01-01T00:00:00Z"} - 使用
Union[float, str]同时支持两种类型
2. /home/tw/SearchEngine/search/es_query_builder.py
修改:build_facets 方法
之前:兼容字典和 Pydantic 模型
else:
if isinstance(config, dict):
field = config['field']
facet_type = config.get('type', 'terms')
...
else:
# Pydantic模型
field = config.field
facet_type = config.type
...
之后:只支持标准格式(str 或 FacetConfig)
# 简单模式:只有字段名(字符串)
if isinstance(config, str):
field = config
...
# 高级模式:FacetConfig 对象
else:
field = config.field
facet_type = config.type
...
理由:
- API 层已经定义标准格式:
List[Union[str, FacetConfig]] - 移除字典支持,保持单一数据流
- 代码更简洁,意图更清晰
3. /home/tw/SearchEngine/search/searcher.py
修改:_standardize_facets 方法
之前:兼容字典和 Pydantic 模型
else:
field = config.get('field') if isinstance(config, dict) else config.field
facet_type = config.get('type', 'terms') if isinstance(config, dict) else getattr(config, 'type', 'terms')
之后:只支持标准格式
else:
# FacetConfig 对象
field = config.field
facet_type = config.type
理由:
- 与 build_facets 保持一致
- 移除冗余的类型检查
- 代码更清晰易读
数据流设计
标准数据流(最佳实践)
API Request
↓
Pydantic 验证 (SearchRequest)
↓ facets: List[Union[str, FacetConfig]]
Searcher.search()
↓
QueryBuilder.build_facets()
↓ 只接受 str 或 FacetConfig
ES Query (aggs)
↓
ES Response (aggregations)
↓
Searcher._standardize_facets()
↓ 只处理 str 或 FacetConfig
List[FacetResult] (Pydantic 模型)
↓
SearchResponse
↓
API Response (JSON)
类型定义
# API 层
facets: Optional[List[Union[str, FacetConfig]]] = None
# Query Builder 层
def build_facets(facet_configs: Optional[List[Union[str, 'FacetConfig']]])
# Searcher 层
def _standardize_facets(...) -> Optional[List[FacetResult]]
# Response 层
facets: Optional[List[FacetResult]] = None
测试结果
✅ 所有测试通过
| 测试场景 | 状态 | 说明 |
|---|---|---|
| 字符串 facets | ✓ | ["categoryName_keyword"] |
| FacetConfig 对象 | ✓ | {"field": "price", "type": "range", ...} |
| 数值 range filter | ✓ | {"gte": 50, "lte": 200} |
| 日期时间 range filter | ✓ | {"gte": "2023-01-01T00:00:00Z"} |
| 混合使用 | ✓ | filters + range_filters + facets |
✅ 前端 422 错误已修复
问题:前端筛选 listing time 时返回 422 Unprocessable Entity
原因:RangeFilter 只接受 float,不接受日期时间字符串
解决:修改 RangeFilter 支持 Union[float, str]
验证:
curl -X POST /search/ \
-d '{"query": "玩具", "range_filters": {"create_time": {"gte": "2023-01-01T00:00:00Z"}}}'
# ✓ 200 OK
代码质量
✅ 无 Linter 错误
No linter errors found.
✅ 类型安全
- 所有类型明确定义
- Pydantic 自动验证
- IDE 类型提示完整
✅ 代码简洁
- 移除所有兼容代码
- 单一数据格式
- 清晰的控制流
最佳实践原则
1. 单一职责
每个方法只处理一种标准格式,不兼容多种输入
2. 类型明确
使用 Pydantic 模型而不是字典,类型在编译时就确定
3. 数据流清晰
API → Pydantic → 业务逻辑 → Pydantic → Response
4. 早期验证
在 API 层就验证数据,不在内部做多重兼容检查
5. 代码可维护
- 删除冗余代码
- 保持一致性
- 易于理解和修改
对比总结
| 方面 | 重构前 | 重构后 |
|---|---|---|
| 数据格式 | 字典 + Pydantic(兼容) | 只有 Pydantic |
| 类型检查 | 运行时多重检查 | 编译时类型明确 |
| 代码行数 | 更多(兼容代码) | 更少(单一逻辑) |
| 可维护性 | 复杂(多种路径) | 简单(单一路径) |
| 错误处理 | 隐式容错 | 明确验证 |
| RangeFilter | 只支持数值 | 支持数值+字符串 |
| 前端兼容 | 422 错误 | 完全兼容 |
后续建议
1. 代码审查
- [x] 移除所有字典兼容代码
- [x] 统一使用 Pydantic 模型
- [x] 修复前端 422 错误
2. 测试覆盖
- [x] 字符串 facets
- [x] FacetConfig 对象
- [x] 数值 range filter
- [x] 日期时间 range filter
- [x] 混合场景
3. 文档更新
- [x] 最佳实践文档
- [x] 数据流设计
- [ ] API 文档更新
4. 性能优化
- [ ] 添加请求缓存
- [ ] 优化 Pydantic 验证
- [ ] 监控性能指标
结论
本次重构成功实现了以下目标:
✅ 代码简洁:移除所有兼容代码,保持单一数据流
✅ 类型安全:统一使用 Pydantic 模型,编译时类型检查
✅ 功能完整:修复前端 422 错误,支持所有筛选场景
✅ 可维护性:代码清晰,易于理解和修改
核心原则:不做兼容多种方式的代码,定义一种最佳实践,所有模块都适配这种新方式
版本: v3.1
状态: ✅ 完成并通过测试
下次更新: 根据业务需求扩展