4-bit Rules of Computing, Part 5

A software team once changed their version control system's API, removing what they saw as redundant legacy functionality. What they didn't realize was that this particular piece of "technical debt" was actually a load-bearing wall in a customer's factory automation pipeline. The result? A business losing thousands of dollars per hour while the production line stood idle.

In software development, every API endpoint, command-line interface, or file format becomes part of someone's workflow, automation, and daily operations. When Microsoft introduced the Ribbon interface in Office 2007, they broke a promise that millions of users had built their muscle memory around. When a content management system I worked with removed its multimedia MIME-type feature in the name of code cleanup, it left a trail of broken websites and frustrated users in its wake.

We need to carefully balance between necessary changes and breaking changes, which brings me to Rule A: "Fix Mistakes, don't Break Promises". When does a "mistake" become serious enough to justify breaking a "promise"? How do we make necessary changes without leaving our users stranded?

This is the sixth part of my blog series expanding on my 4-bit rules of computing.


Rule A: Fix Mistakes, don't break Promises

Two Forces of Change

Software changes for two main reasons. First, our understanding of user needs evolves. When Microsoft introduced the Ribbon interface, it wasn't change for change's sake — it was a response to users struggling with discovering features buried in nested menus. The change aimed to make functionality more discoverable and accessible.

Second, is the pressure of technical debt. Developers face mounting costs maintaining legacy systems, supporting old formats, or working around historical design decisions. That MIME-type feature wasn't removed on a whim — it likely represented a maintenance burden, perhaps even a security risk.

Strategies for Keeping Technical Promises

One could take the approach that both Git and IBM's REST APIs follow: never break existing behavior, only add new alternatives. In Git, your ancient deployment script using git push -f still works, even though they've introduced --force-with-lease as a safer option. Similarly, IBM's APIs maintain old fields and endpoints while adding new ones1 that provide better functionality.

This is an ideal, but it may not be pragmatic to always follow this doctrine. Modern software projects have developed several patterns for managing necessary changes:

Collaborative Exploration

Before making widespread changes, invite customers to explore new approaches alongside developers. This creates a partnership where both sides learn: developers understand real-world usage patterns better, while users help shape solutions that work for their needs. While Microsoft's Ribbon interface change was done in consultation with a UX team and test users, it shows how disruptive sudden changes can be to a broader audience. Their approach with TypeScript demonstrates a better way. TypeScript evolves through a public proposal process2, working with framework authors and the broader community before implementing major language features. This ensures TypeScript meets changing needs while maintaining compatibility.

Automated Migration Paths

Don't just tell users how to move to new methods: help them do it. Tools can analyze existing code or configurations and automatically update them to use new patterns. React provides automated codemods3 for version upgrades, helping developers automatically update component syntax and lifecycle methods4. The key is transparency — users should understand and approve these migrations, not just have them happen silently.

Feature Flags and Gradual Rollout

Rather than forcing changes on all users simultaneously, feature flags let us roll out changes gradually5. Users or organizations can opt in when they're ready, testing the waters before committing to new approaches. Web browsers demonstrate this well — both Chrome6 and Firefox7 use origin trials to let developers test upcoming web platform features before they become standard, while users can experiment with new features through browser settings.

Parallel Systems

When a change is too fundamental for feature flags, we can run old and new systems in parallel. The Python 2-to-3 transition8 initially faced challenges by forcing an either-or choice. The community later succeeded by developing tools that let codebases run on both versions simultaneously, allowing gradual, file-by-file migration.

Smart Defaults with Configuration

Sometimes we can update default behavior while letting users retain old behavior through configuration. The GNU Compiler Collection9 does this well — newer versions default to stricter warning flags and newer language standards, but you can always specify older standards if needed. Your old Makefiles still work, but new projects get better safety checks automatically. This is like an opposite of the Git/IBM ideal, but it may be a better option depending on the user base and the nature of the change.

Multi-Phase Deprecation

When we must remove functionality:

  1. First, mark it as deprecated but keep it working
  2. Add warnings when the deprecated functionality is used
  3. Provide clear documentation about alternatives, and a migration path
  4. Only remove it after several release cycles, when usage metrics show minimal impact

The key observation across all these strategies is this: the cost of change should fall on the software makers, not the consumers. Our customers trust us — breaking that trust should never be done lightly. Don't remove a feature unless and until there is an alternative, and people have migrated to it.

Owning trust in personal relationships

These technical practices reflect values I try to apply more broadly: doing what I say I will, acknowledging mistakes when they happen, and working to make things right. I'm not perfect at this, but I've found that consistent behavior and honest accountability build better relationships, whether in code or in life.

When things must change, communicate it, and work together to come to mutually acceptable compromise.