Valuable insights
1.Simple Is Objective, Easy Is Relative: Simplicity relates to an objective lack of interleaving or braiding, whereas ease is subjective, depending on familiarity or proximity to current skills.
2.Complexity Means Braiding or Interleaving: The opposite of simple is complex, meaning things are folded or braided together. This intertwining significantly limits human ability to reason about the system.
3.Focusing on Ease Slows Long-Term Development: Prioritizing ease—quick setup or familiarity—allows initial speed but complexity accumulates, eventually causing every development cycle to accomplish less.
4.Artifacts Matter More Than Constructs: Software quality must be assessed based on the running artifact's long-term attributes (reliability, changeability), not the programmer's initial experience with the code construct.
5.State Is Fundamentally Complex: State inherently complects value and time, meaning a value cannot be retrieved independent of time. This complexity leaks through encapsulation layers.
6.Abstraction Means Drawing Away From Physicality: True abstraction involves drawing away from the physical nature of something, which is distinct from merely hiding implementation details or obscuring complexity.
7.Use Composition, Avoid Complection: Composable systems involve placing simple components together. Complecting, or braiding things together, is the primary source of complexity that must be actively avoided.
8.Separate What From How: Strictly separating the declarative specification (what the system should do) from the implementation details (how it is done) allows for greater flexibility.
9.Queues Mitigate When and Where Coupling: Directly connecting components creates coupling based on when and where they execute. Introducing queues eliminates this temporal and spatial entanglement.
10.Simplicity Often Means More Components: Achieving simplicity through disentanglement often results in having more, smaller, independent components rather than fewer, highly interconnected ones.
Introduction and Defining Simplicity
The presentation introduces concepts intended to appear deceptively obvious, aiming to equip attendees with tools for conducting discussions about software quality with others. A central theme involves the necessity of building simple systems to achieve reliability, a point emphasized by Dijkstra. The speaker expresses interest in word origins, suggesting that precise use of terms like "simple" and "easy" is crucial when discussing software design.
Appeal to Authority
An appeal to authority begins with the assertion that simplicity is a prerequisite for reliability. While acknowledging potential disagreements with the source on other matters, this specific tenet regarding simplicity's necessity for good systems is strongly affirmed. The speaker notes that insufficient focus is currently placed on achieving this fundamental simplicity in system design.
Word Origins
The origin of the word "simple" stems from "sim" and "plex," meaning one fold or one braid. Literally, one twist equates to no twists at all. Conversely, "complex" means braided or folded together. The core objective of the talk centers on evaluating software based on whether its parts are folded or intertwined.
The Objective Nature of Simplicity
The concept of simplicity, defined as having one fold or twist, can be examined across several dimensions relevant to design work. For something to be simple, it should exhibit focus, fulfilling one role, one task, or one objective, perhaps relating to a single concept like security or a specific problem dimension.
One Fold or One Braid
The critical characteristic sought in simplicity is the absence of interleaving or combining things. If connections or twists are absent upon inspection, the system exhibits simplicity. This characteristic is objective; one can look and verify the lack of connections or braiding between parts.
- Fulfilling one role or task.
- Accomplishing a single objective.
- Relating to one core concept.
- Addressing a particular dimension of the problem.
Cardinality vs. Interleaving
It is essential to distinguish cardinality (counting things) from actual interleaving. Simplicity does not imply there is only one operation or one element; rather, it means there is no interleaving of concerns. This objective assessment of braiding is what separates simple from easy.
Understanding Ease and Familiarity
The word "easy" derives from a root meaning to lie near or be nearby. This notion of nearness applies in several ways within software development. The original physical notion suggests something is easy to obtain because it is immediately accessible, requiring no significant journey or effort.
Notions of Nearness
Beyond physical nearness, software concepts can be near to one's understanding or current skillset, meaning they are familiar or based on something already known. Collectively, developers are often infatuated with these two aspects of ease, focusing intensely on instant accessibility and familiarity, which can be tremendously detrimental.
All one cares about is, 'Can you get it instantly and start running it in five seconds?' It could be this giant hairball that you got, but all you care is 'can you get it?'
Capability and Relativity
A third aspect of ease involves nearness to one's capabilities. Discussing this aspect is often avoided in conceptual work because it can be uncomfortable, potentially trampling egos. Furthermore, unlike simplicity, ease is always relative; what is easy for one person may be hard for another, preventing objective discussion.
Constructs Versus Long-Term Artifacts
A critical area where simple and easy diverge concerns constructs versus artifacts. Constructs are the programming language elements or libraries used, while artifacts are the running software that users interact with over time. Users do not evaluate source code for pleasantness; they evaluate the resulting artifact's behavior.
Programmer Convenience
Infatuation with programmer convenience, which aligns with the first two meanings of easy (at hand and familiar), often benefits employers by making programmers replaceable. If a new hire is familiar with the toolkit and can read the code, replacement is simple, often ignoring the third aspect of ease: whether anyone can understand the code's inherent complexity.
Long-Term Impacts
The attributes that truly matter—whether the software performs its intended function reliably and whether new requirements can be incorporated—have little to do with the construct as typed in. Assessment must shift toward the attributes of the artifact itself, rather than the look and feel of the initial coding experience.
Complexity, Limits, and Reasoning
Making systems flexible and dynamic often involves a trade-off in the ability to understand their behavior and ensure correctness. For systems requiring correctness, understanding is limited by cognitive capacity, which can only handle a small number of items concurrently. This limitation is exacerbated when components are intertwined.
Understanding Limitations
When elements are intertwined, extracting one piece for comprehension requires pulling in the attached piece as well, as they cannot be thought about in isolation. Every intertwining adds a burden that grows combinatorially with the number of elements, fundamentally limiting the ability to understand the system.
Every intertwining is adding this burden and the burden is kind of combinatorial as to the number of things that we can, we can consider.
Reasoning Over Proof
Changing software necessitates analyzing its function and deciding on future requirements, which involves informal reasoning, not formal proof. Practices like Agile or Extreme Programming, while utilizing refactoring and tests, do not eliminate the need to reason about the impact of changes. Tests act as guard rails, preventing crashes, but they do not guide the program toward its intended destination.
The Cost of Prioritizing Ease Over Simplicity
The illusion of speed derived from focusing on 'easy' tools is only sustainable for very short tasks. Programmers who ignore complexity will invariably slow down over the long haul. This relationship is demonstrated through an experiential graph showing that focusing on ease without simplicity leads to complexity eventually killing progress.
Short Races vs. Long Haul
If complexity is ignored, the long-term result is that sprints become less productive, often requiring developers to redo previous work just to move forward marginally. While simplicity work requires an initial ramp-up time, the long-term gains in maintainability outweigh the initial investment.
Incidental Complexity
Complexity that arises from the choice of tools rather than the requirements of the problem is termed incidental complexity. This is complexity that is the programmer's fault, analogous to using a loom that produces a knotted mess, even if the design looks aesthetically pleasing initially.
Benefits of Simplicity and Analyzing Constructs
The benefits derived directly from simplicity include ease of understanding, ease of change, easier debugging, increased flexibility, and greater independence between decisions. When components are simple, making a decision about location can be orthogonal to a decision about performance because they are not interleaved.
- Ease of understanding and debugging.
- Increased flexibility and ability to change policies.
- Greater independence of decisions (orthogonality).
- More robust software structure.
Making Things Easy
Making things easy involves addressing location (making tools at hand via installation) and familiarity (learning or tutorials). The most difficult aspect is nearness to capability, which is hard to address because human cognitive capacity is inherently limited relative to the complexity created.
Parentheses Example
Analyzing language features like parentheses in Common Lisp or Scheme reveals that while they might be familiar (easy), their overloading—wrapping calls, grouping, and defining data structures—is a form of complexity by design. Language designers are responsible for fixing this overloading by introducing distinct constructs, thereby returning simplicity to the language.
Identifying and Replacing Complex Constructs
A systematic comparison of common programming constructs reveals that many readily available tools generate complex artifacts. The speaker emphasizes that programmers often look only for benefits without considering the trade-offs or downsides introduced by these constructs, leading to unnecessary complexity.
The Meaning of Complect
The archaic word 'complect' means to interleave or entwine, and it carries the necessary negative connotation associated with complexity. Complexity arises directly from complecting things, which should be avoided in favor of composition—the act of placing simple components together.
State Complexity
Having state in a program is never simple because it fundamentally complects value and time, preventing the retrieval of a value independent of temporal context. Even when encapsulated within a method, if the wrapping context is stateful, the complexity leaks out, requiring a true functional interface to mitigate.
Environmental Complexity
Environmental complexity is inherent complexity stemming from the execution environment, which is not the programmer's fault. Programs contend for resources like memory and CPU cycles alongside other processes or parts of themselves. This contention is an inherent aspect of implementation space.
Contention for Resources
While segmentation can allocate resources, it leads to waste due to pre-allocation. A major issue is that the policies governing these resources, such as sizing thread pools, do not compose well when multiple components attempt to apply the same policy simultaneously. Finding centralized ways to organize these necessary external decisions remains a significant challenge.
Designing for Simplicity Through Abstraction
When creating new constructs, the first step is choosing tools with simple artifacts, but custom abstraction is often necessary. Abstraction, defined as drawing something away, specifically means drawing away from the physical nature of something, which is different from merely hiding details.
Abstraction Defined
One method for achieving abstraction is systematically analyzing decisions based on Who, What, When, Where, and Why, to help take things apart. A key approach is maintaining a mindset of not needing to know implementation details, focusing instead on defining the 'what'—the operations to be accomplished.
Small Specifications
Abstractions, formed by naming sets of functions (like interfaces or type classes), should be much smaller than typically seen. To avoid complecting the specification with implementation details ('how'), these definitions should only use values and other abstractions in their signatures. Strictly separating 'what' from 'how' allows the 'how' to become someone else's problem.
- Values (immutability via 'val' or 'final').
- Functions (stateless methods).
- Data (maps, sets, sequential structures).
- Polymorphism ala carte (protocols, type classes).
- Queues for decoupling temporal dependencies.
When and Where
The 'when' and 'where' aspects are often accidentally complected when objects are directly connected (A calls B). This creates coupling based on location and execution time. Utilizing queues extensively is the primary mechanism to eliminate this problem by decoupling components temporally.
Conclusion: Simplicity as a Choice
The fundamental takeaway is that simplicity is a choice, and failing to achieve it is the responsibility of the development team. There is a pervasive culture of complexity reinforced by the continued use of tools that yield complex outputs. This rut must be overcome by advocating for simpler tools and techniques.
Programming is not about typing, it's about thinking.
Vigilance Against Entanglement
Achieving simplicity requires constant vigilance, as safety nets like tests do not yield simplicity themselves. Developers must develop sensibilities around entanglement, actively looking for complecting connections between independent concerns. Reliability tools remain secondary safety nets, not solutions to the core problem of complexity.
Achieving Simplicity
The path forward involves choosing constructs with simple artifacts and investing upfront time to simplify the problem space before implementation begins. It is important to recognize that simplification often results in having more, smaller, separate things, which is preferable to having fewer, tightly tied elements, as separation enables change.
Useful links
These links were generated based on the content of the video to help you deepen your knowledge about the topics discussed.