Communicating intentions with strong typing
January 08, 2024

What are the qualities of well written code? In the book Clean Code, authors argue, clean code should:

  • Have predictable control flow, clear logic and thoughtful abstractions.
  • Be precise in communicating its intentions, making it easier to read and hence modify

Clean code should read like well written prose—Grady Booch

We spend a disproportionate amount of time reading code than writing it. Therefore, making it easier to read would make it easier to write. A virtuous cycle of prosperity.

Taking it a step further we can incorporate guardrails to avoid anti-patterns and guide developers when making changes.

To illustrate this, let's look at a refactor I worked on recently of a feature flag control setup. Feature flag allows developers to control the release of a feature through configuration setting and avoids the need for code changes or deployments.

The product has a feature control/flighting setup that can:

  1. Define feature flags and set a default rollout value(true/false)
  2. Flight a feature for a surface (Android, iOS, etc)
  3. Group similar surfaces and enable at once. E.g shorthand to enable on all mobile devices 3.a Override group default for a specific surface. E.g enable on all mobile devices except iOS
  4. Given a feature name, check if it is enabled on a surface

The refactor focused on following things:

  • Improving readability with contextual variable names and simplified logic
  • Guiding developers to use the feature flag setup correctly
  • Adding checks to prevent stale/wrong configurations

Translating to code

Starting off with meaningful variable names and separation of concerns, first step towards writing self-documenting code.

Feature name definition
export type FeatureName = "payments" | "reports"; // ...other features
Surfaces.ts
// List of surfaces and their grouping
export type SmallScreenSurfaces = "ios" | "android" | "mobileWeb";
 
export type LargeScreenSurfaces = "tablet" | "desktop" | "desktopWeb";
 
// grouping mobile surfaces
export type SmallScreenGrouping = "smallScreen";
 
// grouping desktop surfaces
export type LargeScreenGrouping = "largeScreen";

Bulk of the logic resides in the function that checks if a feature is enabled or not. Given a feature, we identify the host environment (iOS, Android) and pick out the rollout status.

Check if a feature is enabled on a surface
import { FEATURE_FLAG_DEFINITION } from "./flightDefinition";
import { environment } from "./environment";
import type { FeatureName } from "./FeatureName";
 
export const isFeatureEnabled = (feature: FeatureName) => {
    const featureControl = FEATURE_FLAG_DEFINITION[feature];
 
    if (featureControl === undefined) {
        return false;
    }
 
    const host = environment.getHost();
 
    // Running on Android
    if (host == "android" && "android" in featureControl) {
        return featureControl.android;
    }
 
    // ...similar checks for other surfaces
 
    // shorthand for small screen devices
    if (
        "smallScreen" in featureControl &&
        (host === "android" || host === "ios" || host === "mobileWeb")
    ) {
        return featureControl.smallScreen;
    }
 
    // similar shorthand for large screen devices...
 
    // return default value if no surface specific rollout is defined
    return featureControl.default;
};

Let's look at the FEATURE_FLAG_DEFINITION in detail.

flightDefinition.ts
import type { FeatureControl } from "./FeatureControl";
type FeatureFlagDefinition = Record<FeatureName, FeatureControl>;
 
export const FEATURE_FLAG_DEFINITION: FeatureFlagDefinition = {
    payments: {
        default: true,
        ios: false,
        android: false,
        desktop: true,
    },
    reports: {
        default: false,
        mobile: false,
        desktop: true,
    },
};

Defining the FeatureControl type that links a feature and its rollout on a surface

FeatureControl.ts
import type {
    SmallScreenSurfaces,
    LargeScreenSurfaces,
    SmallScreenGrouping,
    LargeScreenGrouping,
} from "./Surfaces";
 
type FeatureControlSmallScreen = {
    [key in SmallScreenSurfaces | SmallScreenGrouping]?: boolean;
};
type FeatureControlLargeScreen = {
    [key in LargeScreenSurfaces | LargeScreenGrouping]?: boolean;
};
 
export type FeatureControl = {
    default: boolean;
} & FeatureControlLargeScreen &
    FeatureControlLargeScreen;

It's starting to come together. We have straight forward control flow and basic type safety.
Last step would be to add guardrails preventing stale/bad configuration.

The control config should meet the following criteria:

  1. A default rollout should be defined
{ default: true }
  1. If a surface specific(android) or surface grouping(smallScreen) rollout is defined, it should be the opposite of the default rollout.
// correct
{ default: true, android: false, ios: false }
 
// incorrect
{
    default: true,
    android: true, // redundant as it is already defined in default
    ios: false
}
  1. If a surface grouping(smallScreen) rollout is defined, an override of a surface(android) within that group should be opposite of the group rollout.
// correct
{ default: true, smallScreen: false, ios: true }
 
// incorrect
{
    default: true,
    smallScreen: false,
    ios: false // redundant as it is already defined in smallScreen
}

Since this check is static, we can simply use TypeScript generics to implement it.

// NOT utility type
type Not<T extends boolean> = T extends true ? false : true;
Strong typing using generics
type FeatureControl = FeatureControlBase<true> | FeatureControlBase<false>;
 
// given a default rollout, ensure the surface specific rollout is opposite
type FeatureControlBase<T extends boolean> = {
    default: T;
} & (
    | FeatureControlLargeScreen<Not<T>>
    | FeatureControlLargeScreenGroupOverride<Not<T>>
) &
    (
        | FeatureControlSmallScreen<Not<T>>
        | FeatureControlSmallScreenGroupOverride<Not<T>>
    );
 
type FeatureControlLargeScreen<T extends boolean> = {
    [key in LargeScreenSurfaces]?: T;
};
 
type FeatureControlSmallScreen<T extends boolean> = {
    [key in SmallScreenSurfaces]?: T;
};
 
// group rollout and overrides in the group to have different values
type FeatureControlLargeScreenGroupOverride<T extends boolean> = {
    [key in LargeScreenGrouping]: T;
} & FeatureControlLargeScreen<Not<T>>;
 
type FeatureControlSmallScreenGroupOverride<T extends boolean> = {
    [key in SmallScreenGrouping]: T;
} & FeatureControlSmallScreen<Not<T>>;