[HN Gopher] The 4-chan Go programmer
       ___________________________________________________________________
        
       The 4-chan Go programmer
        
       Author : ingve
       Score  : 177 points
       Date   : 2024-08-28 18:35 UTC (4 hours ago)
        
 (HTM) web link (www.dolthub.com)
 (TXT) w3m dump (www.dolthub.com)
        
       | lemursage wrote:
       | A timeless classic from Buena Vista Social Club comes to mind
       | https://www.youtube.com/watch?v=o5cELP06Mik
        
       | kardianos wrote:
       | The "chan chan Value" or "chan struct{resp chan Value}" is
       | actually a pattern I've used in a very specific situation. It's a
       | situation where a message bus _probably_ could have been used
       | instead, but then you 'd have a message bus to handle...
        
         | mbivert wrote:
         | Probably similar to the concurrent chat server from gopl[0]
         | then. The pattern is a bit surprising at first, still fairly
         | readable.
         | 
         | [0]:
         | https://github.com/adonovan/gopl.io/blob/master/ch8/chat/cha...
        
         | jerf wrote:
         | I get "chan chan"s pretty routinely. It is when I send a
         | message to some internal server/actor, and I also send it the
         | channel to reply on in the message. Usually it ends up being a
         | "chan struct { ... something with a chan in it ... }" rather
         | than a literal "chan chan" you can grep for, but the principle
         | is there. Very useful pattern; I consider it one of the basics
         | in Go.
         | 
         | "chan chan chan" never, as far as I know.
         | 
         | It would be overkill to replace everything like that with a
         | message bus. Very different performance characteristics for one
         | thing. Go's channels are very much an internal-to-the-OS-
         | process construct, if I'm talking internal to the OS process
         | there's no value in "upgrading" to a message bus.
        
       | theideaofcoffee wrote:
       | I'm going to post a low-effort, non-substantive comment because I
       | can.
       | 
       | That meme in the first few paragraphs made me actual lol as a
       | recovering C programmer.
       | 
       | That is all.
       | 
       | I love seeing odd contortions of languages like this, and C has
       | opportunity aplenty, interesting to see it in Go.
        
       | UniverseHacker wrote:
       | As a scientist that ends up working closely with actual
       | professional software engineers... lots of the stuff they do
       | looks like this do me, and I can't for the life of me make sense
       | of why you'd do it.
       | 
       | I have seen a single line of code passed through 4 "interface
       | functions" before it is called that call each other sequentially,
       | and are of course in separate files in separate folders.
       | 
       | It makes reading the code to figure out what it does exhausting,
       | and a few levels in you start to wonder if you're even looking at
       | the right area, and if it will ever get to the part where it
       | actually computes something.
        
         | zimpenfish wrote:
         | > I have seen a single line of code passed through 4 "interface
         | functions"
         | 
         | I once had to deal with a HTTP handler that called `Validate`
         | on interface A which called `Validate` on interface B which
         | called `Validate` on interface C which called `Validate` on
         | interface D which finally did the actual work. There was a lot
         | of profanity that month.
        
           | Cthulhu_ wrote:
           | I mean to a point that makes sense; you got your base data
           | types like idk, a bank account number which can be validated,
           | which is inside a bank account which can be validated, which
           | is in a customer which can be validated, etc etc. Visitor
           | pattern style, I believe?
        
             | binary132 wrote:
             | imagine allowing invalid values to exist
        
               | ozr wrote:
               | Sounds awful.
        
               | withinboredom wrote:
               | This is actually my preferred approach. If you want to
               | put a 4gb base64 as your phone number, go right on ahead;
               | best believe I will truncate it to a sensible length
               | before I store it, but sure. Who am I to question your
               | reality.
               | 
               | Sadly, people abuse shit like that to pass messages (like
               | naming Spotify playlists with messages to
               | loved/friends/colleagues while in jail) and maybe we have
               | to assert a tiny bit of sanity on the world.
        
               | steezeburger wrote:
               | How do prisoners have access to Spotify?
        
               | quectophoton wrote:
               | > imagine allowing invalid values to exist
               | 
               | Pretty common, for example when using databases as a
               | mostly dumb store with all the logic in application code,
               | and then a second application (or big refactor) appears
               | and they introduce a subtle bug that results in an
               | invalid `INSERT` (or whatever), and the database happily
               | accepts it instead of rejecting it.
        
               | agency wrote:
               | imagine enforcing invariants as part of the design of a
               | software system
        
               | nickpeterson wrote:
               | The nerve, taking good jobs away from young qa testers.
               | Wait till the IT Union hears of this!
        
             | zimpenfish wrote:
             | That would make sense but this was one piece of data being
             | validated against a lookup based off that data. The
             | previous devs just had a, uh, Unique(tm) style of
             | development. I swear they must have been on some kind of
             | "editor tab count" bonus scheme.
        
           | layer8 wrote:
           | This can happen when some of the interface operations have
           | added logic, but others (like Validate here) don't, so just
           | get delegated as-is.
           | 
           | One typical example is a tower of wrapped streams, where each
           | layer applies additional transformations to the streamed
           | data, but the Close operation is just passed across all
           | layers to the lowest one (which closes the underlying file or
           | whatever).
        
         | hyperhello wrote:
         | I enjoy reading about new languages on HN. It's weird because
         | the grammar is weird. It's like seeing start var ! oper
         | addition sub var !! mltplctn var % close close or something and
         | eventually you realize it means a + b*c. Why invent a new
         | language? Why not just write list<int> if you want a list of
         | integers or write Channel<string> or one of the idioms that
         | everyone knows? I don't know, but it can be fun to play with
         | communication and meaning and grammar, and maybe even more fun
         | if you get to be part of a group that gets paid for it.
        
           | recursive wrote:
           | What "everyone knows" depends on who everyone is. There was
           | syntax prior to templates with angle brackets.
        
         | eddd-ddde wrote:
         | The coder spectrum, scientists on one end, software engineers
         | on the other. Only balance can save us.
         | 
         | I have read code used in research papers. The theoretical math
         | usually goes beyond my comprehension, so I always dive into the
         | code to better understand the logic, only to find... it's way
         | worse... unintelligible.
         | 
         | At the end of the day we are used to what we do, and anything
         | different will be foreign to us.
        
           | dlisboa wrote:
           | A lot of the code written in math, physics or data analysis
           | settings is really written for the authors alone to
           | understand. They benefit from tens of pages of documentation
           | (papers) plus decades of previous experience from the
           | readers. None of which commercial software systems have.
        
         | mbivert wrote:
         | > I can't for the life of me make sense of why you'd do it.
         | 
         | Over-engineering is a common cause: simple solutions can be
         | deceitfully difficult to find. That being said, additional
         | indirection layers are usually justified by the overall
         | architecture, and -- assuming they're reasonable -- can't
         | always be appreciated locally.                 << I'm always
         | delighted by the light touch and stillness of early programming
         | languages. Not much text; a lot gets done. Old programs read
         | like quiet conversations between a well-spoken research worker
         | and a well-studied mechanical colleague, not as a debate with a
         | compiler. Who'd have guessed sophistication bought such noise?
         | >> (Dick Gabriel)
        
           | prisenco wrote:
           | _" I apologize for such a long letter - I didn't have time to
           | write a short one."_ - Mark Twain
        
             | kubanczyk wrote:
             | Twain? https://news.ycombinator.com/item?id=769624
        
               | prisenco wrote:
               | [?]
        
         | Misdicorl wrote:
         | Ignoring inexperience/incompetence as a reason (which,
         | admittedly, is a likely root cause) domain fuzziness is often a
         | good explanation here. If you aren't extremely familiar with a
         | domain and know the shape of solution you need a-priori all
         | those levels of indirection allow you to keep lots of work
         | "online" while (replacing, refactoring, experimenting) with a
         | particular layer. The intent should be to "find" the right
         | shape with all the indirection in place and then rewrite with a
         | single correct shape without all the indirection. Of course,
         | the rewrite never actually happens =)
        
         | withinboredom wrote:
         | As a software engineer, this is something I get onto with my
         | team. There is a such thing as too much abstraction and
         | indirection. Abstraction should serve a purpose; don't create
         | an interface until you have more than one concrete
         | implementation (or plan to within a PR or two). Premature
         | abstraction is a type of premature optimization, just in code
         | structure instead of execution.
        
         | tomcam wrote:
         | I have seen a single line of code passed through 4 "interface
         | functions" before it is called that call each other
         | sequentially, and are of course in separate files in separate
         | folders.
         | 
         | Trigger warning next time. I was trying to eat lunch
        
         | jdc0589 wrote:
         | this is the classic over abstraction problem so that you _can_
         | change things behind an interface at some point down the line
         | if you ever need to while being totally opaque to any consuming
         | code.
         | 
         | A lot of languages force you to start with this from day one,
         | unless you want to go refactor everything to use an interface
         | later on, so people just do it even when there will literally
         | never be a reason to (and for testability, _sometimes_ ).
         | 
         | The cool thing about Go is the interface system is inverted
         | kind of like duck-typing, so if you write purely idiomatic Go,
         | then the thing receiving an arg in a function call is the one
         | specifying the interface it must meet, rather than the
         | implementing code having to declare every interface that some
         | implementation meets.
         | 
         | People screw this up a lot though, especially if they came from
         | Java/C# backgrounds.
        
           | diarrhea wrote:
           | Do you have a concrete example by any chance?
        
             | devjab wrote:
             | Most C# education will teach you to always make an
             | interface for everything for some reason. Even in academia
             | they'll teach CS students to do this and well... it means
             | there is an entire industry of people who think that over-
             | engineering everything with needless abstractions is best
             | practice.
             | 
             | It is what it is though. At least it's fairly contained
             | within the C# community in my part of the world.
        
         | weitendorf wrote:
         | This is actually really bad practice and a very "over eager
         | junior engineer" way of writing software. You're not off base
         | at all that it seems excessive and confusing. It's the kind of
         | thing that seems technically complex and maybe even "elegant"
         | (in isolation, when you first write the "interesting" code) at
         | first but becomes a technical nightmare when used in real
         | software that has to grow around and with it. You're actually
         | more on point in worrying about the understandability and
         | debuggability this introduces.
         | 
         | I spent the better part of two years unfucking some Go software
         | that (among other things) misused channels. The problem with
         | channels is that you rarely actually need them, but _can_ use
         | them for a lot of different things without too much initial
         | difficulty.
         | 
         | I think a good litmus test for proper use of channels is if you
         | answer no to "could this be done with a direct function call
         | instead?" and "can I use a wait group or mutex instead", and
         | yes to (zooming out a bit to think about what kind of decisions
         | you previously made that led you to think about using channels)
         | "am I really benefitting from concurrency/parallelism enough to
         | justify the technical complexity of debugging concurrent code".
        
           | zachmu wrote:
           | when I was learning Go, I read a guide that told you to fire
           | off a goroutine to walk a tree and send the values back to
           | the main goroutine via a channel. I think about that "just an
           | example" guide a lot when I see bad channel code.
           | 
           | For me the biggest red flag is somebody using a channel as
           | part of an exported library function signature, either as a
           | param or a return value. Almost never the right call.
        
             | lanstin wrote:
             | I've used that pattern to write tools to e.g. re-encrypt
             | all whatever millions of objects in an S3 bucket, and
             | examine 400m files for jars that are or contain the log4j
             | vulnerable code. I had a large machine near the bucket/NFS
             | filer in question, and wanted to use all the CPUs. It
             | worked well for that purpose. The API is you provide
             | callbacks for each depth of the tree, and that callback was
             | given an array of channels and some current object to
             | examine; your CB would figure out if that object (could be
             | S3 path, object, version, directory, file, jar inside a
             | jar, whatever) met the criteria for whatever action at
             | hand, or if it generated more objects for the tree. I was
             | able to do stuff in like 8 hours when AWS support was
             | promising 10 days. And deleted the bad log4j jar few times
             | a day while we tracked down the repos/code still putting it
             | back on the NFS filer.
             | 
             | The library is called "go-treewalk" :) The data of course
             | never ends back in main, it's for doing things or maybe
             | printing out data, not doing more calcualation across the
             | tree.
        
           | SomeCallMeTim wrote:
           | I saw some code in a job I was just starting where they had
           | added several abstractions that I found...confusing.
           | 
           | After taking an extra long time to understand what the code
           | actually did, I realized that some junior engineer had been
           | using some design pattern they didn't really understand, and
           | that added zero actual value to the routine.
           | 
           | After deleting all of that code and refactoring it to use
           | completely different abstractions, everything was suddenly
           | much easier to read and to extend.
           | 
           | Design is a hard skill to learn, and junior developers
           | profoundly haven't learned that skill yet. But that's what we
           | need to teach them as senior engineers, right?
           | 
           | Not that I could teach the author of the code I changed,
           | since I think it was written by an intern that no longer
           | worked for the company. But you do what you can.
        
             | RandomWorker wrote:
             | There is also the fact it's much easier to write something
             | when you know where you are going. When you start you often
             | just make lots of items general in nature to improve later
             | on.
        
         | tejtm wrote:
         | I chalk it up to "complexity envy" If they have real problems
         | why can't we?
         | 
         | source me: CS by edu, science geek by choice.
        
         | yarg wrote:
         | It gets worse (this isn't too much of an exaggeration): https:/
         | /github.com/EnterpriseQualityCoding/FizzBuzzEnterpris....
         | 
         | Sometimes there's a scary lack of understanding and competency
         | where you'd expect to find it.
         | 
         | As an undergrad, I once spent about half an hour peer
         | programming with a computer science PhD - it was enlightening.
         | 
         | He didn't have the slightest understanding of software -
         | calling me out for things like not checking that the size of a
         | (standard library) data structure wasn't negative.
         | 
         | But other times these things are done for a reason; sometimes
         | it's actually sane and sometimes it's just a way to deal with
         | the lunacy of a codebase forged by the madmen who came before
         | you.
        
           | Dansvidania wrote:
           | the fizzbuzz repo hurt. It is oh so true though.
           | 
           | I often wonder if, unbeknownst to me, I am writing similarly
           | over-complicated software. People seem to be unable to tell,
           | so I doubt I can either. It makes me second-guess my code a
           | lot.
           | 
           | Is there any reliable/objective/quantitative way to evaluate
           | such a thing? The repo is a great example of what not to do,
           | but it's so extreme it's hardly useful in practice.. (nor it
           | should care to be..as a joke)
        
             | nickpeterson wrote:
             | Just delete some code and see if it still works. Bonus
             | points if you can delete abstractions.
        
             | devjab wrote:
             | If you have abstractions that you weren't forced to make
             | after exhausting every other option then you can improve
             | your code. If you begrudgingly add abstractions when you
             | can no longer convince yourself that there must be away to
             | avoid it, then you're likely doing well.
        
             | yarg wrote:
             | I think it's circumstantial - do your abstractions make it
             | easier or harder to implement and integrate the sort of
             | features that the application generally requires?
             | 
             | There's sometimes significant use in having some powerful
             | abstractions in place to allow for code reuse and
             | application customisation - but all too often people build
             | fortresses of functionality with no idea how or if it's
             | ever going to be used.
             | 
             | Foresight is useful here; if you can look at new features
             | and break them up into feature specific business logic and
             | generalisable application logic, then similar features can
             | be cleanly integrated with less work in the future.
             | 
             | Sometimes however, the level of customisability far exceeds
             | what is strictly necessary; the complexities involved no
             | longer help, but rather hinder - not only understanding,
             | but feature implementation and integration as well.
        
         | lowbloodsugar wrote:
         | I mean we are in "midwit meme" territory here. 4 levels of
         | indirection look fine to an idiot. A "midwit" hates them and
         | cleans them up. But a very seasoned engineer, thinking about
         | the entire system, will happily have 4 layers of indirection.
         | 
         | Like anyone using a HashSet in Rust is already doing four
         | layers of indirection: rusts HashSet is actually wrapping a
         | hashbrown::HashSet. That set is wrapping a HashTable and the
         | HashTable is wrapping an inner Table.
         | 
         | If you're having trouble comprehending such code then a good
         | IDE (or vim) that can navigate the code on a keypress should
         | help.
        
         | jayd16 wrote:
         | ITT people who have no context passing judgement on code
         | they've never seen described by someone who doesn't know what
         | they're describing.
        
         | ljm wrote:
         | On the contrary, and I do agree that software engineers take
         | the abstraction too far when they don't know better, I don't
         | hold the code produced by people who aren't software engineers
         | by profession in particularly high esteem either.
         | 
         | You're looking at two extremes: the codebase that is spread out
         | too much with too much abstraction, and the codebase with zero
         | abstraction that is basically a means to an end. In both cases
         | they are difficult to work with.
         | 
         | I've certainly dealt with enough python, JS and PHP scripts
         | that are basically written with the mindset of 'fuck this, just
         | give me what I want', whereas people working in the code day to
         | day need the abstractions to facilitate collaboration and
         | resilience.
        
           | computerdork wrote:
           | Agree with this. Abstraction and design patterns _when used
           | in a well-thought out manner_ should make _large or complex_
           | codebases easier to work with.
           | 
           | And like you, have experienced code bases that tried to throw
           | every design pattern in the book at you, even for a
           | relatively simple application, and made it a pain to work
           | with.
           | 
           | But have also seen them used carefully, in a standard
           | company-wide usage that made all the code easier to
           | understand - worked on a high-volume website with a large
           | codebase, where they had micro-services that all used common
           | 3-tier architecture, security-services, tooling... Really-
           | well thought-out and you could work on any one of their ~100
           | microservices and already have a good understanding of its
           | design, how to build and debug it, how its security worked,
           | it's caching...
           | 
           | Yeah, agreed, its how these techniques are used that
           | determine if they are useful or just add complexity.
        
         | LudwigNagasena wrote:
         | It happens so that we don't have "a single 15,000 line file
         | that had been worked on for a decade". We don't have the luxury
         | of asking the GitHub team and John Carmack to fix our code when
         | we are forced to show it to the stakeholders.
        
         | stroupwaffle wrote:
         | I think something people forget is that computer programming is
         | a craft which must be honed.
         | 
         | A lot of people are introduced to it because computers are such
         | an important part of every discipline, but unfortunately the
         | wealth of mistakes maintaining many a code base occur from
         | those who, quite honestly, simply lack experience.
         | 
         | In the authors case, they don't explain the simple
         | understanding that every pointer is simply adding a dimension
         | to the data.
         | 
         | int *data; // one dimensional vector
         | 
         | int **data; // two dimensional matrix
         | 
         | int ***data; // three dimensional matrix
         | 
         | Which is one way to interpret things. The problem is that when
         | folks learn computer programming using Python (or another high
         | level language), it's like using power tools bought from the
         | hardware store compared to some ancient, Japanese wood working
         | technique. The latter takes time to understand and perfect.
         | 
         | Ten thousand hours, so I've heard ;)
        
           | THBC wrote:
           | "every pointer is simply adding a dimension to the data."
           | 
           | No, it's not. The concept of having a pointer to a pointer
           | has nothing to do with the concept of dimensionality.
           | 
           | You must be thinking of lists of lists (of lists...), which
           | can be implemented using pointers. The dimensionality,
           | however, comes from the structure of the list, not from the
           | pointers.
        
         | 7thaccount wrote:
         | This perfectly summarizes a lot of production code I've seen.
         | We once replaced like 20 Java files with like a single page of
         | easy to understand and "to the point" code.
        
         | astrange wrote:
         | This is a popular pattern in apps or other "frameworky" code,
         | especially in C++.
         | 
         | I can think of at least two open source C++ apps I used where
         | every time I checked in the IRC channel, the main dev wasn't
         | talking about new app features, but about how to adopt even
         | newer cooler C++ abstractions in the existing code. With the
         | general result that if you want to know how some algorithm
         | backing a feature works, you can't find it.
        
         | sweeter wrote:
         | A lot of people suffer from the tendency to do a lot of pre-
         | abstraction. They also split everything up way to much without
         | needing to
        
         | stonemetal12 wrote:
         | It is the Law of Demeter. It sounds like a good idea, until you
         | get the chain of wrappers.
         | 
         | https://en.wikipedia.org/wiki/Law_of_Demeter
        
         | devjab wrote:
         | It's called Clean Architecture, Clean Code or SOLID and it's
         | extremely stupid. It's widely used because the man behind it,
         | and a lot of other grifters, are extremely good at selling
         | their bullshit. You also have crazy things like the Agile
         | Manifesto to thank "Uncle Bob" for.
         | 
         | What is the most hilarious, however, is that these things are
         | sold by people who sometimes haven't coded professionally since
         | 15-20 years before Python was even invented.
         | 
         | Anyway, if you want to fuck with them ask them how they avoid
         | L1/L2/L3 chance misses with all that code separation. They
         | obviously don't but you're very likely to get a puzzled look as
         | nobody ever taught them how a computer actually works.
        
       | spookie wrote:
       | > There's an old programming joke from back in the days when C
       | and its derivatives dominated the landscape.
       | 
       | We are still living in those days.
        
       | fricze wrote:
       | Fun stuff
        
       | PhilipRoman wrote:
       | The closest thing to this I've seen in actual code is
       | BlockingQueue<BlockingQueue<List<T>>>, which was used to wait for
       | an element from multiple blocking queues simultaneously, where
       | each element is a list of messages.
        
       | tedunangst wrote:
       | Would benefit from an explanation of what's wrong with the chan
       | chan pattern, which is fairly common. Are you similarly
       | overwhelmed by the fact http.Response has a pointer to a
       | http.Request?
        
       | caerwy wrote:
       | Here's a counterpoint blog using a chan that sends a chan in
       | order to implement a dynamic dispatch mechanism. This is in Limbo
       | not Go. But same concepts. Maybe the complexity proves the point.
       | https://ipn.caerwyn.com/2007/07/lab-78-dynamic-dispatch.html...
        
         | jhoechtl wrote:
         | For those wondering, Limbo is a predecessor of Go hand used in
         | the Inferno operating system, a descendant of plan9.
        
       | malkosta wrote:
       | It reminds me of Joe Armstrong's "My favorite Erlang Program":
       | https://joearms.github.io/published/2013-11-21-My-favorite-e...
        
       | zemo wrote:
       | channel of channel is a normal pattern, although it more commonly
       | looks like a channel of struct vals, and the struct type has a
       | field that's a channel. it's lets you send requests that are
       | completed, whose values can be awaited on out of order, to avoid
       | head of line blocking. something like `type request struct {
       | params, reply chan response }`, where you send those requests on
       | a channel, they get dispatched to some worker, and then the
       | worker does the work and puts the result on the reply chan. But
       | two is as high as I've ever found useful, I don't think I've ever
       | seen a use case for a chan chan chan.
        
       | david2ndaccount wrote:
       | Somewhat a reference to https://wiki.c2.com/?ThreeStarProgrammer
        
       | twp wrote:
       | Sending channels over channels (i.e. chan chan) is a common
       | pattern for publish-subscribe systems, e.g.
       | 
       | https://github.com/twpayne/go-pubsub/blob/master/pubsub.go
        
       ___________________________________________________________________
       (page generated 2024-08-28 23:00 UTC)