Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • The design of error handling in Go is interesting because they wanted to force people to actually handle errors by returning them as values, but then you as the programmer have to decide whether an error should be returned or a panic should be used.

    It reminds me of the original intention of checked exceptions in Java: checked exceptions are for things you force the caller to handle, unchecked exceptions are for "you the programmer messed up". In reality checked exceptions are pretty unpopular and can't be used in many situations, so people fall back to unchecked exceptions.

    If we equate unchecked exceptions with panics in Go, falling back to panics would be an anti-pattern in many cases.

    NPEs in Java have become rarer and rarer recently with the introduction of records (to easily create immutable classes, which are easier to validate for null against). Plus JSpecify annotations get you null denotation that's almost as good as Kotlin's. Combine that with NullAway are you have compile time null safety. Go has nilaway [0]. One interesting thing about nilaway is that you don't null-annotate your code, it just detects nilness when you run nilaway. That makes nilaway a decent tool to get feedback right away, but it doesn't force you to document the intention behind parameters and fields for whether they are nullable or not, which I would argue is one of the advantages to null-annotating Java code with JSpecify.

    [0] https://github.com/uber-go/nilaway

  • I am very ambivalent on this post. On one hand, I agree that excessive defensiveness stinks up a code base. On the other, I am a huge fan of local reasoning. Especially in the world of LLMs, I don't want to rely on my, my teammate's or my LLM agent's ability to know every single code path that results in `Allow` begin called.

    Of course, this really comes down to the type system and the fact that non-nullable pointers are missing.

    The one definite thing I would say, swallowing the error and just trying to do a reasonable thing is the most wrong thing here. At the least, there ought to be an ERROR log, even if one was trying to be defensive against outright panics.

  • This is the mess a language lands on when it conflates optionality (a semantic concept) with references/pointers (purely a machine concept). In Go, the requirement "need (non-optional) a reference to an object" is simply not expressible. This is a solved problem in other languages, for example `&T` vs. `Option<&T>` in Rust.
  • I agree with first point “Nil Check on a Dependency” and disagree with 2nd point

    “Nil Check on a Dependency in the Constructor”, at least in the way it is described in article’s example.

    The _parameter_ check in the constructor is the standard practice of testing on perimeter/blundaries. You test your parameters on the public methods (that constructor obviously is), and assume valid state in private methods. And even there I can accept practice of debug build assertions (DCHECK/TCHECK in Google c++ terminology ).

  • Also known as contract programming vs. defensive programming. This argument is very old, is not specific to golang, and I have found myself on both sides at different points in my carreer.
  • Go is a very unique language in that it is the only language designed to make you understand the frustration of online dating.

    First, seduction, and then as it reveals how little it cares about you, eventual disappointment.

  • I could have forgiven nil checks, but nil checks on interfaces elevated nils to a whole new level, which is annoying, but I do get where they were going with this: you should never nil check an interface. After all,an interface could be valid for a nil value.

    There are ways to decently write go and not deal with nil, but as usual, linters defaults makes it impossible and you have to fight with your team before they will understand (we did this at some point and it was a huge improvement).

    Don't use pointers at all, always allocate structs on the stack, pass them by value.

    You pay the copy price, even with large structs, and that's fine. When there are exceptions, be very explicit about the reason: performance must be critical,not just an optimization.

    Don't ever check interfaces for nil, if you need some sort of optional parameter, make a separate function and make it pass an valid object for that interface that's a null object.

    These two did improve things substantially

  • I very much like Go's simplicity, but nils are terrible. Pointers carry 3 different intents that are not encoded in the code:

    - Optionality

    - Mutability

    - Memory optimisation

    I agree with the author that when the pointer is here for mutability or to limit copies you need to check nullity at the outer layer. But for optionality you need to check it each time you access that value.

    Sometimes the intent is not clear and you are forced to check nullity everywhere. An Option type fixes this but it's not idiomatic Go code and forces to wrap every thing you call

Explore Birbla archives