一个微妙的go错误类型无法帮助解决

73 阅读2分钟

我在阅读Ricardo Gerardi撰写的非常优秀的 《Go中强大的命令行应用程序》时遇到了这个错误

下面的startInterval 函数包含一个嵌套的periodic 函数,该函数将在pomodoro定时器中被调用。很好!

问题是,当应用程序运行时,没有任何东西更新应用界面。就像计时器没有运行一样,但如果我暂停并启动计时器,应用程序的用户界面就会更新一次,然后保持冻结。怎么了?

调试(为什么是的,我是PRINTS调试器)显示,periodic 函数确实按照设计被调用,由ticker通道每秒钟获得一个值。问题出在哪里?你能看到它吗?

startInterval := func() {
  i, err := pomodoro.GetInterval(config)
  errorCh <- err

  start := func(i pomodoro.Interval) {
    message := "Take a break"
    if i.Category == pomodoro.CategoryPomodoro {
      message = "Focus on your task"
    }

    w.update([]int{}, i.Category, message, "", redrawCh)
  }

  end := func(pomodoro.Interval) {
    w.update([]int{}, "", "Nothing running...", "", redrawCh)
  }

  periodic := func(pomodoro.Interval) {
    w.update(
      []int{int(i.ActualDuration), int(i.PlannedDuration)},
      "", "",
      fmt.Sprint(i.PlannedDuration-i.ActualDuration),
      redrawCh,
    )
  }

  errorCh <- i.Start(ctx, config, start, periodic, end)
}

问题是,periodic 函数接受了一个pomodoro.Interval ,但没有把它分配给一个变量。类型匹配(万岁,小类型的胜利),但没有任何东西确保或要求类型被分配。

periodic 函数中,i 的值默默地指向在外层函数范围内创建的i

i, err := pomodoro.GetInterval(config) // <-- this i

这意味着该函数起作用了,并且确实在适当的ticker间隔中被调用。但是,应用程序的用户界面实际上从未看到变化,因为i 内的值都保持着间隔开始时的样子。

如何解决?只需改变一个字符,将pomodoro.Interval 赋值给它i:

periodic := func(i pomodoro.Interval) {
  w.update(
    []int{int(i.ActualDuration), int(i.PlannedDuration)},
    "", "",
    fmt.Sprint(i.PlannedDuration-i.ActualDuration),
    redrawCh,
  )
}

有了这个改动,书中的代码例子就能如愿以偿。

一个更好的方法:不要影射变量名

对我来说,更好的方法是完全不影射外部的i 值,而是给periodic 函数自己的变量名,作为它所期望的时间间隔:

periodic := func(pomodoro.Interval) {
  w.update(
    []int{int(activeInterval.ActualDuration), int(activeInterval.PlannedDuration)},
    "", "",
    fmt.Sprint(activeInterval.PlannedDuration-activeInterval.ActualDuration),
    redrawCh,
  )
}

有了这个改变,那么Go编译器就会立即嚷嚷出这个问题:没有任何东西设置了activeInterval!很容易解决:

periodic := func(activeInterval pomodoro.Interval) {
  w.update(
    []int{int(activeInterval.ActualDuration), int(activeInterval.PlannedDuration)},
    "", "",
    fmt.Sprint(activeInterval.PlannedDuration-activeInterval.ActualDuration),
    redrawCh,
  )
}

替代方案

Go 可以对未分配的函数参数发出警告。它甚至可以要求函数参数被分配。在这种情况下,如果你只想匹配签名,并且真的不在乎实际使用函数参数本身,那么你可以把它赋值给_ ,这在Go中明确是一个 "忽略这个变量 "的声明。

更棒的是将其提升到Elixir级别,它允许给被忽略的变量一个名称,以表明被忽略的内容,例如_interval