โ€ข

tech

Software that does not break is type-safe

Software engineering is the art of implementing solid and resilient apps. The building block of robust software is type safety. This is how you type safety makes apps bug-free


Sandro Maglione

Sandro Maglione

Software

Programming is art ๐ŸŽจ

Just like other arts, every software has the imprint of its author. The main difference is its expression: code lives in a more stringent set of deterministic rules.

The more stringent the rules, the more the software becomes succinct, safe, and bug free ๐Ÿ™Œ

Strict software is type-safe, here is how ๐Ÿ‘‡


Flexible or strict?

The tension in software development is between code that is flexible and easy to change, and code that is strict and precise:

  • Dynamic languages opt for full flexibility: anything can be anything, and anything can change everywhere in any way
  • Pure functional languages opt for complete strictness: everything must be explicit and fitting

You can go full strictness with TypeScript:

const NonNegative = Schema.Number.pipe(Schema.nonNegative());
const Positive = Schema.Number.pipe(Schema.positive());

type NonNegative = typeof NonNegative.Type;
type Positive = typeof Positive.Type;

// ๐Ÿ™Œ Everything is checked by strict typing rules
const fun = (params: { a: NonNegative; b: Positive }): Positive => {
  /// ...
}

The cost is verbosity, flexibility, and explicit complexity:

  • fun can only be used with those exact types, nothing else
  • All the types must be manually specified
  • The code looks long and complex

The language is JavaScript, so you can go on the other dynamic extreme with the same result:

const fun = (params) => {
  /// ...
}

Same exact code at the end. From a user perspective, the result is the same.

So, why am I urging you to choose the first more verbose solution over the first?

Type safety is a long term investment

The difference between good and bad "software engineering" is all in the "engineering" part, and less about the "software" one.

Inexperienced devs (and the current generation of AI ๐Ÿ’๐Ÿผโ€โ™‚๏ธ) focus on coding, while more experienced devs spend more time on "engineering"

Engineering is about the whole construction over the long-term. You want something that works, keeps working, and resilient to changes.

Type safety acts as a solid foundation that prevents software from breaking ๐Ÿ—๏ธ

As such, it requires an initial longer implementation time, but it reaps benefits in the future:

  • Changes are faster and safer (most changes take a few minutes)
  • I don't implement tests, but I hardly find bugs, even after a new update

Example: typeonce.dev contains all the content as a mix of mdx and json.

Extracting all the content requires reading and validating all the files. Nonetheless, the website is (mostly) bug free, and every update is a matter of minutes:

The "data" folder of Typeonce with all the content inside it. This is all extracted, validated, and converted in static pages on the website.
The "data" folder of Typeonce with all the content inside it. This is all extracted, validated, and converted in static pages on the website.

That's because the code is extremely strict: branded types, schemas, explicit errors.

Cons of type safety

What's the catch? Here you are:

  • Explicit complexity
  • Longer initial implementation times
  • Less flexible to changes

The code gets longer, (apparently) more complex, and major changes require whole restructuring.

The source code of Typeonce is not easy to understand or modify for a beginner.

export class Course extends Effect.Service<Course>()("Course", {
  accessors: true,
  dependencies: [Metadata.Default, Content.Default],
  effect: Effect.gen(function* () {
    const { getCourse } = yield* Content;
    const metadataService = yield* Metadata;
    return {
      full: ({ slug }: { slug: string }) =>
        Effect.gen(function* () {
          const { metadata, content, dependencies } = yield* getCourse({
            slug,
          });
          return yield* Effect.all(
            {
              dependencies: Effect.succeed(dependencies),
              metadata: metadataService.course({ source: metadata }),
              modules: Effect.all(
                Array.map(content, (module) =>
                  Effect.gen(function* () {
                    const metadata = yield* metadataService.module({
                      source: module.metadata,
                    });

                    const lessonsMetadata = yield* Effect.all(
                      Array.map(module.lessonsMetadata, (lesson) =>
                        Effect.gen(function* () {
                          const metadata = yield* metadataService.lesson({
                            source: lesson.metadata,
                          });

                          const lessonSlug = yield* Schema.decodeEither(
                            SlugWithoutIndex
                          )(lesson.slug);

                          return {
                            metadata,
                            slug: lessonSlug,
                          };
                        })
                      )
                    );

                    const moduleSlug = yield* Schema.decodeEither(
                      SlugWithoutIndex
                    )(module.slug);

                    return { slug: moduleSlug, metadata, lessonsMetadata };
                  })
                )
              ),
            },
            { concurrency: 2 }
          );
        }),
    };
  }),
}) {}

The example above is a single service to read metadata for a course. Everything is type-safe back to front.

This ensures no bugs even after changes, but the cost is paid in code complexity.


You can find many examples of what I mean with type-safe code on Typeonce. Some recent updates:

The tickets for localfirstconf are out now! See you there if you decide to come ๐Ÿซก

See you next ๐Ÿ‘‹

Start here.

Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights

Not convinced? Well, let me tell you more about it