90s devs: "Did you know about GOTO/CONTINUE for control flow? it's so convenient and powerful!"

00s devs: "GOTO is an antipattern. But did you know about try/catch? You can use it for control flow, just write a lot of exception classes, it's so powerful!"

10s devs: "Using exception blocks for generic control flow is an antipattern. But have you heard about event listeners and observer patterns? It's so powerful!"

Developers are so good at repackaging and reselling square wheels by giving them fresh, impressive sounding names.


  • 15
    @AlexDeLarge It's especially the "not recognizing disadvantages" part.

    It seems most developers learn about powerful technique with valid use cases, and then proceed to craft their code completely around it until it cripples everything.

    I'm not losing my shit when a developer is antipatterning occasionally, like by extending a prototype in JS... but it should trigger some "Fuck me this is risky stuff, let's pay some attention to what I'm actually doing" response.
  • 5
    Exception blocks for control flow¿
  • 3
    @inaba yes, you can for example use it for backtrack algorithms in order to climb the recursive stack up
  • 8

    if (user->id == id) throw new UserFoundException(user);

    Throw/try/catch can skip through large parts of the call stack. That's not always a problem, and maybe even desired when dealing with EXCEPTIONS. But "normal" results should just be handled by the caller!

    Exceptions are great when you write a reusable library, and want to communicate something along the lines of "wtf I don't even...", because you can't handle the nonsense that was provided.
  • 7
    With the examples provided, it's more about readability and extensible code. Also "RTFTJ" - Right Tool For The Job.

    GOTOs are obviously hard to follow and are not very extensible. Reuse of code is low, debugging is harder. Still, they actually have a use in modern languages. Or rather, labels (the other side of a GOTO unless you're using line numbers). In Golang, you can use a label to quickly break out of a nested loop.

    With an Exception, you should understand what throwing does under the covers. They serve a purpose when used properly.

    Observers and listeners also come with an overhead cost. Misuse can complicate code and make it harder to debug.

    I don't like the word "anti-pattern". It always feels like a euphemism for "bad programming".
  • 0
    @Lahsen2016 @xorith

    I personally think try/catch is an antipattern when abused, but even when used right it's still a language flaw: Absence of optional/maybe/either and algebraic data types in general.
  • 2
    I avoided goto for a long time, since I was told it was bad to use it.

    As a result I ended up rewriting the same cleanup code in my C routines, having to check what needed undoing each time.

    These days I set a status and bail out to a label at the end via a goto, to perform any cleanup needed.

    Keeps it fairly simple, for a language lacking exceptions.
  • 1
    Yeah, developers are sometimes like a dog. Always chasing the latest toy. It is (as pretty much always) just a simple matter of the right tool for the right job. All of those three things you mentioned ARE powerful. It's just that people tend to overuse patterns.
  • 2

    I'm not sure if it is a flaw. My three main languages these days are Java, C#, and Golang. I only use exceptions in the first two when absolutely needed. Even then, our in-house API framework is built in a way that exceptions are handled close to where they are thrown and translate into the appropriate HTTP errors.

    When using a library, I try to handle exceptions immediately where they are thrown. I want to minimize how far up the stack they go and produce meaningful output in logs or take the appropriate path at run time.

    I do this because the further from the throw that an exception is actually handled, the more confusing things get and the harder the stack trace is to read. I've also been told (but not tested) that exceptions can be a significant performance hit as everything stops to propagate the exception up the stack.
  • 1

    With Golang, things are a bit more simplistic. Golang allows for multiple return values and also provides a built-in "error" type.

    This creates a rather ugly set of function calls proceeded by conditionals checking for the state of the "error".

    I've also seen checks where you get a value with an optional bool. This way it can return a default or known "empty" value (often nil) and then a bool that indicates if the empty value was the result of a success or failure.

    Finally, Go has a "panic" that is used to halt execution unless "recovered". This is basically the closest analog to exception handling, but I rarely see it used to the extreme that exceptions are in Java and C#.
  • 1
    @xorith I think errors in Go aren't as neat as they could be -- but that's because the language aims to be simple rather than perfectly neat.

    In PHP, the most common bug is "call to a member function on null" -- you have a function which retrieves some User, but your database doesn't contain that User. The proper thing would be for the method to not have User as a return type, but a MaybeUser. PHP implemented the ?User typehint, but that's very simplistic and doesn't force handling of null in the caller method.

    Rust does what Go does on steriods: providing composable monadic error types like option and result, with a whole bunch of chainable methods with brilliant names like .map_or_else(), .and_then(), .unwrap_or_default(), etc.

    I don't know whether simplicity or completeness is "better" -- but I CERTAINLY prefer explicitly returning "packaged" values over the jumpy vagueness of throwing & catching.
  • 0

    I can see where you're coming from. I think in all cases it boils down to using things correctly.

    My personal preference, which may be wrong, is to keep things simple. If a user doesn't exist, return an expected value. I would say null, but there are anti-null purists out there - so you could always have a sort of constant for EmptyUser.

    The only time I really focus on exceptions (Java/C#) or errors (Go) is when it is important to communicate the reason for a failure. If a user doesn't exist, that isn't entirely a failure. It's an expected result. However if the database connection somehow dropped out from under us, that deserves a little more communication.

    Again, just personal preferences. I may or may not be good at what I do in the eyes of my peers.
  • 1
    Meanwhile in functional land where the monads roam and travel the pattern match path...
  • 1
  • 1
    @xorith Anti null purists? Damn
  • 0
    @zickig nullable?
  • 0
    @inaba I don't think null or nil or nothing is wrong, null is the absence of existence, which is a good concept.

    Providing null as a substitute for a defined return type is wrong though, null is the identity to sum types like either/maybe (option).

    You could have an absence of return value, on its own, but in such a case the function must always return nothing (void).

    Haskell even has a "Void" type with functions such as "absurd" and "vacuous" — which are exclusively used to tag nonexistent things and implement the concept of impossibility within the type system, in an ex falso quodlibet (from a false proposition, anything follows) kind of way.

    Being anti-null makes no sense, it's just that many languages implement null weakly into a type system which claims to be strong — leading to bugs. You can't have null without algebraic data types in my opinion, or null would only be valid for void functions.
  • 1
    Johnny come lately amateurs, what can I say. Computed GOTO, of not arithmetic IF.
Add Comment