ThePrimeagen is a big advocate for this concept, and he's mentioned many times that he uses asserts a lot, basically to catch bugs. If you can assert that a function or code module is being used as intended, you can greatly reduce a lot of bugs. I had actually used assert at work to ensure that a certain node module is never imported unless our application was started with a certain flag in the config, because if the module was loaded when that flag wasn't enabled, many other issues would occur. My coworker who reviewed my code wasn't a fan of the fact that we are basically defending the code against ourselves and suggested I remove it. I listened to their suggestion, but funnily enough, I accidentally added code that imported that module as part of another change they requested, and I introduced a bug in our software. If only the assert were still present, I would have caught it... Just thought I'd share a relevant story, I'm a huge fan of this kind of defensive programming, especially since it's so easy to compile asserts out of the binary for production builds, in many languages, so there's no performance downgrades. Subscribed via RSS!
I wrote the above message after only watching 8min of the video, but I see the point of the whole video now after watching it all. I think it's definitely interesting! I'm always interesting in ways to make code more declarative and to state different scenarios in a way that is readable, and makes it easy to write "business logic" to handle when things happen outside the happy path.
This seems a lot like the concept of Errors As Values. Have you ever programmed with that paradigm? I agree a lot with you that thinking about your code in terms of a happy path and an unhappy path with the possiblity of invalid state is an absolutely fantastic way to model problems and code and I really like the way you have put it. If you haven't already, then I would suggest you try working in a language with 'Maybe' and or 'Error' types such as Rust, Go, Haskell, Ocaml or others. I think they are an absolutely brilliant way to write clear and correct code, with features such as Rusts "?" operator making the use of them often quite concise too.
If you do that you just end up in C land again where sometimes a 0 is an error, often it's success and -1 is the error, or maybe the error is 1-who knows if it's even in the return values, maybe it's whether an out parameter is null. You need your return type to be a proper superset of the output domain of your function, else you cannot effectively communicate these messages via return. Certainly not in a standardised way
@julians.2597 oh but I program in C exclusively because c++ is an absolute mess unlike the way it started. Objective-C is apple specific last I checked and C# is a cross between native and interpreted last I checked. Rust is bloated and gets in my way. Zig is promising but still in development. C is the only low level language above asm that is reliable to do what I want it to do. Gonna read the rest of your message and if need be add another reply
@julians.2597 as for the varying meanings of the return value, as I said I'm making my OWN messages where 0 always = success and any other value means a deviation from intended result. I chose to start with a flat copy of all the microsoft status codes for the sake of easy pass through to public api. All the other ones are caught internally and converted to a more meaningful code. The only thing I don't like about it is the need for files that translate the code to text but that is likewise a good thing because it means the local language can be used to make it easier to read. Basically the library I'm designing is meant to be a layer on top of the os that ensures we devs can stick to just *.o and *.a files when distrobuting instead of faffing about with exes and packaging formats. The only thing causing me to slack off on the project is the per heap memory management code that I'm not looking forward to 😭
I fell into a similar hole fairly recently when creating my own Result type. Semantic differences between errors can be expressed in much simpler ways that don't attempt to radically diverge from the standard way things are handled in JS. Extending the Error class achieves this handily, but given that you an throw anything in JS, you're not limited to just throwing errors (although that somewhat betrays the notion of writing "idiomatic" JS). Unsurprisingly, a good number of other languages have multiple ways to handle "errors," where some are arguably more aligned with this than others. Go has multiple return values for error handling, but also has panic and recover statements for unusual/extreme cases. Erlang/Elixir's exceptions aren't particularly unusual, but handling them is usually done at higher levels by supervisors, and is therefore reserved for something "unexpected" or generally unable to be handled, otherwise you just return error tuples and handle them with pattern matching. Rust has panics (that are technically recoverable) that are a much more severe form of runtime exceptions and Result types that you pass around for more intelligent handling. Haskell and OCaml are much like a middle ground between Elixir and Rust, with Exception and Option types, but unlike the BEAM languages, exceptions don't usually propagate as far and aren't used as often. After using my JS Result type for awhile, I got tired of dealing with it, though. It's nice to know that I'm handling something that can error when using JSDoc/TS, but it gets really tedious when I have to wrap every single IO operation or external dependency that could possibly fail. The second someone else touches the codebase, there's also no guarantee that the convention will be strictly followed unless I watch PRs like a hawk. Betraying the convention of a language really hampers productivity and the ease in which someone else can pick up where you left off, in my experience. It's probably a better idea to pick a different language with built-ins that cater to this idea than to try and force it into an ecosystem and runtime that isn't particularly friendly to it.
also I think you should rename referp to attempt as that conveys the right meaning. referp makes me think you mean Refer "P" to X which conveys no further meaning about what P is or what it's for
I think this is very helpful in large code bases where you work with other devs. I use assert() a lot and I don't likethrowing errors, so I mostly return some kind of result, it doesn't always have to be an object with a message, sometimes a simple false in case something went wrong is enough. When we do this, we lose an advantage of errors though which is bubbling including a stack trace. results don't bubble, you gotta do it manually whereas exceptions bubble automatically and include a lot of information. So in some cases, using exceptions is simply better. as always it depends
ThePrimeagen is a big advocate for this concept, and he's mentioned many times that he uses asserts a lot, basically to catch bugs. If you can assert that a function or code module is being used as intended, you can greatly reduce a lot of bugs.
I had actually used assert at work to ensure that a certain node module is never imported unless our application was started with a certain flag in the config, because if the module was loaded when that flag wasn't enabled, many other issues would occur. My coworker who reviewed my code wasn't a fan of the fact that we are basically defending the code against ourselves and suggested I remove it. I listened to their suggestion, but funnily enough, I accidentally added code that imported that module as part of another change they requested, and I introduced a bug in our software. If only the assert were still present, I would have caught it... Just thought I'd share a relevant story, I'm a huge fan of this kind of defensive programming, especially since it's so easy to compile asserts out of the binary for production builds, in many languages, so there's no performance downgrades.
Subscribed via RSS!
I wrote the above message after only watching 8min of the video, but I see the point of the whole video now after watching it all.
I think it's definitely interesting! I'm always interesting in ways to make code more declarative and to state different scenarios in a way that is readable, and makes it easy to write "business logic" to handle when things happen outside the happy path.
@@natemaher1389 I like the idea of naming these three possibilities "happy path", "unhappy path", and maybe "no path".
This seems a lot like the concept of Errors As Values. Have you ever programmed with that paradigm?
I agree a lot with you that thinking about your code in terms of a happy path and an unhappy path with the possiblity of invalid state is an absolutely fantastic way to model problems and code and I really like the way you have put it.
If you haven't already, then I would suggest you try working in a language with 'Maybe' and or 'Error' types such as Rust, Go, Haskell, Ocaml or others. I think they are an absolutely brilliant way to write clear and correct code, with features such as Rusts "?" operator making the use of them often quite concise too.
unrecoverable errors vs recoverable errors. I love it.
14:06 In this scenario it's easier to just do "if ( i
If you do that you just end up in C land again where sometimes a 0 is an error, often it's success and -1 is the error, or maybe the error is 1-who knows if it's even in the return values, maybe it's whether an out parameter is null.
You need your return type to be a proper superset of the output domain of your function, else you cannot effectively communicate these messages via return. Certainly not in a standardised way
@julians.2597 oh but I program in C exclusively because c++ is an absolute mess unlike the way it started. Objective-C is apple specific last I checked and C# is a cross between native and interpreted last I checked. Rust is bloated and gets in my way. Zig is promising but still in development. C is the only low level language above asm that is reliable to do what I want it to do. Gonna read the rest of your message and if need be add another reply
@julians.2597 as for the varying meanings of the return value, as I said I'm making my OWN messages where 0 always = success and any other value means a deviation from intended result. I chose to start with a flat copy of all the microsoft status codes for the sake of easy pass through to public api. All the other ones are caught internally and converted to a more meaningful code. The only thing I don't like about it is the need for files that translate the code to text but that is likewise a good thing because it means the local language can be used to make it easier to read.
Basically the library I'm designing is meant to be a layer on top of the os that ensures we devs can stick to just *.o and *.a files when distrobuting instead of faffing about with exes and packaging formats. The only thing causing me to slack off on the project is the per heap memory management code that I'm not looking forward to 😭
I fell into a similar hole fairly recently when creating my own Result type. Semantic differences between errors can be expressed in much simpler ways that don't attempt to radically diverge from the standard way things are handled in JS. Extending the Error class achieves this handily, but given that you an throw anything in JS, you're not limited to just throwing errors (although that somewhat betrays the notion of writing "idiomatic" JS). Unsurprisingly, a good number of other languages have multiple ways to handle "errors," where some are arguably more aligned with this than others. Go has multiple return values for error handling, but also has panic and recover statements for unusual/extreme cases. Erlang/Elixir's exceptions aren't particularly unusual, but handling them is usually done at higher levels by supervisors, and is therefore reserved for something "unexpected" or generally unable to be handled, otherwise you just return error tuples and handle them with pattern matching. Rust has panics (that are technically recoverable) that are a much more severe form of runtime exceptions and Result types that you pass around for more intelligent handling. Haskell and OCaml are much like a middle ground between Elixir and Rust, with Exception and Option types, but unlike the BEAM languages, exceptions don't usually propagate as far and aren't used as often.
After using my JS Result type for awhile, I got tired of dealing with it, though. It's nice to know that I'm handling something that can error when using JSDoc/TS, but it gets really tedious when I have to wrap every single IO operation or external dependency that could possibly fail. The second someone else touches the codebase, there's also no guarantee that the convention will be strictly followed unless I watch PRs like a hawk. Betraying the convention of a language really hampers productivity and the ease in which someone else can pick up where you left off, in my experience. It's probably a better idea to pick a different language with built-ins that cater to this idea than to try and force it into an ecosystem and runtime that isn't particularly friendly to it.
also I think you should rename referp to attempt as that conveys the right meaning. referp makes me think you mean Refer "P" to X which conveys no further meaning about what P is or what it's for
I think this is very helpful in large code bases where you work with other devs. I use assert() a lot and I don't likethrowing errors, so I mostly return some kind of result, it doesn't always have to be an object with a message, sometimes a simple false in case something went wrong is enough.
When we do this, we lose an advantage of errors though which is bubbling including a stack trace. results don't bubble, you gotta do it manually whereas exceptions bubble automatically and include a lot of information. So in some cases, using exceptions is simply better. as always it depends