Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript Type Traps #1149

Merged
merged 5 commits into from
Aug 6, 2019
Merged

TypeScript Type Traps #1149

merged 5 commits into from
Aug 6, 2019

Conversation

msorens
Copy link
Contributor

@msorens msorens commented Aug 2, 2019

🔩 Description: What code changed, and why?

On the periphery of some UI work I noticed some type safety anomalies where things that should have been compiler errors (or so I thought) were not flagged as such. Upon refreshing myself on how static typing, type inference, and type assertions actually work in TypeScript I made some surprising discoveries. This PR fixes an assortment of such issues.

They are grouped by commit into several violation categories, so follow the commits!

Here is one startling example:

After entering an invalid value (CUSTOMxxx) for a type, the error is not flagged in VSCode until one removes the type assertion:
image

⛓️ Related Resources

Alas, these types of potential pitfalls are not going to be noticed by the computer no matter how much linting you do. So I wrote up some slides ("TypeScript Type Traps") to document and explain these for the team's reference moving forward.


Please read TypeScript Type Traps
image


👍 Definition of Done

A more type-safe UI codebase.

👟 How to Build and Test the Change

Rebuild automate-ui

@msorens msorens added automate-ui chore auth-team anything that needs to be on the auth team board labels Aug 2, 2019
@msorens msorens requested review from a team August 2, 2019 00:31
@msorens msorens self-assigned this Aug 2, 2019
@msorens msorens force-pushed the ms/typescript-type-traps branch 2 times, most recently from c6a4a87 to bc9ce6e Compare August 2, 2019 01:22
Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I appreciate that we try to use typescript properly 😃

@@ -75,7 +75,11 @@ export class UserDetailsComponent implements OnInit, OnDestroy {
// TODO (tc) This needs to be refactored to resemble our other patterns
// for specific object pages.
// combineLatest depends on the user object existing already.
this.user = <User>{};
this.user = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth changing the user interface to something like

interface User {
  id?: string;
  name?: string;
  membership_id?: string;
}

...so we could not set them to empty strings here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my reasoning: in any strongly-typed language, if you have a User object that contains a property name, you know with certainty that the property user.name exists. But with the original code (user = <User>{};), or with your suggested change, we no longer have that certainty. That may or may not have any practical impact; referencing the non-existent user.name would return undefined instead of an empty string. For a non-string field, it becomes more problematic. Say we had a type field that could be 'a' or 'b' only, but making it optional wouldn't undefined also need to be checked? That seems akin to the Worst Mistake in Computer Science (e.g. https://bit.ly/1JHIqbY), no? TypeScript lets us be tolerant, though, as you have suggested... but should we be? Not sure. 🤷‍♂

Copy link
Contributor

@srenatus srenatus Aug 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in any strongly-typed language, if you have a User object that contains a property name, you know with certainty that the property user.name exists.

...but if we set user.name to '', that certainty isn't worth much: we don't know if it's a valid, proper username -- we only know it's not undefined. That's not the kind of certainty I appreciate 😄

That said, there's surely better ways than what I have proposed. Akin to a Maybe or Optional type, we might be able to say User | undefined. I've never done a deep dive into how to do this in TS The Right Way; I'm vaguely aware that there's a project called ts-fp that provides Option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this.user need to be initialized here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcmdarroch: I was trying to answer that very question above; sorry if not clear. In a nutshell:

  1. If you have something that is a User object, you might assign null to it, in which case it certainly does not have an id, name, etc.
  2. But if it is non-null, I do not believe one should have to wonder (read: check or worse, blow-up because you did not check) if it has a property named id. It is a User object: it should always have that property. Now, whether that property itself is initialized--to @srenatus 's point--is worth considering, too, but I'm just taking the first, small step here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I'm just taking the first, small step here.

Let's not spread this, but keep trying to improve it in each step, then 😃

@srenatus srenatus mentioned this pull request Aug 2, 2019
2 tasks
@msorens msorens changed the title Ms/typescript type traps TypeScript Type Traps Aug 2, 2019
Copy link
Contributor

@bcmdarroch bcmdarroch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍

Signed-off-by: michael sorens <msorens@chef.io>
Signed-off-by: michael sorens <msorens@chef.io>
Apply types and destructured types

Signed-off-by: michael sorens <msorens@chef.io>
Signed-off-by: michael sorens <msorens@chef.io>
Does not really make sense to have a
user or a team object that is totally empty.
Their properties might be empty, but their properties
should always exist.
This nicely cleans up the templates, too.

Signed-off-by: michael sorens <msorens@chef.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auth-team anything that needs to be on the auth team board automate-ui chore
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants