Skip to content

Update no-explicit-any.md #7355

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

Closed
wants to merge 6 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions packages/eslint-plugin/docs/rules/no-explicit-any.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: 'Disallow the `any` type.'
>
> See **https://typescript-eslint.io/rules/no-explicit-any** for documentation.

The `any` type in TypeScript is a dangerous "escape hatch" from the type system.
The `any` type in TypeScript is a potentially dangerous "escape hatch" from the type system.
Copy link
Member

Choose a reason for hiding this comment

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

[Praise] +1, heh - this existing opening sentence is a bit strong.

Copy link
Member

Choose a reason for hiding this comment

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

What isn't dangerous about any?
It literally opts you out of every single check that TS might provide - there's nothing that isn't dangerous about that.

Copy link
Member

Choose a reason for hiding this comment

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

Re-thinking after reading your #7354 (comment): yeah you're right. I was coming from the perspective that it can be necessary and, when handled carefully, relatively safe. But as you said:

Your example is only safe purely because you've explicitly annotated the variables to "hide" the any. Essentially you've allowed the unsafety and covered it up quickly.

(and the rest of that paragraph)

Marking as resolved, rescinding my +1 😄

Copy link
Author

@ericelliott ericelliott Jul 28, 2023

Choose a reason for hiding this comment

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

@bradzacher I could go deep into the empirical data behind type safety but this isn't the right forum for a debate. I think it's enough to say that there are some useful things that you just can't do in TypeScript without any - foundational things like proper function composition (see the code example) which can eliminate a huge amount of code in your application and with it, eliminate all the bugs that may have cropped up in the code that got eliminated. In other words, there are cases where using any is a net win for safety, by a huge margin.

In this particular case, it's more dangerous to think of any as a dangerous error than it is to allow it because of the danger it saves us from.

Copy link
Member

@bradzacher bradzacher Jul 30, 2023

Choose a reason for hiding this comment

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

I disagree that any is safe anywhere.
I get that there are cases that you can't type with TS and are stuck using an any or ts ignores - but that doesn't make the usecases safe.

As mentioned in the issue - the only reason your example code is "safe" is because you've hidden the any with an explicit annotation. Had you forgotten the explicit annotation then your code would be in an unsafe state. This approach is simply not scalable though - in small teams it can be sufficient to find and catch in code reviews and thus "make things safe" - but at scale you can't rely on such tribal knowledge and the pattern quickly devolves into an unsafe mess.

Using any is unsafe. Not potentially - it just is.

Copy link
Author

@ericelliott ericelliott Jul 30, 2023

Choose a reason for hiding this comment

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

That dogmatic opinion is not supported by evidence. If you have evidence to support your statement, please supply it. Otherwise, I refer you to the provably true statement: "[function composition] can eliminate a huge amount of code in your application and with it, eliminate all the bugs that may have cropped up in the code that got eliminated." In other words, whether or not static types actually have any causal relationship with bug density (a statement which lacks empirical support), we can say with objective certainty, in the specific case of function composition, any can be empirically shown to reduce the likelihood of bugs by reducing the surface area of code - which, regardless of bug density, will reduce the total amount of code that bugs could appear in, and hence, reduce the total number of bugs over-all.

Empirical Evidence:

  • This article explains how function composition reduces the total amount of code needed, with specific examples of both React Higher Order Component composition and server side request handling middleware.
  • There is no known causal empirical relationship between language choice (including type safety) and defect rates http://janvitek.org/pubs/toplas19.pdf

This paper specifically looking at TypeScript vs JavaScript found:

  • TypeScript apps were actually more bug prone with longer bug resolution times compared to JavaScript. This goes against the expectation that static typing improves bug rates.
  • TS projects needed on average more than an additional day to fix bugs (31.86 vs. 33.04 days) (no causal relationship proven either way).
  • Within TypeScript projects, reduced usage of the "any" type (which ignores type safety) correlated with better code quality and faster bug resolution times. But it did not correlate with fewer bugs. This supports using any as a last resort, but does not support the statement that any is "dangerous".

The paper concludes that while TypeScript has benefits like code quality, it does not directly reduce bugs versus JavaScript in this sample. The perceived advantage of type safety for bug prevention was not observed.

Copy link
Author

