As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:
- querying (getting) an object shouldn't change its state
- Updating a value should not return it's state
I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.
As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return void:
Method Chaining
It's common in contemporary code to return this/self instead of void/None. This allows you to write code like this:
object.foo().bar() instead of:
object.foo() object.bar() I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.
Success Status
Some APIs I've worked will do things like return a boolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped on an update. Doing this with two calls which might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.
Atomicity
I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change might occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.
To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method handle thread safety internally with a much broader array of approaches.