Enhancing System Reliability through Explicit Validation Contracts
In modern software development, one of the most persistent challenges is the question: When and where should data validation occur? Too often, this uncertainty produces two problematic extremes:
As applications grow more complex, this ambiguity leads to fragile systems—ones that are not only harder to maintain but also more prone to errors.
But beneath this surface-level challenge lies a deeper architectural issue: the violation of the Liskov Substitution Principle (LSP), one of the cornerstones of object-oriented design. Let’s unpack how validation relates to LSP, and why making validity explicit in your type system can fundamentally improve system reliability.
The Hidden Violation of LSP
The Liskov Substitution Principle asserts that objects of a superclass should be replaceable with objects of a subclass without breaking program correctness.
Yet in many systems, we often see methods accepting generic objects—say, a File—and silently assuming that these objects have already been validated (that the file exists, is readable, etc.). When this assumption is wrong, defensive checks must be scattered throughout the codebase, or worse, unsafe behaviors slip through.
The problem isn’t that developers are careless; it’s that the type system doesn’t distinguish between validated and unvalidated states. In other words, a File object may or may not represent something the system can safely access, yet the compiler treats it as the same thing. This silent contract breaks LSP by forcing functions to treat subclasses (validated cases) and superclasses (raw cases) differently.
Contracts Through Types: A Structural Solution
The way out of this problem is deceptively simple: Make validation explicit by modelling valid and invalid states in the type system.
Instead of letting any generic input drift into the system unchecked, create dedicated, trusted types that can only be constructed through validation.
For example:
A ValidatedInputData object can only be produced by a validation function or factory. Once constructed, calling code can operate with confidence: no further checks are needed, because the type itself is a guarantee of correctness.
This approach shifts responsibility from developers to the compiler. Functions don’t have to be defensive anymore—they simply declare that they require a valid type. If someone tries to pass in raw input, the system won’t compile, preventing fragile assumptions before they ever reach runtime.
A Concrete Example: Files and Reliability
Consider a function that processes files. The naïve approach usually looks like this:
text
function process(file: File) {
if (!file.exists()) throw Error("File missing");
if (!file.isReadable()) throw Error("File not readable");
// Proceed with operations...
Recommended by LinkedIn
}
The problem is obvious: wherever a file is processed, these same checks get repeated. If a developer forgets them—or adds a different variant—bugs creep in.
Now, consider a new abstraction:
text
function process(file: ReadableFile) {
// No additional checks needed.
// Safe operations guaranteed.
}
What used to be a runtime pattern of defensive checks becomes a compile-time contract. Only valid, trusted ReadableFile instances can ever reach process().
Benefits of Explicit Validation Contracts
Making the Correct Path Easy
Architectural patterns succeed not only when they’re correct but when they guide developers into doing the right thing by default. By modeling validity within your type system:
This subtle but powerful shift ensures software grows more robust, safer, and easier to extend without accumulating hidden contracts.
Conclusion
Uncertainty around data validation is more than just an implementation detail—it’s a structural flaw that undermines core principles like LSP. By explicitly modelling validation as part of your type system—through distinct raw and trusted types—you enforce correctness by construction.
The result is software where:
For architects and developers alike, this is a pattern worth embracing: treat data validation not as an afterthought, but as a first-class contract woven directly into your system’s design.
Thoughtful post, thanks Manutosh