Hacker News new | ask | show | jobs
by Thaxll 2528 days ago
How returning (val, err) is error prone? It's verbose but it's clear and definitely not error prone. I spent so much time working with Java and useless giant stacktraces or with Python and people not knowing what to do inside a try / except.
3 comments

Repetition and verbosity in a language can create errors in at least two ways. First, by the developer losing track of which error case is which (and/or copy-pasting error-handling logic) and doing the wrong thing in the error case. Second, by reviewers who have become trained to notice and gloss over error-handling boilerplate not noticing when there's something wrong with a particular case.

Concise languages can be more challenging to read because you have to understand more about each symbol/word in the language. But verbose languages can be more challenging to comprehend because there's a lot of symbols which don't signify anything.

Interesting perspective. Are you expressing an opinion about "explicit is better than implicit", or is your point on a different axis?

I suppose concise / implicit is fine when the thing that's being hidden can't go wrong, like in:

[i * 2 for i in 1...10]

The loop counter increment logic can't possibly go wrong, so it's fine to not think about it.

Regarding error-handling, don't you want to think about? If you're calling a() followed by b(), what should you do if a() fails? In some cases, b() shouldn't be called, but in others, it should, like deleting a temp file. And if you have to think about it, it's better to be explicit?

My preferences are that error handling is expressed in and enforced by the type system (Haskell's Maybe/Either, Rust's Result), that common error handling tasks be supported by the standard library and by specialized syntax when necessary (Haskell's many Monad/Applicative tools, Rust's "?" operator), and that if a developer neglects or chooses not to handle an error that the most likely outcome is that it bubbles up to some kind of top-level crash handler that terminates the current task and produces whatever useful diagnostics are possible (exceptions in many languages).

To put it more simply: yes, the developer should have to think about what they do in the case of an error. And then the amount of work they do -- and the code produced, and thus the work reviewers have to do -- should be proportionate to how unusual the necessary error handling is. When I see explicit error handling, that signals to me "hey, this is an important case that we need to handle in a particular way".

the amount of work they do -- and the code produced, and thus the work reviewers have to do -- should be proportionate to how unusual the necessary error handling is.

Great comment. I would add how unusual _and critical_.

One of the things I love about Python is that while I know errors can occur on practically every statement written, I only have to add error handling for likely / expected / critical errors. Any unlikely errors that occur, even in production, will show a detailed stack trace (lots of context), making them easy to fix.

In my experience, things work as expected 98% of the time. For some software, like a pacemaker, checking the execution of every single line of code and even having redundant error checking is not overkill. For other software, like the backup software I work on, having one customer out of 1000 get a weird error is something I'd rather deal with as a support ticket rather than having to anticipate it while writing code.

Of course error handling is important, but requiring 3 lines of error handling for every 1 line of actual code has kept me from investigating Go to replace Python for HashBackup. I'd love to get the performance increase, but not for a 4x expansion of LOC.

Honestly when I look to rewriting a python thing to be 'faster' either try using PyPy first, or rewrite it in OCaml instead. OCaml is extremely simple, similar to python in a lot of ways (GIL and all), but runs at native code speeds, near C the majority of the work, and super easy to bind to C libraries if you need.

Or try Rust. ^.^

Concise and implicit are kind of different axes. For example, Python's "x += 1" is more concise than AppleScript's "set variable x to x + 1.", but the exact behavior of the statement is just as clear from reading it, so it is no less explicit.

In this case, I don't think anyone is arguing that error handling should be implicit. They're saying there should be an explicit way of saying "handle this error in the common way." This actually makes the distinction between common and uncommon cases more explicit, because their differences aren't buried in boilerplate.

You're using defer to close the temp file either way.
you have 'catch' to handle those cases. Try and catch are easy to notice when scanning the codebase.
Copy/paste is very error prone. Golang code is full of it. I see lots of similarities between Visual Basic and Golang, incl. the passionate communities behind the languages.
Curious, do you see other communities that are not passionate about their languages?
With the difference that Visual Basic is an academic language full of needless features from Go's community point of view.
Visual Basic is hardly academic; they is a tremendous amount of line-of-business code that has been written in VB over the past 25 years.

But yeah, Go dev do not see VB's features as being "features."

The point was that many of VB.NET features are what many in the community attack as being academic and not worthy of being adopted by Go.
Given that I have worked with VB long before .NET even existed, I have a broader view of it than just the more recent criticisms.

VB's biggest problem IMO is that it tried to compete with C# instead of maintaining its original raison d'être which was to be a highly productive tool that required very little programming skill to be able to start building real solutions for business use.

But I digress...

It's error prone in that you aren't forced to handle the error. In languages such as Rust or Haskell, you have a Result type which can either be an Ok(val) or an Err(err). In order to "unwrap" a Result, you have to check the error case. Basically there's a compile time guarantee that errors are handled.
I'm not a Rust expert but afaik Rust doesn't enforce error checking since you explicitly need to unwrap(). It's very possible to panic because you forgot to check something.

It's similar in Go since you can't compile with unused variable so you need to explicitly discard the error with _. Ex: result, _ := func() This is for multi-value returns, for single value you can even omit the _

https://golang.org/doc/effective_go.html#blank

Having unwrap() in your Rust code is like littering your code base with panic(). It’s not appropriate to use in most production code, but is convenient in prototypes, examples and tests.

Your example re Go errors is incorrect. The go compiler allows you to ignore errors in returns without any compiler error.

For example

err := doThingThatErrs()

and

doThingThatErrs()

are both valid Go code.

There's nothing wrong with unwrap. It's just an assert. Even a[i] is just shorthand for a.get(i).unwrap(). Asserts are definitely appropriate in production code, just not for handling run-time errors.
Then how is this better than anything else? Except for syntactical sugar for:

Try Return [Bla(), null] Catch err Return [null, err] End

I do like this syntax better, bc the different scopes cause a lot of nesting

My example is correct I explained all of that, multi values -> need to omit, single value can ignore everything.
Yup. I misspoke. We’re both right.