What are you solving?
GenAI is what got me thinking about code generation and older versions of it that I have worked with. The appeal is the same as it’s always been: get more done, reduce the toil. That’s all goodness, but it can be used to “solve” the wrong problems too. Problems you could remove and not need to fix. I’ve worked with code generation for decades, solving real problems and making platforms easier to use. ...
AI, CSS, and Accessibility
I used GitHub Copilot to help create a custom dark theme for a MkDocs Material site. Copilot did a good job interpreting CSS selectors (the rules that target specific HTML elements for styling), but the resulting theme’s accessibility was poor. Even when I nudged it toward better choices, some text ended up barely readable with low contrast to the background. I took this on as an experiment because I’m not great at choosing color palettes, the MkDocs Material theme exposes a lot of selectors for customization, and I’ve built a custom dark theme for MkDocs before and remember it being tricky. After sharing the experience on LinkedIn, I found I wasn’t alone. Others had run into similar issues but were able to improve things with trial and error. ...
The GenAI Arc
SQL changed everything with one idea: describe what you want, not how to get it. SELECT * FROM users WHERE age > 25 No loops. No conditionals. Just intent. That idea, declare the goal and let the system figure out the implementation, is the core of Fourth Generation Languages (4GLs). SQL emerged from it in the 1970s and was commercialized alongside other 4GLs in the 1980s. GenAI takes the same arc further: natural language instead of SQL syntax, spanning every domain instead of just data. 4GLs democratized access to data. GenAI democratizes access to systems, and that difference is what makes both the promise and the threat real. ...
Legacy Teams
Legacy teams don’t need cake: they need a seat at the table. With modernization efforts, new systems often get the spotlight and celebrating those wins is important. But beware the “cake in the break area” trap. If your legacy support team’s main interaction with your modernization effort is a leftover piece of cake, you’ve fallen into that trap. Legacy engineers keep the present running while the future is still being built. They bring deep institutional knowledge, production support experience, and insight into why systems were built the way they were, knowledge earned through past rewrites, migrations, and upgrades. ...
Bridging the Knowledge Chasm
Someone retires. The handoff seems fine. Then six months later, a weird production issue comes back … and the only person who understands the “why” is no longer on the payroll. Everyone retires eventually. But for teams running mission-critical systems, retirement can create a knowledge chasm: a sudden loss of operational context that no amount of org charts or knowledge items can replace. The cost of a structured knowledge transition is measured in months. The cost of losing undocumented institutional knowledge is measured in years. ...
Making Space for Discovery
As children, we built forts out of pillows and cardboard boxes … no approvals, no fear of failure, just imagination and play. Now, as adults, we optimize for uptime and reliability. Production must be stable. The challenge isn’t choosing between creativity and stability. It’s building the structures that support both. Safe Spaces for Experimentation You can carve out space away from production: sandbox environments, hack days, prototypes. Or you can enable safe experiments within production: pilot rollouts, feature flags, A/B testing. Ideally, you’re doing both. ...
Willing to Fail
Being willing to fail is different from being reckless. But in software, the line is often drawn in the wrong place. The real risk in engineering isn’t moving too fast or too slow; it’s making irreversible changes without knowing which ones they are. Some decisions are reversible: a feature flag can be turned off, a proof of concept can be thrown away, a pilot covering a smaller set of use cases can be stopped before it scales. A new operational reporting store can be built alongside the existing one, allowing gradual migration without committing to a hard cut-over. ...
Distributed Transactions
Transactions in a single database are well-understood. Transactions across databases, queues, and external services are not. The failure modes are different, the guarantees are weaker, and the abstractions your framework provides often hide what’s actually happening. Common Pitfalls Rollback means everything is undone: Not for external calls or side effects. It’s all or nothing: Only if a single system is involved. Across databases and queues, you need two-phase commit (2PC), compensating transactions, or robust retry. 2PC is better: 2PC means simpler code but requires full protocol support from all participants. The coordinator is also a single point of failure, and under failure conditions it holds locks across all participating systems until the outcome is resolved. That’s why the industry has largely moved away from it in loosely coupled distributed systems. Inside controlled boundaries where the platform supports the protocol natively, the tradeoffs look very different. Compensating transactions are better: While they reduce coupling, they also require significantly more error handling and introduce eventual consistency. For some business workflows, compensation carries real risk … you can’t un-notify a customer. If a payment already went out, all you can do is issue a stop payment, which looks to the customer like you don’t know what you’re doing. Sagas are simple: The saga pattern (choreography or orchestration) gives you a structured way to coordinate compensating transactions across services. But sagas trade atomicity for availability. Every step needs a defined rollback path, and failure handling must be explicit and tested, which means more coding and testing for the same outcomes. Nested transactions are simple: Nope. They cause unexpected locking and failure behavior. In longer-lived codebases they often surprise engineers who do not know they are there. What Engineers Should Focus On Understand what the behavior is for your storage engine for transactions, isolation, rollback, message delivery, and retry. Test error handling for all failure scenarios, including partial failures and retries. Handle side effects explicitly. The outbox pattern (write the side effect to a local table in the same transaction, then process it asynchronously) is a reliable approach. Idempotent operations (those that produce the same result no matter how many times they run) ensure retries don’t cause duplicate effects. Avoid external calls in transactions if possible. These can make transactions run longer and have external side effects. Keep transactions fast and small to reduce database contention and locking issues. Pick the strategy that fits your system. 2PC works well inside controlled boundaries. Compensating transactions and sagas work better across independently deployed services. Eventual consistency is acceptable for some use cases and unacceptable for others. Know which situation you’re in. Your transaction strategy defines your failure behavior. Pick it deliberately, test the failure modes, and design the boundaries between different strategies with the same care you give the strategies themselves. ...
Patterns Worth Knowing
Christopher Alexander wrote about livable cities before the Gang of Four applied his thinking to software. The message was the same: good design solves problems people actually have, not problems that look interesting on a whiteboard. The Pattern Instinct Alexander’s 1977 book, A Pattern Language, revealed how timeless design principles create spaces where people thrive. The same thinking has made software systems more maintainable, more adaptable, and easier to reason about. Even if you don’t know the pattern names, you’ve almost certainly worked with them. ...
The Colossus Bet
“Should we modernize the legacy system or build a new one?” I’ve watched this question consume months of debate. The instinct is to pick one path, commit, and move. But eighty years ago, a wartime memo made the opposite call … and it changed the course of history. Both Options In 1944, British codebreakers at Bletchley Park faced a problem: they needed to crack the Lorenz cipher faster than existing methods allowed. A GCHQ retrospective describes how a memo proposed two competing approaches. One was incremental. The other, Colossus, was called a “much more ambitious scheme” … the first digital computer. The recommendation was to pursue both. ...