-
Notifications
You must be signed in to change notification settings - Fork 164
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
Conversation
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)
aa191bc
to
add520a
Compare
Empty main has a code coverage of 0%, unlike lab-06.
add520a
to
e6dbc8d
Compare
Codecov Report
@@ Coverage Diff @@
## master #590 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 172 176 +4
Lines 3197 3270 +73
=====================================
+ Hits 3197 3270 +73
Continue to review full report at Codecov.
|
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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"} |
There was a problem hiding this comment.
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") | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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:
- Change the field name to
ValueCents
- Add a new type:
type Cents int
Just a thought.
|
||
newID, _ := suite.store.Create(Puppy{Breed: "Greyhound", Color: "Light Brown", Value: 700}) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
The go-course is now closed. Thank you very much for participating. |
Implements custom error handling to the Storer interface. All methods must now return an error.
Errors that are covered:
Fixes / improvements also included:
Fixes #589