-
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
Create CRUD puppy solution with map and syncMap #574
Conversation
Codecov Report
@@ Coverage Diff @@
## master #574 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 234 236 +2
Lines 4613 4662 +49
=====================================
+ Hits 4613 4662 +49
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.
Looks good so far. Good unit test coverage. I have included few suggestions for improvements. The main concern I'm having is comparing error string in test.
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.
Looks pretty solid, just a few minor nitpicks inline.
06_puppy/hsy3418/types.go
Outdated
|
||
//Storer define standard CRUD operations for puppys | ||
type Storer interface { | ||
CreatePuppy(Puppy) 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.
IMO Create should provision a system unique ID and return it - the user/client of this API probably doesn't have complete knowledge of all other entries. Happy for you to argue against it (leave it).
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.
resolving the comment is not exactly arguing against it ;) .
please put forward your case why you think the user/client of your system would have knowledge of (available) ID or change implementation to provision on ID (e.g. by incrementing a counter).
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 agree with @juliaogris on this one. The user should not be expected to understand the complete set of entries. All they want to do is create a puppy, and then have some method of retrieving that puppy. Having the ID returned means the API handles that logic and the user doesn't need to worry about what else exists. They have a means of accessing their object through the id returned from a successful create.
@hsy3418 if you don't agree or have some other view / ideas, please speak up and let us know. I would be happy to discuss and hear your thoughts. Alternatively, please update this one accordingly.
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.
@juliaogris @rokane I totally agree with you the client who is going to create the puppy wouldn't know the ID, they will just enter the puppy info and ID should be handled in the back-end logic, didn't think about that deeply before.
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.
By returning the ID as a sign for creating the puppy successfully make sense to the client definitely, I think I will change the method accordingly, but my question is if there is no error return, would it work similarly to the client? Cause my understanding is if the client just wants to know if the puppy been created or not, they don't really need the ID, unless the front-end logic needs to show the exact ID. Correct me if I am wrong.
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 am going to take that back :), just rethink about it, since the ID is created in backend, there is no error could be returned since there wouldn't be duplicate puppys.
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.
a few more small things.
06_puppy/hsy3418/syncStore.go
Outdated
|
||
// ReadPuppy retrieves the puppy for a given id from puppies store | ||
func (m *SyncStore) ReadPuppy(id int32) (Puppy, error) { | ||
if p, exists := m.Load(id); exists { |
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.
ok instead of exists and invert logic so that error is inside if: if !ok return err
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 need the p within the if block, so a puppy could be created if ok, otherwise I need to have an else block for create the puppy if ok, but if doing so, I couldn't pass lint..So it seems like I need to keep this logic.
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.
If you flip the logic, you won't need the p inside the if statement, and can go about it that way.
func (m *SyncStore) ReadPuppy(id int32) (Puppy, error) {
p, ok := m.Load(id)
if !ok {
return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id)
}
puppy, _ := p.(Puppy)
return puppy, nil
}
I think what Julia is suggesting here is to have your structure following the convention of: if err -> fail early
Normally if there are multiple error cases, I think this is a good approach. When there is a single error case, I am not as fussed either way. I will leave this one up to you.
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.
Got it!
06_puppy/hsy3418/types.go
Outdated
|
||
//Storer define standard CRUD operations for puppys | ||
type Storer interface { | ||
CreatePuppy(Puppy) 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.
resolving the comment is not exactly arguing against it ;) .
please put forward your case why you think the user/client of your system would have knowledge of (available) ID or change implementation to provision on ID (e.g. by incrementing a counter).
06_puppy/hsy3418/syncStore.go
Outdated
return &SyncStore{} | ||
} | ||
|
||
// CreatePuppy adds a nuw puppy to the puppies store |
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.
adds a new puppy to the puppy store
"sync" | ||
) | ||
|
||
type SyncStore struct { |
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.
You have done really well documenting your interface methods, but you forgot these as well. Since the SyncStore and NewSyncStore are also included as part of the exported package, these should also have documentation around them.
06_puppy/hsy3418/types.go
Outdated
|
||
//Storer define standard CRUD operations for puppys | ||
type Storer interface { | ||
CreatePuppy(Puppy) 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.
I agree with @juliaogris on this one. The user should not be expected to understand the complete set of entries. All they want to do is create a puppy, and then have some method of retrieving that puppy. Having the ID returned means the API handles that logic and the user doesn't need to worry about what else exists. They have a means of accessing their object through the id returned from a successful create.
@hsy3418 if you don't agree or have some other view / ideas, please speak up and let us know. I would be happy to discuss and hear your thoughts. Alternatively, please update this one accordingly.
puppies map[int32]Puppy | ||
} | ||
|
||
func NewMapStore() *MapStore { |
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.
Again here, this is also a part of the exported package. Please document
06_puppy/hsy3418/store_test.go
Outdated
assert.Equal(tc.expected, err) | ||
}) | ||
} | ||
|
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.
Little nit. Another new line not required
06_puppy/hsy3418/store_test.go
Outdated
assert.Equal(tc.expectedError, err) | ||
}) | ||
} | ||
|
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.
New line here can be removed.
06_puppy/hsy3418/syncStore.go
Outdated
|
||
// ReadPuppy retrieves the puppy for a given id from puppies store | ||
func (m *SyncStore) ReadPuppy(id int32) (Puppy, error) { | ||
if p, exists := m.Load(id); exists { |
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.
If you flip the logic, you won't need the p inside the if statement, and can go about it that way.
func (m *SyncStore) ReadPuppy(id int32) (Puppy, error) {
p, ok := m.Load(id)
if !ok {
return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id)
}
puppy, _ := p.(Puppy)
return puppy, nil
}
I think what Julia is suggesting here is to have your structure following the convention of: if err -> fail early
Normally if there are multiple error cases, I think this is a good approach. When there is a single error case, I am not as fussed either way. I will leave this one up to you.
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.
Code and tests are looking really good, you've done a great job so far! I have a few comments throughout which I would like addressed :)
A lot of these have already been requested from previous reviewers, but you have resolved the conversations without applying changes. I would advise against doing this. Please in future either make a case for why you think it should remain, which can spark discussion on the review. Or make the changes and then resolve. But don't get in the habit of resolving the conversation without making any changes.
Main things from me are:
- Full set of documentation
- Removing extra new lines from each test case
- Adjusting the interface API so that an id is returned on
CreatePuppy
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 very good. Only minor comments on the code.
However, this PR also contains some changes that should not be here. Have a look at the "Files changed" tab on this PR in guthub and the first five changed files should not be here:
-
Your change to
.gitignore
is for your personal setup, so should not go into the repository. Also, even if it made sense to add, you just put at at the end of the file under the comment# Output of the go coverage tool, specifically when used with LiteIDE
. That would be in the wrong spot even if it were valid to put in. Please pay attention to the details - details matter when writing software. -
You've changed the mode of some files in
00_hello_world/juliaogris
. Please don't add this change to your commit. -
You've deleted your lab 3 submission in
03_letters/hsy3418
. Please don't do that in this commit (or do it at all).
Apart from these git issues, the code itself is looking very good. Thanks.
p2 := Puppy{Breed: "Poodle", Colour: "Grey", Value: 1340.5} | ||
mapStore := NewMapStore() | ||
syncStore := NewSyncStore() | ||
_ = mapStore.CreatePuppy(p1) |
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.
you should save the id from CreatePuppy
and use that to do the ReadPuppy
and not just make the assumption that the puppy ID will be zero. As main()
is a user of the store, it should not make assumptions about the internal workings of the store. The store could start IDs at 1, not 0. Since there is no need to assume because CreatePuppy
returns the ID, you should just use that ID.
_ = syncStore.CreatePuppy(p2) | ||
puppy, _ := mapStore.ReadPuppy(0) | ||
puppy2, _ := syncStore.ReadPuppy(0) | ||
fmt.Fprintf(out, "Puppy ID %d is %v", puppy.ID, puppy.Value) |
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.
Put a \n
at the end of this output and the next. This should output a complete line to be user-friendly.
return m.puppies[id], nil | ||
} | ||
|
||
//UpdatePuppy updates the puppy for the given id |
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.
nit: Put a space after //
like you have for the doc strings above. Do the same for the next comment and also for the comments in syncStore.go
if _, ok := m.puppies[id]; !ok { | ||
return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id) | ||
} | ||
return m.puppies[id], nil |
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.
Instead of looking up the map again, when you did it already before, you should save the value from from the first lookup. It means you can't put it all inside the if
, but that doesn't matter:
puppy, ok := m.puppies[id]
if !ok {
...
}
return puppy, nil
} | ||
} | ||
|
||
func TestStorers(t *testing.T) { |
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 think it is more readable if this function is placed at the top of the file before SetupTest
. This is where the tests in this file start, so it reads better to start here, and then move onto SetupTest
, and then finally the tests themselves.
Fixes #575
Review of colleague's PR #469
Changes proposed in this PR: