Earlier quoted context omitted.
Crazy that actually using your type system leads to better code. Stop passing everything around as `string`. Parse them, and type them.
There's a name for this anti-pattern: "Stringly typed"
How I write HTTP services in Go after 13 years
121–130 of 259 posts
Re: How I write HTTP services in Go after 13 years
#122Re: How I write HTTP services in Go after 13 years
#123> The Valid method takes a context (which is optional but has been useful for me in the past) and returns a map. If there is a problem with a field, its name is used as the key, and a human-readable explanation of the issue is set as the value. I used to do this, but ever since reading Lexi Lambda's "Parse, Don't Validate," [0] I've found validators to be much more error-prone than leveraging Go's built-in type check…
This is a good design pattern, but be wary of doing validation too early. The design pattern allows you to do it as early or late as you like, but doesn't tell you when to do it. Often it's best to do it as part of parsing/validating some larger object. See Steven Witten's "I is for Intent" [1] for some ideas about the use of unvalidated data in a UI context. [1] https://acko.net/blog/i-is-for-intent/
The legitimate insight that they have is that anchoring the state as close as possible to the user input is valuable—I think that that is a great insight with a lot of good applications.
However, there's nothing that says you can't take that user-centric state and put it in a strongly typed data structure as soon as possible, with a set of clearly defined and well-typed transitions mapping the user-centric state to the derived states.
Edit: looks like there was discussion on this the other day, with a number of people making similar observations—https://news.ycombinator.com/item?id=39269886
Re: How I write HTTP services in Go after 13 years
#124Earlier quoted context omitted.
I’ve found it hard to apply this pattern in Go since, if Username is embedded in a struct, and you forget to set it, you’ll get Username’s zero value, which may violate your constraints.
Why? You can easily call NewUsername inside NewAccount for example, just return the error. Or did I misunderstood?
Re: How I write HTTP services in Go after 13 years
#125Earlier quoted context omitted.
> Initialize your struct fields and you won't have to consider their zero state. “Just do the right thing everywhere and you don’t have to worry!” You can’t stop consumers of your libraries from creating zero-valued instances.
Then the zero value is their problem, not yours. You have no reason to be worried about that any more than you are worried about them not getting enough sleep, or eating unhealthy food. What are you doing to stop them from doing that? Nothing, of course. Not your problem. Coq exists if you really feel you need a complete type system. But there is probably good reason why almost nobody uses it.
Except for all those times you're the consumer of someone else's library and there's no way for them to indicate that creating a zero-valued struct is a bug.
Again, it's the philosophy of "Just do the right thing everywhere and you don’t have to worry!" Sometimes it's nice to work with a type system where designers of libraries can actually prevent you from writing bugs.
Re: How I write HTTP services in Go after 13 years
#126Re: How I write HTTP services in Go after 13 years
#127I found fx( https://github.com/uber-go/fx ) to be a super simple yet versatile tool to design my application around. All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize…
>All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize initialization between them. I gotta say, I hate these dependency injection frameworks. In a well designed system th…
Re: How I write HTTP services in Go after 13 years
#128> The Valid method takes a context (which is optional but has been useful for me in the past) and returns a map. If there is a problem with a field, its name is used as the key, and a human-readable explanation of the issue is set as the value. I used to do this, but ever since reading Lexi Lambda's "Parse, Don't Validate," [0] I've found validators to be much more error-prone than leveraging Go's built-in type check…
But surely this is just another way of doing validation and not fundamentally "parsing"? If at the end you've just stored the input exactly as you got it, the only parsing you're potentially doing is in the validation step and then it gets thrown away.
Re: How I write HTTP services in Go after 13 years
#129Earlier quoted context omitted.
There's a name for this anti-pattern: "Stringly typed"
This term is typically used to refer to things like data structures and numerical values all being passed as strings. I don't think a reasonable person would consider storing a username in a string to be "stringly typed".
Which is exactly what's going on here. A username has a string as a payload, but that payload has restrictions (not every string will do) and methods which expect a username should get a username, not any old string.
Re: How I write HTTP services in Go after 13 years
#130Earlier quoted context omitted.
You can use new types with validation too. In fact the approaches seem to be duals. Parse, don't validate: string ParsedString untrusted source -------> parse --------------> rest of system Validate, don't parse: UnvalidatedString string untrusted source ------------------> validate -------> rest of system
The problem is that pattern "fails open." If anyone on the team forgets to define an untrusted string as UnvalidatedString, the data skips validation. If you default to treating primitive types as untrusted, it's hard for someone to accidentally convert an untrusted type to a trusted type without using the correct parse method.
Both cases appear to depend on there being a "checkpoint" all data must go through to cross over to the rest of the system, either at parsing or at UnvalidatedString construction.