【问题标题】:Idiomatic way to return multiple errors or handle them accordingly返回多个错误或相应处理它们的惯用方式
【发布时间】:2018-12-19 20:21:13
【问题描述】:

我有这段代码,我不喜欢这种感觉,更不用说golint 不喜欢error should be the last type when returning multiple items (golint)。解释这段代码:

  • 我想让用户决定他们是否关心返回的任何错误
  • 特别是在此代码中,有时不需要或不需要音频文件,可以忽略它
  • 无论用户在做什么,都可能总是需要视频和输出文件

我愿意以任何方式重构它(无论是将其拆分、移动等)。Go 中有没有更惯用的方式来完成这样的事情?

// Download will download the audio and video files to a particular path
func (r *RedditVideo) Download() (outputVideoFileName string, outputAudioFileName string, errVideo error, errAudio error) {
    outputVideoFile, errVideo := downloadTemporaryFile(
        r.VideoURL,
        TemporaryVideoFilePrefix,
    )
    if errVideo == nil {
        outputVideoFileName = outputVideoFile.Name()
    }

    outputAudioFile, errAudio := downloadTemporaryFile(
        r.AudioURL,
        TemporaryAudioFilePrefix,
    )
    if errAudio == nil {
        outputAudioFileName = outputAudioFile.Name()
    }

    return outputVideoFileName, outputAudioFileName, errVideo, errAudio
}

同样,我发现自己后来在代码中再次使用了同样的模式:

// SetFiles sets up all of the input files and the output file
func (l *localVideo) SetFiles(inputVideoFilePath string, inputAudioFilePath string, outputVideoFilePath string) (errVideo error, errAudio error, errOutputVideo error) {
	// Set input video file
	l.inputVideoFile, errVideo = os.Open(inputVideoFilePath)
	if errVideo != nil {
		return
	}
	if errVideo = l.inputVideoFile.Close(); errVideo != nil {
		return
	}

	// Set output video file
	l.outputVideoFile, errOutputVideo = os.Create(outputVideoFilePath)
	if errOutputVideo != nil {
		return
	}
	if errOutputVideo = l.outputVideoFile.Close(); errOutputVideo != nil {
		return
	}

	// IMPORTANT! Set input audio file LAST incase it is nil
	l.inputAudioFile, errAudio = os.Open(inputAudioFilePath)
	if errAudio != nil {
		return
	}
	if errAudio = l.inputAudioFile.Close(); errVideo != nil {
		return
	}

	return
}

这次在这段代码中再次出现了一些与上面相同的情况:

  • 我们关心视频和输出的设置,可能关心也可能不关心音频
  • 有多个错误需要由用户处理

【问题讨论】:

  • 返回一个音频文件和一个视频文件和一个音频错误和一个视频错误非常非常强烈地表明这个函数做的太多了,应该是两个函数。
  • 特别注意“有时不需要或不需要音频文件,它可以被忽略”我会说这只需要重构为两个函数,每个函数做一件事而不是一个函数做两件事。
  • 我会尝试输入错误,或者只创建 DownloadVideoDownloadAudio
  • 这实际上意味着我们两次下载文件,检查我们是否可以两次打开它并两次都返回错误。在我看来,它可以重构为单一方法。
  • 有了额外的代码,这绝对是需要进行一些严重重构的情况。函数应该做一件事并且把它做好。这不仅在 Go 中是惯用的,而且在任何语言中都是普遍的良好做法。

标签: go error-handling


【解决方案1】:

您可以在标准库中看到相当多的特定函数,它们包装了更通用的非导出函数。请参阅下面的注释代码。

// download is a rather generic function
// and probably you can refactor downloadTemporaryFile
// so that even this function is not needed any more.
func (r *RedditVideo) download(prefix, url string) (filename string, error error) {
    outputFile, err := downloadTemporaryFile(
        r.VideoURL,
        prefix,
    )

    if err == nil {
        return "", fmt.Errorf("Error while download: %s", err)
    }

    return outputFile.Name(), nil
}

// DownloadVideo wraps download, handing over the values specific
// to the video download
func (r *RedditVideo) DownloadVideo() (filename string, averror error) {
    return r.download(TemporaryVideoFilePrefix, r.VideoURL)
}

// DownloadAudio wraps download, handing over the values specific
// to the audio download
func (r *RedditVideo) DownloadAudio() (filename string, averror error) {
    return r.download(TemporaryAudioFilePrefix, r.AudioURL)
}

func main() {

    r := RedditVideo{
        VideoURL: os.Args[1],
        AudioURL: os.Args[2],
    }

    var videoerror, audioerror error
    var videofileName, audiofileName string

    if videofileName, videoerror = r.DownloadVideo(); videoerror != nil {
        fmt.Println("Got an error downloading the video")
    }

    if audiofileName, audioerror = r.DownloadAudio(); audioerror != nil {
        fmt.Println("Got an error downloading the audio")
    }

    fmt.Printf("Video:\t%s\nAudio:\t%s", videofileName, audiofileName)
}

这样一来,返回的错误来自哪个下载就很明显了。

【讨论】:

    【解决方案2】:

    来自https://blog.golang.org/error-handling-and-go

    Go 代码使用错误值来指示异常状态。

    在您的情况下,音频是可选的,而视频是必需的。因此只有视频下载应该返回错误:

    // Download will download the audio and video files to a particular path
    // empty outputAudioFileName indicates inability to download Audio File
    func (r *RedditVideo) Download() (outputVideoFileName string, outputAudioFileName string, err error) {
        outputVideoFile, err := downloadTemporaryFile(
            r.VideoURL,
            TemporaryVideoFilePrefix,
        )
        if err != nil {
            return "", "", err 
        }
        outputVideoFileName := outputVideoFile.Name()
        outputAudioFile, errAudio := downloadTemporaryFile(
            r.AudioURL,
            TemporaryAudioFilePrefix,
        )
        outputAudioFileName := ""
        if errAudio == nil {
            outputAudioFileName = outputAudioFile.Name()
        } else {
            // Log error, it is still important to fix it
        }
    
        return outputVideoFileName, outputAudioFileName, nil
    } 
    

    经验法则 - 任何可以产生异常状态的代码都应该在下一行:

        if err != nil {
            return funcResult, err
        }
    

    【讨论】:

    • 我见过很多:if err = doSomething();错误!= nil {\n 返回错误\n }
    猜你喜欢
    • 2017-03-01
    • 2020-02-19
    • 2016-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 2013-06-15
    • 2018-08-08
    相关资源
    最近更新 更多