err != nil Is GOOD? (And Why)

Поделиться
HTML-код
  • Опубликовано: 8 сен 2024
  • Recorded live on twitch, GET IN
    / theprimeagen
    Become a backend engineer. Its my favorite site
    boot.dev?promo=PRIMEYT
    This is also the best way to support me is to support yourself becoming a better backend engineer.
    MY MAIN YT CHANNEL: Has well edited engineering videos
    / theprimeagen
    Discord
    / discord
    Have something for me to read or react to?: / theprimeagenreact
    Kinesis Advantage 360: bit.ly/Prime-K...
    Hey I am sponsored by Turso, an edge database. I think they are pretty neet. Give them a try for free and if you want you can get a decent amount off (the free tier is the best (better than planetscale or any other))
    turso.tech/dee...

Комментарии • 601

  • @DerekDoes...
    @DerekDoes... 8 месяцев назад +101

    Love this a lot more than the typical 'reacting to videos' videos. Need like a playlist to be able to find these kinds of talks.

    • @reilandeubank
      @reilandeubank 8 месяцев назад +3

      Yup!

    • @manacht2727
      @manacht2727 5 дней назад +1

      to be fair, prime can turn a 5 minute video in a 40 minute one lmao

  • @nisancoskun
    @nisancoskun 8 месяцев назад +359

    In js, most libraries(native ones as well) throws errors that you may not know about. Sometimes this errors aren't even the native Error type. In Golang you know exacly what kind of error you are expecting, I like that explicitly.

    • @retishe7660
      @retishe7660 8 месяцев назад

      nice website 👌

    • @amotriuc
      @amotriuc 8 месяцев назад +14

      libraries should specify what errors they should the throwing, if they don't it is a crappy library.

    • @mdbk2
      @mdbk2 8 месяцев назад +26

      @@amotriuc Or the language is crappy if everyone can just ingore errors.

    • @amotriuc
      @amotriuc 8 месяцев назад +8

      @@jww0007 what about them? See Java /C# Languages they do exception handling quite well. Including those ones. Bad ones are C++ JavaScript.

    • @amotriuc
      @amotriuc 8 месяцев назад +3

      @@mdbk2 it is much easier to ignore a return code then an exception. If you miss an exception it will be caught on top level so you know it did happen. if you ignore result code no1 will know it did happen.

  • @datguy4104
    @datguy4104 8 месяцев назад +101

    Then ironically when you use the return value from the JS function that could be a value or null, you'd still have to do an "if value === null" and handle that case too lol

    • @ekszentrik
      @ekszentrik 8 месяцев назад +1

      What a stupid post upvoted by people who don’t use JS/TS.
      Null is never a native output in JS. It doesn’t happen on its own. Some programmer simply decided to use the “null” object.
      All empty references in JS are undefined.
      Don’t blame the language because your coworker or your own stupid face decide to return “null” objects either due to a cargo cult believe that value is useful, or because they need to signify an assigned but unused value.

    • @v0xl
      @v0xl 8 месяцев назад +3

      by the way, null is the only case when == is preferable over === since it also handles undefined. (*only* undefined + null)
      aka x == null.

    • @Rknife
      @Rknife 10 дней назад

      but null !== null

  • @JoshuaMaciel
    @JoshuaMaciel 8 месяцев назад +185

    I absolutely love Go error handling. Honestly I hated error handling when I started learning programming because I started with JS. When I started Go, I hated the error handling until one day after writing a service in it and realized how nice it is, looks bad but who cares? It works well. I agree though that Rust does error handling better but Go’s is super simple along with everything else about the language

    • @NithinJune
      @NithinJune 8 месяцев назад +1

      this!

    • @raenastra
      @raenastra 8 месяцев назад +1

      agreed - in the big picture, syntax is pretty trivial; what it enables you to do matters more

    • @MomchilAtanasovHome
      @MomchilAtanasovHome 8 месяцев назад +2

      Though rust error handling does a bod job about source of error unless you set the proper env variable in debug. With Go and wrapping, you get better pinpointing. From what I recall, Zig has the best error handling - the best from both worlds.

    • @Tony-dp1rl
      @Tony-dp1rl 8 месяцев назад +2

      Disagree completely. Writing exception handling in Go is awful, because there is so much code that is NEVER executed, but is just there "in case" ... Just yuck.

    • @MomchilAtanasovHome
      @MomchilAtanasovHome 8 месяцев назад +8

      @@Tony-dp1rl what do you mean by code that is never executed? If you know that it is never executed, just use underscore to ignore the error, otherwise it is a branch worth handling.

  • @noamprag9393
    @noamprag9393 8 месяцев назад +76

    Both Go and Rust use monadic error handling: Rust uses the Result monad while Go utilizes a monad known as "Writer" (which basically means a value with some other accumulated value, which in this case is the error). Rust is better in this sense because you cannot represent invalid states. On the other hand: in Go you could return `(nil, nil)` or both an error and a value. The result of this, is that error handling in Go feels more imperative and in Rust more declarative.

    • @Daniel_Zhu_a6f
      @Daniel_Zhu_a6f 8 месяцев назад +6

      writer is when you append things to a list, hashmap, etc. (err, value) is just a pair, ie an anonymous product type. writers are useful outside of FP languages when you want to accumulate multiple errors or accumulate operations and execute in bulk or mock io.

    •  7 месяцев назад +2

      Agree, add to this that the Go compiler does not encourage (force) you as a programmer to handle the error case, it can be happily ignored.
      Whereas Rust compiler forces you to handle all cases making it impossible to access the return value (compile-time error) without checking for an error first. This is a huge win.
      The `Result` type and pattern matching in Rust are simply awesome.

  • @BrianGwaltney
    @BrianGwaltney 8 месяцев назад +102

    Having coded full time in js/ts for 5+ years and 3 months in go, I'm 100% convinced go's way is better. Never want to go back having build production apps in both. In go, I know every error has at least been acknowledged if not handled correctly.

    • @natescode
      @natescode 8 месяцев назад +9

      Go doesn't force you to handle it. Just write a bunch of ugly IF err != Nill checks. Proper wrapped error types are cleaner and guaranteed they're handled.

    • @BrianGwaltney
      @BrianGwaltney 8 месяцев назад

      That's why I said at least acknowledge it. It's up to all of us to handle errors smartly. But at least I always know if a go function can error.@@natescode

    • @ocoolwow
      @ocoolwow 8 месяцев назад +3

      OP should not be anywhere near a computer

    • @OnFireByte
      @OnFireByte 8 месяцев назад

      @@natescode Every languages have the way to write ugly error handling. It's just that TS never tell you if it gonna throw or not. Go at least shows explicitly that error is possible without using any LSP, and if someone write ugly check it's just gonna be very obvious.

    • @aazzrwadrf
      @aazzrwadrf 8 месяцев назад +6

      @@OnFireByte It's not explicit when the majority of nontrivial functions in your program are propagating an unknown union of errors up the stack. The issue with error handling in Go is you don't know what errors are being returned. It's nothing like Result in Rust.

  • @themichaelw
    @themichaelw 8 месяцев назад +58

    If you've ever worked professionally in Go _and_ in another language without error-by-value, I strongly think that most people would prefer EBV. It significantly reduces the cognitive overhead when you need to jump into an unfamiliar service/repo. No surprises on where errors might pop up from.

    • @reddragonflyxx657
      @reddragonflyxx657 8 месяцев назад +12

      I like Rust's Option and Result much more than Go's err. Usually (in code I write) if I'm handling an error I'm dealing with data from the code that may have encountered an error. A Result makes me decide how I want to deal with errors before I can get at the data (I can return the error with "?", panic on errors with ".unwrap()", convert to an Option with ".ok()", map Ok()/Err() values, do clever iterator stuff with arrays containing error values, and otherwise gain a lot of flexibility by having the error and data bundled together).

    • @tsanov86
      @tsanov86 8 месяцев назад +1

      Before Go I did a bit of Delphi and C. I hated try-catch in Delphi, I didn't know where to put, I didn't know where exactly it came from and if you did not set it right initially it was even worse to find what is happening in production. C was such a breath of fresh in regards of errors - very simple and understandable, but really annoying to deal all the time with memory allocations and leaks so when I found Go and had the chance I switched all the codebase to Go in the next few years. This was 5-6 years ago, I still think it was the right call. I have colleagues that when from Delphi to C# and to this day we have this debate where they consider try/catch superior

    • @orterves
      @orterves 8 месяцев назад +3

      I am primarily a C# developer. Exception < (value, Err) < Result, simple as that. The problem with exceptions is as soon as they are possible in a language, they can happen anywhere and everywhere, for any reason, without warning. They are awful for having certainty about how the program will behave.
      Results are superior especially when the language has the ? or equivalent operator to pass the handling up the chain and otherwise enforces the handling of both cases, but I frequently try catch Exceptions in C# as early as possible to convert into a Result type just to get a little control back

    • @johnblomberg389
      @johnblomberg389 Месяц назад

      If there is something I really love it's jumping into a new codebase and the previous developer decided to make a "resilient" and "error handling" application that just does err != nil checks everywhere but instead of actually handling it in a meaningful way just returns it up the stack. Now I suddenly get a error code somewhere but I have no idea where the error originally happened or what could have caused it because the stack is unwound and the program already exited... I really love to then start hunting down all places where the return err is used and add logging just to be able to try and catch it 😍

  • @Grouloo
    @Grouloo 8 месяцев назад +11

    Although returning a tuple is wayyyy better than throwing and catching, I still find it really awkward to handle. I think Rust and functional programming languages got it right by using a Result wrapper type / monad. That way you are sure that is one or the other and it cannot be the error and the value at the same time, moreover, you get methods and functions to deal with error handling in a really logical and secure way.

  • @einargs
    @einargs 8 месяцев назад +7

    I like errors being part of the return type. My problem is that instead of using typed unions / associated value enums / algebraic data types, which is the correct, type safe way to deal with this, where accessing the value requires you to branch on having an error or not... they made it a tuple of nullable values.
    I guess it's good for a simple language that doesn't want extremely basic and important features for modeling data like being able to represent mutually exclusive states. (I'm salty.)
    That said, e.g. LLVM does errors like Go (with a custom wrapper return type) and it's really fun.
    On the other hand, I also really like Elixir style error handling. Elixir is built to do incredibly fault tolerant distributed systems where lots of things can error. So the idea is don't handle individual errors; any time there's an error, just recover from a known good state.

  • @mmmk189
    @mmmk189 8 месяцев назад +10

    If you want to handle errors by value in javascript you can adopt that convention and return a tuple like in go. Then the code will look very similar to go. The code in this video is complex because you are translating error via exceptions into error via return value. If you use exceptions all the way through then in javascript you will just do .catch on your calls to foo and bar and do your handling in there which is imo a lot cleaner than go

  • @IvanRandomDude
    @IvanRandomDude 8 месяцев назад +84

    Exactly. It is comparing apples to oranges. Equivalent Go code to "const val = await foo()" would be "val, _ := foo()". See, it is even shorter and easier to read :)

    • @zaneearldufour
      @zaneearldufour 8 месяцев назад +3

      Aren't panics way worse to deal with than JavaScript errors?

    • @ThienNguyen-bg1kx
      @ThienNguyen-bg1kx 8 месяцев назад

      @@zaneearldufour not if we talking about a panic in http handler with proper recovery middlewares

    • @Gusto20000
      @Gusto20000 8 месяцев назад +5

      @@zaneearldufourhow exactly you get panic from val,_ := foo()? Or you think it won’t panic if you write val, err := foo()?

    • @arturfil
      @arturfil 8 месяцев назад +19

      @@zaneearldufour You don't use panics for error handling typically, you use panics for situations where you can't keep on running your program and thus you panic and see the stack trace of where it panics. I.E when you don't set the port properly for a server (use letters for example) and you have to stop the server so that you correct that before you keep on going with running the program.

    • @VivekYadav-ds8oz
      @VivekYadav-ds8oz 8 месяцев назад +1

      @@Gusto20000 I have not written Go (I do write Rust btw), so I don't know what's the value of `val` when err == nil. But isn't the only logical choice being that it must be nil? In that case, using val should result in some sort of panic shouldn't it?

  • @amirhosseinsoleimani5410
    @amirhosseinsoleimani5410 8 месяцев назад +39

    Absolutely right! The messed up part is, lot of Devs prefer smart looking or cleaner code over secure or more stable code! Btw I love go's error handling or generally, "Errors as value" over old school "Exceptions"

    • @dealloc
      @dealloc 8 месяцев назад +5

      Old school would be to pass the error through arguments, or return an int with negative values, and is arguably worse; If you need the error message you then have to call `strerror(errno)`. Couldn't be anymore magic in my eyes.
      The problem is not exceptions but the language's ability to provide proper ways to deal with them and convey information about them. A language should provide ways to mark functions and things like pattern matching, operators, guards, etc. to handle them. Not just try/catch, which is arguably the worst way to deal with exceptions.

    • @LusidDreaming
      @LusidDreaming 8 месяцев назад +7

      @@dealloc try catch is what makes exceptions what they are. Maybe there is a different syntactic way to express this, but exceptions (in the context of things you "throw") will always have the undesirable behavior of being inherently different than other normal values. Its the ability to throw something up the callstack implicitly that (in my opinion) causes issues, not just the scoping issues with try catch blocks. Being able to throw an exception is essentially a goto statement without an explicit label. They can make the control flow of an application hard to understand and they inherently break locality of behavior. They are also harder for static analysis tools to reason about.
      I will say that Java forcing explicit definitions of what exceptions may be thrown from a method at least gives more explicit information and helps with static analysis. But it still suffers from the non-locality of behavior issue. Having errors as values means you don't need separate constructs to handle errors. They can be handled like any other value in your system, which leaves a lot more flexibility in how you handle your errors. Even when exceptions are values (like being objects in Java), you still have to use special syntax to extract them and pass them around, and I've rarely seen that ever done. You essentially create two separate control flows for your system, one of which is implicit.

  • @z0n_
    @z0n_ 8 месяцев назад +12

    I think it really depends what type of software you are building. For example: If you are building a basic CRUD Web API , I would guess +95% of the errors are "unrecoverable". Like if your database connection fails, what are you going to do? If you have bug in your business logic? If you failed to validate the request parameters correctly? For all of these cases there is no "recovering" from them. So having such fine grained error handling makes no sense. You can handle all of these with middleware (single try/catch).
    It's bit different when you are building something where not-recovering isn't really an option, like an operating system, IOT related or something where there are no transactions to save you. It boils down to using the right tool for the right job.

    • @ANONAAAAAAAAA
      @ANONAAAAAAAAA 8 месяцев назад +3

      Totally agree.
      Errors in backend are unrecoverable for the most cases.
      The only things can be done are: report or log errors, rollback DB transactions and show users sorry messages.
      It's better to return meaningful messages for user-input-validation errors though.

    • @isodoubIet
      @isodoubIet 8 месяцев назад +3

      I think this is pretty much true across the board, regardless of domain. I mean realistically, what strategies are even available to recover from most errors? Trying again?

    • @z0n_
      @z0n_ 8 месяцев назад

      @@isodoubIet Yeah, I guess "recovery" is not the correct word here. It's more like generic and non-generic error handling. Most errors can be handled in a generic way but I can imagine situations where application/tool has a lot of internal state and you have to essentially rollback the mutations manually when errors happen. I can see try/catch being really clumsy in those situations.

    • @isodoubIet
      @isodoubIet 8 месяцев назад

      @@z0n_ I suspect this is not possible to in TS/JS so this might explain why so many in this audience have issues with exceptions, but in C++ you can design things so that everything rolls back automatically in the case of an exception.
      When an exception is thrown, stacks will unwind and destructors will run. You then have several options for how to deal with rollbacks. You can divide your work in a prepare/commit fashion (so that all possible exceptions are thrown in the "prepare" stage, and all changes in system state happen in the commit stage), or you can have custom types that explicitly roll back changes when destroyed, unless explicitly dismissed. A third option, which may sometimes be possible, is to not rollback completely but to allow things to be in a valid but unspecified state after an exception is thrown.
      Either way you're never rolling things back manually, which is pretty clunky no matter what error handling strategy is being used but surely unworkable in the presence of exceptions.

    • @jeffreybritton3338
      @jeffreybritton3338 8 месяцев назад +1

      @@z0n_Or either use a transaction system or defer state modification to the final step of an operation where it can’t fail.

  • @eNtrozx
    @eNtrozx 8 месяцев назад +16

    Yeah but the annoying things is that if you want the error to bubble up, you still have to handle it

    • @ficolas2
      @ficolas2 8 месяцев назад +2

      Which is one of the reasons why rust does a much better job, you can just use the question mark

    • @SandraWantsCoke
      @SandraWantsCoke 8 месяцев назад

      yes, but you simply return the error

    • @eNtrozx
      @eNtrozx 8 месяцев назад +3

      @@SandraWantsCoke That's handling, you write actual code that does that

    • @PamellaCardoso-pp5tr
      @PamellaCardoso-pp5tr 7 месяцев назад

      At this point people just need to start implementing their own Maybe/Either/Result Monad, its not that hard at all, its actually insanely simple, Just a couple 5-6 functions and you're good to go.
      Monads can be implemented in any language and they're insanely usefull (Just look at rust)

  • @user-fr5vv3fy8i
    @user-fr5vv3fy8i 8 месяцев назад +12

    What if 95% of errors in your app just need to be logged and corresponding http status code returned? It's more more convenient to let those errors bubble up to a centralized error handler instead of repeating err != nil checks all across the call stack.
    The other 5% are usually retries/circuit brakers etc. which are handled by libs (talking about web services in this case)

    • @patrickramos1748
      @patrickramos1748 8 месяцев назад +4

      this is the one for me, in most of the code i write, 95% of the time an error happens i just want to rollback the transaction, do some logging, and respond with 500, all of which go in a middleware. that way, i dont have to think about exceptions most of the time

    • @ANONAAAAAAAAA
      @ANONAAAAAAAAA 8 месяцев назад +1

      100% agree.
      I also would recommenced to use error monitoring services like Sentry along with logging, they make life a lot easier.

  • @mctechcraft7
    @mctechcraft7 8 месяцев назад +1

    One thing I did in my C# code was add an extension method to Task that just is a wrapper around a try catch that returns a (T, Exception?) tuple that lets me do this and wrap existing methods so that I never get exceptions

  • @Im_Ninooo
    @Im_Ninooo 8 месяцев назад +4

    I agree, but sometimes it's nice to just not care about handling specific errors and just put a try-catch block in the main function, specially during prototyping.

  • @theowenmccarthy
    @theowenmccarthy 8 месяцев назад +51

    To be fair, as someone who regularly grumbles about Go's error handling I'm not advocating for it to be more like JavaScript's with Try catches, I just want like a '?' operator that would be the equivalent of "if err != nil {panic(err)}" and another like "?{return err, ...}" that would be the equivalent of "if err != nil {return err}" or "if err != nil{return err, nil, etc.}". When I need to do some custom error handling, Go is the best, but when I'm just testing something out or I don't expect any errors that shouldn't be returned I'd rather not have to constantly copy and paste the same codeblock.

    • @DemonButt3r
      @DemonButt3r 8 месяцев назад +1

      This is all I want as well. I dream about it

    • @nskeip
      @nskeip 8 месяцев назад +9

      Not sure if '?' operator should panic

    • @ja31ya
      @ja31ya 8 месяцев назад +8

      If you're just testing stuff out, why not use the placeholder character "_" ? Then you don't have to handle the err case.

    • @MomchilAtanasovHome
      @MomchilAtanasovHome 8 месяцев назад +3

      ? should not panic, it should propagate.
      The problem with pure propagation is that you have leaky abstraction. If you treat each package as standalone OS library, it should propagate typed errors from its package, hence just using ? is not applicable in that package's implementation. Rust fails at that as well.
      I would like tike have `.ErrOnFail(err, "%w: error creating database user: %w", ErrInvalidState, err)` which would propagate a wrapped error.

    • @oxidant5
      @oxidant5 8 месяцев назад +1

      @@ja31ya exactly

  • @DmitrijKudriavcev
    @DmitrijKudriavcev 8 месяцев назад +1

    As old-school C programmer, I can tell that this sort of error hanling was in the C forewer. You always return an error code and then you have an if statement to handle this error code. The Go haven't invented anything new here, except now you can separate an actual function response from the error that gives you this nice shugar.
    The main problem with this error handling is precisely a mess of if or switch statements after each function. You need to handle every error and then pass it on to the caller by wrapping it into another error what happens (face it) in 80-90% of the cases.
    But this extra code can be source of all sort of bugs because it is unnecessary operations that language forces you to do after every function call.
    Not only that, but you cannot chain call the functions this way.
    Lets say you have a function that return a string and another one that parses this string and returns integer. Because you must handle errors, you will need to process the first function response then call the second one and process its response as well. While you don't care if the first function didn't return a string or if the string wasn't a valid integer and the second function have failed, you will have to handle error twice and return a nil, err in both cases.
    This is why exeptions in C++ and Java was so great, it eliminates the need of doing that. You can handle errors in one place, you don't need an if statement after every function.
    There is no real problem to find out what function have failed, all you need is to know what type of error have happened and you can achive it by throwing different clases of exceptions. And thanks to inheritance you can group similar exceptions into groups by subclassing them from the some base class and handle this base class. It gives you way more flexibility on how to handle errors in your application and where to handle them.
    I will not be surprised if exceptions will be introduced in go at some point.

  • @dranon0o
    @dranon0o 8 месяцев назад +1

    ```
    var err error
    if err = something(); err != nil {
    return errors.Join(fmt.Errof("specifics %v", value), err)
    }
    var data Blabla
    if data, err = other(); err != nil {
    return errors.Join(fmt.Errorf("some specifics %v", value), err)
    }
    ```
    You did talked about this sir
    That's how you create wonderful errors management in `go`

  • @vladislavkovechenkov8473
    @vladislavkovechenkov8473 8 месяцев назад +1

    Thanks a lot for the vid. I'm only starting with Go and I was quite confused that error handling in Go was criticized this much, as to me it looked fine after years with JS. Yes, annoying, but as someone said on the internet "at least it makes you pretend you thought about how to handle potential errors", which sounds like a win for me. Maybe I haven't yet written enough Go and I'll have a better idea of where this pattern falls short as I run into more cases of handling errors, but for now I'll move forward with confidence writing Go and not stress about that error handling is "ugly". Also, thanks to everyone who added perspective in the comments sections, I learned a lot while reading about nuances of this pattern!

  • @MatthewAhrensTech
    @MatthewAhrensTech 8 месяцев назад +1

    Either your effects are in your types (tuples, monads, a failure ADT) or they are in their own effect system (exceptions, signals)
    I'm glad you mentioned a monadic approach. It comes down to the same tradeoff:
    I want control -> handle it all in line
    I want ease of expression -> abstract error handling from the "happy path" computation
    Different tools for different jobs

    • @Luxalpa
      @Luxalpa 8 месяцев назад

      Or use Rust and just get both?

  • @Tobsson
    @Tobsson 8 месяцев назад +10

    If err := doSomething(); err != nil {
    Handle error
    }
    Is something I find beautiful. Wish it would work even if a value is returned.

  • @LinxOnlineGames
    @LinxOnlineGames 8 месяцев назад +2

    I think this moves onto another interesting point, exceptions are meant to be exceptional (or so the saying goes), while Go's error is par for the course.
    Many developers use exceptions as an 'all hope is lost' - because if it wasn't that type of situation then it wouldn't be exceptional (stacktraces are expensive afterall).
    I've seen many a function written that instead of throwing an exception, would return false, or an error object, as the problem that arose is expected and part of the potential flow.
    From this video, I get the impression that an error is an error, no matter where it comes from in Go's paradigm.

  • @PeterBernardin
    @PeterBernardin 4 месяца назад

    There's a package called neverthrow that's quite cool, even though it's a bit awkward, it adds that extra layer of security to JS/TS. Takes the mysteries out of errors, and forces you to handle them. To me personally though, although I like the philosophy behind Go's error handling, my issue is that there's no shorthand for the err = ... / err != nil stuff. That pattern is just so common that I just feel it can be shorthanded to like an "or". Something along the lines of `someVal := someFunc() or { handle error }` and the compiler just forces you to handle the error.

  • @ishi_nomi
    @ishi_nomi 8 месяцев назад +5

    This totally make sense but is a bit weird though. I mean, the go-style error handling here is just what all language without try-catch simply do. C also do that, just without the tuple-like return syntax.

    • @youtubeenjoyer1743
      @youtubeenjoyer1743 5 месяцев назад

      The mistake of the Go design is that Error is a separate thing that needs its own interface. Instead of treating "error" conditions as Errors, you can just treat them as normal conditions.

  • @aazzrwadrf
    @aazzrwadrf 8 месяцев назад +8

    The main issue with error handling in Go isn't syntax. It's that the types of errors a function can return are not part of the function signature. You have no idea what errors you're checking for.
    In a nontrivial program, most nontrivial functions end up propagating a union of unknown errors up the stack, which isn't much better than unchecked exceptions.

    • @aazzrwadrf
      @aazzrwadrf 8 месяцев назад

      Go isn't Rust. "Errors as values" cannot be implemented well without union types. Having a special syntax for errors would:
      1) Allow union types to be added for errors and not the entire language.
      2) Go, unlike Rust, has recoverable panics, so the runtime is already maintaining a jump table. Having a special syntax for error handling would allow the happy path to have 0 runtime overhead.
      Go should adopt something like Zig's error handling.

    • @isodoubIet
      @isodoubIet 8 месяцев назад +1

      @@aazzrwadrf Rust also has recoverable panics, they just as you pretty please not to use it. The machinery for it is there, though.

    • @aazzrwadrf
      @aazzrwadrf 8 месяцев назад +1

      ​@@isodoubIetthe process is, but the thread is not recoverable. Therefore the runtime doesn't maintain a jump table to recover from panics.

  • @ilushkinz4424
    @ilushkinz4424 8 месяцев назад +16

    The main point of try-catch approach, is that, in 99.9% of all webapps, or even on 70-80% of software, you do not care about which error is occured.
    If you look at the go code, which has, for example, stack trace of 6 inner function calls, in each of these function calls you probably just sending the error upwards. And this exactly why it sucks. Mostly, you just propogate the error up to the main function, and in this case, you try-catch just does it better, it does it automatically without you requiring to do redurant error checks.
    Another thing what is bothering me about go error handling - it is not restrictive, you can just ignore the error, and it will panic because of that

    • @marcs9451
      @marcs9451 8 месяцев назад +4

      ignoring error states is possible in any language, being able to explicitly not care about the error state is totally fine in some cases.

    • @marcs9451
      @marcs9451 8 месяцев назад +2

      And no. Try-Catch does not work lile you said, the error is not merely propagated, the stack itself is *unwinded*, it's very, very different at a fundamental machine level. Simply propagating the error value as a return is jist another data transformation in your call graph and you can handle it with regular control flow.

    • @gagagero
      @gagagero 8 месяцев назад +1

      ​@@marcs9451The stack is not necessarily unwound. It is in most languages but that's because the language designers were lazy.

    • @taatuu25
      @taatuu25 8 месяцев назад +1

      @@marcs9451 What's the difference? isn't "unwinding the stack" the same as propagating up the stack?

    • @isodoubIet
      @isodoubIet 8 месяцев назад

      @@gagagero Lol what would have been the "non-lazy" choice?

  • @greendsnow
    @greendsnow 8 месяцев назад +40

    I wish there were a single liner error handling with Go, though.

    • @samfelton5009
      @samfelton5009 8 месяцев назад +2

      What language has single line error handling and what does it look like?

    • @antongorov5275
      @antongorov5275 8 месяцев назад +18

      Kinda like "expect" in Rust?

    • @paulooliveiracastro
      @paulooliveiracastro 8 месяцев назад +7

      Zig's try/catch are very cool, but the language has to treat errors as something special. In Go errors are simply some value that you return.

    • @andreffrosa
      @andreffrosa 8 месяцев назад

      ​@@samfelton5009 C has more or less when you use the return value for errors.
      if(err = f(n)) {...}
      When err is a non-zero int, the if evaluates to true and you deal with the error. The "downside" is that you have to use pointers as out-parameters for setting normal results

    • @notuxnobux
      @notuxnobux 8 месяцев назад +12

      @@samfelton5009 zig. In zig instead of "if err != nil return err" you have "try". The try in zig is not exception handling, it just does the go err != nil part automatically. For example:
      try func();
      instead of:
      value, err := func()
      if err != nil {
      return err
      }
      that zig version is the exact same thing as the go version

  • @blenderpanzi
    @blenderpanzi 8 месяцев назад +2

    Yeah, I prefer Rust's error handling over Go's.

  • @mintx1720
    @mintx1720 8 месяцев назад +2

    The problem is actually because of the error, you can't use the result immediately in the same line, ever. Even simple things like string to int, getting a value from a map, etc.

  • @Slashx92
    @Slashx92 8 месяцев назад +2

    The multiple try/catch with outside variables is exactly how I'm dealing with errors in critical processes of an erp we automate lmao. Shit's so ugly

  • @TacticalEntropy
    @TacticalEntropy 24 дня назад

    He touched on it right at the end. The really problem is not that it returns the error. Returning errors rather than throwing them makes them explicit and 100% better. The real issue with go is that "if" statements everywhere is such a shit way to then handle those exceptions.
    Error handling should be built into the language as a first class citizen and languages like Scala that allow you to then pattern match and using things like map, flatMap, for comprehensions, etc are such a better way to handle things. Sometimes you don't need to care about every place it could fail. Sometimes you want to just write code that says do these things as long as they keep working and let me handle the failures at the points I care about. While is why using a Monad and gaining fluid support for chaining map operations is great.

  • @taylorallred6208
    @taylorallred6208 8 месяцев назад +9

    One of hardest pills I’ve ever had to swallow: code aesthetics do not matter nearly as much as code pragmatics.

    • @aazzrwadrf
      @aazzrwadrf 8 месяцев назад +4

      go's error handling is neither pragmatic nor aesthetic.

    • @isodoubIet
      @isodoubIet 8 месяцев назад +1

      Neither matter as much as expressing oneself clearly, though. When you drop a platitude using a made-up expression like "code pragmatics" which people can only guess at the meaning of, you already failed.

  • @tonyb3123
    @tonyb3123 8 месяцев назад +23

    I pretty much never have to read actual stack traces in Go, because I'm hyper dilligent about wrapping _every error_ returned in `fmt.Errorf("some context: %w", err)`. It's genuinely jarring to go back to a Node environment and have to dig through a jumbled stack trace after writing Go and every error is telling me exactly what went wrong in plain english.
    I like to tell people, if you have to dig through an error's stack trace to know what went wrong and where, your error handling is wrong.

    • @thehibbi
      @thehibbi 8 месяцев назад

      In principle I like that idea, but what would happen if many concurrent tasks print to stderr at the same time? Does Go have some nice way to handle that? It's a genuine question for a real problem. Rust has the tracing crate for that.

    • @superderpyderps
      @superderpyderps 8 месяцев назад +5

      ​@@thehibbi fmt.Errorf isn't a print statement, despite looking like one. It's actually wrapping the error and is then typically returned to the caller. So the answer is, wherever you're actually logging errors will have the full stack for its specific error, meaning if you have a batch of errors, you'll see a "stack" for each error you logged individually. Also, stdlib added some other nice conveniences around the same time, allowing you to aggregate multiple errors into a single error and then be able to unwrap them alongside their individual traces. So you could choose to only log at the top most level even if somewhere in the chain you were aggregating multiple errors

    • @thehibbi
      @thehibbi 8 месяцев назад

      @@superderpyderps ah that's good to know. Thanks for the explanation!

  • @rzabcio3
    @rzabcio3 8 месяцев назад +1

    I hated Go's err handling. Until I launched my first maybe small but serious app and there were no errors, fatals, boundary cases or anything like that. I just couldn't believe it!
    After 15+ years in IT, one's used to getting unexpected errors, NPEs and similar snafus all the time, especially during first tries. But in Go... NOTHING. At first I thought I screwed up so badly, I missed some errors. But there is the point - it is impossible to miss errors in Go, I correctly managed all situations!
    It was love from the second sight. ❤

  • @PerfectMakanju
    @PerfectMakanju 8 месяцев назад +1

    This days I tend to use a library like purify-ts to wrap my Promise codes in Javascript. I find it insane not to be able to reliably know what errors a function throws or even be forced to handle it. This however means that I'll have to be using creating more functions to handle errors (like .then promises handles)

  • @mk72v2oq
    @mk72v2oq 8 месяцев назад +6

    I mean yeah, error-by-value is always better than exceptions. So Go is not the worst, but it still sucks.
    Monadic result-type approach is the best atm. Especially when the language supports a syntax for it natively. That's why ? operator in Rust rules.

    • @OnFireByte
      @OnFireByte 8 месяцев назад

      What about doing something before returning? Like doing logging or add context into error. I assume that something like result.or_else(handle)? is possible but are there any other way to do it?

    • @mk72v2oq
      @mk72v2oq 8 месяцев назад +1

      @@OnFireByte well, ? operator is for situations where you don't want to handle an error here. Rust has a handful of ways to explicitly handle errors. You can match over the result obviously. Or use the let-else construct introduced relatively recently in 1.65.

    • @mk72v2oq
      @mk72v2oq 8 месяцев назад

      @@OnFireByte but I just realized that you can actually extend ? behavior. It calls .into() on errors under the hood. So if you have a custom error type, you can implement the Into trait for it and run whatever code you want there.

    • @OnFireByte
      @OnFireByte 8 месяцев назад

      @@mk72v2oq thank

    • @isodoubIet
      @isodoubIet 8 месяцев назад

      " error-by-value is always better than exceptions"
      No, it's not.

  • @aaaaanh
    @aaaaanh 8 месяцев назад +1

    hmm, Go error handling used to make me suicidal cuz it's a pattern I'm not used to. After 2 weeks of usage, I went back to typescript to build out something else and realized Go handling is way better. Then I learned Rust and everything else was history.

  • @pldcanfly
    @pldcanfly 8 месяцев назад +1

    if err != nil { return err; } is a snippet... and copilot is really good at just suggesting that, so it is not that tedious tbh. And having errors as values let's you do amazing things with how they should be handled, etcpp. I don't get when people say go-error-handling is bad, when in reality it's a great thing to have. It's like saying it is annoying that i have to update the type-definition in Typescript whenever I want to assign another property. Yeah, it's work to do, but it is a good thing that you have to do that.

  • @rt1517
    @rt1517 6 месяцев назад

    Depending on the context, implicit error management can help you write simpler code, while explicit error management can help you write more robust applications.
    I would argue that it is almost the same with garbage collector and RAII.
    With garbage collector and/or RAII, you can write simpler code. But you are pushed/forced to ignore errors in destructors so your application is not as rigorous as it would be with explicit resource management.

  • @madskaddie
    @madskaddie 8 месяцев назад +1

    this one I'm not with prime. Note that I also prefer return value based errors, but if the model is exceptions, than for the most cases, the error is forwarded to the client (we can discuss if the forward model is correct - vs wrapping the error, but that is other discussion. And in the go example, the err is also forwarded)
    assuming error forwarding is the model, exceptions minimize undefined behavior by nature. period. no one with experience would return null on the catch branch. It is simply wrong. that is for the client to choose, not the implementation
    I prefer return value based because i really like funcional pipelining and function composition. having a try/catch is a mess to compose (the exception behavior is like a non linear, hidden, return value). I like linear control flow. But having that control does make the happy path less clean. choices

  • @Fernando-du5uj
    @Fernando-du5uj 8 месяцев назад +1

    Thank God, I was waiting for that last "...agen". I thought you will not do it.

  • @itzhakeretzkdosha6875
    @itzhakeretzkdosha6875 8 месяцев назад +5

    Will golang ever go the 'Result' path?
    I'm missing that and union types in GO

    • @OnFireByte
      @OnFireByte 8 месяцев назад

      union types might be possible. But for result type, it's certainly not. Imagine everyone have to update their codebase to change from (T, error) to Result[T]

    • @climatechangedoesntbargain9140
      @climatechangedoesntbargain9140 8 месяцев назад +2

      ​@@OnFireBytenot everyone has to do that - they're compatible

  • @beofonemind
    @beofonemind 8 месяцев назад

    thanks for hammering this home. I've been telling myself not to cut corners with error handling. I will reap the benefits of it later.

  • @emilemil1
    @emilemil1 8 месяцев назад +10

    This obviously differs, but at my workplace we do very minimal error checking because even when functions can error, they shouldn't, because we control the input. The only places where we really check for errors is for network requests where failure is out of our control. If something breaks and we get an unexpected error then the appropriate response (for us) is to let that error bubble to the surface so we can fix it, not attempt to handle it which often results in suppressing bugs.

    • @chudchadanstud
      @chudchadanstud 8 месяцев назад +5

      No such thing. You're pretty much relying on humans not making a mistake. The issue with the "bubble to the top" nonsense is that errors can get masked and transformed along the way. You also don't know how much damage it has done along the way.
      Handling errors is about soft landings when mistakes occur, or creating self healing systems. Remember, failing to prepare is preparing to fail.

    • @gileee
      @gileee 8 месяцев назад +1

      ​@@chudchadanstud No, if someone makes a mistake in their input, that's still their input they're responsible for. Errors don't get "transformed along the way" and any error causes the system to stop processing the entire request so nothing is changed if an error occurs.

    • @isodoubIet
      @isodoubIet 8 месяцев назад

      @@chudchadanstud " The issue with the "bubble to the top" nonsense is that errors can get masked and transformed along the way. "
      That seems to suggest a fundamental problem with how your overall system design approaches errors. There shouldn't be any opportunity for errors to be "transformed along the way" because you should only have error handlers at or near the top level in the first place. If you're trying to "handle" errors by wrapping a try catch around every function that can fail, you're working against how the scheme is intended to be used and setting yourself up for these kinds of problems.
      Disclaimer: I don't do JS/TS and my experience with exceptions comes from C++ where they actually make sense.

    • @chudchadanstud
      @chudchadanstud 8 месяцев назад +2

      @@isodoubIet Bubbling to the top doesn't guarantee that your error won't be transformed. You're just throwing an exception and hoping someone else handles it, hoping that the system crashes or the process/request is killed unpredictability. You're also killing a process for a useless error.
      The Go styles ensures that your not placing your errors on hopes and dreams and that your function only throws errors in needs to throw.
      Consider that you press a stop button in a machine and during the clean-up operation a files fails to close. That is a trivial issue, your machine shouldn't crash or cut the power because a file failed to close. You can simply log the error and move on with the rest of the steps.

    • @chudchadanstud
      @chudchadanstud 8 месяцев назад +1

      @@gileee What of you have multiple erros that occur whilst your bubbling to the top. How do you catch those?

  • @dzisonline
    @dzisonline 8 месяцев назад +3

    My biggest issues with errors are not mine but library's. Hunting errors in huge libraries is quite tedious.

  • @Sw3d15h_F1s4
    @Sw3d15h_F1s4 8 месяцев назад +3

    i think it was Java that has "throws [some type]" for method signatures? so you know exactly what error is thrown and by what? seems like a decent solution, yeah its a bit boilerplate but at least theres a heads up.

    • @phoenix-tt
      @phoenix-tt 8 месяцев назад

      It could have been a lint at least. Typescript's had this proposal for a long time, yet it hasn't been merged.

  • @mementomori8856
    @mementomori8856 8 месяцев назад +4

    I LOVE it personally, just deal with your error right away and be done!

  • @romsthe
    @romsthe 8 месяцев назад +1

    Error handling is hard. We have automated coredump analysis and monitoring to make sure it cores in the usual places we don't bother to fix 😅

  • @DemonButt3r
    @DemonButt3r 8 месяцев назад +1

    I just wish go had rusts '?' just so i don't have to constantly return something like nil, err in situations where i want the error treated a little higher up

  • @jasonscherer2631
    @jasonscherer2631 8 месяцев назад

    I learned how much this makes sense after now doing a golang code base, I love it so much more.

  • @danielsan901998
    @danielsan901998 8 месяцев назад +10

    With zig you can use try and avoid all that boilerplate.

  • @shellderp
    @shellderp 8 месяцев назад +2

    whats nice in Kotlin is you can do val x = try {} catch { return } and not have a var/val that was ever null

    • @copypaste4097
      @copypaste4097 8 месяцев назад

      it boggles my mind, that he never has looked into kotlin

  • @aidemalo
    @aidemalo 8 месяцев назад

    Well, equivalent would be not return Promise, but throwing error anyway (even after special handling)

  • @CodyDBentley
    @CodyDBentley 8 месяцев назад

    Something we do at my job is if/else chaining related blocks, and I haven't really seen it done in other places. All the values are scoped to the entire if/else chain, and the only downside is sometimes function signatures can break the blocks if it has no error branch, but most things we write or interact with have error returns. Example (assuming foo and bar are int values or something):
    ```
    if fooVal, err := foo(); err != nil {
    return 0, fmt.Errorf("foo failed: %w", err)
    } else if barVal, err := bar(); err != nil {
    return 0, fmt.Errorf("bar failed: %w", err)
    } else {
    return fooVal + barVal, nil
    }
    ```

  • @paulomattos6753
    @paulomattos6753 8 месяцев назад +1

    You could do this in js:
    async function main (){
    const fooValue = await foo().catch((e)=>throw new Error('foo error:',e);
    const barValue = await bar().catch((e)=>throw new Error('bar error',e);
    }

  • @makeshiftartist_420
    @makeshiftartist_420 8 месяцев назад +1

    100% agree with you. I just don't think that comparing Go's error handling to JavaScript isn't fair since JavaScript is 90% bad design and 10% incredible optimization

  • @Voidstroyer
    @Voidstroyer 8 месяцев назад +5

    I mean, sure Go forces you (kinda) to handle all possible errors, while in JS one try catch block can handle many function calls that could possibly throw. There are however many situations in which you don't care about handling every error individually for which try catch works perfectly. And if you wanted to, as was shown in the video, you could potentially wrap each individual function call inside its own try catch block and achieve the same that Go has. just with more verbose syntax. This also means that in this case, JS actually wins over Go since it gives you the flexibility.

    • @sda-jf3cc
      @sda-jf3cc 8 месяцев назад +1

      not quite, you can simple use _ in go and move on without handling the err

    • @marcs9451
      @marcs9451 8 месяцев назад +1

      you make a mess, you clean it up or be explicit about not cleaning it. if you can just "ignore errors" they are not *real* errors, they are possible states to your algorithm that you are lazily discarding. at least in go you can do
      foo, _ := bar()
      and just ignore the returned error explicitly

    • @Voidstroyer
      @Voidstroyer 8 месяцев назад +1

      @@sda-jf3ccyeah that's why I added the (kinda)

    • @Voidstroyer
      @Voidstroyer 8 месяцев назад

      @@marcs9451 I understand your point, but it doesn't relate to what I said though. I said that in JS one try catch block can be used to handle multiple errors. You aren't ignoring the error, just handling multiple at once. A crude example of this is if you have a process that has multiple steps, let's say you are doing a database transaction that updates multiple different tables. You could use one try catch block in which you don't care about any specific step failing. As soon as one step fails, you just rollback the transaction and log the error that occured, regardless of which step caused the error. Sure the same thing can be done in go but then you would have to write the rollback statement multiple times, while in JS you just write it once.

    • @OnFireByte
      @OnFireByte 8 месяцев назад

      The problem is that you never know which function can throw an error in JS. This behavior like this is acceptable for api server that you have an root try catch that will respond to the request with 400,500 if error get thrown anyway (which is the main use of Go ironically). But beside from that I don't think it's really a win for JS.

  • @photon628
    @photon628 8 месяцев назад +13

    rust error handling is the best error handling I have ever used with result set and question mark operator 👍

  • @programaths
    @programaths 8 месяцев назад

    You can also mitigate the issue. I do Apex, and I have objects with embedded error handling. That sounds weird since it breaks the single responsibility principle, but it simplifies the code for the caller.
    You can chain methods using the same objects; each method knows to bail out if you provide an error. This means you can write your implementation and check for errors in one place, handle the error anywhere, and provide an error-free object to continue the chain.
    The API is mostly for noncoders who can't handle errors (they don't even think about it), so it has to be as robust as possible.
    And by avoiding throwing exceptions, it's made very explicit.
    The language doesn't support Generics; otherwise, I would have done a wrapper with overloaded methods accepting those wrappers.

  • @capability-snob
    @capability-snob 8 месяцев назад +1

    If you aren't doing manual resource management - e.g. you have defer/with/using/RAII/Context - there's not many reasons to care which function call failed. Where I handle these differently in Go, I'd otherwise use the type to distinguish cases I can handle from those that I can't.
    Well done btw, well done 4:30

  • @philipp04
    @philipp04 8 месяцев назад +1

    I've got C++ at university programming classes (I'm studying maths but we do have some programming) and my professor just outright banned exceptions for low performance, weird ass implementation and being wack in general lol. The man is also a CTO of a company which develops software for finding petroleum sources in the ground, the project takes fuckin 20 minutes to build and it has no try catch anywhere in it

    • @romsthe
      @romsthe 8 месяцев назад

      And he's right. Status checking like in go is better for perf and maintenance. He will probably tell you to use std::expected now that it's there

    • @isodoubIet
      @isodoubIet 8 месяцев назад +1

      "my professor just outright banned exceptions for low performance, weird ass implementation and being wack in general lol."
      He's wrong on all three counts. Exceptions perform better on the happy path, the implementation is just fine, and they're very easy to understand.

    • @Tuniwutzi
      @Tuniwutzi 8 месяцев назад +1

      @@isodoubIet Super agree. Especially in C++, where you can avoid writing try/catch in 99% of cases by just using RAII, exceptions are by far the best way. A bit sad that professors are teaching this nonsense when the C++ standard itself encourages the use of exceptions.

    • @isodoubIet
      @isodoubIet 8 месяцев назад +1

      @@Tuniwutzi Yeah I can see why someone in a space-constrained environment might want to avoid exceptions, but the rest is largely just FUD.

  • @flammea_
    @flammea_ 8 месяцев назад +3

    No Joke. Why would I handle a potential error from JSON.parse? What am I supposed to do with the JSON.parse error?

    • @alberto3641
      @alberto3641 7 месяцев назад

      no one really knows lol

  • @sbditto85
    @sbditto85 8 месяцев назад +3

    It definitely could be better though and it hurts the scan ability of the code as in it can make it harder to understand the non-error handling code flow. Still things could always be worse…

    • @jamesprendergast7158
      @jamesprendergast7158 8 месяцев назад

      The 'idiomatic' way of error handling in Go is to try to align the happy path on the left - if you do that then you can configure your IDE to collapse 'if err != nil{ ... }' blocks so that you only see the non-error case. But TBH the happy path is almost always trivially simple - the more interesting and critical parts of your code will probably be the error handling

  • @hbobenicio
    @hbobenicio 6 месяцев назад

    Very good point. So when you don't need/want to handle the error, JS way is better/simpler than go. But when you need/want to handle the error, golang is actually better. Agreed. But, I would say that most of the time we are actually raising up the error to be handled higher up the stack (with perfectly fine error handling code). So most of the time golang's way would not be really "pleasant".
    For me the best selling point in favor of golang's error handling (which I do agree is much better then JS and almost any other language) is the *explicity* of it! Explicit is always better then implicit, IMHO. So it's generally a bit verbose and awkward I must admit but REALLY consistent, explicit and clear on intents. So it reads really really fast and natural to everyone. No misteries or hopes or technical theories about it. An this is why golang error handling is better then most

  • @esser50k
    @esser50k 8 месяцев назад +1

    learn to love go error handling.
    It actually makes the code super easy to read, for the happy case you just read whatever is on the left side of the code (the editor often even just hides the return err lines).
    It also makes it super easy to test your functions and make them go into every error case..

  • @npc-drew
    @npc-drew 7 месяцев назад

    There is this too:
    ```
    func check(e error) {
    if e != nil {
    panic(e)
    }
    }
    func main() {
    err := ...
    check(err)
    value, err := ...
    check(err)
    }
    ```

  • @crum--business
    @crum--business 8 месяцев назад +1

    You can add .catch after the method
    function foo() {
    method1().catch(err=>/*handle it*/)
    method2().catch(err=>/*handle it*/)
    method3().catch(err=>/*handle it*/)
    method4().catch(err=>/*handle it*/)
    }
    More like this
    function foo() {
    const value1 = method1().then(res=>res.json()).catch(err=>/*handle it*/)
    const value2 = method2().then(res=>res.json()).catch(err=>/*handle it*/)
    const value3 = method3().then(res=>res.json()).catch(err=>/*handle it*/)
    const value4 = method4().then(res=>res.json()).catch(err=>/*handle it*/)
    }

  • @qwelias
    @qwelias 8 месяцев назад +1

    That works only if you treat errors as uniq special cases and thus need to handle them with uniq special code. How about just don't? In your average HTTP API server if you fail to parse JSON just let the error bubble up and return some sane description to the user. Same goes for most of business-logic cases, if the error is thrown manually it should be descriptive and it should be handled by top-level handler that will just send it to the client in a nice way; but if it's not a manually thrown one then it's probably a server fuckup (i.e. 500), if it's on a cricial path -- sure handle it with some cleanup, but that's rare. Most of your APIs should be stateless too, unless you're from 2000s.

  • @danielmelo389
    @danielmelo389 8 месяцев назад +1

    VBA error handling, you can add a number in front of every line of code, so when an error happen you can point out precisely where it happened and why. Is it perfect? no. Is it any good? Also no. Have to add line numbers is a real mess

  • @NickSteffen
    @NickSteffen 8 месяцев назад +4

    I think this is better handled with a result type. So your methods return the result or a result. The result type contains two fields an error which is normally null and the return value. It HasFailed method that checks if the error is null or not. This basically makes the JS syntax look like Go. Most languages have libraries that can supply the result type.

    • @dealloc
      @dealloc 8 месяцев назад

      No. Never do this, especially in library code. There are multiple problems with it;
      First is that it would require everyone to use the same exact objects. If it's provided by a third-party library, that's even worse, because you now introduce the potential for incompatibility when the API changes and every outdated library would be rendered useless.
      Additionally, the ergonomics would be terrible in order to make it compatible with other code that does not rely on it; you'd need to wrap every darn function to follow the convention.
      Additionally, in JavaScript, it would also introduce additional overhead, because it would require creating objects all the time, and those need to be garbage collected. If not, it would result in some nasty memory leaks, if those objects are not marked for collection for one reason or another.
      The solution is not to use some convention, but instead it should be a language feature and provide proper APIs and syntax to deal with it, similarly to async/await and Promises.

    • @NickSteffen
      @NickSteffen 8 месяцев назад

      @@dealloc First, Obviously it should be a language feature but it’s not. We to deal with the world we live in. Second, most people don’t write reusable package libraries, they write non library code or they write libraries for their own use. Lastly throwing exceptions is worse and most of the time someone will forget to document them fully, and your library users will also forget to catch them. so unless you use a generic catch your gonna miss something and if you do catch all of the exception types then your code is a mess.
      Result types end with you being the least screwed. I’m not super familiar with JS but the same applies to most languages (Java, c#) you can use structs for your results instead of classes to avoid garbage collection if needed. I usually create a one of each as if your returning a class already you’re already incurring the gc penalty.
      I do agree that if you’re making a package you should follow convention and not do this though

    • @dealloc
      @dealloc 8 месяцев назад

      @@NickSteffen I was specifically talking about JS. And it is a mess, even in application code. You end up having to wrap every call or function of third-party libraries.
      In JavaScript all objects are dynamically allocated, and there's no such thing as structs. Everything is passed by reference, so passing them through multiple code paths means they each have to hold on to the object. Considering that these they are meant to be fundamental, and used everywhere to have any remote usefulness, this naturally leads to excessive object allocations. You also make it harder for the optimizer and compiler (i.e. V8/JSC) to perform well as you're wrapping your return values.
      Using it everywhere is signing a contract that is hard to back out of, as you've introduced fundamental difference in how errors are handled.
      For Java and C#, yes, you could potentially do this and avoid the issues I listed above. But those have the benefits of being typed languages and having structs that are passed by value would make more sense. Though, you'd still need to wrap third-party libraries with glue code.
      What I like about Swift is that it has both Result, Option and throws, and they are trivial to convert between and handle through language syntax. That's why it works well there.

  • @juice2
    @juice2 8 месяцев назад +1

    I really love this design choice in golang

  • @sda-jf3cc
    @sda-jf3cc 8 месяцев назад +1

    and go does not even force you to handle it, you can just use _ and move on, when you really really need to move fast
    I just do err.ifn and autocomplelete writes the rest for me, and err.pri and again autocomplete writes the rest for me.
    the worst thing in ts is changing your types and including null just because there could be an error, it sucks

  • @little-ghost-rider
    @little-ghost-rider 3 месяца назад

    You can just create some wrap function
    So that it returns undefined rather than throw error

  • @patrickaikonia853
    @patrickaikonia853 7 месяцев назад

    I love the take and I also raw dog my JSON.parse every single time unless I have more than on potential error source

  • @Samuronchik
    @Samuronchik 8 месяцев назад

    In this simple example explicit error handling may be better, but I think realistically it more often not possible to handle error than handle it gracefully. In such cases the error must bubble up, be logged, be traced in telemetry and so on. And any time you add try/catch you make error invisible from the tooling that is in place. It may not be logged, you may not receive and alert and so on. So I strongly believe that try/catch statement should be very justified to be present and most often should be removed. On the other hand with go error handling it is way more difficult to ensure you don't mess with tooling. In this case monadic handling like Error monad is much better. You explicitly handle the success state and let it propagate to caller site to be handled by tooling explicitly so everyone understands that you have this tooling in place. With catching errors using some framework machinery may not be so obvious for all the team. Shotrly put, both approaches sucks, JS way is easier to check, use Error monad if you want, don't handle errors if absolutle sure what to do with them

  • @chickenduckhappy
    @chickenduckhappy 8 месяцев назад +21

    Catching exceptions all the time is nonsense. They're best used for unexpected errors. Return values, callbacks, wherever are much better for expected errors that need to be dealt with as part of normal control flow.

    • @gileee
      @gileee 8 месяцев назад +2

      True. No one actually writes js code like he did in this example.

    • @Slashx92
      @Slashx92 8 месяцев назад +8

      ​@@gileeeHi, we automate an erp with javascript (it's scripting languaje) his example is exactly how we deal with errors to do the proper rollbacks. When you care WHAT failed, you have to do that try catch hell

    • @davidmcdonnel4831
      @davidmcdonnel4831 8 месяцев назад +7

      @@Slashx92This, as long as you are writing an application that does something you are going to want some sort of fault tolerance. What if the network goes down? What if the filesystem isn't available? What if there isn't enough ram to complete the operation? How do you fail gracefully? What happens to the logs? How do you recover? Do the users need to be prompted that their action failed?

  • @HelloForeignWorld
    @HelloForeignWorld Месяц назад

    Imagine if people invest into learn to work with effectful systems like Haskel or Scala with Cats effect or ZIO. The entire humankind will progress and prosper and probably live longer

  • @acuteaura
    @acuteaura 8 месяцев назад +5

    go's error handling has worse warts, like it being entirely untyped strings with no proper interface, we had this huge file full of string matching "things the net and net/http can throw" to status code and error message conversion in our reverse proxy, implementing basically all cloudflare error codes and some extra because we had this stupid SAP app as a customer that only logged status code and they had connection issues.

    • @marcs9451
      @marcs9451 8 месяцев назад +3

      Yes, I much prefer error enums like Zig/Odin/Rust. Go's biggest weakness is not having real enums and unions

  • @Wh4tsupy0
    @Wh4tsupy0 8 месяцев назад

    I think the lesson of this video is that Go’s error handling is only bad in comparison with languages like Rust and Zig that have better syntax for handling errors, but is still good when compared to languages that don’t use errors-as-values.

  • @MysteryJake89
    @MysteryJake89 8 месяцев назад

    The JavaScript version should probably throw again in the exceptional cases, rather than conflating an error with a null value (which could probably be considered an antipattern). This keeps the return type clean, the caller also doesn't know where it failed either if you simply start returning null in place of an error. Think NodeJS and the error codes that get attached to errors to do error handling with context clues (e.g. ENOENT). Either you know how to handle the error and you do, or you wrap the error with some context, or you just let it flow up.

  • @lighty262
    @lighty262 8 месяцев назад

    Writing code while recreating/solving problem while also talking. That sounds like a native speaker of a computer language.

  • @ybabts
    @ybabts 8 месяцев назад +1

    Yeah error handling in JS is terrible, which is why i made my own library to make the error handling closer to languages like Rust. Its so much nicer to handle errors as values instead.

  • @lukekurlandski7653
    @lukekurlandski7653 8 месяцев назад

    I honestly started doing this in Python to some degree when I need more granular control over error handling. Sometimes, I want to handle different ValueErrors differently, and the doing so requires subclassing ValueError with a dozen or so different exception types and its just gets crazy. Way easier to return an error code and read the function’s documentation to figure out what that error code means.

    • @teleraptor6076
      @teleraptor6076 8 месяцев назад

      Why not create a single subclass which has the error code as an attribute?

  • @martijnvdven
    @martijnvdven 8 месяцев назад

    I am not sure I understand why Primeagen is returning null incase of the error state in the JavaScript function. As he said, when you are calling the function from the outside you must always wrap it in a try-catch because anything can throw and there is no way for you to know. (Or not wrap and accept the consequence of the error travelling up.) Given you must handle thrown errors anyway, why not have the function only declare the happy path return (Promise) and have it always throw on errors? That is what I have been doing.

  • @robertlemiesz7143
    @robertlemiesz7143 8 месяцев назад

    With the js example you can wrap them in an allSettled

  • @brady1123
    @brady1123 8 месяцев назад

    The only problem with Go's error-handling is that you're allowed to stuff the `err` result in a bit bucket and ignore it. I wish the compiler would require you to always check the `err` value.

  • @maximdolina899
    @maximdolina899 2 месяца назад

    please wine for this gentleman

  • @JeffCaldwell-nemo_omen
    @JeffCaldwell-nemo_omen 8 месяцев назад

    Man, I'm just picking up Go as someone who's primarily coded in JS/TS and watching this makes me feel more sane. Up to this point I was just like "Wow, handling errors is such a gigantic pain in the ass." Go may not have ideal error handling, but it's so much less complicated than handling try/catch forks, especially in the lower layers of an app.

  • @incursion45
    @incursion45 8 месяцев назад

    Unpopular opinion the best way to do this is the way they do it in C# and using expressions in the catch

  • @nanthilrodriguez
    @nanthilrodriguez 8 месяцев назад

    Go's error handling being better than typescript doesn't mean its good, nor is it a very high bar. Why not have syntactic level construct to handle errors rather than constantly checking return values? Zig isn't perfect, but its an idea.
    I would far rather an assert equivalent that implicitly returns the error when there is an error, save from polluting vertical space with boilerplate of either flavor.

  • @felgenh399
    @felgenh399 8 месяцев назад

    Preach
    Never really thought of this. Good content

  • @andreizarra9972
    @andreizarra9972 8 месяцев назад

    Oh men I totally agree with you because my code in typescript looks exactly like the example that you do with all those try catches for handle all possible errors, I realized that I'm using the wrong language

    • @xyangst
      @xyangst 8 месяцев назад

      Just don't use exceptions at all

  • @dealloc
    @dealloc 8 месяцев назад

    The real problem with JavaScript is that they don't have the necessary language features to provide a good way to handle errors. They should honestly look at Swift's approach, which is very similar albeit much more powerful thanks to pattern matching, guards, and other language features that makes it a breeze to work with.
    Exception-based error handling is bad when the language doesn't provide ways other than try/catch to deal with error propagation.
    I'd say the same for Go. They should provide ways to invert the error handling, or make it implicit, like Rust does with the ? (previously try!) for Optionals and Result (as long as the return type is also Result/Option)

    • @dealloc
      @dealloc 8 месяцев назад

      Also Prime's example of using try/catch is not equivalent to handling the error with != nil in Go. In Go the error propagation is explicit (return err), whereas in JavaScript and other languages that use exception-style error handling, they are implicit or "automatic". You can still handle the error in a function and then rethrow the error to propagate the error. It's just a separate control flow, which Go doesn't have, so it's not 1:1.
      The major drawback of JavaScript is that there's no way to mark a function as throwing. It's a lack of syntax problem, not a conceptual problem.

  • @Sapperbounded
    @Sapperbounded 8 месяцев назад

    I dont think most people have a real issue with error by value, they have issue with the extreme tedium of doing the same sort of boilerplate error handling and having nothing to simplify it for the 99% use case.

  • @CallousCoder
    @CallousCoder 8 месяцев назад

    I always loathed exceptions it’s a sneaky hidden control flow.

  • @nickbanderson
    @nickbanderson 8 месяцев назад

    *dabs in rust superiority*

  • @isodoubIet
    @isodoubIet 8 месяцев назад +2

    Exceptions are designed to work with RAII so that any rollback on error can happen on destructors. 90% of the time the top level error handler should just log unrecoverable errors, that's it. If you're trying to roll back state in an exception handler you're going to have a real bad time.

  • @nehua6164
    @nehua6164 8 месяцев назад +4

    Can't you use the promise syntax to chain a then() to return the value and a catch() function to handle the error in JS? Seems like a skill issue.