This journal is generated by AI

State Machines for Simplifying Complex Logic

This week I dove into using state machines as a tool for managing complex application logic. Dave Thomas’s approach in his book “Simplicity” resonated with me: state machines aren’t as complicated as people make them out to be, and they’re a great way of turning deeply nested code into nice linear chunks.

  • Data Over Code: The core insight is that data is easier to work with than code—it’s easier to reason about, manipulate, and change. You don’t need complex libraries or long-winded pattern-based approaches when you need a state machine; all you need is a lookup table.
  • State Transition Formula: The fundamental model is State(S) x Event(E) -> Actions(A), State(S'). This simple formula captures the essence of event-driven behavior in a declarative way.
  • Event Generation: Actions can return either new state values or generate new events. If an event is returned, the code immediately performs another transition—this allows for elegant chaining of state changes.
  • State Enter Calls: Erlang’s gen_statem has a powerful feature where the state callback is automatically called with special arguments whenever the state changes, making it easy to perform entry actions without cluttering transition logic.
  • Postponing Events: A clever pattern is the ability to postpone events—if an event arrives but shouldn’t be handled in the current state, you can defer it until a state change occurs when the event queue restarts from the oldest postponed event.

Sources:

Tokio and Async Rust Patterns

Continuing my study of async Rust, I worked through the Tokio tutorials and explored patterns for shared state and concurrent data structures.

  • Mutex Selection: As a rule of thumb, using a synchronous mutex from within asynchronous code is fine as long as contention remains low and the lock is not held across calls to .await. The Tokio mutex’s primary feature is that it can be held across an .await without issues, but if contention becomes a problem, the best fix is rarely to switch to the Tokio mutex.
  • Concurrent Hash Maps: For high-contention scenarios, there are several excellent crate options: dashmap provides a sharded hash map implementation, while leapfrog uses leapfrog probing, and flurry is a port of Java’s ConcurrentHashMap. I also discovered papaya, which is optimized for read-heavy workloads.
  • Async Trait Variants: The trait_variant and dynosaur crates help with async trait patterns—useful for creating both sync and async versions of traits without code duplication.
  • Testing with tokio-test: The tokio_test::io::Builder provides mock I/O for testing async code, making it easier to write deterministic tests for network protocols.

Source: Tokio Tutorial - Shared State

Clean Code: Maintenance as the Core of Software

I revisited Robert C. Martin’s “Clean Code” this week, and the framing around maintenance struck me differently in the AI era.

  • 80% is Maintenance: In software, 80% or more of what we do is “maintenance”—the act of repair. Rather than the Western focus on producing good software, we should think more like home repairmen or auto mechanics: proactive maintenance rather than waiting for bugs to surface.
  • A Million Selfless Acts: Quality is the result of a million selfless acts of care—not just of any great method that descends from the heavens. These acts are simple but not simplistic, forming the fabric of greatness in any human endeavor.
  • Reading vs Writing Ratio: The ratio of time spent reading vs. writing code is well over 10:1. We are constantly reading old code as part of the effort to write new code, so making code easy to read actually makes it easier to write.
  • LeBlanc’s Law: “Later equals never.” We’ve all felt the relief of seeing our messy program work and deciding that a working mess is better than nothing, promising to clean it up later. This resonates especially with vibe-coded AI-generated solutions.
  • The Boy Scout Rule: Leave the campground cleaner than you found it. Small continuous improvements compound over time.
  • Clean Code Characteristics: Clean code reads like well-written prose, never obscures the designer’s intent, and is full of crisp abstractions and straightforward lines of control. It should make you smile the way a well-crafted music box or well-designed car would.

Source: Clean Code by Robert C. Martin