Engineering is building systems that both reduce mistakes and safely absorb the mistakes that do occur. In software engineering specifically, we should apply test-driven development (TDD) and adopt domain-driven design (DDD), use types systems to make illegal states unrepresentable, model what kinds of effects are legal in languages like Unison, and formally model and even prove how parts of our systems work: all tools for more effective “theory-building”, to borrow Peter Naur’s term.
But we also have two serious problems: (1) the limits of translating our thoughts into code; and (2) the limits of translating any human activity into software systems. Many “schemes to improve the human condition” have failed, as James C. Scott observes. We should learn from those failures.
We should not give up on software—or on software engineering! But great engineers know not only when and how to apply their tools, but also the limits of their tools and their whole trade.