top | item 28842563

(no title)

jorisd | 4 years ago

Most of the implementation details here don't really matter until you need to modify these advanced types directly. That Ensure type definition line in that example is a low level detail that you put in a library somewhere, import throughout your codebase, and then mostly forget about.

In practice you'd have someone that understands this set it up once, and then document its usage for others, maybe document the implementation to make it easier to modify later.

The TS compiler is surprisingly good at giving you good readable error messages as well when your code violates these advanced types; the errors tell you what you specified and what is supported, it doesn't display the low level type logic as part of the error users see. This means that there's very little need for anyone to really how these type definitions work.

EDIT: clarifications and spelling.

discuss

order

alpha_squared|4 years ago

Until it's the root cause of code not working as expected, by another developer far removed from initial implementation. Non-obvious code is harder to maintain. Code is written for people, not machines; that means the harder it is for people to maintain, the less useful it actually is.

jorisd|4 years ago

Valid point. On the other hand, these kinds of advanced types can prevent lots of bugs and maintenance work, and may therefore be worth the day of debugging when it breaks after two or three years of usage.

I've used types like this in a pretty advanced TypeScript UI project consuming lots of services to enforce compile time errors. We were using generated TS clients for all of the APIs we consumed, and the compiler would automatically throw readable errors wherever we were missing form fields or types became incompatible. I committed the advanced type once, documented its usage, and I don't think anyone has had to deal with it since, whilst the types have steadily prevented errors.

And even then: it's just type definitions. If it really becomes a maintenance burden or someone has no clue what it does, you can simply replace the type with "any" or something similar and all of your problems are gone and typescript won't complain anymore (at the expense of less type error checking).

EDIT: improved wording

pavel_lishin|4 years ago

> In practice you'd have someone that understands this set it up once, and then document its usage for others, maybe document the implementation to make it easier to modify later.

In practice, that someone then leaves the company, leaving this nightmare underfoot.

> The TS compiler is surprisingly good at giving you good readable error messages as well when your code violates these advanced types

Only if you're that original person who understands it! I would still have no idea what is happening, no matter how clear.

jorisd|4 years ago

The sample type code mentioned above will give the following error on the TypeScript Playground (https://www.typescriptlang.org/play):

  "Type '{ foo: number; }' is not assignable to type 'B'.
    Property 'baz' is missing in type '{ foo: number; }' but required in type '{ foo: number; baz: number; }'."
I've had the compiler emit errors much like the following for way more complicated types that combined several of these kinds of structures together to form much more bespoke type checks (reproduced from memory, so I'm not 100% certain on the error or use case):

  const e = form.email
  ^
  ERROR: "email" is not in '"name" | "firstname" | "lastname" | "e-mail" | "birthdate" | "password"'
The thing to note here is that it often doesn't expose the details of the implementing type and underlying (admittedly complicated) type system primitives at all to users. That said, I'll have to be honest and say that I have seen it throw much more difficult to understand nested errors referring to the underlying type implementation when I was working on the type system itself to create stricter type checks for functionality that was previously unchecked (i.e. treated as "any" by the compiler).

The other thing to note is that these things are really only doing type checking. If it becomes troublesome and it does start to spit out type errors incorrectly, throw unreadable errors, or otherwise become a maintenance burden, these types are not particularly difficult to remove, and by removing them you won't break your code. Consider that to be the equivalent of removing a linting rule or no longer requesting a review from a colleague. Though it's probably a good idea to document how to remove these advanced checks for when people find them annoying when someone leaves ;)

Incorrect type checking implementation is probably the biggest problem with these things getting complex, though. If your type check is incorrectly throwing errors for implementations that don't contain any errors at all, that's going to set you back a lot!

jakelazaroff|4 years ago

Isn't this true for any abstraction, though? If the person who wrote it is inaccessible, you have to understand it by reading the source.