Skip to content

Instantly share code, notes, and snippets.

@polerin
Created August 9, 2018 17:50
Show Gist options
  • Save polerin/c0d92aaabf0c8ffc61ab5df76203ceb1 to your computer and use it in GitHub Desktop.
Save polerin/c0d92aaabf0c8ffc61ab5df76203ceb1 to your computer and use it in GitHub Desktop.
Soapbox -- Immutability and predictable systems
polerin [12:08 PM]
Well, lets discuss something. I'm not saying I'm right y'all. Just that I have... opinions.
heh
OK, so the general subject is this: Immutability/static behavior in OOP
As I mentioned in #ideabin, I find it frustrating when objects change state through method calls that don't seem like they should do that.
it introduces a lot of variability and potential for unexpected behavior.
polerin [12:10 PM]
So I feel like there are two general categories of classes/objects in what I'd consider a well structured OO environment.
Data containment/manipulation objects, and command/functional objects.
polerin [12:13 PM]
functional is a bad word there... uh.. confusing. Hm. execution? coordination?
polerin [12:14 PM]
(This is not a formal argument y'all, though I think I've seen some on it. just off the cuff soapboxing.. large mountains of salt for this.)
So the important distinction is this
Data containment and manipulation objects should be concerned only with their state, and with propagating changes into any other contained/linked data objects as appropriate.
they shouldn't have functional outcomes other than, *maybe*, "persist this data to a store".
validation, data cleaning/transforms, etc can live in here if appropriate, but I'm always open to arguments about how transforms should be elsewhere.
Technically, these are just beefed up structs/data objects, with some protective/utility logic that helps centralize and encapsulate functionality into an area allows for easy access and less inspection logic in consuming code.
that last part is an important thing.. I feel like a properly structured data | coordination split allows for far less inspection and management logic in classes that are not actually data related.
So lets go to the coordination side of it for a second.
(I lied, one more thing) Operations performed on Data objects should be expected to have significant state change implications, and any code interacting with them should be constructed in a way that checks their current state, instead of assuming state at any point in the flow.
and that leads to the difference between data and coordination objects
Coordination objects can have state, but that state should be relatively immutable while performing the operations the object is primarily responsible for.
Any state changing methods should NOT be called while executing standard operations.
Instead, these methods should be clearly called out by name and documentation, and should be limited to things like dependency injection or overall behavior configuration. They, in general, should also only be used during a setup or teardown phase of execution.
So how do you get nuance in the behavior of these objects then?
Two main ways.
1) behavior through object composition. Inject dependencies during runtime setup phases that have specific desired behavior. Classic example of this is the strategy pattern enabling different compression or encryption schemes.
2) Behavior based on stateful data objects that have been passed in, or injected as a dependency. In this way, execution pathways can be determined based not on the state of the coordinating object, but on the state of objects that can be inspected and manipulated in other ways, including objects that are sensitive to the overall state of the application.
#2 is important, because it helps centralize application state into a smaller, more manageable, and more easily understandable set of objects. This means that it is harder to have weird edge cases caused by a state change failure in an obscure part of the object graph.
Which, in the end, is why this whole thing is important.
An operation in one part of the object graph should not cause unexpected behavior in a different part of it. Changes in state should be easily detectable, and contained in objects that are treated as volatile by an otherwise unchanging object graph.
</soapbox>
polerin [12:39 PM]
oh, sorry, one last bit.
Unexpected state change in coordination objects is problematic as it leads to operational leakage, where something from one operation is applied to the next, or where an operation unexpectedly fails due to an undetected state change in the coordinating object. This leads to more and more state inspection logic being repeated throughout a code base, with the potential for missing checks, and sometimes the complete inability to reset the coordinating object's state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment