5 ms·
The way you're describing it sounds to me like you are adding failure handling to handle cases where the programmer is intentionally misusing the thing. I would
by strongly-typed 2y ago
The way you're describing it sounds to me like you are adding failure handling to handle cases where the programmer is intentionally misusing the thing. I would argue that in this type of situation, the error handling is not necessary because it would hide the fact that the thing is being misused.
Or perhaps I'm misunderstanding your comment. When you do `as OrgId` or `as UserId`, where do you envision those casts in ways that would require handling failures?
- dvt 2y agoMy point is that the API consumer does not guarantee types (say it's REST or something), so the assumption that the string you send it will always be the right type (or call it "format") seems like a bad one. Unless you control API usage from end-to-end (which kind of defeats the point of an API), you need error checking (or at least exceptions to bubble up). A lot of times branding is used to mark data received from a database, e.g.: this field is an `OrgId`, but I can do all kinds of things to that string which might make it not-an-`OrgId` at any point. Then, I'll try to reuse it the `OrgId`, and I'll get some weird error or blowup and I'll have no idea why. So the point is that (a) branding is a dubious feature because it can obfuscate soft-type-breakage (I call it "soft" because at the end of the day, we're just dealing with strings), and (b) it still doesn't preclude runtime error checking unless you're okay with blowups.
- dbalatero 2y ago> Unless you control API usage from end-to-end (which kind of defeats the point of an API) Isn't this all frontend client bundles that talk to their own private backend API? Those are controlled end-to-end. My company has one, yours probably does too!
- Dylan16807 2y ago> My point is that the API consumer does not guarantee types (say it's REST or something), so the assumption that the string you send it will always be the right type (or call it "format") seems like a bad one. The raw data from the API will not have any of your internal types applied to it yet, it'll be raw bytes or typed `string`. So I don't really see the connection between this and "I will still always be able to do `as OrgId` or `as UserId` so you need to do have some failure handling". Only your own trusted code can do "as OrgId", so... don't do that unless you have an OrgId. And once your own trusted code has made an OrgId, you don't need any runtime checking to see if it actually is an OrgId. > A lot of times branding is used to mark data received from a database, e.g.: this field is an `OrgId`, but I can do all kinds of things to that string which might make it not-an-`OrgId` at any point. What kind of things? Strings aren't mutable so you must be making a new string. But a new string will have a type like `string`, not `OrgId`, and then using it as an OrgId won't compile.
- dvt 2y ago> And once your own trusted code has made an OrgId, you don't need any runtime checking to see if it actually is an OrgId. Right, and once I have a verified OrgId, I'll just keep using the `myOrgId` variable throughout my code, and I don't really need branding. Maybe I can do type aliasing to make the code easier to read (type OrgId = string), but hardline type verification via branding seems moot unless you can make strong runtime guarantees. I mean, don't get me wrong, I think it's a cute novelty, but it doesn't really do anything. > But a new string will have a type like `string`, not `OrgId`, and then using it as an OrgId won't compile. Exactly. Maybe I'm wrong, but in a real codebase, I bet branding would probably just confuse people. "Why can't I change the last number of an OrgId?"—well, you see, once you do that, you lose the brand so now you need to manually do `as OrgId`.
- kristiandupont 2y agoIf you have a function addMemberToOrg(memberId: string, orgId: string), you can accidentally call it like this: addMemberToOrg(myOrgId, myMemberId) and nobody will complain. With branded types, the compiler would mark it as an error.
- dvt 2y agoFunction signatures already solve that problem. We have all kinds of functions that take two numbers that mean different things (width/height, x/y, etc.). Branding seems like a solution looking for a problem. I just think it's too much overhead and confusion for too little gain. In fact, a common pattern is to pass fully-qualified objects, e.g. `dimensions = {width: number, height: number}`, which makes mixing up variables even less likely since you have to explicitly specify them.
- kristiandupont 2y ago>Function signatures already solve that problem. I literally just showed you how they don't. And you even go on to describe a pattern that makes the problem "even less likely" in the next sentence.. >Branding seems like a solution looking for a problem. You do you.