You need to enable JavaScript to run this app. Go穆斯林祈祷时间应用出现PreCurrNextMonthPrayer意外多次返回问题
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.02 02:24:02