Choose a reason for hiding this comment

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

If you're thinking of pointing to the widely cited "To Type or Not to Type" whose conclusions were specifically rejected by the last article I mentioned above, you should know the original "To Type or Not to Type" article is the primary source I used to show a very small relationship between static types and bug density in The TypeScript Tax because the study showed that "specification errors caused about 78% of the publicly classified bugs studied on GitHub". This implies that type safety cannot be the primary safety net that you use to prevent bug defects, and after applying other safety measures (design review, tdd, code review) the number of remaining bugs is very small, so even factoring for a further 20% bug reduction from TypeScript (which the paper did not achieve, even knowing the root cause of the bugs studied and specifically trying to solve for them using TypeScript typings), the difference in total bugs found is quite small, relatively speaking:

typescript-bug-reduction-after-quality-measures

Copy link
Author

@ericelliott ericelliott Jul 30, 2023

Choose a reason for hiding this comment

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

One last point: The back-and-forth in THIS PR about attempts to improve type safety using features like generics to try to make composition type safe reveals examples where the attempt to eliminate any in the type definition actually introduced bugs, breaking the type checking for compose and pipe by making the types more restrictive than they should have been, or leading to incorrect typings for the functions.

This supports my assertion that sometimes, just using any is safer than trying to remove it from your code.

Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg Jul 30, 2023

Choose a reason for hiding this comment

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

I think we're talking past each other. The following points are ones we seem to agree on:

  • Type correctness does not guarantee program correctness (intentionally quoting your article): agreed ✅
  • Functional programming paradigms improve program quality: agreed ✅
  • any is sometimes necessary in TypeScript to fully represent type signatures not representable in its type system: agreed ✅
  • any should not be used when a more appropriate type representation is available: agreed ✅ with the caveat that we might not agree on what is more appropriate when
  • Many cases of any outside of limited necessary uses reduce TypeScript's usefulness: agreed ✅ because by definition, features such as go-to-definition and property name checking are turned off on anys

The following two opinions are considered canon in this repository. It would be a waste of time to debate them here, as they're generally-accepted TypeScript best practices and discussing them would quickly devolve into huge discussions that likely won't change anybody's mind:

  • any is dangerous: even if it's necessary, it's very easy to get wrong
    • Something can be dangerous and necessary. Using as type assertions is sometimes necessary too, but they're also -to a lesser extent- dangerous
  • Type correctness improves program quality
    • Caveat: it can't be the primary safety net (of course!)
    • You might only agree with us that it improves developer capabilities, but I don't think it's necessary for our discussion to debate whether it reduces bugs

The main contentious point seems to be the intent and wording around whether to use any as a backup / last resort. Yes, there are cases where using any is better than an incorrect type description. If (or, I hope, when) we do take in a PR mentioning that, it'll probably end up being 2-3 sentences at most. We don't need to pull in all of programming and paradigm type theory to discuss those 2-3 sentences.

I'll post a root comment on this PR. edit: #7355 (comment)

Using `any` disables many type checking rules and is generally best used only as a last resort or when prototyping code.
This rule reports on explicit uses of the `any` keyword as a type annotation.

Expand Down Expand Up @@ -158,7 +158,27 @@ interface Garply {

## When Not To Use It

If an unknown type or a library without typings is used
**Difficult-to-represent TypeScript types**.
Some code patterns can be difficult to represent exactly in the TypeScript type system. For example, functional programming concepts such as composing and piping sometimes necessitate using `any`.

```ts
// eslint-disable no-explicit-any
type a2a = (x: any) => any;
const pipe = (...fns: a2a[]) => (x: any) => fns.reduce((y, f) => f(y), x);
const compose = (...fns: a2a[]) => (x: any) => fns.reduceRight((y, f) => f(y), x);
// eslint-enable no-explicit-any

type n2n = (n: number) => number;
const g: n2n = n => n + 1;
const f: n2n = n => n * 2;
type n2s = (n: number) => string;
const exclaim:n2s = (n) => `${n}!`;

const h: n2s = pipe(g, f, exclaim);
const j: n2s = compose(exclaim, f, g);
```

**Unknown Types**. If an unknown type or a library without typings is used
and you want to be able to specify `any`.

## Related To
Expand Down