37 ms·
Bad TypeScript Habits
- SmellyPotato22 6y agoI disagree with claim that One letter generics are a bad habit. In my opinion, it is proper to use one letter generics but start with A then B, then C... so it enables to reason about generics positionally. Pseudo example of this: `map<A,B>(fn:(el:A)=>B):B[]`
- jgwil2 6y agoI think I agree with you (though I would favor T, U, V as the sequence). What would the alternative look like in what you wrote? In and Out? Range and Domain? Generic type variables are often too abstract to give a sensible name to. This is the one "bad habit" that I think is a bit silly - naming conventions can vary by project.
- SmellyPotato22 6y agoIn the context I am thinking about, writing a generic iterator, you have 0 context of the what the concrete types will ever be.
- MrPatan 6y agoDo you also use one letter variables and parameters? If not, what is different about generics? Even in your example, I'd rather read this: map<IN,OUT>(fn:(el:IN)=>OUT):OUT[] Plus, it really is a habit. If you stop doing it for these kind of examples that really are very generic, maybe people wouldn't also use only one letter for cases where there is more of a difference between them.
- SmellyPotato22 6y agoKinda gets a little hairy if you start to add more types. Example: `//fork :: [a] ->((a -> b ), (a -> c)) -> ([b] ,[c])` `fork<A,B,C>(fn1:(el:Iterator<A>)=>B,fn2:(el:Iterator<A>)=>C)`
- MrPatan 6y agoWhat would be the opposite of "fork", something like "zip" that gets two lists and combines them into one? Would it have a signature also starting like this: `zip<A, B, C>(...`
- SmellyPotato22 6y agoI think that about right. Although I think this is slightly different than regular zip which is [a] -> [b] -> [(a,b)] and instead would be ([a],[b]) -> [c] or if I remember currying right [a] -> [b] -> [c].
- legerdemain 6y ago> Do you also use one letter variables and parameters? A question in response to your question... Do you ever give an ALL CAPS name to a class? Why not? Maybe it's the set of conventions I'm used to, but I find T, U, V familiar and IN, OUT jarring and dramatically less readable at a glance.
- MrPatan 6y agoI use ALL_CAPS in generics so they are easy to tell apart from functionNames and ClassNames
- Dylan16807 6y agoIt might be 'jarring' but looking over the original line I didn't parse what those types were doing at all, and T/U/V wouldn't be any better, while IN/OUT I can understand without explicitly thinking about it.
- Kranar 6y agoSometimes I do, sometimes I don't. If it's an iterator I often use one letter names such as i, j, k. Similarly if it's a generic unbounded type I also give it a one letter name.
- brundolf 6y agoHard disagree. Having come into a TS codebase from the outside that made liberal use of generics, they can make things incredibly difficult to follow if they don't have really good naming and/or docs.
- Garlef 6y agoYes. But do long names even help? I imagine, in such a project the system of generics can be so complex that a new dev cannot use them to their intent without completely understanding the code.
- brundolf 6y agoYou could say the same thing about variable names: "But do long names even help? I imagine, in such a project the system of logic can be so complex that a new dev cannot use them to their intent without completely understanding the code." Meaningful names help a person gain an understanding of the code. And that's not even getting into whether or not "completely understanding the code" is a tractable goal in the first place (I don't think it is, even for the people who wrote the code, much less those who come later)
- Kranar 6y agoLong variable names don't help either. It's been mostly a stylistic choice and a recent trend that stylistically long variable names are nice. But there are plenty of projects that use short variable names or even one letter names, and I don't see any evidence that those projects are less productive because of it. The actual research is that long variable names make it harder to understand code. It makes the variable name become an integral part of any code snippet that a reader feels compelled to memorize the specific names used, and that memorization results in errors and increased recall time. Short variable names, including single letter names, allow a reader to basically ignore the name itself which allows them to accurately memorize an entire chunk of code without feeling the need to memorize long names. This improves recall time. In simpler terms, when a snippet of code uses descriptive variable names your brain is inclined to believe that those names matter and that you need to remember them, compared to short variable names that your brain will not have to bother remembering and instead frees you up to focus on the structure and semantics of the code snippet. The study that you can read, which is quite insightful is here: https://pdf.sciencedirectassets.com/271600/1-s2.0-S0167642309X00055/1-s2.0-S0167642309000343/main.pdf https://pdf.sciencedirectassets.com/271600/1-s2.0-S016764230... Note that long function names DO help, but long variable names do not. That said, feel free to use long variable names if that's what you like and your project does it that way. Don't, however, express that as some kind of extensively studied software engineering principle that has research backing it. It's basically one of those things someone decided was true and everyone else just decided to believe it without ever doing the kind of work needed to validate the hypothesis. In software engineering, we are still in the days of Aristotle, where we simply believe things to be true because we want them to be true. Just like the days of Aristotle I wouldn't be surprised if it takes us literally thousands of years before someone comes along and decides to question and test basic assumptions we all take for granted.
- notapenny 6y agoI wouldn't agree they're a bad habit either, but only when used with care. Types like T, and K, V might be as clear to most people as i and j in array operations. Your example is clear enough because the signature is simple, but in more complex functions I've found that becomes obscure real fast, particularly if types are then passed down to something which also uses generic types. Enabling people to reason about things is not as clear as actually telling them what it is. If the 2nd of 3 generic types is the return type for whatever reason, call it SomethingReturn type and remove that ambiguity.
- valand 6y agoAgree, generic name should mirror its meaning. For generic that doesn't have any specific meaning we can use T. Adding a meaning to meaningless generic will add confusion, like in the example, Element might conflict with HTMLElement while it can be something else entirely.
- llimos 6y ago11. Going down the rabbit hole spending three whole days getting your typings perfect for some weird use case, instead of writing actual code. Sometimes there's a benefit down the line to having done it, often not, and knowing the difference is where greatness lies.
- Garlef 6y agoThis is definitely something that typescript constantly lures you into. I recently switched back to JS for a prototype of a programming language. I immediately felt more productive. At times, the TS systems feels like a theorem prover. Until it doesn't. And then you're left with a bunch of types that almost do what you want... but not quite; Always tempted to experiment again for a day or two. But let's see what happens down the road. Maybe I'll miss the incidental documentation the typings provide.
- teh_klev 6y agoThis isn't a fault of TypeScript, but any language. Trying to predict the future, trying to predict and eliminate technical debt, in a rapidly developing product.
- dimitrios1 6y agoBut it is the fault of TypeScript for having such an obvious path for falling into that trap. Part of what it means to have TypeScript code is to have typing files. Part of good language design is attempting to minimize bad programming habits. In this case, a language with a HM type system would avoid the issue all together, while affording the same productivity gains from having static types.
- eyelidlessness 6y agoThis isn’t actually a fault of TS, it’s an explicit goal. They design the type system not to guide usage but to make existing usage explicit and trustworthy. The places where the type system encourage rabbit holing are almost always where bad types/APIs are already prevalent. Where you’re starting at the type level and don’t have an inclination to allow all manner of dynamic nonsense it’s not that different from types in an ML-family language.
- dbartholomae 6y agoHi everyone! Happy to see my article shared here. If there are any questions, just let me know. And yes, the list is opinionated ;) Especially the question on one-letter generic types has been debated a lot.
- h1fra 6y agoI think the code sample after `Use the new ?? operator, or, even better, define the fallback right at the parameter level.` is not correct, it stops after new Date without any mention of ?? edit: read too fast :facepalm:
- dbartholomae 6y agoIt uses a fallback at parameter level instead of the ?? operator.
- h1fra 6y agooops indeed, but I'm not crazy there was a missing parathensis no? It's too late :D Anyway great blog post
- dbartholomae 6y agoYes, someone found the typo, send it to me via Twitter, and I fixed it. This is also where I know from that the article is on Hacker News :D
- ulucs 6y agoIt's defined at the parameter level, using function parameter defaults
- jgwil2 6y agoYeah, the example just shows the "even better" option: it gives a default value for the date parameter.
- deleted 6y ago[deleted]
- nepeckman 6y agoI think #6 (optional properties) is a pretty contrived and unrealistic example. Most people use optional properties for extra options or enhancements, not as substitution for an object hierarchy.
- jgwil2 6y agoI've definitely used them in the way the article describes. Think about an API that gives a list view and then a detail of an object that has more properties than are sent in the list view (maybe because they are expensive to compute or maybe just to save bandwidth). This scenario can definitely be more accurately modeled with interface inheritance than with optional properties.
- nepeckman 6y agoOkay your answer is more illustrative to me of the problem and solution. Totally agree, if you've got a list view and detail view those should be separate classes. Though my personal take in that senario is you might be better off using TypeScripts mapped types to define a subset of your detail view as a list view.
- drew-y 6y agoAgreed. The author is conflating discriminated unions with optional properties. His example is better off as a discriminated union because the optionals are set based off of it's type. There are plenty of situations where optional properties do make sense. Particularly where a default value can be used.
- deleted 6y ago[deleted]
- deepfriedrice 6y agoGood stuff. I don't get the example for #3. I understand using "unknown" instead of "any", but both examples look effectively the same in terms of type safety. I also don't get the example for #4. Side effects are the inherit "blind spot" of TypeScript. If you're really worried about your API changing on you, it seems like you'd be better off modeling it in something like JSON schema. Unless maybe your API is trivial.
- deleted 6y ago[deleted]
- jgwil2 6y agoIn the fixed version of #3, the function return type is `Product[]` instead of `any`. The example is further refined in #4. What type guards are good for is marrying the compile time checks with runtime safety. They allow TSC to infer that in a given code path, the type can be narrowed down to something more specific. This is nice because it lets a single function e.g. handle a few different types without littering your code with `as` or `!`.
- ivan888 6y agoI also don't understand 4. This looks terrible. Why use TypeScript if we have to make very manual assertions about the structure? Isn't that the purpose of TS itself?
- jakelazaroff 6y agoTypeScript's type checking is static, so on its own it can't know that values it receives from elsewhere (like JSON coming from a string) is the correct type. The only option is for the programmer to check at runtime, which allows TypeScript to know the type along the code path in which the check succeeds.
- ivan888 6y agoThis makes sense; but it still doesn't feel like the right answer. Facilities should exist for runtime type checking based on TS definitions, and syntactic features should enable a 'hard check' of the actual object when necessary
- Garlef 6y agoRegarding "don't use `x as Y` but instead use type guards". Type guards have a runtime overhead, don't they? (You could argue: "If TS can not infer the type at this point so neither can you. Better to check at runtime." But from my experience, sometimes TS is simply not smart enough.)
- throwanem 6y agoType guards do have a runtime overhead (since they're functions that get called), but so does any other method of validating untyped data at runtime. That's probably the 99% case with type guards, so I feel like the specific overhead on that isn't such a huge deal since anything else (e.g. runtypes) would probably have more. (I can think offhand of one case in the last year where I've wanted to use a type guard for a reason that didn't have to do with runtime validation, anyway. Even then, it wasn't the solution I ended up going with.)
- Garlef 6y agoOut of interest: What was the context where you needed the validation? In most contexts, the reasonable approach would be to only do runtime validation at your system boundaries and not anywhere inbetween.
- throwanem 6y agoThe case I mentioned didn't have to do with validation - in a case like that, I would have used a type guard, or more likely a runtype. This was a Javascript corner case that I hadn't previously encountered, where if you load the same module from two different paths, they aren't `instanceof`-equivalent. In this case, both the codebase of a module I was working on, and the codebase of one of its consumers, imported the same module from the same package, and I ran into the issue when I `npm link`-ed the module into its consumer's node_modules dir for testing and to ensure I hadn't inadvertently broken an implicit contract. The `instanceof` mismatch, between an object instantiated in module code and the class imported by the consumer, confused the type system and gave me spurious errors as a result. Thinking about how to solve this, it occurred to me to use a type guard, since it would have effectively convinced the type checker that my object instance was in fact an instance of the class of which it is an instance. But it would've been a wart and also imposed the aforementioned runtime overhead - not that that would've been meaningful in this case, it wasn't in a hot loop or anything, but I still didn't like it. So I ended up instead exporting the shared dependency from the module and having the consumer import it from there, rather than from its own (IIRC otherwise unused and thus eliminated) copy of the shared dependency.
- izolate 6y agoI like the "why we do it" part in every section. Kudos to the author for that. However, the type guard section seems a bit off to me. Is the suggestion to check each and every property of `Product` in `isProduct`? Seems a bit verbose. I tend to use Axios, which uses generics to set the payload return type (obviously, some trust in your API responses is needed here): axios.get<Product[]>(...);
- sakarisson 6y agoI think the idea is that you validate the data once, on an API level. You don't need to validate the same data again after the initial inspection. In our team we use io-ts to validate all of our endpoints, but simple type guards could achieve the same goal. https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts
- adriancooney 6y agoRuntypes is another excellent alternative. Highly recommend. https://github.com/pelotom/runtypes https://github.com/pelotom/runtypes
- Lunrtick 6y agoI've also been using Zod for the same purpose - they've got a nice comparison section that included io-ts https://github.com/colinhacks/zod#comparison https://github.com/colinhacks/zod#comparison
- agloeregrets 6y agoThe word in the back of my head wasn't Verbose. It was `Expensive`. Explicit looping and checking values like that seems like the answer to 'why is our app slow'.
- bwestergard 6y agoThe latency will be linear with the size of the JSON payload, and the constants will be tiny.
- 6y ago
- elnygren 6y agoGood points! Seems like for "6. Optional properties" One should rather use tagged unions (see: Abstract Data Types, variants) that are usually written like this in TS: type Product = | { type: 'digital', id: string, sizeInMb: number } | { type: 'physical', id: string, weightInKg: number } At least I find it more elegant, concise and fun to work with :)
- agloeregrets 6y agoI like this way more. Wayyy more.
- elnygren 6y agoAlso, for using type guards to validate incoming data, check out https://github.com/pelotom/runtypes https://github.com/pelotom/runtypes. Or if brave enough to dive into the deep end of FP, io-ts is nice. https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts
- bengalister 6y agoAccording to https://github.com/microsoft/TypeScript/wiki/Performance#preferring-base-types-over-unions https://github.com/microsoft/TypeScript/wiki/Performance#pre... using union types in your example vs interfaces like #6 is bad for compilation performance.
- bryzio 6y agoAs the docs mention, the effect is only material when there's a non-trivial number of union elements. For unions containing only a handful of elements the effect is inconsequential.
- contravariant 6y agoWell it all depends on how you're expecting your types to be extended. I'm not sure what TypeScript's support for interfaces is like but it might make sense to have interfaces for both (just in case you ever have a product that's both physical and digital). Tagged unions might get a bit messy if you ever need to extend them, but sometimes you simply have some source / sink of data that requires a variant type.
- drenvuk 6y agoI think using it would be the first one. I don't think I'll ever be able to convert to using typescript, I'd rather have validators running in the code checking external inputs for the proper fields at runtime before the rest of the system blithely processes whatever it gets due to safe assumptions. Add integration test for specific modules and everything works fine. I don't want to compile languages that don't need compilation and I don't like the fact that I need to create all of these fake feeling types which are actually just objects rather than javascript classes. It's just feels like boilerplate when you could instead include the 'type' that it's supposed to be in the name and look up a validator for that 'type'. I'm probably wrong, but it really doesn't feel like it.
- koolba 6y agoStatic typing check is a huge boost for code refactoring. It allows you to quickly make changes and be reasonably confident that you haven’t horrendously broken something. It doesn’t obviate the need for dynamic validation nor is it meant to do that. But it does give you a clear point of where to perform those validations rather than littering it everywhere throughout your application. It doesn’t obviate the need for testing either. But it does eliminate an entire class of dumb programming errors such as incoming a function with the wrong arguments.
- throwanem 6y agoYeah, I mean I don't want to just say "you're wrong", but you kind of are here. TypeScript alone doesn't make any assertions and doesn't offer any checking at system boundaries ("external inputs"). That's not what it is for, and the types you specify with it aren't objects. All that stuff gets compiled out before the code is even run. The point of TypeScript is so that you don't have to run your code to know whether the way it handles data internally is the way you intend it to. Validation at system boundaries is a separate concern, and handled separately. I like to do it with a package called "runtypes", which both supports arbitrarily complex shapes in its validators, and expresses them in a way that's very close to TS and can be trivially converted into compile-time TS types so that you don't have to write the same types twice. I wasn't sure what to make of TypeScript for a while, too. But having once tried it, and since become very conversant with it, I'll go back only for trivial tasks or under significant duress. Sure, writing types is a fair bit more work up front and a little more ongoing, but it really does make a huge difference in maintainability; in my experience, it's a little work now to save a lot of work later, as the resulting code is both less likely to exhibit an entire class of often quite subtle bugs, and easier to reason about and modify.
- agloeregrets 6y agoAs much as I agree with most of this, I also think that some of it can lead to some weird issues for new devs or dangerously, lead to a world where people are helpless once they accidentally break out of the world of typescript. In a few cases, there is actual runtime overhead for the choices made, for example the typechecking example.
- paxys 6y agoNice article, but all the negatives made it a little hard to read. For some points it was hard to figure out whether the author was in favor of doing or not doing it.
- drinchev 6y agoBtw in this context I would like to share Type Fast by sindresorhus [1]. I find it excellent and definitely a helpful tool to think about when you are struggling with type safety vs runtime data. 1: https://github.com/sindresorhus/type-fest https://github.com/sindresorhus/type-fest
- paxys 6y agoSoft disagree with 3. unknown might be better than any but then the typechecker isn't going to let you read any fields from the object, even if you are using a null check. And the given example is weird because you don't need to add a type annotation there at all. The problem really is that TypeScript doesn't have an "unknown deserialized JSON" type. For #7, in a lot of (most?) cases, the name of a generic isn't meant to be descriptive. I'd argue that functions that expect it to be are doing it wrong. If I have a `function reverseArray<T>(arr: T[])`, the point of T is that it can be anything. Saying `reverseArray<ArrayTypeToBeReversed>` serves no purpose. TypeScript also really, really needs to have a strict typechecking mode. Yes I know the "it compiles to raw JS" arguments, but it could easily add extra checks in the JS code (basically what example #4 shows).
- horsawlarway 6y agoDisagree with you on 3. 'unknown' is handy to prevent 'any's from leaking into the codebase. It's very easy to accidentally assign an 'any' to another variable and then return it, or to accidentally read a property off of it before ensuring it exists. Unknown is basically asking the dev to confirm the choice (ideally through a typeguard, but also with a simple cast). And if you're using a typeguard, just define the argument as 'any' ex - type AType = { c: string } let a: unknown = { c: 'test', } if (isAType(a)) { console.log(a.c) // no type errors! } function isAType(a: any): a is AType { return typeof a.c == 'string' }
- calebegg 6y agoI think calling your generic type 'Element' is a bad choice -- that's shadowing a built-in type (https://developer.mozilla.org/en-US/docs/Web/API/Element https://developer.mozilla.org/en-US/docs/Web/API/Element) and it makes the code confusing to read at a glance. That's one thing I like about T, it's very self evident that it's just 'the' generic type. But maybe I'm just used to it. With multiple generic types I can see how longer names would be useful.
- hooksfordays 6y agoAgreed. I think naming generic types makes the most sense with multiple types, or when more specific names make sense. For example, if a generic argument must inherit from a certain type: `class SomeView<ViewState extends BaseViewState>` or some such. `Element` is as non-descriptive as `T` in the author’s example and, IMO, doesn’t add any tangible benefit.
- endgame 6y agoAnd often in parametric code, there is nothing you can say about the types you're working with.
- danbolt 6y agoI write C++ for a living, and one thing I've kind of noticed a similar thing to what you've mentioned. "very generic" template code often is best left to a name such as T, but a very specialized piece of metaprogramming often benefits from specific naming. The latter scenario being that hopefully the person debugging your logic a decade from now has some insight into what you were thinking, namely.
- munchbunny 6y agoI think of “T” as the template parameter the same way I think of “i” as a loop iterator. It’s a conventional shorthand, so if it’s contextually clear, like if T is the only template parameter and there aren’t complex constraints on how T behaves, there isn’t much harm in using it for succinctness.
- ducharmdev 6y ago
- bengalister 6y agoAbout #2 Wasn't so sure about typescript so I tested it to confirm that it worked. But in Python3, the default arguments are evaluated at module time loading if I am not mistaken. The following will display roughly the same timestamp from datetime import datetime import time print(datetime.utcnow()) def show_ts(d=datetime.utcnow()): print(d) time.sleep(5) show_ts() where as in TS, the 2nd one will be roughly 5s older: const now = Date.now(); console.log(now); function logTs(d=Date.now()) { console.log(d); } setTimeout(()=> { logTs(); }, 5000);
- horsawlarway 6y agoThat's because default params in JS are essentially syntactic sugar ex - function example(a = 1) { ... } behaves the same as function example(a) { a = (typeof a !== 'undefined') ? a : 1 ... }
- sorahn 6y agoWhich also means the suggestion to use ?? has a different result then using a default param. ?? will catch both null and undefined, where as a default parameter will only trigger on undefined.
- horsawlarway 6y agoYup, you're absolutely correct. Null is considered a valid value for a default param. function test(a = 1) { console.log(a) } test() // -> 1 is printed test(null) // -> null is printed test(undefined) // -> 1 is printed
- nawgz 6y ago?? catches any falsy value, so it is quite semantically different than default arguments, and also quite different from ||
- Dylan16807 6y agoDid you write that backwards? || checks for falsy, ?? checks for null/undefined
- dlbucci 6y agoI agree with most of these, but I pretty strongly disagree with #10 (don't use `!= null`). The reasoning is that you can use `null` and `undefined` to mean different things, like `null` being "no first name" and `undefined` meaning "haven't asked yet". I agree that strict TS lets you do stuff like that fairly easily, but I think this is a really bad idea, because there's nothing that would indicate to a reader of that code the meaning of `null` vs `undefined` for those properties. I feel in that case you should have some type union of `"notAsked" | "none" | { t: "some", v: "Name" }`, because that would be more clear of what each possible value means. Let `null` and `undefined` be synonyms for `nil`.
- codefined 6y agoI've been regularly trying to reduce / remove usage of `null` in various projects. Sindresorhus has some interesting discussions on the topic here[0]. [0] https://github.com/sindresorhus/meta/discussions/7 https://github.com/sindresorhus/meta/discussions/7
- Normal_gaussian 6y agoI strongly advocate for using both BUT not propagating undefined. That is to say, use null and handle undefined. You'll need to write undefined as a potential type for potentially undefined object members, and as such it means exactly that - undefined. For everything else there is null. A sanity check would be - undefined can only appear in a function parameter type or object member type as a union with other types, or in a === comparison. For everything else there is null.
- mdtusz 6y agoThe way I always see it is that `null` is for known unknowns, and `undefined` is for unknown unknowns. Explicitly defining something as `undefined` is paradoxical and IMO should never be done. I've tried to explain this concept to some co-workers without much lasting success, but it's a pretty common concept to other languages. Undefined should basically never be used as a value directly.
- mantap 6y ago
- recursive 6y agoDisagree on #10. The amount of code I've seen that intentionally distinguishes between null and undefined is... well, zero. On the other hand, the amount of code I've seen that unintentionally distinguishes between them when it shouldn't have is not zero.
- dvdhnt 6y agoHmm, not sure what you're saying exactly. Null and undefined are different. A common example would be any sort of patch operation - setting a value to null is valid, whereas if the value of that property is undefined, it likely should be ignored. Sorry if I am misunderstanding your statement.
- srcreigh 6y agoA concrete example of this is setState in React. T | null signifies that it can be cleared by using setState({prop: null}). T | undefined means that it may start out undefined, but if it becomes defined it'll probably stay that way. If you try doing setState({prop: undefined}), it won't work.
- recursive 6y agoThere doesn't seem to be any fundamental reason why that had to be the case. I'm not a react expert, but it seems like it would have been more obvious to me if null and undefined were treated the same. The distinction should be whether the key exists in the object.
- presentation 6y agoIt's a poor design carried over from legacy JavaScript, but there are clear differences at the language level. While not the only one, the biggest one to me is this kind of code: const x = {}; x.foo // returns undefined const y = { foo: undefined }; y.foo // also undefined Basically a member not being provided will return undefined, whereas `null` had to actually be set. Hence I think the tendency for checking for the absence of something via `undefined`, while expressing the deletion of something with `null`. Sure, you can assert more specific types in TypeScript to step around that, or you can do something less intuitive like checking for `keyof` to detect absence; but this is both straightforward and presents less issues when interfacing with inputs from non-typescript-using users.
- s0uthPaw88 6y agoThe before and after code examples for #2 are not equivalent. Default parameters are only applied if no value or undefined is passed. In the case of the value being `null`, no default parameter is assigned, but using `date: date || new Date()` would assign `new Date()` as the value. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- dvdhnt 6y ago`null` isn't a valid value for "date" because it is defined as `date?: Date` which evaluates to the type "Date or undefined"
- s0uthPaw88 6y agoYes assuming you are in strict mode, but not true otherwise.
- s0uthPaw88 6y agoIf you had something typed as `any` and the value was `null` it would be possible to encounter an issue
- react_burger38 6y agoReading through this list reminded me why I actually don’t want to use TS in the first place. I think we need to have a discussion about the benefits of dynamically typed languages and why many people purposely choose them over statically typed ones. In my experience, 95% of the time TS would not have helped anything. And the other 5% where it would have is not worth the constant work required to type the whole application. And sure you can use ts just a little bit... but everyone knows that organizationally the pressure then begins to use TS for the whole app. Types are nice in some circumstances but are they really worth it? I feel like the answer is no for the majority of applications.
- roberttod 6y agoI totally agree. I thought typescript might grow on me as I got to grips with it, but alas a year of using it and it's still slowing me down a lot for how much it's helping. Also "don't use 'any'" - there are a lot of edge cases where this can be very difficult to avoid. Even the TS experts have failed to help me figure out how to remove some "any"s. I'm sure there is a correct way, but it just shouldn't be that difficult.
- bdefore 6y agoWhen enforced like a code coverage score I agree. If in your day-to-day there's static checking against any/unknown, well, that's effectively saying 'only type-checked code welcome'. Which is kind of sad and against the origins of TS, which meant to supplement JS without bringing all that uncool into the school. Sometimes I dig into source of a supposedly JS library and witness the author not just the type-checking, but OOPy interfaces, private/public, and I again feel the itch that this superset is a more refined embrace, extend, and extinguish.
- richeyryan 6y agoI'm kind of resigned to it. There's places where I imagine I'll end up using it and others where I'll do without. Someone who talks intelligently about the strengths of dynamic languages is Rich Hickey. His talks challenge some of the ideas where static typing is superior to dynamic typing. He is arguably biased in his position but it makes for a refreshing contrast to the static types argument. Clojure is a great example of the qualities required to make dynamic typing really powerful, the main one being the large library of functions that can operate over all collections. This reduces the amount of custom code you write for data munging, reducing surface area for bugs. The complete counterpoint to that is Go with no utilities for doing common data transformations, forcing users to re-implement similar logic repeatedly. It may as well be statically typed at that stage to avoid mistakes from writing the same code over and over. The more powerful statically typed languages with generics then fall somewhere in the middle where you don't have to repeat yourself as much.
- jestar_jokin 6y agoI've got a team that insists all types go into `.d.ts` files. For every type internal to the app (an API backend). The types are never exported, not even to the frontend. Apparently because the tech lead thinks importing types is noisey. My pleas, of "just fold the imports in your editor" and "it makes the types globals, so it's not modular" and "interface /declaration merging becomes an issue" all fell on deaf ears. Some people like their bad habits because it makes sense to them, I guess.
- xixixao 6y agoTypeScript got `!= null` wrong. In Flow (and CoffeeSript) null and undefined are treated the same, and `!= null` is preferred. You do not want to deal with code that treats null and undefined differently. Otherwise this is a great list!
- recursive 6y agoOne of the foundational philosophies of typescript is that the language does not have different semantics than javascript, so that wasn't an option.
- xixixao 6y agoNah. This doesn’t change the semantics of JS. As numerous sibling commentors pointed out, distinguishing between null and undefined is not a good idea in normal JS either. This was simply a bad design decision in TS, especially given that CoffeeScript has done it “right” many years before. Unfortunately I feel like every language design team prefers to learn their mistakes on their own instead of learning from others (with the usual “our language is different” excuse).
- seniorsassycat 6y agoI think the suggestion is for typescript to have one none type equal to undefined | null. The type checker could not error on undefined when null is required or vice versa. Then code should use != null comparisons which match the none types semantics without needing runtime code generation. I think this would violate another typescript principles that existing libraries and patterns should be expressable in typescript. Some schmuck wrote a library that accepts null but not undefined so typescript has to distinguish them.
- EugeneOZ 6y agoI do want.
- deleted 6y ago[deleted]
- ssalka 6y agoI agree with all of these except bangbang bangbang for life
- slifin 6y agoJust because you can specify something doesn't mean you always should There's a cost to type coupling and over specification
- brainlessdev 6y agoGood list, but type guards are just as unsafe as type assertions. I wrote a blog post about this: https://fnune.com/typescript/2019/02/27/typescript-series-2-beware-the-type-guards/ https://fnune.com/typescript/2019/02/27/typescript-series-2-... TypeScript will believe what you tell it in a type guard's signature without looking at the body of the function further than "does it return a boolean".
- SamBam 6y agoTypeScript can't always save you against yourself. But adding in the TypeGuard makes the intent explicit. If your guard isn't actually verifying the thing has the structure you say, of course it's going to fail. But at that point your code was going to fail anyway (say if you just cast it to the type), with a less-useful error message. I don't quite understand the criticism. You may as well say that unit tests are just as unsafe as not writing unit tests, because your test might not do what you think it's doing.
- g_delgado14 6y agoI think their point is simply that type guards aren't a panacea.
- xmprt 6y agoWhat's the difference between failing a type assertion vs failing a type guard? Both fail at runtime, right? So I don't understand how one would be better than another. Type assertions seem pretty explicit to me (saying you want this result to be as a certain type).
- Aeolun 6y agoYou can use guards for things other than primitive types.
- SamBam 6y agoA failing type assertion that correctly fails because the input is not that type is good: it lets you divert to another path. E.g. if it's Type X do this, if it's Type Y do that. Or even just put up a vaguely-useful error to the user: "The third-party service didn't send the expected data" instead of throwing the obscure error `Cannot read property 'prop' of responseData` in the console and freezing.
- tengbretson 6y agoThe section about preferring setting defaults in function parameters or using the `??` operator glosses over the fact that these two strategies have different behaviors for the value `null`.
- pabs3 6y agoAre there any tools that call out bad habits when writing TypeScript? The only one I know of is tslint: https://palantir.github.io/tslint/ https://palantir.github.io/tslint/
- wungsten 6y agoActually, tslint is deprecated now, and the community effort has moved to typescript-eslint. https://github.com/palantir/tslint/issues/4534 https://github.com/palantir/tslint/issues/4534
- eyelidlessness 6y agoI totally agree with most of this list but have some clarifying and contrary: 2. The recommendation goes too far in suggesting fallbacks in the parameter list (and would if it had suggested the same for destructuring options/objects generally), without explaining that for some braindead reason fallbacks aren’t `null` safe. 3. This leads into #4 but if readers leave here they’ll think the cast from `unknown` is somehow safer. I have seen this happen, people just cargo cult stuff. I’d much rather a 9-length list than risk that. 5. This particular problem/example is spot on but I’d like to expand the “in tests” scope to explicitly explain why some unsafe types are justified in tests. I always specifically unlint rules about non-null assertions in test because a non-null assertion is an additional safety mechanism in that context: it validates that the runtime agrees with the type system. If you intentionally but explicitly bypass the type system and don’t see an error you should know that’s a bug and fix it. 7. Is mostly spot on but conditional. There are unfortunately a lot of conventions at play. Anytime there’s ambiguity definitely add verbosity. If your type is truly generic without constraint adding that verbosity is actually harmful. 10. This is just plain wrong for the vast majority of cases. It’s also a JavaScript-ism that doesn’t translate well to other languages and the “bad” habit helps avoid mistakes. For most use cases `null` should only have one meaning: absence of a value. The cases where it doesn’t are almost always a mistake (oops this was an “object”), and the cases where the distinction does matter are usually (but not always) a sign that your interface is bad (the exception being explicitly passing `null` sentinel values to overwrite existing data in a stateful external system like a database). This should be the exception not the rule and making it syntactically clear is useful.
- eyelidlessness 6y agoActually thinking more about #5, there’s a class of problems where I think `any` is better than anything else: testing any kind of untrusted input type guarding. Granted `unknown` is safer in your program but `any` is most like the real world and you’re far more likely to catch mistakes in your expectations if you throw the compiler out the window and pretend it never existed.
- throwaway4good 6y agoIt is not proper TypeScript until the code is twice the size of the original JavaScript.
- EugeneOZ 6y agoStarting from #6 all "rules" are controversial, to say it lightly. #6 is absolutely ridiculous.
- Vinnl 6y agoI don't know who uses `!= null`, but just... Ban the != operator from your codebase altogether using a lint rule, in favour of !==. The severity of the bugs you'll avoid very much outweigh the muscle memory you'll need to develop to type a comparison to the actual value you want to match against.
- valand 6y agoType guard in the example should use runtime type checkers or codec like io-ts (https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts), otherwise it will just become a type-assertion with partial correctness. With codec, one can make sure that the type is correct and defining type and type-guard becomes a one-time task.
- valand 6y agoI'm surprised the list doesn't mention untyped-exception and how to deal with the inability to figure out the type from the catch block. Untyped exception in TypeScript is one of the most vulnerable point of static type escapes. Unlike any, which is explicit, exception caught in catch block is implicit. Personally I prefer to wrap catches into a specific type/symbol `UnknownError { data: unknown }`, and discriminated union to indicate success/failure on return. fp-ts (https://github.com/gcanti/fp-ts https://github.com/gcanti/fp-ts) is my go-to library for this
- rad_gruchalski 6y agoOn generics, parroting many here already: > This habit grew I guess because even the official docs use one-letter names. It is also quicker to type and requires less thinking to press T instead of writing a full name. This habit grew because every language with generics uses such short generic type names and frankly, they are easier to read than MyFancyGenericTypeName. Thanks for the rest of the article. Pretty good read.
- nailer 6y ago> (Single character generics) they are easier to read than MyFancyGenericTypeName. Disagree. Also this same logic can be applied to every other token (ie this argument suggests we should write minified code). function head<T> (arr: T[]): T | undefined { return arr[0] } function head<Element> (arr: Element[]): Element | undefined { return arr[0] }
- rad_gruchalski 6y ago> Disagree. Also this same logic can be applied to every other token (ie this argument suggests we should write minified code). No, it does not. Think of the type variable as a template. The value of this kind of variable does not change during the execution of a function, once given, stays the same. When reading code with generics it does not even make sense to compare the names across two different functions unless they are methods in a class. Basically, a T in head<T> may be something else than T in tail<T> depending on what the container declares. And what if I use your head<Element> as my head<Leaf>. For me semantically it is not an Element so your forced generic naming is simply going to mess with my brain logic. Leave my T alone. My T is my Type, not some imaginary thing the person who wrote the API imagined I should be putting in there.
- nailer 6y ago> Think of the type variable as a template. The value of this kind of variable does not change. This new logic can be applied to every other constant. > When reading code with generics it does not even make sense to compare the names across two different functions unless they are methods in a class. Agreed. But I don't think anyone was discussing that.
- WorldMaker 6y agoRegarding the "bangbang" operator: I agree that the syntax is overly cute and frustratingly confusing to new users of the language, but I still find I often want a "not falsy" operation. I've moved to using the `Boolean(variableToCheck)` pattern. It's certainly far more characters to type (without autocomplete) than the pithy double not "bangbang", but benefits from being explicit in what it is doing and benefits from some small performance fast paths in some JS engines over !!. (Micro-optimizations like that are rarely a good primary reason to switch to a different tool, but it's a nice side benefit from all the other reasons including and especially making the code easier to read.)
- andrewprock 6y agoOn the plus side, this is showing up on the front page concurrently with "Cargo Cult Software Engineering" https://news.ycombinator.com/item?id=26016890 https://news.ycombinator.com/item?id=26016890
- foo42 6y agoThere seems to be a lot of people finding that they have to use the "any" escape hatch quite frequently. Our team has found we practically never use this anymore since starting to use tools such as "zod" to simply and quickly define schemas for input data, such as JSON, which we can then use to parse/validate untyped data into typed data to satisfy both the compiler, and us, that the data is the shape we expect. If you can find ways to avoid the escape hatches you can then get the compiler to carry more weight and trust in the types without blowback.