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
pkg/instancetype/instancetype.go: Avoid named returns #11446
Comments
/sig code-quality |
/label good-first-issue |
/assign |
Hey @0xFelix!
I'm sorry, but I disagree with this statement. Let me give you an example of a situation I think you're right: func doSomething() (err error) {...} In this case the However, here's a case when I think your statement is not correct: func getPersonInformation(p Person) (string, string, bool, int, error) {...} In this case I have absolutely no idea what the return values actually are or what they represent. I would have to dive into the implementation in order to figure it out. I think that this function is much easier to understand: func getPersonInformation(p Person) (firstName, LastName string, hasChildren bool, age int, err error) {...} This is only one case where named return values are valuable, but there are other cases, for example to avoid declaring a IMHO this should be reviewed on a case-to-case basis. Looking at #11452 for example, I'm not sure that this PR actually improves code quality in any way. |
I've just now realized you were targeting something specific. Sorry for not noticing. func inferDefaultsFromVolumes(vm *virtv1.VirtualMachine) (defaultName, defaultKind string, err error) { The PR turns it into: func inferDefaultsFromVolumes(vm *virtv1.VirtualMachine) (string, string, error) { This specific case proves my point from the comment above. for _, volume := range vm.Spec.Template.Spec.Volumes {
if volume.Name != inferFromVolumeName {
continue
}
if volume.PersistentVolumeClaim != nil {
return m.inferDefaultsFromPVC(volume.PersistentVolumeClaim.ClaimName, vm.Namespace, defaultNameLabel, defaultKindLabel)
}
if volume.DataVolume != nil {
return m.inferDefaultsFromDataVolume(vm, volume.DataVolume.Name, defaultNameLabel, defaultKindLabel)
}
return "", "", NewIgnoreableInferenceError(fmt.Errorf("unable to infer defaults from volume %s as type is not supported", inferFromVolumeName))
}
return "", "", fmt.Errorf("unable to find volume %s to infer defaults", inferFromVolumeName) So now I have to dive even deeper into other function's implementation.. all of that just to understand what values are being returned and what they represent. |
This could be solved with a good comment on what the function returns. Not always the variable names are clear either |
@iholder101 as already mentioned in the PR, I encourage you to write this comment in the coding best-practice issue: #11259 |
This comment was marked as resolved.
This comment was marked as resolved.
@lyarwood: The label(s) In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/area instancetype |
As the original author I obviously agree with @iholder101 here that in this specific case there's value in using named return variables when the implementation delegates the returns. That said the shared underlying function used by all paths does actually define these return variables within the function and should have its named return values dropped: kubevirt/pkg/instancetype/instancetype.go Lines 830 to 836 in ee87726
I also agree with @alicefr that empty returns using named return variables should be avoided and should be documented as something to avoid. |
@alicefr Let's look at a simple example again: // First return value is first name, second is last name
func getPersonInformation(p Person) (string, string) {
firstName := getFirstName()
LastName := getLastName()
// ...
return firstName, lastName
} Now, let's say that I'm refactoring the code and actually want to return the first name, and address. This can very easily happen: // First return value is first name, second is last name
func getPersonInformation(p Person) (string, string) {
firstName := getFirstName()
address := getAddress()
// ...
return firstName, address
} Now we have a comment which provide false information, and yet for some reasons (as often happens with comments) people trust it to be the source of truth. This is horrible. I think the following mistake is MUCH harder to make: func getPersonInformation(p Person) (firstName, lastName string) {
firstName := getFirstName()
lastName := getAddress()
// ...
return
} In general - I think that comments should be avoided whenever we can document stuff with actual code. |
IMHO I should not have to look at a function implementation to understand what it returns. That's what function signatures are for! so you would be able to reason about the basic purpose of the function without reading its implementation.
Why? :) |
@iholder101 yes, I agree on what you said about the comments :). Don't get me wrong, I'm personally not strongly against named return values. However, when I have tried to use them in the past, I got several reviews telling me to avoid it. What I personally dislike is having a mix of coding styles. For example in https://github.com/kubevirt/kubevirt/blob/main/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go, we have a mixture of name returned values and not for the same variable type ( |
100% agree. I would say that, in general, unless there's a policy that defines code conventions (like @0xFelix is trying to create) then comments about matters of style can be given as nits, but they shouldn't block the PR or dictate a specific code style (expect for extreme cases). In addition, while I think that uniformity sometimes has many advantages, in other times it's perfectly fine to use different styles on a case-to-case basis where it makes sense. |
Yeah that's fair. In that specific case the function is so short that I didn't think peeking at the implementation was a big issue but as a convention using named returns as a means to document the differences between multiple return variables of the same type in the signature is sound.
I thought about this a little more and I think my issue with naked returns is that they are not explicit enough when the function is over a certain size and you end up having to lookup the signature again to regain context. The https://github.com/alexkohler/nakedret?tab=readme-ov-file#purpose |
To that I can agree :) |
I share @lyarwood's view about naked returns and I think that is what bugs me the most when thinking about named returns. What do you think about @EdDev suggestion in #11456? @alicefr I also share your view about different coding styles in the same file. IMO we should stick to one coding style in a file to make reviews easier. |
@lyarwood Is |
Yes it's part of golangci-lint and yes I think it would be valid to enable it: |
/cc |
Helps if I actually check if it's enabled already.... Line 90 in a18b070
@0xFelix is there anything else to discuss within this issue or can we move the discussion back to the conventions PR? |
/close Okay closing this out, thanks all. |
@lyarwood: Closing this issue. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Is your feature request related to a problem? Please describe:
Named returns make the code harder to understand than necessary, therefore they should be avoided.
Describe the solution you'd like:
Remove named returns in
pkg/instancetype/instancetype.go
L814-908 and define the variables in the function bodies instead.The text was updated successfully, but these errors were encountered: