-.- --. .-. --..

Making functions unsafe_

In a codebase that has a mix of Typescript/Flow and JavaScript, functions get used across these boundaries, usually. When that’s done without care, it might end up introducing subtle bugs that may not be caught during compile time. For instance:

// buildRenderData.ts

export function countCells (input: { cells: Array<Array<object>> }) {
}


// menu.jsx

import { countCells } from './buildRenderData.ts';


const something = {
   thats: 'undefined' // ಠ‿ಠ
}

countCells(something.thats.undefined); // fail!

In this pseudo-real-world example, the countCells function was used only in the file where it was defined, so all the necessary compile-time guards were provided using TypeScript. Intentionally, to avoid unnecessary checking for argument shape (because why do something that’s provided by the tooling, right?) no explicit checks were added to countCells function.

Later in the implementation, I had to reuse the function inside the component file that was originally written in JavaScript. If I don’t check for the validity of the input at the location it’s getting used, that function might break at runtime and crash the program. Now, since I wrote both the pieces in the code, I know that I have to add this check. But what about more complex functions that take complex arguments, but are inherently reusable? You won’t want to sprinkle type-checking code for every function that’s exported, because that’s almost repeating what TypeScript is doing for you, and worse, the code you write does the checking during runtime which may get costly. How do you, as an author of a function, hint others that a function is inherently “unsafe”?

This is something I’m stealing from React land. When deprecation a function in React, the team generally marks that function with a UNSAFE_ prefix. So you, as a library user, know that this feature is going to get removed in the future. You may use it today, but marking it as such makes the decision to use it an explicit opt-in.

So I’m starting to write the small, reusable functions that don’t have any explicit checks like so:

// buildRenderData.ts

// unsafe_ prefix denotes that this function doesn’t do any explicit
// input validity.
export function unsafe_countCells (input: { cells: Array<Array<object>> }) {
}

// menu.jsx

import { unsafe_countCells as countCells } from './buildRenderData.ts';


const something = {
  thats: 'undefined' // ಠ‿ಠ
}

// still fails, but at least now it’s YOUR fault. ᕕ( ᐛ )ᕗ
countCells(something.thats.undefined);

I think this’ll work nicely :)