I try to have as many pure functions as possible, but if I can't, I at least try to make the side effects as explicit as possible.
Here is an example (in Go)
type State struct {
count int
}
func (s *State) IncreaseCount() *State {
s.count += 1
return s
}
func main() {
s := new(State)
s = s.IncreaseCount()
}
Since s
is a pointer, s.IncreaseCount()
would be sufficient.
I find s = s.IncreaseCount()
makes it more obvious that s
has changed.
But is this a good practise? Or it just makes the code more complicated than it could be?
As a side note, removing the pointer would actually make IncreaseCount
a pure function:
func (s State) IncreaseCount() State {
s.count += 1
return s
}
but imagine State
is a big complicated struct and we cannot afford copying it.
1 Answer 1
A statement like
s = s.IncreaseCount()
can have the opposite effect of what you are trying to accomplish: it can give the reader the impression IncreaseCount
does not mutate s by itself and instead returns a new object exactly because of this. Hence the assignment looks like it would be required. This makes the next dev (or yourself in 6 months) think
s2 = s.IncreaseCount()
leaves s unchanged.
Instead
s.IncreaseCount()
is much clearer to me for two reasons:
the name of the method is quite clear
since there is no return value, the method must have some side effect, otherwise it would be superfluous
See also my former answer here to the question Is modifying an object passed by reference a bad practice?
-
1Oh, boy, +1 for this answer. The first thing I imagined after reading this question was debugging why
s
changed when I thought thats2
was the only thing that changed. Nothing can burn out a programmer faster than rooting out gremlins like that.Greg Burghardt– Greg Burghardt11/15/2022 21:09:19Commented Nov 15, 2022 at 21:09