DEV Community

Discussion on: Attempting to Learn Go - Building Dev Log Part 04

Collapse
 
ladydascalie profile image
Benjamin Cable • Edited

Thanks for including me in your article!
I'm going to jump right back in and point out the following:

b, err := ioutil.ReadAll(r)
if err != nil {
    return nil, err
}
return b, nil

Can be simplified to:

return ioutil.ReadAll(r)

Which means, incidentally, you can get rid of getContentsOf altogether if you wanted to!

I'll also point out that you might want clearer errors in some places.
Now this is slightly nit-picky, given the context of what you are doing, but consider this:

contents, err := getContentsOf(openedFile)
if err != nil {
    fmt.Errorf("failed getting contents of file %s: %v", openedFile.Name, err)
}

You also have a couple cases of very nested / complex if/else blocks.

I'll pick on this one:

 descIntf, ok := fm["description"]
  if ok {
    description, ok := descIntf.(string)
    if ok {
      post.Description = description
    } else {
      post.Description = ""
    }
  } else {
    post.Description = ""
  }

You could refactor it in the following way:

descIntf, ok := fm["description"]
post.Description = ""
if ok {
    if desc, ok := descIntf.(string); ok {
        post.Description = desc
    }
}

The interesting part in this one is that you make the default case useful (set the description to empty string), and only act when your conditions are met.

Classic case where flipping the logic can make your code cleaner!

Collapse
 
shindakun profile image
Steve Layton

Thanks so much for the comments! Part of the reason for these posts is to not to just learn Go but put stuff out there and find out if there are "proper" (or alternate) way to do things. I was worried at first about posting since I'm kind of just going for it, the code works but definitely wouldn't be "production" quality. My hope was, (and it looks like it's being realized) that people such as yourself would take the time to comment and help where things come up.

I do need to rethink the way I'm writing these though as I typically just leave panic()'s all over the place which is fine for these kinds of projects but, really not good form. Moving to more detailed error messages and properly dealing with them would make everything more robust. I think a change in habit is really what is needed.

The nested ifs really left a bad smell, I almost just left all the checking out again but in the end, I decided some checking is better than none. There is a post from Mat Ryer which I kept meaning to go back to just for this section of code. Which I think is right in line with your comments. Your refactor is much easier to follow the the orignial and would drastically improve the overall readability of the makePost function.

Thanks again! 😃

Collapse
 
ladydascalie profile image
Benjamin Cable

definitely wouldn't be "production" quality

If it works, it works! As long as you don't have egregious design flaws, you can always refactor to a clean codebase.

I think a change in habit is really what is needed

That is quite accurate. I remember the early days of using Go within my team. people just starting to use the language would frequently make that same mistake of either just retuning all errors up the call stack, or using panic when they didn't really know how to respond to the failure.

In your case, it very well may be that you do not want to continue processing any further in case of an error, and there's nothing inherently wrong with that. Having detailed messages will help you track down your issues easily.

Here's an interesting tidbit for you to consider as well:

If you've learned about panic and recover, then you'll know that one of the interesting properties of panic is that it will still run defered function calls. If you haven't read up on that, I encourage you to read this article: golangbot.com/panic-and-recover

In your case, that means that if you open a file, and then run defer f.Close(), even if you were to panic later on, your will try and cleanup the file by closing it properly.

However, a naked panic call only takes in a string as a message, and so it is unwieldy to construct proper error messages with it.

The solution here would be to use log.Panicf, which the docs describe as "[...]equivalent to Printf() followed by a call to panic()."

That makes it easier to use, and you can reuse known idioms:

log.Panicf("releveant error context: %v", err).

Of course you will have scenarios where you do not want to panic immediately, and you do want to return the error up the callstack.

In this case, I generally do this, if I do not control the source of the error, or don't care about it very much.

thing, err := something() {
    return fmt.Errorf("wrapped error: %v", err)
}

If I do control it, or care, then I will approach this two ways:

You can easily create your own error types, to make control flow easier on yourself:

type ParseError struct { error }

Simply embedding error into the struct makes this a one liner:

fm, err := parseFrontMatter(input)
if err != nil {
    return ParseError{error:fmt.Errorf("could not parse front matter with error: %v", err)}
}

Now, instantly, you get more context, and you are also able to do this, higher up the call stack:

if err := process(thing); err != nil {
    switch e := err.(type) {
        case ParseError:
             // Do what you need in that case
        default:
             log.Panicf("Unexpected error %T: %[1]v", e)
    }
}

Side note:

This line log.Panicf("Unexpected error %T: %[1]v", e) uses explicit argument indexes, which are documented in the fmt package.

Thread Thread
 
shindakun profile image
Steve Layton

I wish I could like this twice. ;) Thanks so much for the detailed comment and the links! panic and recover are on my list of things to get to, I've only given recover a cursory glance so far. And honestly, I didn't realize log.Panicf() was a thing, don't recall seeing it used anywhere (but could have missed it) though it makes perfect sense that it would since log.Panic() exists. I only have one spot where I gave myself a more detailed error message and that was after the return of parseFrontMatter() and that was only because I hit a weird issue when I originally wrote the function. I wish I had noted it down so I could have included it in the post but forgot.

I hope to fix up the front matter code to handle errors better by next week and you've given me a lot to consider which I think will help with that section and hopefully my code over all.