1. 16

I’m asking this specifically here because I’m looking for an answer from experienced devs (especially, experience in languages that are not Go).

Every single book I’ve read about programming best practices has the advice “use concise but descriptive variable names, avoid abbreviations”. Across every job I’ve had, I’ve never seen this contested (maybe poorly applied, but everyone seemed pretty OK with the idea). IMO a very basic interpretation of this is “if a variable contains a car, name it car unless you have a good reason not to”.

Enter Go, where I can’t name my variable car because that’s the name of the package defining the Car type. What’s your solution to this? I can see a bunch, but none of them are satisfactory:

  • Name the variable car_ (IMO the least bad, but I’ve just never seen it)
  • Name the package carPkg (cumbersome, given how often we repeat package names shorter is better)
  • Name the variable c. (then you directly contradict the above rule)

I’m very aware that the third solution is the most popular one, but I find functions littered with one-letter variable names so hard to read. Is that really the way? Am I simply in denial and will eventually get used to it?

Note that this is not a rant but a genuine question, I actually quite like that fact that you have to prefix every external name with a package name; I just wish package names did not conflict with variable names.

    1. 8

      This is typically a sign of poor package naming. The one package I run into this with routinely is net/url. If you look in net/http you can see they alias it to urlpkg to fix the problem. For a contrived vehicle example, it should probably be vehicles.Car, so there’s not much chance of overlap.

      1. 2

        What would you have named net/url? urls?

        1. 1

          That’s a good question. Maybe uri because what the hell is a URI anyway? ;-)

          I think url, err := netloc.Parse(s) would be okay.

      2. 1

        Makes sense, thanks! Unrelated to my original question, but aren’t package names supposed to be singular? This is not an attack on your answer, I’m just new to Go and think I’ve read that multiple times.

        1. 2

          There are exceptions in the standard library like strings and bytes. I think the reason for those are because string and byte are types already. “slices” and “maps” coming in Go 1.21 with similar name reasons.

    2. 8

      This issue pops up now and then, and usually Dave Cheney’s tip about naming packages after what they provide, not what they contain, is what I recommend going for. It feels a little too easy to make everything in Go a package, so try to build bigger packages if you see this as a frequent problem. (In general, grouping things together based upon what assumptions they rely on is the best, though identifying those is not always obvious.)

      It won’t always work though. If you have an email package, it will still contain an email.Email even though it also contains email.Server and, say email.Address.

      1. 2

        Thanks for the pointer to Dave Cheney, I’ve skimmed through “Practical Go” and I’ve already learned a lot!

    3. 7

      As suggested by other commenters, a type Car struct should probably not exist in a package car, but rather in a package vehicles or similar. Go packages describe categories, or groups, of things, rather than individual/specific things themselves. A package should be relatively large. The existence of something like car.Car or package.Package is usually a red flag that should motivate a refactor.

      If you definitely have to instantiate a value of type car.Car then definitely do not call that variable car_, and almost always do not alias the package car import to something else. A variable named c is OK in many situations, otherwise I tend to prefer something like carVal or cval or etc.

    4. 4

      The problem is silly and just one of many ridiculous non sense warts of the language that gets a pass “because Google”.

      But what’s with people breaking down applications in a million packages? Where does this need of mapping packages to essentially database tables (for lack of a less ambiguous reference point) comes from? What do people gain from it?

      I feel tempted to leave a brief dismissive solution like: just do whatever you prefer and works best for you. But the question is well outlined, complete with a good example. I would probably go with the first one, but as others pointed, this does feel like a smell for too granular packages.

      Tangent: whenever the phrase “best practices” gets used, my alarm goes off. Best practices according to whom? And for what reason? If you know the reason, then you can directly assess if you should apply it or not.

      1. 2

        Best practices according to whom?

        In my case, I’d say the authors of the books :) But I totally get the feeling, “best practice” or “idiomatic” are often used to cover a lack of concrete arguments, and it can be very frustrating.

      2. 1

        A package is the unit of encapsulation (data hiding) in Go, which I think is the source of the dilemma. This results in people attempting to model packages as they would encapsulated objects.

        1. 1

          Packages define API (declaration) boundaries, hopefully no package is maintaining data (state)! 😉

    5. 3

      Use short functions so local names are relatively insignificant. Also rename the import if you really wish to use the package name as a variable.

      import f “fmt” … fmt := 2 f.Printf(“Hey %v\n”, fmt)

    6. 3

      For most stuff, even though it doesn’t go into your concern I follow this: https://go.dev/doc/effective_go#names

      In Go it’s usually that people go with “the more local a variable is the shorter it can be”. For things like net/url that is mentioned a lot in this context I usually go with “what kind of URL is this?” For example imageURL, apiURL, etc. It’s similar in other languages. Many standard libraries have URL, file, path, etc. If you use a car in a five line comment body it’s fine to name it c, of course one needs the self-discipline to rename things, should the context change, but that’s a good idea anyways.

      If you go for longer, more specific, context-describing names you also don’t into situations where you have car and otherCar or similar confusing things. I imagine carToPark, carToRepair, crashedCar, driverlessCar, etc. If there is only one car, and the method isn’t giant single letter is actually pretty fine. Say you have a method to query a car from the database, or more general setters, getters things are clear. And if the logic is bigger it makes a lot of sense to define it anyways, because of potential for misunderstanding which car we are referring to. People sometimes overlook this can even happen when there is only one car, to precise how it is used. Maybe even indicate whether something is cloned or referenced.

    7. 3

      In go, single character variable names are idiomatic, especially when the scope is small.

      1. 3

        I’ve read that, and I try to follow idioms as much as possible, but I’m having a really hard time with this one because I really can’t find any justification for it (i.e. what makes Go so special that decades old advice is suddenly irrelevant?). I can see it if “small scope” means 1 or 2 lines, but beyond this I’m having a really hard time keeping track of them.

        But even if one accepts the idiom, how do you solve my issue when the scope is not small?

        1. 4

          http://doc.cat-v.org/bell_labs/pikestyle

          Ah, variable names. Length is not a virtue in a name; clarity of expression is. A global variable rarely used may deserve a long name, maxphysaddr say. An array index used on every line of a loop needn’t be named any more elaborately than i. Saying index or elementnumber is more to type (or calls upon your text editor) and obscures the details of the computation. When the variable names are huge, it’s harder to see what’s going on. This is partly a typographic issue; consider

          1. 8

            This is such a dishonest argument, though, because it presents a false dilemma between very short names and “huge” names.

            In Python I routinely write loops like:

            for entry in BlogEntry.query():
                # do thing with entry...
            

            This is more readable than a single-letter name would be, and doesn’t fall into any “huge name” trap I’m aware of.

            It’s not the 1970s anymore. Our computers have the disk and memory space to let us use names like “format” instead of “fmt”. And so we should, and should leave the 1970s conventions in the dustbin of history where they belong.

            1. 5

              I’m not sure how this is a dishonest argument.

              Your Python code is equally well expressed if entry is named e, as far as I can see. It is not obvious that entry “is more readable than a single-letter name” of e, at least without more context.

              Preferring fmt over format is not a decision made from 1970s-era technical constraints. It’s possible to prefer the shorter form over the longer form, even if our servers have bajillions more available resource-bytes.

              1. 4

                I’m not sure how this is a dishonest argument.

                Because, as I said, it presents a false dilemma. If the only possible choices were single-character names, or “huge” unwieldy names, there might be a point. But there are in fact other options available which aid in readability by providing context without being “huge”.

                It is not obvious that entry “is more readable than a single-letter name” of e, at least without more context.

                Single-letter names rarely provide context. If a file contains multiple functions, each of which contain at least one loop, single-character names fail to differentiate which loop (or which function) one is looking at.

                Also, I find it somewhat amusing that single-character loop variables are extremely likely to lose context or even collide during refactorings, yet as far as I’m aware the reason why Go stylistically discourages some other naming conventions (such as a “this” or “self” parameter name for functions which receive an instance of a struct defined in the same file) is that refactoring might cause loss of context.

                But mostly, the single-character thing just feels to me like another instance of Go’s designers attempting to stand athwart the history of programming language design and practices, and yell “Stop!”

                1. 3

                  Single-letter names rarely provide context.

                  The normal guidance in Go is to name variables with expressivity (length) proportional to their lifetime, and the constraints of their type.

                  The name of a variable that’s only in scope in a single line block needs to provide far less context compared to the name of a variable that’s in scope for an entire e.g. 100 line function.

                  A variable of type SpecificParameter can be named sp because that meaning is unambiguous. A variable of type string that represents an e.g. request ID should probably not be called simply id, better e.g. reqID, especially if there are other string parameters that exist with similar lifecycles.

                  If a file contains multiple functions, each of which contain at least one loop, single-character names fail to differentiate which loop (or which function) one is looking at.

                  It’s not the job of a variable name to disambiguate at the file, or really even function, level. An index variable for a one-line for loop is probably appropriately named i no matter how many times this situation is repeated in a function or file.

                  1. 4

                    A variable of type SpecificParameter can be named sp because that meaning is unambiguous. A variable of type string that represents an e.g. request ID should probably not be called simply id, better e.g. reqID, especially if there are other string parameters that exist with similar lifecycles.

                    Or name them SpecificParameter and requestID. Pointlessly shortening variable names far too often leads to names that confuse rather than communicate. Is reqID a request ID? A requirement ID? A requisition ID? If I see that name out of context – say, in a debug log – how am I supposed to know which one it is?

                    And even in context it’s not always clear. Going back to the “self” param debate, as I understand it the standard Go approach if I want to write, say, a function that takes and operates on a Server struct, is to declare the parameter as s. Some obvious and more communicative names are ruled out by other aspects of Go’s naming rules/conventions, but how exactly is that informative? If I’m not directly in that file, and just see a call to the function, I’m going to need to either go look up the file or rely on IDE lookup showing me the type of the parameter to figure out what it’s supposed to be, since outside of the original context it might be s for Server, or might be s for Socket or for SSLContext or SortedHeaders or…

                    (and the loop-index case does still lose context, and also needing to name loop index variables is an indication of a language with poorly-thought-out iteration patterns)

                    But really it just comes down to a question of why? Why does requestID need to be shortened to reqID? Is the target machine going to run out of disk space or memory due to those four extra characters in the name? No. So there’s no concrete resource constraint requiring an abbreviated name. Is there any additional clarity provided by naming it only reqID? No, in fact clarity is lost by shortening it. What is the alleged gain that is so overwhelmingly important that it requires all variable names to be shortened like this? As far as I can tell, it really is just “that’s the way we did it with C in the 1970s”.

                    And it’s not even consistent – someone linked below a buffered I/O module that’s shortened to bufio, but the Reader and Writer types it works with are not shortened to Rdr and Wrtr. Why does it have a function Discard and not a function Dscrd? Why NewReaderSize and not NwRdrSz? There’s no principle here I can detect.

                    1. 4

                      Once I’m familiar with the convention, it’s a lot easier for me to recognize the shape of reqID than it is to parse the text “requestID”.

                      Right now I’m working in a codebase where we have to write sending_node_component all over the place and it’s so miserable to both type and parse that the team unanimously agreed to just alias it is snc.

                      1. 1

                        You put my feelings into words, thank you

                    2. 2

                      Why are you abbreviating ‘identifier’ throughout your post? What about SSL? Communication is full of jargon and abbreviations that make sense in context. They’re used to make communication smoother. Code does the same. Variable names are jargon.

                      But really it just comes down to a question of why?

                      Because to many people, it’s simply more readable.

                      1. 3

                        “SSL” is an initialism, not an abbreviation.

                        Variable names are jargon.

                        So there’s an agreed-upon set of standard names which all programmers everywhere are trained on, aware of, and understand? Cool, so why does this thread – asking about how to name variables – exist in the first place?

                        They’re used to make communication smoother.

                        And I gave examples of how abbreviated variable names fail at this, where slightly-longer versions do not have the same issues. I note that you haven’t actually engaged with that.

                        1. 2

                          I simply don’t find your examples compelling. I simply don’t find your supposed failures harder to understand.

                          And if you insist on being pointlessly pedantic, single letter variable names are also initialisms of single words.

                          1. 2

                            So, suppose we look at a set of possibilities:

                            • requestIdentifier
                            • requestIdent
                            • reqIdentifier
                            • requestID
                            • reqIdent
                            • reqID
                            • reqI
                            • rqID
                            • rqI
                            • rI
                            • r

                            Do you honestly believe that all of them are basically equally useful and informative, even when viewed out of context (say, in a log or error message when trying to debug)?

                            1. 3

                              I never see variable names in log files, let alone without additional information.

                              Do you typically dump raw variable names into logs, with no additional context? Don’t you think that indicates a problem with the quality of your logs?

                              It seems pathological to focus on readability of variables in the context of poorly written log messages.

                              1. 2

                                Log messages are one example of when a name might appear out of context.

                                Do you believe all of the above names are basically equally useful and informative, even when viewed out of context? Because if you work someplace where you never have to debug based on small amounts of context-missing information, perhaps you are lucky or perhaps you are THE ONE, but the rest of us are not. So I’d appreciate your input on the hypothetical missing-context situation.

                                1. 3

                                  I think the basic disagreement is whether the names need to make sense without context because I don’t think they have to. The context is the surrounding code.

                                2. 2

                                  Or maybe the names of local variables matter a lot less, and the way the code scans when skimming matters more than you acknowledge.

                                  It’s easier for me to read code with shorter lines. The shape of the code matters about as much as the names, maybe more.

                                  even when viewed out of context?

                                  This is a non-consideration. I don’t view them out of context. Short names tend to read better in context.

                            2. 1

                              Nobody is suggesting this.

                              The claim is that the verbosity of a variable name ought to be a function of the lifetime of that variable, and the ambiguity of its name versus other in-scope variables.

                              Variable names always exist in a context defined by source code.

                    3. 1

                      If you have a for loop over SpecificParameter values which is 3 lines of code long, there is no purpose served by naming the variable in that loop body as specificParameter, in general. It’s laborious for no reason.

                      Variable names always exist in the context of the source code in which they are written. There is no realistic way that the literal name of a variable as defined in source code could end up in a debug log.

                    4. 1

                      What is the alleged gain that is so overwhelmingly important that it requires all variable names to be shortened like this?

                      So why did you use the abbreviation IDE instead of spelling it out?

                      1. 3

                        “IDE” is not an abbreviation, it’s an initialism. If you’re going to be pedantic like this you have to be correct about it.

                        1. 2

                          According to Wikipedia, an initialism is a type of abbreviation.

                      2. 1

                        “IDE” is a lexicalized abbreviation. “req” is not: you wouldn’t say “I sent you a req”, would you?

                        1. 1

                          Sure I would. Especially in the context of hiring.

                2. 1

                  Single-letter names rarely provide context. If a file contains multiple functions, each of which contain at least one loop, single-character names fail to differentiate which loop (or which function) one is looking at.

                  I would say if the name of the function is not visible on screen at the same time, your function is probably too long. But what’s your point here? That local variables should have names that are unique in a source file, although they are not in global scope?

                  But mostly, the single-character thing just feels to me like another instance of Go’s designers attempting to stand athwart the history of programming language design and practices, and yell “Stop!”

                  This one I don’t understand. Newer languages do not have/allow longer variable names than older languages (for the most part). So I don’t see how the length of variable names has anything to do with the history of language design.

                  1. 2

                    I would say if the name of the function is not visible on screen at the same time, your function is probably too long.

                    I’d argue there’s no such thing as a function that’s too long, only too complex. Do you think this function would be worthy of splitting, being over a hundred lines long?

                    In my opinion it’s not, as it’s just a linear list of tasks to be done in order to generate a chunk of bytecode for a function. It’s like a cake recipe. It’s a list of steps telling you how to mix and bake all the different ingredients to produce a cake.

                    Now obviously, some cake recipes are fairly complicated, and they can be split into sub-recipes - but that is a result of deep nesting being hard to keep track of, not length. In Unreal Engine for instance there are lots of functions which go incredibly deep in terms of nesting, and those could probably be factored out for readability, but I still wouldn’t be so sure about that, as moving out to smaller functions tends to hide complexity. It’s how accidental O(n²) happens.

                    Which is why I tend to factor code out to separate functions if I actually need to reuse it, or to make a “bookmark” showing a future extension point.

              2. 2

                equally well expressed if entry is named e

                If it was for e in BlogEntry.entries(), I’d agree (and I’d say it should be renamed to this because query is too vague) because then you know you’re getting an entry back from that method. But a generic BlogEntry.query() doesn’t give you any hint as to what’s coming back - which is where for entry in ... is helpful because it signals “hey, I’m getting an entry object back from this”. Means you don’t have to hunt down BlogEntry.query and find out what it returns.

                1. 2

                  I’d argue if that’s not clear then it’s not e that is the problem, but .query that is. Also if you use some form of IDE the “hunting” down is usually okay. You anyways want to know whether it returns an error. And sometimes you want to know whether it’s some response type rather than the actual data or something. But in this example I think e would be as clear as entry - at least it is for the scenarios I can think of.

            2. 1

              100% with you on this, but if this thread shows something it’s that this is way more subjective than I thought.

              TBH I don’t mind it as much when it’s in the standard library, a few well known exception to the rule are not too bad.

            3. 1

              The quote compares i to index. You are misrepresenting the argument.

        2. 3

          what makes Go so special that decades old advice is suddenly irrelevant?

          Go is a reaction against earlier mainstream languages, in particular C++ and Java, and part of the reaction is to “reset” what are acceptable and what are not.

        3. 1

          (i.e. what makes Go so special that decades old advice is suddenly irrelevant?).

          Even older advice and experience, from the people that wrote Go. They (and I) find it more readable to use short names, so they did.

          For example: https://github.com/golang/go/blob/master/src/bufio/bufio.go

    8. 3

      I run into this all the time. I don’t have a good solution. A related problem is when you have a Car type that doesn’t need to be exported, so you rename it to car. In particular, if your package is a program rather than a library, all types should be lowercased like this. You are allowed to shadow it like car := car{}, but that’s fairly limiting because now if you write car2 := car{} you get “car (variable of type car) is not a type”.

      1. 2

        Now I’m sad :(. I guess knowing I’m not alone helps though, thanks!

    9. 2

      One more thought that others haven’t mentioned:

      Name your variables such that if you refactor and move that line elsewhere, it’ll still make sense. That is, the name should identify what the line represents (not be too generic that it’ll collide often), but it shouldn’t include extraneous baggage from surrounding context (so that it can be moved around reasonably without renaming). But maybe more importantly, it should be consistent with how you name other such variables.

      For example, say you have u := getUser(), this is fine especially if you have that line all over the place. But if you have something like u := getAdminUser(), that can get dangerous if you refactor and forget whether you’re dealing with an admin or not. In that case I’d want to be consistent about calling it admin := getAdminUser() or something.

      Related post: https://shazow.net/posts/receivers-in-go/

    10. 2

      I usually solve this by re-scoping the package so that it is called vehicle or something like that, so now you have car := vehicle.Car{} and no shadowing. Often, since Go doesn’t really do OOP, this ends up being a handy gut check about where I’m splitting up my abstractions. That being said, use your judgement :-)

    11. 1

      I do like

      • Uppercase for structs and other types
      • lowercase for variables
      • always camel case

      Sometimes you stumble with situatuons where the type of the value is specified and it already gives you the context about the value, like err for error, f for file handle, r and w if the scope has only one reader and writer and i, j, k and l for loop indexes. Otherwise you can name with one or a few words, nothing Java level but descriptive.

      Use single letter, or small number of letters, for situatuons that are more like common sense.

      Thats how I do so far and I might be very wrong.