Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement custom error handling to Storer interface #590

Closed
wants to merge 6 commits into from

Conversation

patrickmarabeas
Copy link
Collaborator

Implements custom error handling to the Storer interface. All methods must now return an error.

Errors that are covered:

  • Puppy value < 0
  • Puppy ID not found

Fixes / improvements also included:

  • The value property in Puppy has had its type changed to an int to represent a numerical cent figure so that it may be properly throw an error if the value supplied is less than 0
  • Proper locking in SyncMap

Fixes #589

Adds a NewError function which creates a new error of the custom error
type `Error` (contraining fields: `Message` and `Code`) with the given
enum.

Error enums for the package begin at 1001.

Errors being handled:
- Puppy value < 0
- Puppy ID not found

Lab 07 (anz-bank#589)
In order to implement the Storer interface, all methods must now return
and error.

The value property in Puppy has had its type changed to an int to
represent a numerical cent figure so that it may be properly throw an
error if the value supplied is less than 0.

Lab 07 (anz-bank#589)
- Value < 0 in Create and Update
- ID not found in Read, Update and Delete

Lab 07 (anz-bank#589)
Empty main has a code coverage of 0%, unlike lab-06.
@codecov
Copy link

codecov bot commented Aug 19, 2019

Codecov Report

Merging #590 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #590   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files         172    176    +4     
  Lines        3197   3270   +73     
=====================================
+ Hits         3197   3270   +73
Impacted Files Coverage Δ
07_errors/patrickmarabeas/mapStore.go 100% <100%> (ø)
07_errors/patrickmarabeas/errors.go 100% <100%> (ø)
07_errors/patrickmarabeas/main.go 100% <100%> (ø)
07_errors/patrickmarabeas/syncStore.go 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a0f8dbd...e6dbc8d. Read the comment docs.

Copy link
Contributor

@camscale camscale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally pretty good. And I really like the split up of commits, but there's one issue there with making all commits buildable and working.

main() is a bit odd - maybe it makes sense to use the storers you implement somehow, but i note there is no requirement to in the README, so i guess that's ok!

Have a read of your commit messages - i noticed a spelling mistake in one but github does not allow me to comment at the commit level, and now I've forgotten exactly which one it was.

}

const (
NegativeValue ErrorCode = iota + 1001
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear why you have decided to start at 1001 for the error codes, and also set Unknown to 1999. What if in future you need >1000 error codes? Since this is an enum, the actual values for the enum are not relevant, unless they map to some external system. In that case you should document that mapping. But if there is one, don't suggest there is one but you're not telling by using a different set of meaningless numbers to the default.

I'd suggest starting with "Unknown = iota" and going up from there. That does not give you that artificial limit of the number of error codes when you do eventually get to more than 1000 error codes (which will come up in lab 22).

Perhaps you have chosen to use 1000+ so you get a consistent formatting width when converting the error to a string, but I'd suggest you do formatting at the place where formatting is specified (i.e. [%04d])

return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

// NewError creates a new error with the given enum
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Godoc comments should end with a full-stop, as they appear as sentences in the documentation (https://blog.golang.org/godoc-documenting-go-code).

another nitpick: I'd say "given error code" instead of "given enum" - seems a little clearer to me.

case IDNotFound:
return &Error{code, "Nonexistent Puppy ID"}
default:
return &Error{Unknown, "Unknown error"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throws away the error code passed in, which may be useful when trying to debug where a strange error is coming from. Perhaps set the string as fmt.Sprintf("Unknown error (%d)", code) if code != Unknown? Just an idea.

error := Error{1, "message"}
formattedError := error.Error()
a.Equal(formattedError, "[1] message")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see that you have 100% test coverage with this commit. There are a couple of branches in NewError() that are not tested. They probably get tested in a later commit by using it, but since you have structured your PR as a series of small cohesive commits (good), you should ensure each commit passes all the tests and has complete coverage. There is some git rebase --exec magic you can use to run all your tests and coverage checks on every one of your commits automagically, but that usually requires a makefile or script to run all the checks you want to.

I will share the magic in a subsequent comment where I have noticed your tests failing...


type Puppy struct {
ID int
Breed string
Color string
Value string
Value int // cents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two ways you could convey this information without a comment:

  1. Change the field name to ValueCents
  2. Add a new type: type Cents int

Just a thought.


newID, _ := suite.store.Create(Puppy{Breed: "Greyhound", Color: "Light Brown", Value: 700})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar. You do not expect this Create() call to fail, so require that it does not.

func (store *SyncStore) Create(puppy Puppy) int {
func (store *SyncStore) Create(puppy Puppy) (int, error) {
if puppy.Value < 0 {
return -1, NewError(NegativeValue)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to mapstore - use a zero value for unspecified return values.

puppy.ID = store.uuid
store.Store(puppy.ID, puppy)
store.uuid++
store.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usual Go pattern is to put this right after the Lock() with defer:

store.Lock()
defer store.Unlock()

This way if you later add an early return in the middle for error handling, you do not forget to unlock.

if value, ok := store.Load(id); ok {
return value.(Puppy), nil
}
store.RUnlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug, because you are not unlocking the store inside the if statement. Instead use the defer pattern above and not worry about it any more.

store.Delete(id)
store.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto. use the defer pattern, even for a simple one line block like this one.

@juliaogris
Copy link
Contributor

The go-course is now closed. Thank you very much for participating.

@juliaogris juliaogris closed this Feb 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lab 7 - Errors
4 participants