Go Style
google.github.io
google.github.io
> The general rule of thumb is that the length of a name should be proportional to the size of its scope and inversely proportional to the number of times that it is used within that scope.
> A variable created at file scope may require multiple words, whereas a variable scoped to a single inner block may be a single word or even just a character or two, to keep the code clear and avoid extraneous information.
I love how it covers both short iterators and long descriptive names under a single principle.Apple APIs are infamous for their long names. Not sure what they gain by it
builder.makeThing("a", true, true, 46)
[builder makeThingNamed:"a" isRound:YES isRed:YES size:46]
or lately
builder.makeThing(named: "a", isRound:true, isRed:true, size:46)
There is no "kind of true".
> It also increases cognitive load for people who either do already understand the parameters
By letting them see what the parameter they remember is? There's no cognitive load to seeing what you expect.
> or who are doing something for which the parameters are not relevant;
If the language doesn't have optional parameters, all parameters are relevant. By making parameters named, you avoid having to count positional parameters as in MS APIs to ensure you didn't get one in the wrong slot.
> it's harder to ignore a long thing than to ignore a plain "true".
Which is very much valuable. It's much easier to notice mistakes than when you've got 11 positional parameters of which 2/3rds are usually set to 0/null.
Clarity.
It's also a factor of Objective-C's compound methods (inherited from Smalltalk), as each parameter names appears in the method, and which leads to a very phrase-like style.
By osmosis, the C APIs use a similar style.
It often takes longer to read a single character than a word, because I have to mentally map the character to the word anyway.
Reminds me of when people go nuts aliasing table names in SQL queries, which IMO makes it harder to read as well.
- With such rules, variable length is a hint of its scope. For example, I tend to use "i" when the loop body is small, "idx" or "index" when it is a bit larger, and a more descriptive name when it exceeds one screen. This way, just by looking at a single line, I already have a hint about its context.
- Just because a variable name is short doesn't mean it is meaningless. For example "i", "j", "k", are loop indices, "x", "y", "z" are point coordinates and "dx", "dy", and "dz" are differences, "a" and "b" are both sides of a comparison function, "t" can be a temporary variable or a time depending on context, etc... The corollary is to make sure you use your single letter variables consistently. If I see an "x" in a place where a coordinate can be used and it does not refer to a coordinate, or an "i" that is not a loop index, I will be confused.
There are a few interfaces in Go that are used heavily and I don't mind that people often use a single character for them.
It's the same thing as everyone using `i` for iterators.
It's all preference, of course.
Imagine you're new to Go and you haven't mentally mapped all of these abbreviations. What would you rather read, `r` or `reader`?
There is also nothing wrong with naming variables after what they're for instead of what they are. Consider io.Copy(dst, src). That's nicer than io.Copy(reader1, reader2).
It isn't a huge deal when the code is simple, e.g. you create a reader named `r` and in the next line you use it. That's easy to understand. The problem is that the pattern propagates. A few more lines of code are added, the reader is still named `r`. It's name `r` here, so we start putting it in a struct as `r`. Soon enough the codebase is littered with instances of `r`. Not great.
If you think about it, when you solve a physics problem, for instance, you call every mass “m1”, “m2”, etc. Maybe this would be another step in Go’s direction of conforming style to make code more standard and readable.
The idea of having a standard library of types related to what are effectively program keywords... could be really good. Or hideous like global vars.
I wonder if any language has tried this?
However, in many cases a more descriptive name is way appropriate. On top of my mind:
- Multiple variables of the same type. How are you supposed to distinguish between req and req2? Compare it to something like "apiReq" and "cdnReq"
- Primitive types, that does not inherently carry a domain value. An integer called "seconds" or "max_offset" has a lot more meaning that one named "num"
Forcing such a notation into the language would make impossibile to represent all these cases
I've been writing Go cloud stuff for the better part of a decade and "req" for a request has never been ambiguous because I go ahead and send the request and process the response before sending another one, at which point I can just reassign the variable and let the first one fall out of scope.
If you were just coding physics problems you could have types restricted to the physics domain, that is, physical units. Which it looks like MATLAB does now support: https://www.mathworks.com/help/symbolic/units-of-measurement...
In Go, I would probably model this as:
type mass float64
type speed float64
type acceleration float64
That way, they'd all have float64 as the "storage type", but it would be harder to accidentally pass an acceleration when I wanted a mass.This is why some people recommend a practice I've forgotten the name of, where they defined a new type that maps to language type. So type Mass could just be a float, but you know better how to treat it.
For sensitive infrastructure this makes sense, i surely wouldn't want to write other code like this.
I think a better heuristic rather than tying it to scope is to sort of semantically huffman encode. Using an iterator is probably the most used variable name that starts with an 'i', so it gets just plain 'i'. As a _concept_ gets more specific, then it gets a longer name.
for distance, time in driving_metrics:
speed = distance / time
is a lot clearer than for d, t in driving_metrics:
s = d / t
even if it's used in a tiny scope once. But some things don't have a valuable meaning, or the meaning can trivially be inferred. 10 FOR F = 1 TO 10
20 PRINT F, F*F
30 NEXT F
Because "FOR" and "F" shared the same key, on my ZX Spectrum keyboard.eg https://www.old-computers.com/museum/photos/sinclair_zx-spec...
The low cost and quirky masochism was mainly why we loved it.
One thing I would add is if you're writing a library, think of the people using it. Are they going to curse you every time they have to write YourModule.ExtremelyVerboseMethodNameWhyOhWhyIsItSoLong, even if it only appears once in your module?
As soon as you start sharing code, you can't know how many times a name will appear. So err on the side of brevity, for our sakes.
How does one declare a variable to be of file scope in Go? An ordinary "var x int" has package scope, visible from any file in the directory (aside from the special cases around _test.go files).
Scope of package import declarations is narrowed to a single .go file, but these of course are not variables.
A human typed in something less precise than you'd like in a style guide. Find a bug tracker to request a verbiage change. It's not that big of a deal.
No one is asserting that variables are in fact file scoped.
I like this rule. Most companies violate it everywhere. There are good times to ignore it but I always push for
func NewThing
To return something other than the interface type. The last Go interview I was in I brought this up and the argument was that you cant test things if you are not returning the interface and I disagree entirely.I love the power of duck typing with interfaces but give me concrete things that quack.
var _ IfaceType = &ConcreteType{}
Somewhere, else you risk not knowing that a change to ConcreteType broke is implementation of IfaceType in a way that you might not know about until a rare runtime code path is executed.Why would runtime be involved? Surely if ConcreteType doesn't satisfy the interface anymore then the compiler will catch that at any site where a ConcreteType is being used as / cast to an IfaceType?
Or are you talking about iface->iface side-cast?
type FooReader struct{}
var _ io.Reader = FooReader{}
func (r FooReader) Raed(p []byte) (int, error) { return 0, nil }
Before you even use FooReader as an io.Reader, you can find that you typo'd "Read". This is useful in practice because you probably wrote FooReader to have a bunch of other methods that aren't implementing io.Reader, and it's possible that your tests for this object don't actually ever use it as an io.Reader. So you won't notice this mistake until someone tries it elsewhere in the codebase.It also acts as documentation that you intend for this thing to be an io.Reader. But I also recommend that you document Read as "// Read implements io.Reader." to be extra explicit.
I have a longer rant about this here: https://jrock.us/posts/go-interfaces/
I was struggling with this just today: Realizing I keep putting off writing tests for some modules because mocking a certain mega interface is a pain.
Your post gives me some great hints for things to try. Looking forward to experimenting with it.
Adding private members to my structs for testing was something the Go team forced me kicking and screaming to do when I worked at Google. I remember writing this adaptor for some internal network filesystem, and had an interface and two implementations: one that used the network for real, one that did everything in-memory for tests. I sent it off for code review and they were like "nope! don't do that in go!" and suggested an if statement in every method to handle the in-memory implementation. I was unhappy about it for months because it went against everything I knew about programming at the time, but like 10 years later, I appreciate that they were right. (I will say that everyone I explain this to has approximately the reaction that I had at the time. It is an easier pill to swallow when the person telling you not to do it wrote Go, but they don't have that benefit ;)
An issue I can think of is for instance: consider a system where each time a new request comes in a new transaction is started. That situation would result in having a new `DBImpl struct` for each request. It seems that that last "but you probably only have one of these DB objects" doesn't hold. How would you tackle that issue?
Looking at database code I have written, I never do the object thing (but was replying to an author who does). I typically have a package with functions that take transactions as an argument, like this:
https://github.com/jrockway/jsso2/blob/master/pkg/store/sess...
(If you poke around the package, methods that don't make more than one database call take a sqlx.ExtContext instead of a *sqlx.Tx. Functions that need to start their own transactions take the connection instead, though I'm not sure I'd recommend that approach because it hurts composability.)
To test it, I just run against an actual Postgres instance which is pretty easy to find. (I am not sure I would steal my database setup / teardown utilities from that project. The tests end up indented too far. A more idiomatic Go way is "app := testapp.New(); defer app.Cleanup(); tests go here"; in that codebase I picked the "testapp.Test(t, func(t testing.TB) { tests go here })" pattern. Definitely something I look for and avoid these days, but didn't in 2020 ;)
I struggle very hard creating my own interfaces for my own programs. I haven't found any literature online teaching the _right way_ of coming up with your interface.
For example, I very often struggle with function parameters and return types: should my interface functions only take basic type and return basic types? Can my interface function take more concrete types? Should my interface function take interface types?
I'd love to be directed to a guide on how to create a good interface.
As an example, here's a recent interface declaration I wrote:
type Checker interface {
Check(context.Context, *object.Commit, bool) (bool, []string, error)
Name() string
Describe() string
}
My program runs through a list of commits and runs "Checks" on them. These checks "pass (true)" or "fail (false)".I want the user of my package to be in charge of the implementation details of a "Check", so I created the interface above.
Is it OK for the Check() function to take a *object.Commit? Should I instead pass a `SHA string` and let the implementer figure out how to get a *object.Commit from that string?
What you might want to do is also define an interface for what Git calls "commit-ish"[0] objects, and use that in place of *object.Commit. Then you could pass e.g. *object.Tag in place of *object.Commit.
Think of the interface as a contract between implementations and users of the data structure. Typically, you use an interface when multiple implementations are capable of fulfilling the same contract. If there's only one implementation, you likely don't need an interface—for instance, you can ignore my suggestion about abstracting commit-ish if you're pretty sure your checker would never be called for a tag.
Interfaces are often useful when testing, because a mock implementation can fulfill that same contract. For instance, you might implement a *object.MockCommit which just uses static values instead of actually operating on a Git repository, and then pass that for your commit-ish in unit tests for various Checkers.
[0] https://git-scm.com/docs/gitglossary#Documentation/gitglossa...
In all seriousness, I am very proud of the team’s work in both writing and publishing this externally. It was a lot of work.
> Maintainable code minimizes its dependencies (both implicit and explicit). Depending on fewer packages means fewer lines of code that can affect behavior. Avoiding dependencies on internal or undocumented behavior makes code less likely to impose a maintenance burden when those behaviors change in the future.
Obviously this guide was written for internal Google use, but it's interesting to see a recommendation that appears to run a bit counter to how ecosystems tend develop for languages that have easily consumable packaging solutions. It's not uncommon to look at a Go, Rust, Node, Python, etc project and see many dependencies (direct and indirect) in use, most of which you have no idea what they're being used for outside of some of the most popular packages. Not that I think the suggestion is wrong, I fully support fewer dependencies and using external packages where they make sense. I start to get uneasy when you see a tree of dependencies of unknown code, but I don't have the time myself to read through all of those packages.
I think where I still struggle is “rules for thee but not for me”. At some point, someone on the team (usually the person in charge) is just going to be better at picking which external libs to let in. I find this really hard to teach without it seeming arbitrary. I find it a lot easier to teach good coding habits.
I think as an organization grows to some combination of available resources and severity of an outage this view becomes more and more common.
On the one hand, you don't want to be constantly re-inventing the wheel. If someone has made a great package for doing a specific thing you need (printing data in tables, making ergonomic http requests that cover edge cases, reading an epub file, etc), then IMO you're better off using theirs. They care about it, they've tested it, and the implementation of it is one less thing you have to think about it. If you consider that every line of code that you write/maintain is a liability, then offloading that to others is very convenient.
On the other hand, external packages typically come without guarantees. Their owner/maintainer could lose interest, get hit by a bus, get hacked, anything. You're running their code in your projects and suddenly, all this uncontrolled code becomes the liability. You'd do best to do _everything_ in house in order to limit your external risk.
Ultimately, I think there's no single right answer here. Sure, you probably shouldn't add dependencies for things like "left-pad", but the line is a little blurrier for things like npm's "is-promise". It sounds simple enough (and the implementation [0] is only a 3-line function), but I'm unlikely to have written it correctly if I did it from scratch. Plus, it's tested! And lastly, I think your risk is lower the larger an external dependency is. Large projects, like React or Django, are much more upside than liability; it's everything in the medium range that you really need to consider.
[0]: https://github.com/then/is-promise/blob/ec9bd8a3f576324a1343...
This breaks the linter in most cases because "magic numbers". Like having to declare a constant for the number of cents in a euro/dollar.
I think Google are right in this case, and linters need to be smarter.
I agree with your point, but this is a bad example. Naming these constant CentsInEuro and CentsInUSDollar is consistent with the style guide.
As silly these examples are in isolation (of course a cent is 1/100 of a euro or a dollar), if you are writing code that processes currencies beyond euro and dollars, you will quickly end up with (using Chinese Yuan as an example):
const (
JiaoInCNYuan = 10
FenInCNYuan = 100
...
)
At which point adding const (
CentsInUSDollar = 100
CentsInEuro = 100
...
)
is not just reasonable but a good idea.Additionally, suppose your code needs to deal with historical currencies, you'll most likely need:
const (
ShillingsInOldBritishPound = 20
PenceInOldBritishShilling = 12
...
)
This is also a good example that the knowledge that a cent is 1/100 of a dollar or euro is highly culture-dependent. A British programmer from as late as 1970 will tell you that it's silly to create constants for these: of course there are 20 shillings in a pound and 12 pence in a shilling, everyone knows that! (I also like to think that they'd assume that a "cent" is 100 dollars because that's what centum means in Latin, but it's probably highly unlikely for them to never know US dollars and cents.)For me, the use of those annoying constants is a form of mental relief: I don't have to remember why I'm dividing by 60 or 100 or whatever in this specific spot, it's written out in a way that reading it provides the context.
These all seem like good reasons to make then functions (taking a timestamp as a n argument) rather than mostly-correct constants.
I swear, the more I learn about calendars and timekeeping, the more I realise I never ever want to deal with it.
SecondsPerStandardHour = 3600 // or NormalHour or TypicalHour
HoursPerStandardDay = 24 // or NormalDay, TypicalDayGo takes this to an extreme, though. Generics helps, but the implementation is so limiting that it doesn't help much.
> The general rule of thumb is that the length of a name should be proportional to the size of its scope and inversely proportional to the number of times that it is used within that scope.
Some (most) of the go code I've seen where I work suffers from Enterprise Java style identifiers. That is, TheyAreGenerallyTooLongAndIHateThemAll.
In general, please stop panicking in library Go and Rust code. It's rude.
The style point that people are missing here is that every type should have a usable zero value. If (&SomeStruct{}).Method() panics, that's an API design flaw.
1. Do not create "assertion libraries" like `assertEqual(x, y)` [1]
2. Leave testing to the Test function [2]
3. Intialisms (HTTPURL, IOS, gRPC) [3]
4. Function formatting [4]
For the record I'm not saying I disagree with these. I just think that folks coming from other languages have a lot of built in muscle memory to do it other ways. [1] https://google.github.io/styleguide/go/decisions#assertion-libraries
[2] https://google.github.io/styleguide/go/best-practices#leave-testing-to-the-test-function
[3] https://google.github.io/styleguide/go/decisions#initialisms
[4] https://google.github.io/styleguide/go/decisions#function-formatting if got == nil {
t.Errorf("blog post was nil, want not-nil")
}
Better than assert.NotNil(t, got, "blog post")
? They seem to suggest that you lose context, but their "Good" examples are similarly devoid of context.For example for a simple string comparison it's 12 lines:
--- FAIL: TestA (0.00s)
--- FAIL: TestA/some_message (0.00s)
main_test.go:11:
Error Trace: /tmp/go/main_test.go:11
Error: Not equal:
expected: "as\"d"
actual : "a\"sf"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-as"d
+a"sf
Test: TestA/some_message
vs. 2 lines with a "normal" test: --- FAIL: TestA (0.00s)
--- FAIL: TestA/some_message (0.00s)
main_test.go:11:
have: "as\"d"
want: "a\"sf"
With your NotNil() example it's 4 lines, which seems about 3 lines more than needed.This kind of stuff really adds up if you have maybe 3 or 4 test failures.
I've seen a lot of gore in code that uses assertion libraries like assert.Equals(t, int64(math.Round(got)), int64(42)). Consider the error message in that case when got is NaN or Inf.
It is so easy to write your own assertions. I can type them in my sleep and in seconds:
if got, want := f(), 42; got != want {
t.Errorf("f:\n got: %v\n want: %v", got, want)
}
if got, want := g(), 42.123; math.Abs(got - want) > 0.0001 {
t.Fatalf("g:\n got: %v\n want: %v", got, want)
}
Why implement a NotEquals function when != is built into the language?(The most popular assertion library also inverts the order of got and want, breaking the stylistic convention. It's so bad. Beware of libraries from people that wrote them as their first Go project after switching to Go from Java. There are a lot of them out there, and they are popular, and they are bad.)
Finally, cmp.Diff is the way to go for complex comparisons. Most use of assertion libraries can be replaced by cmp.Diff. I wouldn't use it for simple primitive type equality, but for complex data structures, it's great. And very configurable, so you never have to "settle" for too much strictness or looseness.
(One thing I absolutely abhor is those assertion DSLs similar to rspec)
> Complex assertion functions often do not provide useful failure messages and context that exists within the test function.
I think the best compromise is to avoid combining individual logical units into one assertion. For example:
Bad:
assert.True(t, myStr == "expected" && myInt == 5)
Good: assert.Equal(t, "expected", myStr)
assert.Equal(t, 5, myInt)
Real world example: at my workplace we have some code that tests HTTP request formation for correctness (right URL, body, headers, etc). Replacing big all-or-nothing booleans with individual assertions on each property of the request provides much more useful test failure messages.As with any published "best practices" like this, have an open mind but don't just cargo cult whatever Google does. Best to be selective about what does and doesn't work for your situation.
https://google.github.io/styleguide/go/decisions#initialisms
And what's the basis of capitalizing D in ID?
First, the case for "id":
id abbreviation
1 [Latin idem] the same
Now for "ID" ID noun, plural ID's or IDs [REVISED] [identification]
1 a : documentation bearing identifying information func ParsedTimeOrNil(s string) *time.Time {
t1, err := time.Parse(time.RFC3339, s)
if err != nil {
return nil
}
return &t1
}That said, I hate this, all the ways it's opinionated and praised zealously for it as the best thing since sliced bread.
If time.Parse is failing, you either have bad input or a bug, right? If you are ok with this failing without even logging the error, something might be wrong with the design.
You can't build your own programming language inside of Go. If you absolutely cannot mentally handle functions returning an error and you having to type "if err != nil { fix the problem }", you really need to find a different programming language. But, errors happen all the time, and handling them correctly is the difference between an unreliable piece of garbage that randomly fails and software you and your users can trust. There is, unfortunately, no automation around making software reliable.
(BTW, stack trace proponents... stack traces don't capture loop iterator variables, or "why" you're calling a particular function. But when you write a quick error message with fmt.Errorf, you can include all of those things.)
Software engineering is a job of continuous improvement. Make it really easy to find the right place to target improvements!
A language where you need to constantly write wrappers around everything just to get basic functionality is quite a miserable language, imho. That's like C…
Which makes complete sense, since Go was designed as Google's C for Dummies.
A few lines later:
// Good:
if err := doSomething(); err != nil {
// ...
}
"Tell me, how many lights you see?"This position has obviously prompted a philosophical war and not everyone agrees, but the two statements are consistent under Go's philosophy.
For example they recommend not using %w for errors- because library authors need to be careful about what information is exposed. However, as an application code developer, %w should be used by default- this avoids an accidental bug where annotating an error could cause an upstream error check to fail- which they allude to in the guide- but now their rule is getting complicated whereas "default to %w" would be safer and improve velocity in application code.
> Functions that return something are given noun-like names.
> // Good: > func (c Config) JobName(key string) (value string, ok bool)
> A corollary of this is that function and method names should avoid the prefix Get.
> // Bad: > func (c Config) GetJobName(key string) (value string, ok bool)
That's dumb. I'd like a function to be GetJobName to indicate that it doesn't mutate anything. Maybe CreateJobName to indicate mutation. Just JobName is useless.
The other day, I spent a whole day trying to figure out the "idiomatic" way to return a an object not found case from my db. Do you return a nil pointer (don't, passing pointers leads to bugs), or an empty struct (then how do you reliably "test" its emptiness?) or an error? And of course, there's no real hierarchy of errors, no built-in extensible handling of errors that is semantic and makes sense, so every project just goes and reinvents their wheel.
> func Car(id string) Car
How do I know whether it fetched that from the database or created a new one and returned that to me?
func (d CarDB) func Car() (Car, error)
The above tells you everything you need to know. As for usage, again, the type and now also the variable names should let you infer everything you need.
func f(db *CarDB) { c, _ := db.Car() }
The function name makes as little of a guarantee as to the underlying "how" as these other factors.
That advice is a consequence of Go expecting function signatures to be read and understood. That's not the right choice for all programming languages, but for Go it works.
But I thought Golang doesn't have function overloading, so you also have to name the function appropriately for its use?
Like I said, absurd.
> The other day, I spent a whole day trying to figure out the "idiomatic" way to return a an object not found case from my db. Do you return a nil pointer (don't, passing pointers leads to bugs), or an empty struct (then how do you reliably "test" its emptiness?) or an error?
sql.ErrNoRows is often used for this.
It can't mutate anything with a non-pointer receiver. Mostly.
> how do you reliably "test" its emptiness
See time.IsZero() for an example.
For an app that has medium-sized structs in hot paths, this tradeoff introduces considerable tension into the development flow.
However, there is one takeaway for us devs: Don't count on help from the inlining optimization pass of the compiler to evaporate away calling overhead of structs, neither when passed as the receiver nor as an ordinary arg.
There is still room for a style guide.
Style may also include naming, documentation requirements, recommendations regarding function length, etc.
> Line length
> There is no fixed line length for Go source code. If a line feels too long, it should be refactored instead of broken. If it is already as short as it is practical for it to be, the line should be allowed to remain long.
> Do not split a line:
> Before an indentation change (e.g., function declaration, conditional)
>To make a long string (e.g., a URL) fit into multiple shorter lines
fmt doesn't force arbitrarily short lines in lieu of readability (looking at you python + pep8; I can't recall how many lines of code were a few characters long and pep8 formatting made the multiple lines a mess to visually parse). The Google Team decided that it was important to not break up perfectly readable lines and gave guidance on how to do that.
There are a few (rare) cases where gofmt will wrap lines, but it mostly leaves it alone which is one of the better "features" IMHO. Many *fmt tools really got this wrong.
Automated formatters are nice, but in the end there is no substitute for human eyes and common sense.
Go fmt doesn't change anything that is not formatting.
google has published a style guide, but it doesn't seem like facebook and netflix do.
it would be awesome to have one central list of best practices from leading tech companies.
we started one on github, but it's woefully limited: https://github.com/HotpotDesign/Developer-Style-Guides
could anyone kindly recommend better ones?
> This applies even when it breaks conventions in other languages. For example, a constant is MaxLength (not MAX_LENGTH) if exported and maxLength (not max_length) if unexported.
Good. All-caps for constants would make no sense in a language like Go.
I'm looking forward to this document.
> The standard net/http server violates this advice and recovers panics from request handlers. Consensus among experienced Go engineers is that this was a historical mistake. If you sample server logs from application servers in other languages, it is common to find large stacktraces that are left unhandled. Avoid this pitfall in your servers.
I don't think I've ever seen a server library — HTTP or otherwise — that didn't have a top-level "catch all exceptions" or "recover from panic" step in place, so that if there's a problem, it can return 500 (or the Internal Server Error equivalent) to the user and then carry on serving other requests.
My reasoning is that any panic-worthy programming error is almost certainly going to be in the "business logic" part of the server, rather than the protocol-parsing "deal with the network" part, and thus, recoving from a panic caused by processing a request is "safe". One incoming request could cause a panic, but the next request may touch completely unrelated parts of the program, and still be processed as normal. Furthermore, returning a 500 error but having nobody read the stacktrace is bad, yes, but it's way, way, way better than having your server crash meaning nobody can use it ever.
Oh wait, is the assumption here that your service is being run under Borg and has another 1000 instances running ready to jump in and take the crashed one's place? Is this another case of Google forgetting that people use Go outside of Google, or am I reading too much into this?
There is one advantage to not having a top-level catch: if it fails in testing, it's very obvious immediately.
Though I do agree with you - and this can be done with a feature flag for dev/prod servers.
It's definitely an opinionated approach though.
I think opinions in 3rd party Go code on what “fatal” means vary, that’s the issue. Sure it was intended to mean “this error is so bad the entire program needs to die right now” but in practice there’s cases where it’s treated more like an unchecked exception in Java, i.e., “I can’t recover from this so _I’m_ going to give up but _you_ can keep going.” Or put another way, what a library might consider fatal the caller doesn’t. It can be argued whether or not that’s the right thing to do, but the fact is it happens in the wild, so for something like a server you probably should trap it.
Java has a supertype for unrecoverable problems like out-of-memory-errors. It's called "Error", a subtype from Throwable. Exceptions are also Throwables, but they are NOT errors.
No, because both approaches could be right at a higher level, to pretake that decision at this level is definitely wrong here?!
OTOH, maybe your priorities are different and you would prefer to be more available than correct. In that case by all means add a recover to the top level of your request handlers. But it was a mistake to have made this decision for all users of net/http ahead of time.
The reality is this is far more common than something truly fatal. Mistake for someone or not, it probably is correct for most use cases
C/C++ based servers running into an error like that would possibly be open for attacks. Go will be a bit more resilient, but it's still better to avoid situations like that.
That said, logging the error and going on serving responses is fine I think (pragmatic), as long as the error is analyzed. But an error that doesn't trigger immediate action is a warning, and warnings are noise [0].
[0] https://dave.cheney.net/2015/11/05/lets-talk-about-logging#:...
mu.Lock()
foo := bar[baz] // <- throws exception / panics
mu.Unlock()
Go is sold as a language without exceptions, so people don't write exception-safe code. Which is fine, except when exceptions are actually caught.But despite you and me, I'm saying there's a lot of broken code out there because of this doesn't-but-actually-does misinformation.
And it's very annoying that you have to tell people to do:
var i int
func() {
mu.Lock()
defer mu.Unlock()
i = foo[bar]
}()
Clean code, that is not. (even if you simplify it by having the lambda return the int)Now this middle ground leaves you having to write triple verbose if err != null on every third line of your code and still not be safe from panics-that-shouldnt-have-been-panics.
As parent says, the only way panics can ever work is if the top-level never catches and recovers from them. I'm no expert in go but that would mean in such perfect world, defer should hardly ever be needed at all, not even for locks? Only for truly external resources? But now with popular web servers doing such recovery, the entire ecosystem got polluted and all need to handle it?
This has happened during my couple years at Google at least once, even though it wasn't in an HTTP handler, but the issue was very similar.
> probably is correct
Yeah I don't know...
Even more so if a database is involved (which is generally the case), because odds are the transaction just gets rolled back and there's basically nothing that could be corrupted.
Something like: https://github.com/go-chi/chi/blob/master/middleware/recover...
1. Catch the panic/exception.
2. Track the rate of these panics or exceptions. If it is too high some data structure has probably been corrupted or some lock has been poisoned. If a lot of requests are failing abort.
And ideally: 3 signal that you are in a degraded state so that some external process can gracefully drain your traffic and restart you. Although very few people have this level of self-healing infrastructure set up.
Whether or not they are forgetting aside, this is Google’s style guide for code bases in Google. I don’t think non-Google Go programmers were a consideration for them.
On a broader note, it seems as though anytime Google publishes something people interpret it as “industry standard” (see their C++ style guide) and apply it to their non-Google projects. I personally don’t see this as healthy.