开发经验
1. 时间获取与时间窗口的封装经验
Section titled “1. 时间获取与时间窗口的封装经验”在许多涉及时间窗口(如优惠券有效期校验、定时报表生成、速率限制等)的开发任务中,直接调用系统时间是一个常见的反模式:
# ❌ 不良实践:核心业务逻辑直接绑定物理系统时间,导致测试困难def is_coupon_valid(coupon): now = datetime.now() # 无法在单元测试中轻易 Mock return coupon.start_time <= now <= coupon.end_time1.1. 痛点与核心驱动力
Section titled “1.1. 痛点与核心驱动力”- 单元测试困难: 测试用例必须依赖于当前的物理时间执行,无法稳定地验证“已过期”、“未开始”等边界时间窗口逻辑。
- 不支持手动偏移: 在测试环境下,如果想人为将时间回拨或快进以调试某项特定任务,必须修改服务器的物理时钟。
1.2. 解决方案:抽象时间提供者 (Time Provider)
Section titled “1.2. 解决方案:抽象时间提供者 (Time Provider)”1.2.1. Python 实现示例
Section titled “1.2.1. Python 实现示例”通过将获取当前时间的行为封装为一个可配置的函数:
import datetime
# 统一封装的时间获取函数_time_provider = datetime.datetime.now
def get_current_time() -> datetime.datetime: """获取当前系统时间或设定的模拟时间""" return _time_provider()
def set_mock_time(mock_now: datetime.datetime): """手动设定模拟的时间起点""" global _time_provider _time_provider = lambda: mock_now
def reset_time_provider(): """恢复默认的物理系统时钟""" global _time_provider _time_provider = datetime.datetime.now1.2.2. Go 语言接口化设计示例
Section titled “1.2.2. Go 语言接口化设计示例”在 Go 中,推荐定义 Clock 接口以便于在 Service 结构体中进行 Mock 注入:
package timeutil
import "time"
// Clock 定义了时间接口,用于在业务层替换物理时间获取type Clock interface { Now() time.Time}
// realClock 实际的物理系统时钟type realClock struct{}
func (realClock) Now() time.Time { return time.Now()}
// MockClock 专门用于测试的模拟时钟type MockClock struct { CurrentTime time.Time}
func (m MockClock) Now() time.Time { return m.CurrentTime}在业务结构体中使用:
type OrderService struct { Clock timeutil.Clock // 依赖注入时钟接口}
func (s *OrderService) CheckTimeout(order *Order) bool { // 统一通过注入的 Clock 接口获取时间,方便在单元测试中注入 MockClock return s.Clock.Now().Sub(order.CreateTime) > 30*time.Minute}