Laws
After our last discussion about common idioms in software engineering, it’s time to explore the laws that govern our field. I took a short break for the holidays, but now I’m back and ready to dive into the fascinating world of engineering laws. You’ve likely encountered many of these laws in your daily work without realizing they’re well-established phenomena defined and tracked by others. Not long ago, I had a hilarious experience with an engineer who unknowingly explained Amdahl’s law. They had never heard of it but were experimentally proving the law to some extent. It was amusing to watch, so I let them explore and rediscover the law in greater detail before finally pointing them to its definition and resources. You should have seen the mixed emotions on their face – happiness, sadness, shock, and awe – when they realized they had just rederived a common law on their own.
It’s important to note that these “laws” we refer to in software engineering aren’t necessarily laws in the formal scientific sense. They don’t represent immutable, universal truths like the laws of physics or mathematics. Rather, they are principles, observations, or adages that describe common patterns or behaviors in our field. While not as rigid as scientific laws, these “laws” still provide valuable insights and guidance. They distill the collective experiences and wisdom of numerous practitioners, encapsulating concepts that can help us understand, predict, and navigate various challenges and tradeoffs. By recognizing and internalizing these laws, we can make more informed decisions, avoid common pitfalls, and better comprehend the underlying dynamics at play in our projects and systems.
Gall’s Law: “A complex system that works is invariably found to have evolved from a simple system that worked.” This law suggests that successful complex systems are rarely designed from scratch but rather evolve over time from simpler systems that proved themselves first. Gall’s Law emphasizes the importance of iterative development, incremental expansion, and evolutionary design. It cautions against attempting to build overly complex systems from the outset, as they are more prone to failures and unforeseen issues. Instead, the law encourages starting with a minimal viable product or a simple working solution, and then gradually enhancing and expanding it over time as requirements evolve and new insights are gained. This iterative approach allows for continuous validation, testing, and refinement, reducing the risk of introducing catastrophic failures or architectural flaws. Gall’s Law also highlights the value of modularity, loose coupling, and extensible designs that facilitate incremental growth and adaptation without compromising the existing functionality.
Brooks’ Law: “Adding human resources to a late software project makes it later.” This law highlights the challenges of scaling up a team midway through a project. While it may seem logical to bring in more developers to accelerate progress, the onboarding and communication overhead can actually slow things down initially. Software engineering projects often face deadline pressures, and there’s a temptation to throw more resources at the problem. However, Brooks’ Law reminds us that expanding a team, especially for complex projects with significant interdependencies, introduces inherent inefficiencies and ramp-up costs. It emphasizes the importance of careful planning, realistic scheduling, and efficient communication channels from the outset, rather than relying on last-minute staffing increases as a solution.
Conway’s Law: “Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.” This law states that the structure of a software system will mirror the structure of the organization or team that designed it or that the organization will mirror the structure of its software. Conway’s Law highlights the profound impact that team organization and communication patterns have on the architecture and design of software systems. If a team is divided into specialized units with limited cross-communication, the resulting system is likely to be a collection of interconnected but siloed components, reflecting those organizational boundaries. Conversely, if a team fosters open communication and collaboration across roles, the system design is more likely to be cohesive and well-integrated. This law emphasizes the importance of aligning organizational structures, team dynamics, and communication channels with the desired architectural goals of a software project. It underscores the need for intentional planning and fostering effective collaboration to produce systems that meet quality and maintainability objectives.
Hyrum’s Law: “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 law states that as a software interface or API gains more users, any observable behavior, even if unintentional or undocumented, may become a de facto part of the interface that users rely on. Hyrum’s Law has significant implications for software engineering, particularly in the context of building and maintaining APIs, libraries, and frameworks used by third-party developers or clients. It highlights the challenge of making backwards-compatible changes or refactoring internal implementations, as users may have come to rely on undocumented behaviors or side effects. Even if the official contract or documentation does not guarantee certain behaviors, some clients may have built dependencies on them, making changes potentially breaking. This law underscores the importance of carefully designing and documenting APIs from the outset, as well as the need for comprehensive testing and careful management of changes to avoid inadvertently breaking existing clients.
Amdahl’s Law: “The performance improvement to be gained from using some faster mode of execution is limited by the fraction of the time the faster mode can be used.” This law describes the theoretical maximum improvement that can be achieved through parallelization or optimization of a system. Amdahl’s Law highlights the importance of identifying and focusing optimization efforts on the true bottlenecks in a system. Even if one part of a program or process is significantly sped up, the overall performance gains will be limited by the portions that remain unoptimized. It underscores the need for careful profiling and analysis to pinpoint the critical paths and areas that will yield the most substantial improvements when parallelized or optimized. Amdahl’s Law manages expectations and guides developers to prioritize efforts wisely, rather than pursuing optimizations that may have diminishing returns on the overall system performance.
Murphy’s Law: “Anything that can go wrong will go wrong.” This adage captures the idea that if there is a possibility for something to fail or cause problems, it will eventually happen. In software engineering, Murphy’s Law serves as a reminder to expect and plan for failures, errors, and unexpected scenarios. It highlights the importance of robust error handling, thorough testing, and contingency planning in software development. Developers should anticipate and account for potential issues, edge cases, and unforeseen circumstances that could arise during the lifetime of a software system. Murphy’s Law encourages a mindset of defensive programming, where assumptions are questioned, and precautions are taken to mitigate risks and minimize the impact of failures. It also underscores the value of monitoring, logging, and diagnostics tools to aid in troubleshooting and recovery when things inevitably go wrong.
Little’s Law: “The long-term average number of customers in a stable system (L) is equal to the long-term average effective arrival rate of customers (λ) multiplied by the average time a customer spends in the system (W), or L = λW.” This law relates the average arrival rate, average number of items, and average wait time in a queuing system. In software engineering, Little’s Law is applicable to various scenarios involving queues, buffers, and resource contention. It helps understand the relationships between throughput, latency, and concurrency in systems dealing with requests, tasks, or jobs. By analyzing the arrival rate and service time, Little’s Law can provide insights into system bottlenecks, capacity planning, and performance tuning. It guides decisions around resource allocation, load balancing, and concurrency limits to optimize throughput while managing latency and queue lengths.
Occam’s Razor: “The simplest solution is most likely the correct one.” This principle states that when presented with multiple possible explanations or solutions for a problem, the one with the fewest assumptions or complexities is usually the correct choice. While its not a law per-se, its close enough. Occam’s Razor encourages simplicity and parsimony in design, architecture, and problem-solving. It serves as a guiding principle for developers to favor straightforward, elegant solutions over convoluted or overly complex ones. By embracing this principle, software engineers can produce systems that are easier to understand, maintain, and extend. Occam’s Razor also aligns with principles such as the KISS (Keep It Simple, Stupid) philosophy and the concept of minimalism in software design. It helps avoid unnecessary complexity, technical debt, and over-engineering, which can lead to increased development costs, higher potential for bugs, and reduced flexibility. While simplicity should not be pursued at the expense of essential functionality or requirements, Occam’s Razor reminds developers to critically evaluate the trade-offs and prioritize simplicity whenever possible.