Hacker News new | ask | show | jobs
by kelnos 643 days ago
I think a better solution is to write smaller, single-purpose functions. To refer to your example downthread, you should have one function that only writes the file, and another that does the "whole" operation -- calling the function to write the file, checking for errors, and then updating the database.

Then you can use defer in the file-writing function if you so please, and not bother to close at the end explicitly, without issue. A more robust example might be to even include the sync call in the deferred function (and even clean up the file itself on error). To re-use your example from your blog post:.

    func helloNotes() (err error) {
        var f *os.File
        f, err = os.Create("/home/joeshaw/notes.txt")
        if err != nil {
            return
        }

        defer func() {
            if err == nil {
                err = f.Sync()
            }

            cerr := f.Close()
            if err == nil {
                err = cerr
            }

            if err != nil {
                os.Remove("/home/joeshaw/notes.txt")
            }
        }()

        err = io.WriteString(f, "hello world")
        return
    }
I would probably move that out into a helper, though, so I could do something like

    defer SafeClose(f, &err)
instead, and be able to use it elsewhere. Hell, even without defer, it's nice to have a helper that will sync and close for you so you can avoid the boilerplate, if you have lots of different bits of code that writes files.

FWIW, I'm not sure why you are so negative on named return values, but I'm at best a novice Go programmer, so perhaps I don't fully understand why they aren't great (I guess it does look weird to me to have bare `return` statements that do actually return a value even though it doesn't look like it). Your argument about the return value possibly being modified after the core function finishes being unintuitive doesn't really strike me as a big deal either.