Software Design/Code robustness

Interface robustness characterizes how easy or hard it is to introduce a bug in code using the interface when that code is originally written, or while changing the code. Introducing a bug in these definitions means not just an insertion of a bug, but an insertion of a bug that won't be caught during all code quality checks and will go into production. For example, it may be relatively easy to insert a bug into code written in some dynamically typed language, but if the code has an extensive test suite, most of such bugs would be caught during testing. In this case, the codebase should be considered robust overall.

Different "levels of robustness" may be defined depending on at which stage of software quality checking most errors are surfaced: compilation, linting/static analysis, unit testing, integration testing, code review, etc. Software design practices that aid bug discovery during the quality control stages which are performed more frequently and/or sooner after coding (the moment of the bug insertion) may be considered to ensure "stronger" code or interface robustness.

Types of software errors
Boehm, McClean, and Urfrig identify four types of software errors:
 * 1) Communication: errors due to miscommunication of interface (API) contracts, requirements, assumptions.
 * 2) Completeness: errors due to incomplete grasp of requirements and contracts.
 * 3) Consistency: conceptual errors implementing requirements or coding against interface (API) contracts.
 * 4) Clerical: usually, typographical errors, such as using a wrong constant, mistyping   sign instead of   in a formula, etc.

This taxonomy of errors is used below in this article.

Named parameters
If the programming language supports named parameters, their usage (albeit optional) reduces the risk of confusing the order of passed arguments (a clerical error): In the first version of the statement, would be hard to not notice a error if the  and   arguments were provided in the wrong order. But it would be easy to miss such a error during development and code review with the second version of the statement.

Safe builders
Builder pattern, among other things, makes the code more evident because each parameter of the object being constructed is configured via a function with a name, which is not achievable with constructors in languages which don't support named parameters. On the other hand, builder creates a room for completeness errors: a developer may forget to configure some parameters. Accompanying each field with a boolean flag provides runtime protection against this type of error:  Expand CarBuilder.java example This could be simplified when there are some values of the field type not allowed for the field, e. g.  for   and   for   in the Java example above.

Type-safe builder pattern ensures compile-time robustness against completeness errors.

Design by contract
The practices of verifying function's inputs (preconditions), outputs (postconditions), and class invariants in code (e. g. using assertions) combined with automated testing (such as unit or integration testing) helps to surface communication and consistency errors associated with using functions, objects, APIs, and components.

RAII
RAII is a technique which doesn't leave room for completeness errors of developer forgetting initialize an object by calling a separate function after creating it.

Mutation testing
Mutation testing is a testing practice that helps to protect against completeness errors in test code itself: for example, forgetting to verify the result of a call to a function under test, or forgetting to test a certain aspect of the behavior of the function or the class at all.

Relations to other qualities
If a function's or class's implementation is highly complex then developers are more likely to make consistency and completeness errors. For example, in the implementation of a complex interaction protocol, the order of two steps could be confused.

It may be easier to spot completeness, consistency, and clerical errors in highly structured code because these errors will break the visual pattern of the code. A developer or reviewer can mentally skip through the repetitive structure and focus only on the critical details.

Bugs can also be found more easily in clearer code because there are less distracting details.

If the meaning of some code is not obvious, reviewers are more likely to skim through this code without really trying to verify its semantics mentally, so the chances increase that there are some latent bugs remaining in such code.

When dependencies between code constructs are unapparent there is a higher risk for a developer to alter one of them without updating another (because the developer may be unaware of the dependency) and thus to introduce a communication or consistency error.

Change amplification makes bugs more likely in two ways. First, high change amplification makes changesets larger, which, in turn, are reviewed less effectively in terms of uncovering programming errors. Second, if the compiler doesn't enforce change in all required places in the codebase, it's easier for a developer to forget making some of them when there are many.

When modifying code highly robust against programming error, developers may experience less background alertness because they know errors if happen will be caught by the compiler or the test suite so they don't need to constantly watch and check after themselves for not inserting bugs. Less alertness means less cognitive load.

Mistake tolerance
Code robustness is at odds with mistake tolerance of the dependencies (i. e. functions and classes used in the code): if the dependency permits erroneous usage, by definition it's easier to insert bugs in the code using the dependency. The mistake tolerance of dependencies could be mitigated by some quality assurance practices such as static analysis, integration testing, and mutation testing, as well as an operations practice of making the dependency to log or alert erroneous usages (though tolerating it) and targeting zero such warnings or alerts in production operation of the system.

Relevant practices

 * Design by contract: check (assert) function's preconditions, postconditions, state invariants
 * Specify interface contracts
 * Catch the most specific type of exception
 * Hide an object access chain (in a concurrent environment)
 * Share state by communicating
 * Use class instead of primitive type