It is just another Thursday of adding features to your mobile app.
You have blasted through your task list by extending the current underlying object model + data retrieval code.
Your front-end native views are all coming together. The navigation between views and specific data loading is all good.
Git Commit. Git Push. The build pops out on HockeyApp. The Friday sprint review goes well. During the sprint review the product manager points out that full CRUD (Create, Read, Update, Delete) functionality is required in each of the added views. You only have the ‘R’ in ‘CRUD’ implemented. You look through your views, think it just can’t be that bad to add C, U and D, and commit to adding full CRUD to all the views by next Friday’s sprint review.
The weekend passes by, you come in on Monday and start going through all your views to add full CRUD. You update your first view with full CRUD; start navigating through your app; do some creates, updates, and deletes; and notice that all of those other views you added last week are just broken. Whole swaths of classes are sharing data you didn’t know was shared between them. Mutation to data in one view has unknown effects on the other views due to the shared references to data classes from your back-end object model.
Your commitment to having this all done by Friday is looking like a pipe-dream.
Without realizing it you are now a victim of code that is not locally reasoned due to heavy use of reference types.
Local reasoning is the ability of a programmer to look at a single unit of code (i.e. class, struct, function, series of functions) and ensure that changes made to data structures and variables within that unit of code don’t have an unintended effect on unrelated areas of the software.
The primary example of poor local reasoning is handing out pointers to single references of classes to many different areas of the application. These pointers to single references are then mutated by all the clients that were handed the reference to the shared instance.
Adding to the pain is the possibility that a reference to your single instance was held by a client, then mutated by a parallel running thread.
Code that you thought that was one-pass, single-path, easy-peasy has now become a spider web of live wires blowing in the wind that short circuit and spark at random times.
With the recent rise of Swift, there has been a movement to use value types to avoid all that sparking caused by random mutation within reference types.
For so long, we were conditioned to use classes types for everything. Classes seem like the ultimate solution. Lightweight hand off of references (i.e. pointers), instead of allocation and copying of all internal members across function calls. The ability to globally mutate in one shot via known publicly exposed functions. It all looks so awesomely ‘object-oriented’. Until you hit the complex scenarios that a CRUD based user interface has to implement. Suddenly that awesome class based object model is being referenced by view after view after subview after sub-subview. Mutation can now occur across 10s of classes with 10s of running threads.
Time to go way too far up the geek scale to talk about possible solutions.
A classic trope in many Star Trek episodes was something sneaking onto the ship. Once on the ship, the alien / particle / nanite / Lwaxana Troi would start to wreak havoc with the red shirts / warp core / main computer / Captain Picard’s patience (respectively).
By using nothing but classes and reference types, even with a well defined pure OO interface to each, you are still spending too much time with your shields down, and letting too many things sneak onto the ship. It is time to raise the shields forever by using value types as an isolated shuttlecraft to move values securely between the ship and interested parties.
Apple has been emphasizing the use of value types for the past two years via their release of the Swift language.
Check out these WWDC 2015 /2016 presentations which emphasize the use of Swift value types as a bringer of stability and performance via using the language itself to bring local reasoning to code:
- Protocol and Value Oriented Programming in UIKit Apps
- Building Better Apps with Value Types in Swift
- Protocol-Oriented Programming in Swift
- Improving Your Existing Apps with Swift
Apple has even migrated many existing framework classes (i.e. reference typed) to value types in the latest evolution of Swift 3.0. Check out the WWDC 2016 Video: What’s New in Foundation for Swift.
At Minnebar 11, Adam May and Sam Kirchmeier presented on Exploring Stateless UIs in Swift. In their presentation, they outline a series of techniques using Swift to eliminate common iOS state and reference bugs. Their techniques meld together stateless concepts from React, Flux, and language techniques in Swift, to dramatically increase the local reasoning in standard iOS code.
Riffing off of Adam and Sam’s presentation, I came up with a basic representation of stateless concepts in Xamarin to solve a series of cross-platform concerns.
Even the PUT, DELETE, POST, and GET operations lying underneath REST based web interfaces are recognition of the power of local reasoning and the scourge of mutation of shared references to objects.
C# and .NET languages are very weak on the use of value types as a bringer of local reasoning. For so long Microsoft has provided guidance along the lines of ‘In all other cases, you should define your types as classes‘, largely due to performance implications.
Have no fear, you can bring similar concepts to C# code as well via the struct.
The one draw back of C# is the ease with which the struct can expose mutators in non-obvious ways via functions and public property setters.
Contrasting with C#, Swift has the ‘mutating‘ keyword. Any function that will mutate a member of a struct requires the ‘mutating‘ keyword to be attached in order for compilation to succeed. Unfortunately, there is no such compiler enforced mutability guard for structs in C#. The best you can usually do is to omit the property set from most of your struct property definitions, and also use private / internal modifiers to ensure the reasoning scope of your type is as local as you can possibly make it.
The next time you see a bug caused by a seemingly random chunk of data mutating, give a thought to how you may be able to refactor that code using stateless architecture concepts and value types to increase the local reasoning of all associated code. Who knows, you may find and fix many bugs you didn’t even realize that you had.