Need help?
<- Back

Comments (70)

  • zoogeny
    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.
  • retrodaredevil
    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
  • liampulles
    At a previous job, we introduced a simple Optional generic type, with JSON implementation, and it pretty much solved uncertain nil issues. Sometimes the solution is simple and boring.
  • Fire-Dragon-DoL
    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
  • galkk
    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 ).
  • nilirl
    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.
  • usrnm
    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.
  • umvi
    Go needs a good static analyzer to detect potential nil pointer dereferences. That would help identify and eliminate any unnecessary nil checks. .NET has a good one for C#.
  • Joker_vD
    > You may attempt to address this by pushing the problem up one layer. You now check for nil and return an error to flag the nil dependency as an invalid state.> It’s better, but it’s still not correct. Why not? Because we still allowed the invalid state to enter our system. A nil pointer is still being passed to our function, which puts the burden of deciding whether to trust the input on code that should have received a valid value in the first place.> The constructor is not where the error happened. The error happens at the initialization site:> Once initialization fails, we should handle that error immediately. We should not continue with a nil pointer and force the next, deeper layer to rediscover the outcome. Doing so also removes the need for the rate limiter constructor to return an error in the first place!But... surely it'd be better to leave this guard rail of a nil check in the rate limiter constructor, to quickly and accurately detect regressions in the very possible future where you reshuffle the code that constructs your objects?> The check belongs at the boundaryWait... is the author operating under an assumption that I control (almost) the whole of my codebase, so there is no need to have the boundaries inside of it?
  • diarrhea
    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.
  • rzwitserloot
    A good point and the java ecosystem makes similar mistakes. In general any:``` if (x != null && !x.isEmpty()) doAThing(x); ```is either:[A] Code directly on the boundary between systems; the other system is explicitly documented to treat null and empty as semantically equivalent, which is bad, but given that the mistake lies in a system beyond the control of this programmer, they're working around it. It can exist in this boundary code and nowhere else, or[B] Extremely rare, but there is a real semantic difference between the notion 'x is null' and 'x is empty' but this code wants to do the same thing in both semantically separate cases, or[C] it's bad code.NPEs are better than endless defensive dealings. If code checks for null I'd expect that null has a semantically identifiable meaning, and one that isn't also covered by something else (such as some notion of 'empty', e.g. an empty string or an empty list).
  • ceving
    The problem the author overlooks is that `RateLimiter` is public, meaning no one is forced to call the constructor.
  • bediger4000
    This is good advice for humans: they can quantify to decide "too many nil checks" or not. But it's not good for agentic coding, which we're entering the age of. Although agents are the worst they'll ever be right now, they're never going to be great at quantifying too many nil checks. I think we'll have to get used to far more nil checks than even bad programmers put in. But that doesn't matter to agents, they've got infinite attention spans, no cognitive bias and large working memories. Sonn we'll see no nil checks.
  • kstenerud
    I'd really really wished, with all of the history behind us, that golang would have learned from it. All they had to do was make pointers nonnull by default.Immutable-by-default would also have been nice. A man can dream...
  • turtleyacht
    What about wrapping nil in a Maybe or Option type?
  • lenkite
    Delegate all `nil` and bad input checks to a validation framework and use it in all your constructor functions.
  • guilhas
    To avoid propagating the pointer I would change *RateLimiter to RateLimiterDecodeRequest can return Request instead of *Request, or error if not validAlso I would replace `if userID == "" {` with `if err != nil {`. If an object is not loaded successfully returning error I think is more standard
  • anon
    undefined
  • kissgyorgy
    This is exactly what LLMs are really bad at. They don't have the knowledge (and don't ask for) the invariants of the system and write defensive code at every step of the way, which is not just unnecessary, it's bad because if an unexpected state still get into the system, you will never notice and bad data will flow through and makes everything unpredictable.
  • glove2477
    applies to all languages, actually. Fail fast, handle errors at place, etc. I really hate Java for its runtime exceptions, you really have no idea where and how your code will fail
  • Beigale
    [flagged]