Go穆斯林祈祷时间应用出现PreCurrNextMonthPrayer意外多次返回问题
问题成因
你观察到的两次返回打印,并非同一次方法执行中触发的两次return,而是两次完全独立的GetMonthPrayers()方法调用触发的defer打印,核心逻辑漏洞来自三个地方:
NewMonthPrayer的结构体覆盖逻辑错误
构造函数先初始化了一个带完整custData字段、prayers数据源切片的有效遍历结构体t,调用GetMonthPrayers()拿到取数结果后,直接把原t覆盖成了返回的纯数据结构体。这个返回的纯数据结构体本身是空的prayers切片、空的custData字段,根本不具备数据源遍历能力。- 测试代码的重复调用逻辑错误
测试误以为NewMonthPrayer返回的是带遍历能力的结构体,因此在拿到返回值后又手动调用了一次GetMonthPrayers()。但此时返回值的prayers切片是空的,循环逻辑完全不会执行,直接走到循环外的分支:因为monthlyData是初始nil值、err是初始nil值,最终返回了nil结构体,对应日志里第二次打印的零值地址。 - 日志和实际执行流的对应关系
你看到的两行defer打印分别对应:- 第一行非空打印:来自
NewMonthPrayer内部第一次调用GetMonthPrayers(),成功遍历到S3数据源并拿到有效数据,触发循环内的return逻辑 - 第二行nil打印:来自测试代码第二次手动调用
GetMonthPrayers(),因为遍历源为空,直接触发循环后的nil返回逻辑
- 第一行非空打印:来自
附带的潜在bug:S3数据源实现中连续三次给同一个err变量赋值的逻辑,会把前两次取数的错误完全覆盖,只有第三次取数的错误会被检测到,可能出现前两个月S3取数失败、但因为最后一个月取数成功就不触发Aladhan兜底的异常情况。
修复方案
1. 修正NewMonthPrayer的结构体赋值逻辑
不要用取数返回的纯数据覆盖原带遍历能力的结构体,而是把取到的有效数据填充到原结构体的对应字段:
func NewMonthPrayer(input *LookupInput, prayers ...MonthPrayerIface) (*PreCurrNextMonthPrayer, error) { t := &PreCurrNextMonthPrayer{ custData: input, prayers: prayers, } // 独立保存取数返回的纯数据结果 data, err := t.GetMonthPrayers() if err != nil { return nil, err } // 把有效数据填充回原结构体,保留原结构体的遍历能力字段 t.CurrentMonthData = data.CurrentMonthData t.PreviousMonthData = data.PreviousMonthData t.NextMonthData = data.NextMonthData return t, nil }
2. 修正测试代码的重复调用
因为NewMonthPrayer已经完成了全链路的数据源取数逻辑,不需要再手动调用GetMonthPrayers(),直接校验返回结构体的字段即可:
func TestPrayer(t *testing.T) { p, err := NewMonthPrayer( &input, &S3Store{}, &AladhanStore{}, ) if err != nil { t.Errorf("error: %s", err) } // 删除多余的 p.GetMonthPrayers() 调用 if p.CurrentMonthData == nil { t.Error("current month prayer data is empty") } t.Logf("Test address: %p", p.CurrentMonthData) t.Logf("data: %+v", p) }
3. 修复S3取数的err覆盖问题
逐次校验每一个月份的S3取数结果,任意一次取数失败都触发Aladhan兜底逻辑,避免错误被覆盖:
// 逐次取数、逐次校验err cMonthS3Data, err := s3pData(input) if err != nil { adata, err := getAladhanData(input) if err != nil { fmt.Printf("err: %s", err) return nil, err } return adata, nil } pMonthS3Data, err := s3pData(pMonthInput) if err != nil { adata, err := getAladhanData(input) if err != nil { fmt.Printf("err: %s", err) return nil, err } return adata, nil } nMonthS3Data, err := s3pData(nMonthInput) if err != nil { adata, err := getAladhanData(input) if err != nil { fmt.Printf("err: %s", err) return nil, err } return adata, nil }
内容的提问来源于stack exchange,提问作者Mo Ali
相关产品推荐
相关产品推荐

