Hacker News new | ask | show | jobs
by nayroclade 1198 days ago
I’d say generally the opposite is true. Most commercial software I’ve worked on typically contained only simple typing—discriminated unions are about as complex as it gets—and it’s more of a problem that people get lazy and start using “any” too much than they go overboard.

Where complex types can be a problem is when working with open source libraries, especially when the types are community-developed, separate to the library itself. The library API may not be particularly amenable to easy typing, and the community types can end up being rather confusing, especially to people who developed neither the types nor the original library.

1 comments

I tell my fellow developers at work: "Any is banned. If you want any, use JavaScript, and we don't use JavaScript here. Perhaps you haven't heard about unknown?"

In my experience, 90% of the time when a developer uses any, they just don't know about unknown. 9% it's because they are lazy. 1% is because you are implementing something from an imported library, and they fell into the other 99%.

I make an exception for using any in type params which extend type params, eg

  const foo = <T extends Record<string, any>>(dict: T) => …
This is a good signal that foo maps over dict in some generic way that cares more about its dictionary-ness than its values. Sure, unknown works in that position too, but at least IMO the “doesn’t care” bit is more informative than “doesn’t know”. The latter might imply more type narrowing will happen than is the case.
The problem with that is that when consuming of the dictionary, “doesn’t know” is actually more appropriate. If you then access Object.values(foo) in your method you are given an iterable of anys which is unsafe.
If the function is doing something with the values which is unsafe, sure. My point was the more relaxed constrain on the type signature can be used to imply it’s only concerned with the dictionary’s keys.
Coming back to this after the other thread cooled down a bit lol - to me, unknown actually implies that the function doesn’t care about the values more than any, as the compiler will enforce that they’re unused.

And in any case, I’d almost always lean towards the option with stronger type safety guarantees. Especially in a team environment when someone else may be modifying your code later. As a convention, I almost never use any.

To me `unknown` signals an intent to know, as in “I don’t know yet” (or put another way, “I don’t have any prerequisites for accepting this value, I can and will narrow it as appropriate”). In a codebase that otherwise takes type safety seriously, `any` in a type parameter (again to me) means “I don’t have any type narrowing agenda for this thing, it’s along for the ride and coming out the other side the same way it showed up”.
Then use unknown. Either you know what's in the dictionary and can type it or you don't. Stop being lazy.
I’m not being lazy? I am taking extra time and expending extra energy to make sure the metadata I put in code is as informative as possible. In this use case `any` carries more information. I use `unknown` in place of `any` exactly as it’s intended wherever I’m able.

Also, please don’t continue to be a jerk at me.

Any doesn't mean "doesn't care". Any means "YOLO, do whatever you want, I'm one of those cool parents who'll let you smoke and drink beer."
What is the distinction?
The distinction is just because you "don't care" about the values right now, nothing stops the next developer (including future you) from needing the values.

So now you've gone from not caring to "enabling someone to shoot themselves in the foot" if they don't read the types of the parameters carefully. That's the difference.

The case I was trying to convey doesn’t narrow the actual type for anything outside its own function body. Any footgun that exists after the function call already existed before it. It just implies “I’m only looking at your keys not your values”.
Use "unknown" and TS complains that you're treating something as an object.

Use Object.defineProperties and TS complains because that stuff is invisible to it after how many years?

I think you're right, of course, but TS is hardly perfect and treating its ways as gospel is not an improvement over JS. The "right ways" change over time and beliefs are not shared among everyone.

If you know what's in the object cast it as that type or be sure by saying if ('propertyName' in unknownObject).

TS is far from perfect. These aren't its ways (it provides any, so of course it's fine with it). These are my restrictions: if you're using a type system, actually use it. Don't lie to yourself and throw anys in your code.

any is the last resort.

How do you get around properties assigned via Object.defineProperties recognised by TS, though? I really don't know. Is is an unrelated question

Here's how I would do it:

    interface IOptionsX {
        x: number;
    }
    
    interface IOptionsY {
        y: number;
    }
    
    const testObj: IOptionsX = {x : 0};
    
    Object.defineProperties(testObj, {
        y: {
            value: 100,
            writable: true
        },
    });
    
    if ('y' in testObj) { // Could also do `&& typeof testObj.y === 'number'` to be VERY sure.
        const testObjWithY = testObj as typeof testObj & IOptionsY;
        console.log(testObjWithY.y)
    }
Though honestly I've gotten this far without EVER using defineProperty so I'd probably continue that streak.