The Accumulated Wisdom of Software Engineering, Tested Against Reality
Source: hackernews
There is a certain kind of page that collects aphorisms about software development: short, pithy observations attributed to someone clever, presented as immutable truths. The Laws of Software Engineering is one of these, and its 446 points on Hacker News with 218 comments suggests the topic still generates genuine discussion. What I find interesting is not the laws themselves — most working programmers have internalized them already — but the question of why they persist, which ones actually generalize, and which ones are better understood as observations about a specific era or context.
The Laws That Actually Predict Things
Brooks’ Law, from Fred Brooks’ 1975 book The Mythical Man-Month, remains the most empirically defensible: “Adding manpower to a late software project makes it later.” The mechanism is concrete. New developers require onboarding time from existing developers, communication overhead grows quadratically with team size (n*(n-1)/2 communication channels for n people), and the work that can be parallelized is usually already being done. Brooks was writing about OS/360 at IBM, a project so large and painful that it practically invented modern software project management, and his observations have survived the shift from mainframes to microservices with minimal modification.
Conway’s Law, published in 1968, is perhaps more subtle: “Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.” The interesting part is that this has been studied empirically. A 2008 paper by MacCormack, Rusnak, and Baldwin analyzed the Linux kernel versus a proprietary operating system and found that the more modular, loosely-coupled codebase corresponded to the more distributed organization. The Inverse Conway Maneuver, as described by Martin Fowler and others, suggests you can deliberately restructure your organization to produce the architecture you want — which is one of the founding motivations behind the microservices movement.
Hofstadter’s Law from Gödel, Escher, Bach (1979) is self-referential by design: “It always takes longer than you expect, even when you take Hofstadter’s Law into account.” The recursion is the joke, but it points at something real about how humans model complex systems. We are bad at estimating because we reason about the happy path and discount the long tail of things that go wrong. This is related to the planning fallacy described by Kahneman and Tversky, which suggests the problem is not ignorance but systematic optimism bias.
Hyrum’s Law: The One That Keeps Coming Up
Hyrum’s Law, formulated by Hyrum Wright at Google, is more recent and more specific to large-scale software maintenance: “With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.”
This one is worth sitting with. It explains why large platform migrations are so painful even when the API surface is stable. If your HTTP endpoint returns a JSON object with fields in alphabetical order, and you never documented that ordering, someone will still parse it in a way that depends on that order. When your new implementation returns fields in insertion order, their parser breaks. The law formalizes what every library maintainer eventually learns: your implementation is your interface.
I have run into this directly while building Discord bots. The Discord API makes guarantees about event payloads, but behavior that falls outside those guarantees — timing, ordering, undocumented fields that appear in some guild contexts — gets depended on anyway. When Discord ships an internal refactor and a behavior changes, even one outside their documented spec, the community notices immediately.
The Laws That Are More Context-Dependent
Not every law generalizes cleanly. Zawinski’s Law — “Every program attempts to expand until it can read mail. Those programs which cannot so expand are replaced by ones which can” — was a sardonic observation about 1990s Unix software bloat. It does not describe the current landscape of single-purpose microservices or CLI tools, and it arguably never described embedded systems at all.
Wirth’s Law — software gets slower faster than hardware gets faster — was plausible in the 1990s when Moore’s Law was reliable and managed runtimes were new. Today the hardware trajectory is more complicated: single-threaded performance improvements have slowed significantly, and the performance of modern software is as likely to be bottlenecked by memory bandwidth or cache behavior as by raw algorithmic inefficiency. The law captures a real phenomenon, but the mechanism behind it has changed.
Greenspun’s Tenth Rule — “Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp” — is clever but narrow. It was written by Philip Greenspun in the early 1990s as a pointed argument for Lisp’s expressiveness. The insight (complex systems grow their own scripting layers and dynamic dispatch mechanisms) still appears in practice: Lua embedded in games, Lua embedded in nginx, JavaScript in everything, configuration languages that slowly become Turing-complete. But the framing around Lisp is specific to a particular moment in the language wars.
Gall’s Law as Design Principle
Gall’s Law from Systemantics (1977) is underrated: “A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it works. You have to start over with a working simple system.”
This is not just an observation; it is actionable advice. The history of software is littered with second-system efforts that tried to build the right architecture from the start and shipped years late or never shipped at all. The second-system effect that Brooks described independently is a special case: engineers fresh from success on a constrained project, now given freedom and resources, over-engineer their follow-up.
In systems programming, this pattern shows up clearly. The Linux kernel was not designed from scratch as a production operating system; it was Linus Torvalds’ hobby project that grew. SQLite started as a replacement for a Tcl extension and became one of the most deployed pieces of software on earth precisely because it was built incrementally, with tight scope. The attempts to build correct-by-construction, formally-specified operating systems — while intellectually admirable — have produced research artifacts rather than deployed systems.
What Connects Them
Looking across these laws, a few themes emerge. Human factors consistently matter more than technical ones: teams communicate imperfectly, individuals estimate optimistically, and users depend on everything observable. Complexity is self-propagating: systems grow, interfaces calcify, and the effort required to change them grows faster than the systems themselves. Simple working systems outperform elaborate designed ones, not because simplicity is virtuous in the abstract but because evolution is more robust than specification.
The laws that age best are the ones rooted in these durable human and organizational realities. The ones that age poorly are anchored in the technology or economics of their moment.
The collection at lawsofsoftwareengineering.com is worth reading not as a list of rules to follow but as a record of what practitioners kept rediscovering, independently, across different decades and technology stacks. When the same observation surfaces in 1968 and again in 2006 and again in a 2024 Hacker News thread, it is probably pointing at something real about the craft.