simple-made-easy


Easy is “near at hand”, familiar, subjective, a property of the process.
Simple is objective, a property of the artifact. How many concerns are entangled?

These are orthogonal. ORMs are easy but not simple. Raw SQL is simple but not easy.

We focus too much on {programmer convenience, programmer replaceability} for immediate productivity gains, rather than on the long-term aspects: {software quality/correctness, maintenance, change}.
We must assess constructs by the properties of their artifacts.

Because complexity compounds: every new change futher complects the mess.
A complex-but-easy system becomes unmaintainable faster than a simple-but-hard one.
Favour simple and become familiar to makek it easy.

Tests are like guardrails. They don’t guide you, they don’t prevent you from bumping into them, they don’t yield simplicity.
All bugs you find pass all tests (and the typechecker – well, hopefully not for much longer if Taelin ships).

You can only juggle so many balls. How many extra/incidental balls do you wanna bring in? (developer tools, language/framework/library complexity, …).

Simplicty comes from modularity / near-decomposability But don't be fooled by partitioning and stratification…

Programming, when stripped of all its circumstantial irrelevancies, boils down to no more and no less than very effective thinking so as to avoid unmastered complexity, to very vigorous separation of your many different concerns. – Edsger Dijkstra

Complecting

To interleave, entwine, braid… things cannot be considered independently anymore.
Leads to the opposite of modularity / decomposability, i.e. it is the source of compelxity.

State is never simple

… because it complects value and time. You can’t get values independent from time.
It is easy in the at-hand and familiar sense.

Stateful poison: Ask the same question, get a different answer.
This complexity leaks like a virus, interweaves everything it touches.

“I have modularity - that assignment is inside a method”: If every time you call a method with the same arguments, you might get a different result … it doesn’t matter that you can’t see the variable.

It’s not about concurrency/asynchrony, it’s not mitigated by modules, encapsulation, “code organisation”.

To debug you need to recreate the state of the client…

Abstraction for Simplicity

Abstract means “drawn away” … “I don’t know, I don’t want to kow”
Abstraction is not complexity hiding (ORM, inheritance hierarchies, …).

When designing an system, ask: Who, What, When, Where, Why and How

Answer each question separately, in order:
What: the interfaces/protocols. What operations exist?
Who: what entities/data are involved. Pass them in as arguments rather than hardwiring. Don’t let a component reach into another’s internals.
When/Where: don’t wire components together directly. Use queues so timing and location don’t matter.
Why: business logic, rules, policies. Keep them declarative rather than scattering conditionals throughout.
How: the actual implementation. Isolated behind the what.

Strictly separating what from how makes how someone else’s problem. If your interface only specifies what, the implementer is free to choose how.

In other words: decouple

Simplifying

Identifying individual threads/roles/dimensions.
Following through the user story/code.
Disentangling.

You often end up with more things after simplifying. It’s not about counting (LOC, files, …).
That’s fine. It’s better to have more things hanging nicely straight down, than just a couple, twisted together in a knot.
And the beauty of having them separate is that change is much easier.

Information is simple. Represent data as data.

Objects were originally made to encapsulate IO devices (screen, socket, file handle); things with genuine identity and side effects.
Wrapping information in objects is category error.
A class with .getName() is a micro-language that blocks generic data manipulation and ties logic to representation.
Use maps, sets, vectors directly.

“this is what you say when someone wants to tell you a sophisticated type system”

Simplicity is the ultimate sophistication – Leonardo da Vinci

ComplexSimple (via)Complects
StateValues (final, persistent collections)Everything it touchesSame question, different answer. Leaks through any interface.
ObjectsValuesState, identity, valueWrapping data in objects creates a micro-language per data shape, blocking generic data manipulation
MethodsFunctions, Namespaces (stateless)Function + stateCan’t use logic without the class
varsManaged refs (Clojure/Haskell)Value, timeA var mixes up “the current value” and “the thing that changes over time.” Managed refs let you snapshot the value.
InheritancePolymorphism a la carte (protocols, typeclasses)TypesCan’t understand or change parent/child without the other
Switch/matchingPolymorphism a la carteWho/what pairsAdding a new type means finding and updating every switch site. With polymorphism you implement the new type in one place
SyntaxData (maps, sets, JSON)Meaning, orderCustom syntax needs a special parser to read and special tools to manipulate. Plain data uses the same tools as everything else.
Imperative loops, foldSet functions (map, filter)What/howfor i in range(len(xs)): if pred(xs[i]): out.append(xs[i]) vs filter(pred, xs). Set functions abstract implementation details (index, order, accumulator).
ActorsQueuesWhat/whoWith actors, the sender must know about the receiver. Queues decouple time and space, sender and receiver.
ORMDeclarative (SQL/LINQ/Datalog)OMGData access + identity + caching + lazy loading + schema all braided together
ConditionalsRules (libraries, Prolog)Why, rest of programBusiness logic scattered across the codebase in control flow, instead of being inspectable in one place
InconsistencyConsistency (transactions, values)Consistency means stand together. Inconsistency → need reason about things that stand apart, simultaneously. See: eventual consistency