paritybit.ca

Software Complexity and Simplicity

Software Simplicity

Simplicity is not about the number of parts something has, it’s about how intertwined they are.

Mutable state or immutable state both contribute to complexity because the more state you have, the greater the number of possibilities in your program. This grows non-linearly. Branching also grows complexity non-linearly. Timelines/threads that need to communicate also contribute. There are 1,000,000 different ways that two processes can execute 12 steps, for example.

More state also means more code to handle consistency. It is hard to reason about program outcomes when there is lots of state.

Mutable state is worse than immutable state because there is change over time.

Naturally, reduce complexity by removing constructs that contribute to it. Keep mutable state small so that it’s easier to manage.

Simplicity is hard. It requires conscious effort to maintain.

Object Oriented Programming is uniquely hard to make simple. OOP has state and procedures that manipulate this state which can get very complex and hard to limit or restrict. OOP leads to additional complexity because more code is needed to manage this state. Languages that don’t fall into this trap seem to be Actor-style languages and SmallTalk (the original OOP language, from which OOP seems to have been corrupted into whatever the heck it is today). Pure functional programming eliminates state and is naturally highly parallel sizable because it avoids state.

Software Complexity

There are three areas:

Essential complexity is the essence of the problem as seen by the users. It is complexity that cannot be avoided because it is the very nature of what the program needs to do.

Accidental complexity is all the rest that the developer would not have to reason about in the real world (e.g. manipulating registers, memory management, unnecessary abstractions, over re-use of data types, data hiding, etc.).

A useful data structure for managing complexity is the tree since it can store exponentially more data for each unit of depth.

A useful way is to program in a data-oriented way where you program at different data interpretation levels (e.g. byte->char->json->http) without needing to define an API inside your program. You are free to move up and down the levels without indirection.

Don’t prematurely add complexity to code because “one day you might need to use another database engine” or something like that. Wait until you actually need that other database engine to make the changes. You will have to rewrite some things for the new database engine, but your code overall will be simpler.

Avoid abstraction barriers: places where abstractions become black boxes, hard to interact with or reason about.

Interesting Resources: