<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mykola Haliullin</title>
    <description>The latest articles on Forem by Mykola Haliullin (@ngaliullin).</description>
    <link>https://forem.com/ngaliullin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3397393%2Fd3219524-4a3a-4711-8866-00baac3d28bc.jpg</url>
      <title>Forem: Mykola Haliullin</title>
      <link>https://forem.com/ngaliullin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ngaliullin"/>
    <language>en</language>
    <item>
      <title>Encapsulated Collaboration: Using Closures to Extend Class Behavior Without Violating Interface Boundaries</title>
      <dc:creator>Mykola Haliullin</dc:creator>
      <pubDate>Tue, 05 Aug 2025 13:33:41 +0000</pubDate>
      <link>https://forem.com/ngaliullin/encapsulated-collaboration-using-closures-to-extend-class-behavior-without-violating-interface-fhm</link>
      <guid>https://forem.com/ngaliullin/encapsulated-collaboration-using-closures-to-extend-class-behavior-without-violating-interface-fhm</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is part of a broader discussion in my book &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;Safe by Design: Explorations in Software Architecture and Expressiveness&lt;/a&gt;. If you like it, you might enjoy the full read for free on GitHub.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: The Hidden Cost of Interface Changes
&lt;/h2&gt;

&lt;p&gt;In software architecture, one of the most subtle forms of technical debt comes not from what we write - but from what we expose.&lt;/p&gt;

&lt;p&gt;It often starts with a small, well-meaning change: we want to add access to a private field or internal method of an important class. But we only need that access in one very specific client - a test, a legacy integration, a specialized handler. And so we think: maybe we should just extend the interface a little?&lt;/p&gt;

&lt;p&gt;But this decision, while easy to justify in the moment, has long-lasting consequences. Every extension to an interface creates a new public contract. And public contracts are sticky. Other developers start to rely on it. The internal detail becomes external behavior. You've added surface area - and lost encapsulation.&lt;/p&gt;

&lt;p&gt;This article explores a practical architectural technique to solve such cases elegantly: using closures to provide internal access without exposing internal state, and without bloating your interface. We'll walk through the problem, naive solutions, and then land on a design pattern that preserves encapsulation, keeps dependencies clean, and minimizes ripple effects in your codebase.&lt;/p&gt;

&lt;p&gt;Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: One Class, One Secret, One Needy Client
&lt;/h2&gt;

&lt;p&gt;Imagine you're working with a critical class in your codebase - let's call it ImportantService. It's well-designed, well-tested, and widely used across the system. Somewhere deep inside, it holds a private detail - say, a cache map or a connection state - that is intentionally hidden from the outside world.&lt;/p&gt;

&lt;p&gt;Now suppose you have a very specific client - maybe a migration job, a diagnostic tool, or an adapter - that needs to interact with that internal detail just once, in one specific place. It doesn't need full control over the class. It doesn't even need to know how the field works. It just needs to use it once, responsibly.&lt;/p&gt;

&lt;p&gt;But there's a problem: that field isn't accessible. The only way to reach it is by modifying ImportantService's public interface. And that feels wrong.&lt;/p&gt;

&lt;p&gt;You hesitate, and rightfully so. Because modifying a public interface means opening a door that was previously closed. And once that door is open, it rarely gets closed again. Even if your intention was to use it just once, you can't stop other parts of the codebase from wandering through it later.&lt;/p&gt;

&lt;p&gt;So you're stuck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't want to break encapsulation.&lt;/li&gt;
&lt;li&gt;You do need access in one place.&lt;/li&gt;
&lt;li&gt;And you're not sure how to solve this cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the problem we're going to tackle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naïve Solution: Just Expose the Field
&lt;/h2&gt;

&lt;p&gt;The most straightforward way to solve this is deceptively simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Just add a new method to the public interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, you might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ImportantService {
    private val cache = mutableMapOf&amp;lt;String, String&amp;gt;()
    fun getCache(): MutableMap&amp;lt;String, String&amp;gt; {
        return cache
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your client can interact with the internal cache freely. Problem solved?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;You've just violated the principle of encapsulation - and done so permanently. What was once an implementation detail is now a public API. Even if your intention was to use this method in only one place, you've effectively told every other developer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, feel free to reach into this internal structure whenever you like."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This creates three major problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fragility &lt;/strong&gt;- The internal structure can no longer change freely. If you swap cache for a different mechanism later, you'll break all clients relying on getCache().&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misuse Risk&lt;/strong&gt; - Other parts of the codebase might start using the exposed method in ways you didn't anticipate - maybe even mutating shared state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Pollution&lt;/strong&gt; - The class's public interface becomes bloated with methods that serve niche needs, making it harder to understand and harder to maintain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach works in the short term but almost always causes regret later. It's a technical shortcut with long-term cost.&lt;/p&gt;

&lt;p&gt;And most importantly: it introduces a contract where none was meant to exist. The moment you expose something, you own it - forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better But Clumsy: Interface Splitting and Casting
&lt;/h2&gt;

&lt;p&gt;If exposing a private field directly is too risky, a slightly better solution might be to split the interface. That is, define two interfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A public interface that contains only the safe, general-purpose methods used by the majority of the system.&lt;/li&gt;
&lt;li&gt;A specialized interface that includes access to the internal detail - used only where truly needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface PublicAPI {
    fun doSomething()
}
interface InternalAPI : PublicAPI {
    fun getCache(): MutableMap&amp;lt;String, String&amp;gt;
}
class ImportantService : InternalAPI {
    private val cache = mutableMapOf&amp;lt;String, String&amp;gt;()
    override fun doSomething() {
        // ...
    }
    override fun getCache(): MutableMap&amp;lt;String, String&amp;gt; = cache
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your codebase, you expose ImportantService only as PublicAPI. But in the one place that needs &lt;code&gt;getCache()&lt;/code&gt;, you can do a cast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val service: PublicAPI = getImportantService()
val internal = service as? InternalAPI
internal?.getCache()?.put("debug", "value")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach does preserve encapsulation to an extent - the &lt;code&gt;getCache()&lt;/code&gt; method isn't visible unless you explicitly cast to InternalAPI.&lt;/p&gt;

&lt;p&gt;But it comes with its own set of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unsafe Type Casting - Even though you "know" the type at runtime, you're opting out of compile-time safety. That's a slippery slope and a code smell in most modern systems.&lt;/li&gt;
&lt;li&gt;Leaky Abstractions - You've now introduced multiple views of the same object. If other developers find and misuse the InternalAPI, you're back to polluting your architecture.&lt;/li&gt;
&lt;li&gt;Refactoring Overhead - You've complicated your class hierarchy and added extra interfaces to maintain, all to solve a one-off case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In theory, interface segregation is a clean architectural practice. But in this scenario - where the need is rare, isolated, and very specific - it can feel like overkill. Worse, the as? cast becomes a tacit admission:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We broke the type system a little, but it's probably fine."&lt;br&gt;
There must be a better way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;If you're enjoying this so far, there's a lot more in &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;the book&lt;/a&gt; - same tone, just deeper. It's right here if you want to peek.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Elegant Trick: Using Closures to Encapsulate Access
&lt;/h2&gt;

&lt;p&gt;Instead of modifying the interface or relying on unsafe casts, we can flip the problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if we don't expose the internal detail, but instead expose a controlled interaction with it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where closures (or lambdas) come in.&lt;/p&gt;

&lt;p&gt;Rather than letting the client access the internal field, we let the internal field access the client logic - but only in a very narrow and safe way.&lt;/p&gt;

&lt;p&gt;Let's take the same ImportantService, but this time, we give it a method that accepts a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ImportantService {
    private val cache = mutableMapOf&amp;lt;String, String&amp;gt;()
    fun &amp;lt;R&amp;gt; withCache(action: (MutableMap&amp;lt;String, String&amp;gt;) -&amp;gt; R): R {
        return action(cache)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val service = ImportantService()
service.withCache { cache -&amp;gt;
    cache["debug"] = "value"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks similar to accessing the cache directly, but it's a different animal architecturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this better?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Controlled Exposure&lt;/strong&gt;
The internal detail is still private. You're only giving clients a momentary, scoped interaction with it - and only when you choose to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Interface Pollution&lt;/strong&gt;
You didn't add a new getter. You didn't expand your public API. The class's surface area remains tight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Type Leaks&lt;/strong&gt;
No casting, no subclassing, no interface proliferation. Everything is encapsulated within the boundary of the method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear Intent&lt;/strong&gt;
By naming the method withCache, you signal that this is a controlled and intentional handoff - not an invitation to poke around.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bonus: Functional Composition
&lt;/h2&gt;

&lt;p&gt;You can combine this pattern with other functional tools - mapping, chaining, filtering - to do expressive work with internal state without ever revealing it.&lt;/p&gt;

&lt;p&gt;This is already a much cleaner solution. But it has a hidden cost we'll explore next - and it involves dependency direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  But Wait: Inverted Dependencies
&lt;/h2&gt;

&lt;p&gt;At first glance, the closure-based approach seems perfect: it keeps your internal field private, avoids interface pollution, and provides clients with just enough power to get their job done.&lt;/p&gt;

&lt;p&gt;But there's a subtle shift happening here - one that could quietly violate a key architectural principle: dependency direction.&lt;br&gt;
Let's look again at the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun &amp;lt;R&amp;gt; withCache(action: (MutableMap&amp;lt;String, String&amp;gt;) -&amp;gt; R): R {
    return action(cache)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Who's in control here?&lt;/p&gt;

&lt;p&gt;The client defines the action, i.e. the logic that operates on the cache.&lt;br&gt;
The ImportantService receives and executes that logic.&lt;/p&gt;

&lt;p&gt;This means the core class - the thing we want to keep stable and authoritative - is now depending on a function provided by an external client.&lt;/p&gt;

&lt;p&gt;That's an inversion of control. And not the good kind.&lt;br&gt;
In traditional architecture, the stable component should not depend on the volatile one. That's the essence of the Stable Dependencies Principle. And here, we just flipped that - the ImportantService now calls out to a client-defined function.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why is that a problem?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hidden Coupling &lt;/strong&gt;- Now the core class indirectly relies on logic defined elsewhere. This creates temporal and behavioral coupling that's hard to trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change Fragility &lt;/strong&gt;- If the client's closure changes in a way that breaks assumptions, it can affect the core class, even if its own code hasn't changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harder to Test in Isolation &lt;/strong&gt;- Testing the core class now requires mocking or simulating the injected closure logic, which might not be desirable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So while closures give us encapsulation, they come at the cost of reversing the dependency graph.&lt;/p&gt;

&lt;p&gt;To keep things clean, we need to restore the direction - without losing the flexibility. And that's exactly what we'll tackle next.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Final Abstraction: A Dedicated Closure Carrier
&lt;/h2&gt;

&lt;p&gt;To resolve the dependency inversion without giving up the elegance of closures, we introduce a new architectural element:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a small, dedicated abstraction that acts as a neutral carrier of logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of passing the closure from the client directly into the ImportantService, we wrap that logic inside a purpose-built object - let's call it &lt;code&gt;CacheAction&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun interface CacheAction&amp;lt;R&amp;gt; {
    fun execute(cache: MutableMap&amp;lt;String, String&amp;gt;): R
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This interface does one thing: defines a contract for interacting with the internal cache. It is not the client, and it is not the service - it's an abstraction between them.&lt;/p&gt;

&lt;p&gt;Now, the ImportantService depends on CacheAction, not on an arbitrary client-provided lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ImportantService {
    private val cache = mutableMapOf&amp;lt;String, String&amp;gt;()
    fun &amp;lt;R&amp;gt; perform(action: CacheAction&amp;lt;R&amp;gt;): R {
        return action.execute(cache)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the client side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val action = CacheAction&amp;lt;String&amp;gt; { cache -&amp;gt;
    cache["debug"] = "value"
    "done"
}
val result = service.perform(action)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why is this better?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Flow is Restored&lt;/strong&gt;
The service depends only on the abstraction (CacheAction), which is stable and controlled. It doesn't care where the implementation comes from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encapsulation is Preserved&lt;/strong&gt;
The cache is still private. No getters, no leaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface is Clean and Intention-Revealing&lt;/strong&gt;
The method name perform + the typed parameter make the intent explicit: you're performing an action on behalf of the service, using its private parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability is Improved&lt;/strong&gt;
You can now test ImportantService with stub CacheActions, or test different CacheAction implementations independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional Reuse and Registry&lt;/strong&gt;
In larger systems, you can even register reusable CacheActions - or compose them - giving you plugin-like extensibility without ever exposing internals.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern gives you the power of closures, the safety of clean dependencies, and the expressiveness of functional composition - all while keeping your architecture tidy.&lt;br&gt;
We've now solved the original problem without polluting the interface, without unsafe casts, and without inverting control in the wrong direction.&lt;br&gt;
Let's wrap it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: Extend Without Breaking
&lt;/h2&gt;

&lt;p&gt;Let's recap the journey. We started with a simple but frustrating problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do you give a specific client access to a private part of a class without compromising the design of the system?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We explored several options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exposing a getter&lt;/strong&gt; - quick but harmful. It breaks encapsulation and pollutes the interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Splitting interfaces and casting&lt;/strong&gt; - safer in theory, but clunky and error-prone in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using closures&lt;/strong&gt; - elegant and concise, but introduces inverted dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introducing a dedicated abstraction&lt;/strong&gt; - the best of all worlds: encapsulated behavior, clean dependency flow, and composability.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When to use this pattern
&lt;/h2&gt;

&lt;p&gt;This isn't a tool for every situation. But it's a great fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to access internal state in a very limited scope.&lt;/li&gt;
&lt;li&gt;You want to avoid expanding the public API.&lt;/li&gt;
&lt;li&gt;You care about preserving architectural boundaries and avoiding coupling.&lt;/li&gt;
&lt;li&gt;You prefer explicit contracts over ad hoc conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's especially useful in large codebases or long-lived systems, where interface cleanliness and dependency direction have real consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
   Closing thought
&lt;/h2&gt;

&lt;p&gt;Architecture is about trade-offs. But sometimes, with a bit of creativity, we can have our cake and eat it too - extending functionality without breaking design, hiding complexity without losing power, and building systems that are both flexible and principled.&lt;/p&gt;

&lt;p&gt;Closures, when used thoughtfully, are one of those quiet superpowers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you enjoyed this article, you might like my book &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;Safe by Design: Explorations in Software Architecture and Expressiveness&lt;/a&gt;. It dives deeper into topics like this one - contracts, type safety, architectural clarity, and the philosophy behind better code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;👉 Check it out on &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>design</category>
      <category>oop</category>
    </item>
    <item>
      <title>Designing a Flexible Ability System for Games</title>
      <dc:creator>Mykola Haliullin</dc:creator>
      <pubDate>Fri, 01 Aug 2025 13:41:04 +0000</pubDate>
      <link>https://forem.com/ngaliullin/designing-a-flexible-ability-system-for-games-5fee</link>
      <guid>https://forem.com/ngaliullin/designing-a-flexible-ability-system-for-games-5fee</guid>
      <description>&lt;p&gt;✨ This article is part of a broader discussion in my book &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;Safe by Design: Explorations in Software Architecture and Expressiveness&lt;/a&gt;. If you like it, you might enjoy the full read for free on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsgt7df3nl6n9p3kkl10p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsgt7df3nl6n9p3kkl10p.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: Why Ability Systems Must Be Flexible
&lt;/h2&gt;

&lt;p&gt;In game development, the ability system is often one of the most demanding components in terms of flexibility. At the design stage, it’s nearly impossible to predict what spells, abilities, or skills will exist in the final version — or what updates will introduce in the future.&lt;/p&gt;

&lt;p&gt;This article is about how I approached this uncertainty by abstracting the process of executing abilities.&lt;/p&gt;

&lt;p&gt;At its core, an ability is nothing more than a set of actions. A minimalistic ability interface might consist of a single method like &lt;code&gt;apply()&lt;/code&gt;. But in practice, things are rarely that simple. The complexity doesn't lie in calling the ability — it lies in determining whether the ability can or should be used at all.&lt;/p&gt;

&lt;p&gt;In order to manage this complexity and allow for future expansion, we need a flexible, modular approach that decouples ability execution from the conditions under which it may or may not proceed. This leads us to rethink how to structure ability logic from the ground up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Layer: Ability Checks as Chainable Components
&lt;/h2&gt;

&lt;p&gt;Every ability begins with a series of checks that determine whether it can be used. These checks are usually things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the ability off cooldown?&lt;/li&gt;
&lt;li&gt;Does the character have enough mana?&lt;/li&gt;
&lt;li&gt;Is the target within range?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right away, it becomes obvious that not every ability needs every check. For instance, some abilities might not require mana, or may be usable at any distance.&lt;/p&gt;

&lt;p&gt;This means different abilities require different sets of preconditions. However, many of these checks are reused across multiple abilities. Cooldown, mana, and range checks are common across dozens of spells. If these checks are duplicated everywhere, any change to their logic must be applied in many places — creating fragility.&lt;/p&gt;

&lt;p&gt;To avoid duplication and enable flexibility, we can extract each check into its own object implementing a shared interface. Then, we link them together in a single, ordered chain.&lt;/p&gt;

&lt;p&gt;This is the classic Chain of Responsibility pattern.&lt;/p&gt;

&lt;p&gt;Here’s what such an interface might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface CastChecker {
    CastChecker nextChecker { get; set; }
    bool check();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here’s an example of a simple chain:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CooldownChecker → ManaChecker → CastRangeChecker&lt;/code&gt;&lt;br&gt;
Each checker performs a specific validation and, if successful, passes control to the next in the chain.&lt;/p&gt;

&lt;p&gt;This structure allows for reuse, recombination, and centralized changes — the foundation of a truly flexible system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Executing the Chain: Sequential Validation and Error Handling
&lt;/h2&gt;

&lt;p&gt;Once we’ve assembled a chain of CastChecker objects, the system can process them sequentially to validate whether an ability can be used.&lt;/p&gt;

&lt;p&gt;Each checker in the chain follows the same logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If its own condition fails, it stops the chain and reports an error (e.g. “Not enough mana”).&lt;/li&gt;
&lt;li&gt;If the condition passes, it calls the next checker, continuing the validation process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a simple implementation outline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool CastChecker.check() {
    if (!thisConditionIsMet()) {
        showErrorMessageToPlayer();
        return false;
    } else if (nextChecker != null) {
        return nextChecker.check();
    } else {
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design introduces a few key benefits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ 1. Composable and Maintainable Checks&lt;/strong&gt;&lt;br&gt;
You can build a custom validation pipeline per ability without rewriting shared logic. For example:&lt;/p&gt;

&lt;p&gt;A fireball might need mana, cooldown, and range.&lt;br&gt;
A healing spell might only need cooldown and line of sight.&lt;br&gt;
&lt;strong&gt;✅ 2. Readable Flow&lt;/strong&gt;&lt;br&gt;
Since each check is self-contained, its logic stays focused and understandable. The CastChecker interface allows adding new conditions without modifying existing ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ 3. Centralized Error Handling&lt;/strong&gt;&lt;br&gt;
Each checker can report its own failure reason — giving clear, targeted feedback to the player.&lt;/p&gt;

&lt;p&gt;This modularity is what sets the system apart from ad hoc validation logic. We’re no longer writing giant if statements or switch-cases. Instead, we assemble abilities like LEGO blocks — combining reusable, testable pieces.&lt;/p&gt;
&lt;h2&gt;
  
  
  Abstraction via SkillCastRequest
&lt;/h2&gt;

&lt;p&gt;Now that we’ve covered how to validate an ability using a chain of checkers, we need to think about how the ability actually gets executed — and more importantly, how to represent that execution as an abstract, independent process.&lt;/p&gt;

&lt;p&gt;Let’s introduce a new interface: SkillCastRequest.&lt;/p&gt;

&lt;p&gt;This interface doesn’t care whether the ability is an instant fireball or a multi-phase ritual. It simply represents “a request to perform an action,” and exposes a standard way to start or cancel it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface SkillCastRequest {
    void startRequest();
    void cancelRequest();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This abstraction lets us treat the execution logic as a first-class citizen in our architecture.&lt;/p&gt;

&lt;p&gt;Instead of having every ability directly embed its own complex execution logic (animations, delays, input windows, etc.), we separate that into a reusable request object.&lt;/p&gt;

&lt;p&gt;Benefits of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusability: The same request logic (e.g., a charging bar or input sequence) can be used for multiple skills.&lt;/li&gt;
&lt;li&gt;Interruptibility: Requests can be paused, canceled, or restarted independently from the ability system.&lt;/li&gt;
&lt;li&gt;Asynchronicity: Since startRequest() doesn’t return anything, it can easily support coroutine-like or event-driven flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In essence, this abstraction decouples what the skill does from how it gets initiated — a critical distinction for building flexible gameplay systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  TerminalChecker and Executing the Skill
&lt;/h2&gt;

&lt;p&gt;We now have two powerful tools in our toolbox:&lt;/p&gt;

&lt;p&gt;A chain of CastCheckers that validates whether a skill can be used.&lt;br&gt;
A SkillCastRequest that encapsulates the process of executing that skill.&lt;br&gt;
But how do we tie them together in a way that guarantees execution only happens if all checks pass?&lt;/p&gt;

&lt;p&gt;That’s where the TerminalChecker comes in.&lt;/p&gt;

&lt;p&gt;It’s a special node in the chain — always placed at the end — whose job is to trigger the actual startRequest() call when all prior checks succeed.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TerminalChecker implements CastChecker {
    CastChecker nextChecker = null;
    SkillCastRequest request;

    bool check() {
        request.startRequest();
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a full chain, it might look like this:&lt;/p&gt;

&lt;p&gt;CooldownChecker → ManaChecker → RangeChecker → TerminalChecker&lt;br&gt;
Only if the first three validations pass will the request begin.&lt;/p&gt;

&lt;p&gt;Why separate the final execution?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keeps responsibilities clean.&lt;/strong&gt; Each checker only checks; only the final node triggers execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier to reuse.&lt;/strong&gt; You can create different TerminalCheckers for different types of execution (e.g., networked requests, instant local effects, delayed effects).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports asynchronous operations.&lt;/strong&gt; For example, some skills might involve charging, targeting, or waiting for input before resolving. The request object can handle that without polluting the checker logic.&lt;/li&gt;
&lt;li&gt;This final step bridges the gap between should the ability run and go ahead and run it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📘 &lt;em&gt;If you’re enjoying this so far, there’s a lot more in &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;the book&lt;/a&gt; — same tone, just deeper. It’s right here if you want to peek.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Binding the Skill and the Request
&lt;/h2&gt;

&lt;p&gt;We’ve now split ability logic into two distinct domains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation logic — handled by the CastChecker chain&lt;/li&gt;
&lt;li&gt;Execution logic — encapsulated in a SkillCastRequest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But how do we represent an actual skill — something the player can activate?&lt;/p&gt;

&lt;p&gt;Simple: we bind both parts together under a unified interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining the &lt;code&gt;Skill&lt;/code&gt;interface:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface Skill {
    string name;
    SkillCastRequest request;
    CastChecker checker;

    bool cast() {
        return checker.check();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the player tries to use a skill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The cast() method is called.&lt;/li&gt;
&lt;li&gt;The checker chain is executed.&lt;/li&gt;
&lt;li&gt;If the final TerminalChecker is reached, it starts the SkillCastRequest.
This design gives us complete separation of concerns:&lt;/li&gt;
&lt;li&gt;The ability’s name and metadata live in the Skill object.&lt;/li&gt;
&lt;li&gt;Validation logic lives in its checker chain.&lt;/li&gt;
&lt;li&gt;Execution logic lives in the request.
Why this is powerful:&lt;/li&gt;
&lt;li&gt;You can reuse checkers and requests across multiple skills.&lt;/li&gt;
&lt;li&gt;You can dynamically assemble or swap out parts at runtime.&lt;/li&gt;
&lt;li&gt;You can subclass or wrap Skill objects to add logging, cooldown tracking, analytics, or multiplayer synchronization — without changing the base structure.
This turns your skills into pure data + behavior composition, making them ideal for designers, modders, and procedural generation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Example and Conclusion: A Universal Execution Framework
&lt;/h2&gt;

&lt;p&gt;Let’s put it all together with a concrete example: the TeleportationSkill.&lt;/p&gt;

&lt;p&gt;Teleportation is a perfect case because it breaks common assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn’t require mana.&lt;/li&gt;
&lt;li&gt;It can’t be used in combat.&lt;/li&gt;
&lt;li&gt;It requires the player to stand on a teleportation pad.&lt;/li&gt;
&lt;li&gt;It has a long cooldown.&lt;/li&gt;
&lt;li&gt;It must wait for the player to confirm the destination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using our architecture, this complex behavior is no problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  We assemble it like this:
&lt;/h2&gt;

&lt;p&gt;Checkers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CooldownChecker&lt;/li&gt;
&lt;li&gt;InCombatChecker (custom logic: player must be out of combat)&lt;/li&gt;
&lt;li&gt;SurfaceChecker (verifies player is on correct surface)&lt;/li&gt;
&lt;li&gt;TerminalChecker (starts the request)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TeleportationRequest, which:&lt;/li&gt;
&lt;li&gt;Opens a destination selection UI&lt;/li&gt;
&lt;li&gt;Waits for confirmation&lt;/li&gt;
&lt;li&gt;Moves the character&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skill object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Skill teleport = new Skill(
    name = "Teleport",
    checker = new CooldownChecker(
        next = new InCombatChecker(
            next = new SurfaceChecker(
                next = new TerminalChecker(request = teleportationRequest)
            )
        )
    ),
    request = teleportationRequest
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This entire skill is fully declarative and composable. No tight coupling, no duplicated logic. If we later want to use the same teleportation behavior for enemies or items — we just plug in the same request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;By separating validation, execution, and composition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We gain modularity: each component is testable and replaceable.&lt;/li&gt;
&lt;li&gt;We gain extensibility: adding new checks or execution styles is trivial.&lt;/li&gt;
&lt;li&gt;We gain clarity: game logic becomes declarative, not imperative.
Summary Diagram
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Skill
 ├── name: "Teleport"
 ├── checker:
 │    └── CooldownChecker
 │         └── InCombatChecker
 │              └── SurfaceChecker
 │                   └── TerminalChecker → request.startRequest()
 └── request: TeleportationRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a universal framework not just for spells or attacks, but for any game mechanic where action depends on conditions.&lt;/p&gt;

&lt;p&gt;You can use this to build skill trees, item usage systems, interaction mechanics — anything where “can I do this?” must be evaluated before “do this.”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you enjoyed this article, you might like my book &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;Safe by Design: Explorations in Software Architecture and Expressiveness&lt;/a&gt;. It dives deeper into topics like this one — contracts, type safety, architectural clarity, and the philosophy behind better code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;👉 Check it out on &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>architecture</category>
      <category>design</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>What’s Wrong with Data Validation — and How It Relates to the Liskov Substitution Principle</title>
      <dc:creator>Mykola Haliullin</dc:creator>
      <pubDate>Tue, 29 Jul 2025 14:03:02 +0000</pubDate>
      <link>https://forem.com/ngaliullin/whats-wrong-with-data-validation-and-how-it-relates-to-the-liskov-substitution-principle-18b3</link>
      <guid>https://forem.com/ngaliullin/whats-wrong-with-data-validation-and-how-it-relates-to-the-liskov-substitution-principle-18b3</guid>
      <description>&lt;p&gt;&lt;em&gt;✨ This article is part of a broader discussion in my book &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;Safe by Design: Explorations in Software Architecture and Expressiveness&lt;/a&gt;. If you like it, you might enjoy the full read for free on GitHub.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: When You Don’t Know if You Should Validate
&lt;/h2&gt;

&lt;p&gt;In everyday software development, many engineers find themselves asking the same question: “Do I need to validate this data again, or can I assume it’s already valid?”&lt;/p&gt;

&lt;p&gt;Sometimes, the answer feels uncertain. One part of the code performs validation “just in case,” while another trusts the input, leading to either redundant checks or dangerous omissions. This situation creates tension between performance and safety, and often results in code that is both harder to maintain and more error-prone.&lt;/p&gt;

&lt;p&gt;In this article, I want to examine this common problem from a design perspective and show how it’s related to a deeper architectural issue: a violation of the Liskov Substitution Principle (LSP), one of the core tenets of object-oriented design.&lt;/p&gt;

&lt;p&gt;But beyond that, I will propose a structural solution — based on leveraging types as contracts of validity — that shifts the responsibility of validation from the developer to the system itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trouble with Data Validation
&lt;/h2&gt;

&lt;p&gt;When designing a system, it’s common to introduce validation logic to ensure that a given data structure meets certain constraints. Formally, we might say: we receive some input, validate that its values fall within a defined domain of acceptable values, and then proceed. Later in the program, the same structure may be validated again, either defensively or out of uncertainty. If the data hasn’t changed, this revalidation is redundant.&lt;/p&gt;

&lt;p&gt;While validation may impact performance, the more significant issue is ambiguity: Who is responsible for ensuring that the data is valid at any given point in the program?&lt;/p&gt;

&lt;p&gt;This uncertainty creates friction in both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some developers may repeat validations unnecessarily, slowing down the system and cluttering the code.&lt;/li&gt;
&lt;li&gt;Others may skip validations, wrongly assuming the input has already been checked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both scenarios result in fragile systems. A function may receive data that violates preconditions, not because the validation failed, but because it was silently omitted. Over time, this inconsistency becomes a source of bugs and unreliable behavior.&lt;/p&gt;

&lt;p&gt;What seems like a minor technical detail — “just a simple check” — is actually a structural weakness in how the program models trust and correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Liskov Violation
&lt;/h2&gt;

&lt;p&gt;This ambiguity around validation responsibility is more than just a code hygiene problem — it’s a design flaw. More precisely, it often leads to a violation of the Liskov Substitution Principle (LSP), one of the foundational ideas in object-oriented programming.&lt;/p&gt;

&lt;p&gt;LSP states that objects of a subclass should be substitutable for objects of a superclass without altering the correctness of the program. That is, if a method expects an instance of class Parent, it should work correctly with any Child : Parent, without needing to know the exact type.&lt;/p&gt;

&lt;p&gt;Now consider the following pattern, which may look familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Parent { ... }
class Child : Parent { ... }
...
// somewhere
void processValidObject(Parent parent) {
    if (parent is Child) {
        // process
    } else {
        // error
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a textbook LSP violation. The method claims to accept any Parent, but in reality, it only works if the object is a Child. The contract implied by the method signature is broken, and the type system no longer tells the truth.&lt;/p&gt;

&lt;p&gt;In validation terms, the same mistake is often made implicitly: a method declares that it accepts a generic structure (e.g., User, InputData, Request), but silently assumes that this structure has already passed some validation process — without enforcing it or expressing it in the type system.&lt;/p&gt;

&lt;p&gt;As a result, invalid inputs creep into places where they were never meant to go, and the program becomes reliant on undocumented, non-local assumptions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;📘 If you’re enjoying this so far, there’s a lot more in &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;the book&lt;/a&gt; — same tone, just deeper. It’s right here if you want to peek.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way: Validity Contracts and Subtypes
&lt;/h2&gt;

&lt;p&gt;Instead of relying on documentation or developer discipline to enforce validation, we can model validity explicitly in the type system. The core idea is simple:&lt;/p&gt;

&lt;p&gt;If a certain structure can be either valid or invalid, then the type representing the “valid” version should be distinct and separate.&lt;/p&gt;

&lt;p&gt;For example, imagine you have a generic InputData type. Rather than passing it around and hoping it's valid, you can define a subtype — say, ValidatedInput — which represents data that has already passed all checks. The only way to obtain an instance of ValidatedInput is through a dedicated validation function or factory.&lt;/p&gt;

&lt;p&gt;This creates a contract of validity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ValidatedInput guarantees invariants such as non-null fields, correct formats, and logical consistency.&lt;/li&gt;
&lt;li&gt;Any function that accepts ValidatedInput can trust that these preconditions are already satisfied.&lt;/li&gt;
&lt;li&gt;If the data is not valid, the caller cannot construct the object — the compiler will prevent misuse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach shifts responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From “every method should validate”&lt;/li&gt;
&lt;li&gt;To “only the constructor (or factory) can validate — and once it does, the value is safe forever.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s the same principle behind concepts like non-nullable references, refined types, or units of measure. But here, we’re applying it to domain logic: using the type system to separate raw input from trusted data.&lt;/p&gt;

&lt;p&gt;By doing so, we eliminate both redundant validation and unsafe assumptions. We no longer ask, “Should I validate this again?” — because the type system enforces the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Examples: Files, Access, and the Danger of Assumptions
&lt;/h2&gt;

&lt;p&gt;To illustrate how this principle works in practice, let’s consider a common example: file access.&lt;/p&gt;

&lt;p&gt;Imagine a function that takes a File object and is supposed to read from it. Simple enough — but what does the File object really represent? Is it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A path on disk, regardless of whether it exists?&lt;/li&gt;
&lt;li&gt;A file that has been checked to exist?&lt;/li&gt;
&lt;li&gt;A file that is guaranteed to be readable?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the type is just File, all of these interpretations are possible, and developers may start adding conditional logic like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun processFile(file: File) {
    if (file.exists() &amp;amp;&amp;amp; file.canRead()) {
        // read file
    } else {
        // log or throw
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, the function is claiming to handle any File, but actually assumes a subset of files that are already valid for reading. This leads to repeated checks, unclear responsibilities, and potentially missed edge cases.&lt;/p&gt;

&lt;p&gt;Now consider an alternative: introduce a type ReadableFile that can only be created by a factory method like:&lt;/p&gt;

&lt;p&gt;fun tryMakeReadable(file: File): ReadableFile?&lt;br&gt;
This factory encapsulates all the necessary validation (existence, permissions, etc.). Any function that receives a ReadableFile can proceed without additional checks, because the precondition has been promoted to the type level.&lt;/p&gt;

&lt;p&gt;This approach scales naturally to more complex systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A User that is already authenticated.&lt;/li&gt;
&lt;li&gt;A Document that has passed schema validation.&lt;/li&gt;
&lt;li&gt;A Request that has been rate-limited and sanitized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these becomes a type whose very existence guarantees a set of invariants, eliminating entire classes of bugs caused by misplaced trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering Lessons and Takeaways
&lt;/h2&gt;

&lt;p&gt;The problem of repeated or missing validation is not just a nuisance — it’s a sign that the system doesn’t express its assumptions clearly. When validity is implicit, every part of the code must remain paranoid, constantly checking and re-checking conditions that may or may not be satisfied.&lt;/p&gt;

&lt;p&gt;By elevating validity to a type-level concern, we give ourselves a powerful tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Functions no longer need to perform defensive checks.&lt;/li&gt;
&lt;li&gt;Invalid data is rejected at the boundary, not passed around inside.&lt;/li&gt;
&lt;li&gt;The compiler enforces correctness by construction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design pattern is applicable across languages and paradigms. Whether implemented through sealed classes, wrapper types, smart constructors, or dependent types, the goal is the same: encode the program’s invariants in the types themselves.&lt;/p&gt;

&lt;p&gt;And in doing so, we honor the Liskov Substitution Principle — not by remembering to follow it, but by making it impossible to break accidentally.&lt;/p&gt;

&lt;p&gt;Ultimately, good software design is not just about correctness. It’s about making the right thing easy and the wrong thing hard. Using types to model validation is one of the most effective ways to achieve that goal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you enjoyed this article, you might like my book &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;Safe by Design: Explorations in Software Architecture and Expressiveness&lt;/a&gt;. It dives deeper into topics like this one — contracts, type safety, architectural clarity, and the philosophy behind better code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;👉 Check it out on &lt;a href="https://github.com/SanQri/safe-by-design" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>architecture</category>
      <category>data</category>
    </item>
  </channel>
</rss>
