I think the balanced answer is that one should look for opportunities to minimize or avoid side effects or to think of where they are and look for opportunities to move them somewhere else to make code easier to understand.
Here I give two versions of some code. When I started to look for opportunities to change code that looks like the first version into code that looks like the second version, my life got better:
It might have an object with attributes first, second, third and methods first_method, second_method, third_method like
def first_method(self):
# calculate and set self.first
def second_method(self):
# use self.first to calculate and set self.second
def third_method(self):
# use self.first and self.second to calculate self.third
and then a higher level method:
def method(self):
self.first_method()
self.second_method()
self.third_method()
Now imagine the names aren't prefixed by "first", "second", "third" but describe what the methods do, and that the program is more complex. This is a problem I routinely face when exploring code that abuses side effects. I constantly have to look at the implementation of the functions to understand what the effect of calling them is and how they work together.
Now without going nuts with the "side effects are evil" stuff, we can still benefit from a desire to avoid side effects:
def first_func():
# calculate the first thing and return it
def second_func(first_thing):
# use first_thing to calculate second_thing and return it
def third_func(first_thing, second_thing):
# use first_thing and second_thing to calculate third_thing and return it
and that higher level method could look like
def method(self):
self.first_thing = first_func()
self.second_thing = second_func(self.first_thing)
self.third_thing = third_func(self.first_thing, self.second_thing)
We still have side effects, method sets attributes of the object, but when we go to try to understand how the functions that make it up work together, it's crystal clear that you have to call them in this order and it is also crystal clear what each of them need to do their work.
Also, with the first version, when looking at the implementation of method, who knows what other attributes of the object get changed by each function. When looking at the second version, it's plain for all to see that calling method changes only three attributes without having to look at the implementation of the functions.
This example might seem simplistic because I crafted it out of thin air for this explanation. I tried my best to convey some real problems that I have when trying to understand code that was written without a certain slight disdain for side effects. Real world code that operates like the first version is harder to understand than real world code that operates like the second version.
My take on side effects, as others have said, is that it is worth while to ask yourself whether the side effects can be moved or avoided and that doing so often results in code that is easier to understand.