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

Create CRUD puppy solution with map and syncMap #574

Closed
wants to merge 1 commit into from

Conversation

hsy3418
Copy link
Collaborator

@hsy3418 hsy3418 commented Aug 10, 2019

Fixes #575

Review of colleague's PR #469

Changes proposed in this PR:

  • Create CRUD puppy solution with map and syncMap

@codecov
Copy link

codecov bot commented Aug 10, 2019

Codecov Report

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

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #574   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files         234    236    +2     
  Lines        4613   4662   +49     
=====================================
+ Hits         4613   4662   +49
Impacted Files Coverage Δ
00_hello_world/juliaogris/main.go 100% <ø> (ø) ⬆️
06_puppy/hsy3418/mapStore.go 100% <100%> (ø)
06_puppy/hsy3418/main.go 100% <100%> (ø)
06_puppy/hsy3418/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 95053ed...c48a205. Read the comment docs.

Copy link
Collaborator

@kasunfdo kasunfdo left a 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.

Copy link
Contributor

@juliaogris juliaogris left a 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/main.go Outdated Show resolved Hide resolved
06_puppy/hsy3418/mapStore.go Outdated Show resolved Hide resolved

//Storer define standard CRUD operations for puppys
type Storer interface {
CreatePuppy(Puppy) error
Copy link
Contributor

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).

Copy link
Contributor

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).

Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Copy link
Contributor

@juliaogris juliaogris left a 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/mapStore.go Outdated Show resolved Hide resolved
06_puppy/hsy3418/syncStore.go Outdated Show resolved Hide resolved

// 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 {
Copy link
Contributor

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

Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Collaborator Author

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/syncStore.go Outdated Show resolved Hide resolved
06_puppy/hsy3418/syncStore.go Outdated Show resolved Hide resolved

//Storer define standard CRUD operations for puppys
type Storer interface {
CreatePuppy(Puppy) error
Copy link
Contributor

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).

return &SyncStore{}
}

// CreatePuppy adds a nuw puppy to the puppies store
Copy link
Member

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 {
Copy link
Member

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.


//Storer define standard CRUD operations for puppys
type Storer interface {
CreatePuppy(Puppy) error
Copy link
Member

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 {
Copy link
Member

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

assert.Equal(tc.expected, err)
})
}

Copy link
Member

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

assert.Equal(tc.expectedError, err)
})
}

Copy link
Member

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.


// 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 {
Copy link
Member

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.

Copy link
Member

@rokane rokane left a 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

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.

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:

  1. 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.

  2. You've changed the mode of some files in 00_hello_world/juliaogris. Please don't add this change to your commit.

  3. 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)
Copy link
Contributor

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)
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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) {
Copy link
Contributor

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.

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 5 #574
6 participants