[HN Gopher] Go Naming Conventions: A Practical Guide
___________________________________________________________________
Go Naming Conventions: A Practical Guide
Author : yurivish
Score : 92 points
Date : 2026-03-24 20:49 UTC (4 days ago)
(HTM) web link (www.alexedwards.net)
(TXT) w3m dump (www.alexedwards.net)
| red_admiral wrote:
| Another of mine: don't name a struct after an interface method
| that it's supposed to implement. If you have a package
| linearalgebra, then making a custom error type
| linearalgebra.LinearAlgebraError is too "chatty" but
| linearalgebra.Error will cause you pain if it implements "Error
| string()", as it probably should, and you decide to make a
| linearalgebra.MatrixSingularError that wraps a
| linearalgebra.Error to "inherit" its methods.
|
| In the end, it ended up called linearalgebra.Err .
|
| P.S Alex Edwards' "let's go" and "let's go further" are great
| books to get someone up to date with golang, just keep an eye on
| features that are newer than the book(s).
| DauntingPear7 wrote:
| The booms receive regular updates. I got an email about an
| update to Let's Go Further on 3/12 for Go 1.26
| nasretdinov wrote:
| I like this article -- short, accurate (which is somehow not a
| given these days...) and useful, just like Go language itself.
| qezz wrote:
| I was surprised to see literally invalid names in the "bad"
| section, e.g. "Cannot start with a digit". Why even presenting
| this if it's rejected by the compiler?
| 0x696C6961 wrote:
| The example with the dash in it confused me as well.
| alexfoo wrote:
| I wondered if you could sneak in some unicode digit but it
| seems to reject those too: $ go run z.go
| # command-line-arguments ./z.go:6:2: identifier cannot
| begin with digit U+0661 '1' ./z.go:7:27: identifier
| cannot begin with digit U+0661 '1'
|
| (I tried a few of them but not all.)
| pphysch wrote:
| "Chat, generate me a table of bad Golang making practices"
| alex_edwards wrote:
| Author here. The answer is because I mentioned it as one of the
| bullet pointed hard-rules, and I wanted to include an example
| to illustrate it.
| laserlight wrote:
| > we use the identifier p to represent a value in the people
| slice -- the range block is so small and tight that using a
| single letter name is clear enough.
|
| No, it's not. When you see `p.Age`, you have to go back and find
| the body of the loop, see what it operates on and decipher what p
| stands for. When you see `person.Age`, you understand it. I've
| never understood what is gained by using `p` instead of spelling
| it out as `person`.
| piekvorst wrote:
| Long lines make reading rhythm uncomfortable (long jumps,
| prolonged eye movements) and long words make the text too dense
| and slow down the reading. It's bad typography.
|
| I have heard an idea that a good variable should be understood
| by just reading its name, out of context. That would make
| "ProductIndex" superior to "i", which doesn't add any clarity.
| monooso wrote:
| I would argue that ambiguity and uncertainty slow down
| reading, and more importantly _comprehension_ , far more than
| a few additional characters.
| lokar wrote:
| It depends on whom you are optimizing for. Someone who
| knows the language, but not this system/codebase, or
| someone who works in this area often?
| Joker_vD wrote:
| A good variable name is the one that is understood by reading
| it _in context_ , which is why you don't have names like
| "current_person" or "CurrentIndexOfProductBeingUpdated".
| diath wrote:
| Something like "AnIteratorObjectWithPersonPointer" would be a
| long word, "person" is absolutely not. If a 6 letter
| identifier causes you that much trouble with code being too
| verbose, then it's likely a screen resolution/density/font
| issue, not a naming issue.
|
| > That would make "ProductIndex" superior to "i", which
| doesn't add any clarity.
|
| And then you introduce extra two levels of nested loops and
| suddenly "i", "j", and "k" don't make any sense on their own,
| but "ProductIndex", "BatchIndex" and "SeriesIndex" do.
| bborud wrote:
| _And then you introduce extra two levels of nested loops
| and suddenly "i", "j", and "k" don't make any sense on
| their own, but "ProductIndex", "BatchIndex" and
| "SeriesIndex" do._
|
| ijk for indices in loops _are_ actually clearer than random
| names in nested loops precisely because it is a *very
| common convention* and because they occur in a defined
| order. So you always know that "j" is the second nesting
| level, for instance. Which relates to the visual layout of
| the code.
|
| You may not have known of this convention or you are unable
| to apply "the principle of least astonishment". A set of
| random names for indices is less useful because it
| communicates less and takes longer to comprehend.
|
| Just like most humans do not read text one letter at a
| time, many programmers also do not read code as prose. They
| scan it rapidly looking at shapes and familiar structures.
| "ProductIndex", "BatchIndex" and "SeriesIndex" do not lend
| themselves to scanning, so you force people who need to
| understand the code to slow down to the speed of someone
| who reads code like they'd read prose. That is a bit
| amateurish.
| Groxx wrote:
| I partly agree, and partly don't. When ijk really is
| unambiguous and the order is common (say you're
| implementing a well-known algorithm) I totally agree, the
| convention aids understanding.
|
| But nesting order often doesn't control critical
| semantics. Personally, it has much more often implied a
| heuristic about the lengths or types (map, array, linked
| list) of the collections (i.e. mild tuning for
| performance but not critical), and it could be done in
| any order with different surrounding code. There the
| letters are meaningless, or possibly worse because you
| can't expect that similar code elsewhere does things in
| the same nesting order.
|
| This likely depends heavily on your field though.
| bborud wrote:
| I think I know what you mean. Let's assume a nesting
| structure like this:
|
| Company -> Employee -> Device
|
| That is, a company has a number of employees that have a
| number of devices, and you may want to traverse all cars.
| If you are not interested in where in the
| list/array/slice a given employee is, or a given device
| is, the index is essentually a throwaway variable. You
| just need it to address an entity. You're really
| interested in the Person structure -- not its position in
| a slice. So you'd assign it to a locally scoped variable
| (pointer or otherwise).
|
| In Go you'd probably say something like:
|
| for _, company := range companies { for _, employee :=
| range company.Employees { for _, device := range
| employee.Devices // ..do stuff } }
|
| ignoring the indices completely and going for the thing
| you want (the entity, not its index).
|
| Of course, there are places where you do care about the
| indices (since you might want to do arithmetic on them).
| For instance if you are doing image processing or work on
| dense tensors. Then using the convention borrowed from
| math tends to be not only convenient, but perhaps even
| expected.
| zahlman wrote:
| > ijk for indices in loops are actually clearer than
| random names in nested loops precisely because it is a
| _very common convention_ and because they occur in a
| defined order. So you always know that "j" is the second
| nesting level, for instance. Which relates to the visual
| layout of the code.
|
| In problem domains that emphasize multidimensional
| arrays, yes.
|
| More often nowadays I would see `i` and think "an element
| of some sequence whose name starts with i". (I tend to
| use `k` and `v` to iterate keys and values of
| dictionaries, but spell `item` in full. I couldn't tell
| you why.)
| bborud wrote:
| I think this may be related to how people read code. You have
| people who scan shapes, and then you have people who _read_
| code almost like prose.
|
| I scan shapes. For me, working with people who _read_ code is
| painful because their code tends to to have less clear
| "shapes" (more noise) and reads like more like a verbal
| description.
|
| For instance, one thing I've noticed is the preference for
| "else if" rather than switch structures. Because they reason
| in terms of words. And convoluted logic that almost makes
| sense when you read it out loud, but not when you glance at
| it.
|
| This is also where I tend to see unnecessarily verbose code
| like
|
| func isZero(a int) bool { if a == 0 { return true } else {
| retur false } }
|
| strictly speaking not wrong, but many times slower to absorb.
| (I think most developers screech to a halt and their brain
| goes "is there something funny going on in the logic here
| that would necessitate this?")
|
| I deliberately chose to learn "scanning shapes" as the main
| way to orient myself because my first mentor showed me how
| you could navigate code much faster that way. (I'd see him
| rapidly skip around in source files and got curious how he
| would read that fast. Turns out he didn't. He just knew what
| shape the code he was looking for would be).
| ConcurrentCrab wrote:
| I think this is pretty insightful, and I might add this as
| another reason LLM code looks so revolting. It's basically
| writing prose in a different language, which make sense -
| it's a _language_ model, it has no structural comprehension
| to speak of.
|
| Whereas I write code (and expect good code to be written)
| such that most information is represented structurally: in
| types, truth tables, shape of interfaces and control flow,
| etc.
| zer00eyz wrote:
| > I think this may be related to how people read code. You
| have people who scan shapes, and then you have people who
| read code almost like prose.
|
| I think this is an astute observation.
|
| I think there is another category of "reading" that
| happens, is what you're reading for "interaction" or
| "isolation".
|
| Sure c.method is a scalable shape but if your system deals
| with Cats, Camels, Cars, and Crabs that same c.method when
| dealing with an abstract api call divorced from the
| underlying representation might not be as helpful.
|
| I would think that we would have more and better research
| on this, but the only paper I could find was this:
| https://arxiv.org/pdf/2110.00785 its a meta analysis of 57
| other papers, a decent primer but nothing ground breaking
| here.
|
| > I scan shapes. ... verbal description.
|
| I would be curious if you frequently use a debugger?
| Because I tend to find the latter style much more useful
| (descriptive) in that context.
| bborud wrote:
| _dealing with an abstract api call divorced from the
| underlying representation_
|
| I don't understand what you mean. Could you give me an
| example?
|
| _I would be curious if you frequently use a debugger?_
|
| I practically never use a debugger.
| functional_dev wrote:
| The shape argument works well in small packages but it
| starts to fail once you have multiple domain models
| starting with the same letter
| bborud wrote:
| I wasn't talking about just symbols but entire paragraphs
| of code as well.
| jayd16 wrote:
| > That would make "ProductIndex" superior to "i", which
| doesn't add any clarity.
|
| Adds a ton of clarity, especially if you have a nested loop.
| Groxx wrote:
| and god help you if those loops are pairing People and
| Products.
|
| though now that I write that out... it would be really nice
| if you could optionally type iteration vars so they
| couldn't be used on other collections / as plain integers.
| I haven't seen any languages that do that though, aside
| from it being difficult to do by accident in proof-oriented
| languages.
| jayd16 wrote:
| You usually don't need an index that can't be used
| elsewhere. If you don't then you can abstract it away
| entirely and use an iterator or foreach features.
| Groxx wrote:
| Depends on the language. Doing that is a huge pain in Go
| (until fairly recently, and it's still quite abnormal or
| closure-heavy), so the vast majority of code there does
| manual index-pairing instead of e.g. a zip iterator when
| going through two paired arrays.
| saghm wrote:
| I've felt strongly for a while now that abbreviations should be
| "lossless" in order to be useful; it should be unambiguous now
| get back to the unabbreviated form. For whatever reason, people
| seem to love trying to optimize for character count with
| abbreviations that actually make things more confusing (like
| `res` in a context where it might mean either "response" or
| "result).
|
| I just don't get the obsession with terseness when we have
| modern tooling. I don't type particularly fast, but
| autocomplete makes it pretty quick for me to type out even
| longer names, and any decent formatter will split up long lines
| automatically in a way that's usually sane (and in my
| experience, the times when it's annoying are usually due to
| something like a function with way too many arguments or people
| not wanting to put a subexpression in a separate variable
| because I guess they don't know that the compiler will just
| inline it) rather than the names being a few characters too
| many.
|
| Meanwhile, pretty much everywhere I've worked has had at least
| some concerns about code reviews either already being or
| potentially becoming a burden on the team due to the amount of
| time and effort it takes to read through someone else's code. I
| feel like more emphasis on making code readable rather than
| just functional and quick to write would be a sensible thing to
| consider, but somehow it never seems to be part of the
| discussion.
| ickyforce wrote:
| > and any decent formatter will split up long lines
|
| Any decent editor can wrap long lines on demand. But it's
| even better not to have to do either of those if not
| necessary.
|
| > I've felt strongly for a while now that abbreviations
| should be "lossless" in order to be useful
|
| This is how we got lpszClassName. The world moved away from
| hungarian notation and even away from defining types for
| variables in some contexts (auto in cpp, := in Go, var in
| Java). Often it just adds noise and makes it harder to
| understand the code at a glance, not easier.
| saghm wrote:
| I'd argue there's a stark difference between abbreviating
| words and adding extra ones. `p` as a shorthand for
| `person` is silly to me, but that doesn't mean that
| `personObject` would also be silly to me. I fundamentally
| don't agree with the premise that it's possible to be too
| verbose means that terseness is its own goal; the goal
| should be clarity, and I don't think that lossy
| abbreviations actually help with that except when someone
| already knows what the code is doing, in which case they
| don't need to read it in the first place.
| cookiengineer wrote:
| I agree with this comment so much.
|
| Tried to use the new slices package or comparables? It's a
| nightmare to debug, for no reason whatsoever. If they would've
| used interface names like Slice or Comparable or Stringable or
| something, it would have been so much easier.
|
| The naming conventions are something that really fucks up my
| coding workflow, and it can be avoided 100% of the time if they
| would stop with those stupid variable names. I am not a
| machine, and there is no reason to make code intentionally
| unreadable.
| jplona wrote:
| I think this is clearly a matter of preference. Shorter
| variable (or rather, appropriately short variables for the
| context) for me are easier to recognize and disambiguate. They
| take up fewer tokens, so to speak. When I see `p.Age` I don't
| have to go back and look at the beginning of the loop because I
| just read that line and I remember it.
| bborud wrote:
| If your loops are so long you can't fit them on one screenfull
| you have much more fundamental issues.
| zer00eyz wrote:
| You arent wrong, but it is not an absolute.
|
| Furniture maker, house framer, finish carpenter are all under
| the category of woodworking, but these jobs are not the same.
| Years of honed skill in tool use makes working in the other
| categories possible, but quality and productivity will
| suffer.
|
| Does working in JS, on the front end teach you how to code,
| it sure does. So does working in an embedded system. But
| these jobs might be further apart than any of the ones I
| highlighted in the previous category.
|
| There are plenty of combinations of systems and languages
| where your rule about a screen just isn't going to apply.
| There are plenty of problems that make scenarios where "ugly
| loops" are a reality.
| bborud wrote:
| I didn't say it was an absolute. But once a scope grows to
| the point where you have to _navigate_ to absorb a function
| or a loop, both readability and complexity tends to worsen.
| As does your mental processing time. Especially for people
| who "scan" code rapidly rather than reading it.
|
| The slower "readers" will probably not mind as much.
|
| This is why things like function size is usually part of
| coding standards at a company or on a project. (Look at
| Google, Linux etc)
| laserlight wrote:
| person.Age is easier to understand than p.Age regardless of
| the loop size.
| esrauch wrote:
| This is something that it seems some Go people just don't
| "believe" in my experience, that for some people that letter in
| that context is not mentally populated immediately.
|
| It's honestly a shame because it seems like Go is a good
| language but with such extremely opinionated style that is so
| unpleasant (not just single letters but other things stuff
| about tests aren't supposed to ever have helpers or test
| frameworks) feels aggressively bad enough to basically ruin the
| language for me.
| gadflyinyoureye wrote:
| I think the community is split on such things. I ended up
| telling the side that gets persnickety about short names and
| only using if statements in tests to pound sand. I use things
| that make my life easier. I now care less about some rude
| rando on r/golang than I did five years ago.
| zahlman wrote:
| >you have to go back and find the body of the loop
|
| If the loop is long enough that you don't naturally remember
| how it was introduced, that's the problem. In the given
| example, the use of `p.Age` is literally on the next line of
| code after ` for _, p := range people`.
|
| > I've never understood what is gained by using `p` instead of
| spelling it out as `person`.
|
| Wisdom I received from, IIRC, the Perl documentation decades
| ago: tightly-scoped names should be shorter and less attention-
| grabbing than more broadly-scoped ones, because you should
| really notice when you're using a global, and you don't want to
| suffer attention fatigue. (I'm sure the exact wording was quite
| different.)
|
| Also because it's better for information density. As I recall,
| Larry Wall also had the idea that more commonly used language
| keywords should be shorter than rare ones. Good code uses the
| locals much more often than globals, so you shouldn't need to
| expend the same amount of effort on them. (The limiting case of
| this is functional programming idioms where you can eliminate
| the variable name _completely_ , in cases like (Python
| examples) `lambda x: int(x)` -> `int`, or `(foo(x) for x in
| xs)` -> `map(foo, xs)`.
| laserlight wrote:
| > remember how it was introduced
|
| The problem is that many times I have not read the definition
| to remember. Debugger puts me into a context where I have to
| figure out what `p` stands for. I go up the call stack and
| now there's `s` to be deciphered. Worse is the reuse of `p`
| for person, product, part, etc. in different contexts.
|
| Debugging is not the only problem. Code is read rarely
| linearly. Many times I browse different uses of a function,
| or see how a data structure is modified in different
| contexts. Looking up single letter variables is just a waste
| of time.
| gopher_space wrote:
| > Debugger puts me into a context where I have to figure
| out what `p` stands for.
|
| `p` stands for "the process in question".
|
| I like to think of single-character vars as idea or topic
| headers that track the single thing I'm currently up to.
| I'm rarely working with more than one at a time, frequently
| it's the _only_ variable, and there are contexts where I
| wouldn 't use them at all.
|
| IMHO if you're in a situation where `p` isn't obvious to
| you, "something has gone wrong".
| hackthemack wrote:
| But what if your codebase has to interact with leads,
| customers, and another 3rd party system called Metrica with
| leads, customers?
|
| When you write a loop, do you now name the variable
|
| OurPerson.Age MetricaPerson.Age
|
| ?
|
| What if, 3 years from now, you include another 3rd party vendor
| into the system and have to write code against that data and in
| the data they name their stuff OurPerson.Age?
|
| Not saying you are wrong at all. Just naming things is hard and
| context dependent. I think that is why it is endlessly argued.
| giancarlostoro wrote:
| This comes from some dated idea for stuff like C where "its
| okay to use shorthands for variables" but is it really? The
| only place I allow it is simple iterators, but now we have
| enhanced loops where even this is unnecessary. We don't need to
| save on pixel screen space like if its still the 90s. Even with
| a simple 1080p monitor you can fit plenty of words and code.
|
| Give your variables, functions, classes meaningful descriptive
| names that make sense to humans.
| HumblyTossed wrote:
| People have been arguing this stuff since the dawn of (computer)
| time. I don't get it. I've been at it so long now, IDGAF what or
| how you name something. Short names in loops? Long? I don't care.
| I really don't. Just be consistent in what you decide and I can
| read it.
|
| All this arguing... FFS, go DO something with your time!
|
| EDIT: Oh, yeah, as for the article itself, it's a good article.
| But again, just be consistent in what you choose.
| menno-dot-ai wrote:
| This is great! My team started using Go last year so I fed this
| article to set off a fleet of agents on our Go codebase and
| generate a report out w/ code samples based on it. Ended up with
| a pretty good little document to present on Monday :)
| graynk wrote:
| Why do you need AI agents for this?
|
| You either catch and enforce it with a linter (e.g.
| https://golangci-lint.run/docs/linters/configuration/#revive)
| (in which case you don't need AI to tell you the current state,
| you just add the same config to all projects) or you don't
| enforce it (because everyone will forget unless it's automated)
| voidfunc wrote:
| Using a linter doesn't get you noticed by leadership and net
| you a promo.
| graynk wrote:
| Sure it does, just say you "established org-wide coding
| standards and drove adoption of automated linting tooling,
| reducing review friction and enforcing style consistency at
| scale" in your assessment.
| ManuelKiessling wrote:
| Better yet, post about it on LinkedIn and explain what it
| taught you about marriage proposals!
| Mawr wrote:
| > Words that are acronyms or initialisms (like API, URL or HTTP)
| should use a consistent case within the identifier. So, for
| example, apiKey or APIKey are conventional, but ApiKey is not.
| This rule also applies to ID when it is used as shorthand for the
| words "identity" or "identifier" -- so that means write userID
| rather than userId.
|
| Outdated.
|
| Over time, it's become clear that breaking the camelCase
| convention in this manner is inappropriate:
|
| - The inconsistency with the convention is jarring, consider
| `APIURL` - is that a variable (ApiUrl) or a constant (APIURL)?
|
| - The inconsistency introduces doubt on how to write any given
| identifier - which is why the above advice even needs to exist
|
| - The whole point of the convention is to make separate parts of
| the name visually separate, consider
| `someAPIURLHTMLJSONExtension` vs `someApiUrlHtmlJsonExtension`
|
| - It's hard to keep this consistent - we may reasonably disagree
| whether `ID` should be capitalized or not, meaning you may just
| as well find both `ID` and `id` across codebases. This erases the
| benefits of capitalization altogether.
|
| The benefits of keeping these acronyms capitalized are dubious
| and don't outweigh the downsides.
|
| And of course, the real solution is to use the one correct naming
| convention - `snake_case`. Then you can capitalize all you want
| without trouble - `some_API_URL_HTML_JSON_extension`.
| syngrog66 wrote:
| one rando set of opinions. stopped reading fast
| wpollock wrote:
| Allowing Unicode characters, then stating best practice is to
| stick with ASCII, is weird. (Go is not alone in this practice.)
| Unicode identifiers have a host of issues, such as some
| characters have no case distinction, some have title-case but not
| uppercase, some "capitalize" the last letter in a word and not
| the first (Hebrew has five "final form" letters), etc. Does Go
| specify the meaning (exported or not) if a letter has no case, or
| if an identifier starts with a zero-width joiner character?
| Without a huge list of detailled rules, too much is left to the
| implementation to decide. I prefer to stick with ASCII for names.
|
| Fun fact: When printing with movable type began, printers would
| travel with large "type cases" containing the small wood or metal
| blocks with glyphs on them. The ones the used frequently were
| kept in the lower half of the case, in easy reach. That's where
| the terms "lowercase" and "uppercase" come from.
___________________________________________________________________
(page generated 2026-03-28 23:01 UTC)