Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Good software design starts with software that does the job people need, behaves dependably, and remains understandable as requirements change. Santiago Garcia’s seven-virtue framework offers a practical way to discuss those qualities and their trade-offs—not a formal standard or a replacement for requirements, testing, or established design methods.

What are the seven virtues of good software design?

Garcia presents the virtues in this order: effectiveness, robustness, maintainability, flexibility, reusability, scalability, and efficiency. The sequence reflects his argument about priorities, not a universally accepted ranking.

Virtue What it asks
Effectiveness Does the software meet the user’s actual need?
Robustness Does it handle invalid, missing, or unexpected conditions without uncontrolled failure?
Maintainability Can developers understand, correct, and extend it?
Flexibility Can it accommodate different uses or data without rigid assumptions?
Reusability Can a capability be used by more than one program or interface?
Scalability Can it handle increased throughput as the platform grows, within acceptable constraints?
Efficiency Does it produce useful results with appropriate time and resource use?

1. Effectiveness: solve the right problem

Effectiveness means the software does the job the user needs. It is the foundation in Garcia’s framework: fast, portable, or elegant software is still a failure if it does not serve its purpose.

Start with the requirements and the people or systems the software serves. A design review should ask whether the intended behavior is clear and whether the proposed design can deliver it. This keeps technical qualities in service of the outcome rather than treating them as ends in themselves.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Robustness: handle conditions beyond the happy path

Robust software responds predictably when data is missing, malformed, null, or inconsistent, and when other unexpected conditions arise. The goal is not to pretend that errors cannot happen; it is to avoid letting them produce uncontrolled failure.

Consider what the software should do when an input is absent or invalid, and make that behavior explicit. Robustness matters particularly at boundaries where data or assumptions come from outside the component. Defensive handling can improve reliability, but unnecessary layers of checks can also make code harder to understand, so target the cases that matter.

3. Maintainability: make future changes manageable

Maintainability is the ability to read, correct, and extend software without making every change risky. Garcia associates it with clear names, limited scope, reduced duplication, useful abstractions, and documentation.

These practices help developers see what a component does and where a change belongs. Abstractions are useful when they clarify responsibilities or reduce repeated work; they are not automatically helpful just because they make code more general. The test is whether a future reader can understand the design and modify it with confidence.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Flexibility: accommodate change without losing coherence

Flexible components can support different uses or data without being locked to a single rigid assumption. Interfaces and composition can help provide that flexibility, while keeping each component’s responsibilities clear.

More flexibility is not always better. Generality can weaken cohesion, and connections between components can become difficult to manage. Add variation where there is a real need or plausible change, rather than designing for every imagined future use.

5. Reusability: separate capability from a particular interface

Reusability means a core capability can serve more than one program or environment. Low coupling and clear interfaces make this easier. For example, separating a reusable core from a command-line front end can let another interface use the same underlying capability.

Reuse is not guaranteed by adding abstractions. A component tied to assumptions from its original environment may be difficult to reuse elsewhere. Keep environment-specific behavior at the edges when reuse is a genuine requirement, and compare the cost of separation with the value of sharing the capability.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

6. Scalability: account for growth and bottlenecks

Scalability concerns whether a system can increase throughput as its underlying platform grows while maintaining acceptable performance. Growth can expose bottlenecks, high memory use, and contention for heavily used resources.

Evaluate scalability against the system’s expected workload and platform constraints. A design that works at one scale may behave differently when demand increases; identifying the likely bottlenecks is more useful than assuming that additional platform capacity will solve them automatically.

7. Efficiency: use resources appropriately

Efficiency is the use of time, memory, and other resources to produce useful results. Garcia puts it last because optimizing too early can make design harder to understand and more error-prone, while an understandable system is easier to correct or optimize later.

That ordering is an argument, not a universal rule. If a system has strict performance requirements, resource limits, or a workload where latency is central, efficiency needs to be addressed as a requirement from the outset. Otherwise, avoid complexity for speculative gains and make optimization decisions in light of actual needs and constraints.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should teams prioritize the virtues?

Garcia’s ordering suggests first delivering the intended function and dependable behavior, then preserving the ability to understand and adapt the system, and then addressing reuse, growth, and resource efficiency. It is a useful starting point for discussion, not an empirically proven hierarchy. Priorities vary: a prototype, a short-lived tool, and a long-lived product do not necessarily need the same balance.

The virtues can pull in different directions. An abstraction might enable reuse while making a small program harder to understand; extra defensive behavior may improve reliability while adding complexity; generality can blur a component’s purpose; and optimization can reduce readability. State the project’s requirements and constraints before deciding which trade-offs to accept.

Compare designs against the same needs

When comparing two approaches, use the same requirements and workload for both. Assess whether each:

  • Meets user needs.
  • Handles invalid or unexpected input appropriately.
  • Can be understood and changed without undue risk.
  • Accommodates the additional uses the project actually expects.
  • Can be reused across the environments that matter.
  • Supports increased throughput within platform constraints.
  • Uses time and other resources appropriately.

Then identify which of those dimensions are most important for this project. A design is not best in every context simply because it scores well on every virtue in the abstract.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How the virtues relate to established design techniques

Virtue lists help teams communicate about desirable qualities; they do not replace specific engineering methods. Industrial Logic makes this distinction about its own, different code-virtues framework: “This doesn’t replace mechanisms such as the SOLID principles or the 4 Rules of Simple Design, it merely helps us communicate clearly about the code we love.”

There is overlap between Garcia’s concerns and other design guidance. The California Department of Technology’s The Big Plan summarizes Unix design rules involving modularity, clarity, composition, separation, simplicity, transparency, robustness, and repairability. It states: “Developers should design for simplicity by looking for ways to break up program systems into small, straightforward cooperating pieces.” That guidance aligns with several virtues; it does not endorse Garcia’s exact list or its ordering.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.