Any function in your application depending on the state of a mutable object === a bad time.
What is so bad about depending on the state of a mutating object for a function to work correctly every time?
Everything. Here is an abridged list of all of the things.
- Things will break when something goes wrong in the mutating object. Notice I said when, I’ll get to that, but for now you’re screwed because the dependent function will behave in ways that may or may not be correct. And this is worse than wrong, because things that are sometimes wrong can make it to production. Awesome right? This highlights the problem with non deterministic functions.
- Not only do you have two objects tightly coupled, but the coupling is implicit. Because the mutable state has no explicit tie to the dependent function, a developer has no way of knowing that any mutations or functional changes to said mutable object might alter the function of the system in seemingly unconnected ways. Congratulations, you planted a land mine. This highlights the importance of single responsibility principle and separation of concerns principle.
- Something will go wrong because land mines get stepped on when code is iterated upon. This highlights the importance of adhering to principles as justification for why you do things instead of “I had to because [insert contextual and shortsighted reasoning here]”
- The problem you didn’t solve in the first place will become apparent when the dependent function goes wrong, and solving it now may require serious refactoring. The thing about decisions is that they stack and branch. If you make a bad one early on, then any good decision downstream is compromised. This highlights the importance good planning and design as well as the open closed principle.
- RACE CONDITIONS! Not only is the function dependent on the state of the mutable object, but it’s also dependent on the timing between when the dependent function is called and when the object is mutated. The best part about this is that you can now make a change in a completely unrelated area of the system and cause this race condition to exhibit. This highlights the impact of state altering control flow and how class cohesion (or lack of it) impacts maintainability.
So how do we avoid this? There are lots of ways, but ultimately every situation is different and good principles are a great guide (just like real life!). Also, look into function purity.
Choosing domain specific heuristics is also a good idea. Things like: tend toward flat structure, prefer long descriptive names over short ambiguous ones, try to push branching logic up the call stack, and anything else that can add a bit of direction to a given situation when you’re not sure what to do. Which for me is most of the time.