Practical Go: Real-world advice for writing maintainable Go programs
dave.cheney.net
dave.cheney.net
> In this case consider conf or maybe c will do if the lifetime of the variable is short enough.
This seems petty. Is it really that problematic to type out a few extra characters?
> Functions should do one thing only. ... In addition to be easier to comprehend, smaller functions are easier to test in isolation, and now you’ve isolated the orthogonal code into its own function, its name may be all the documentation required.
Using single-caller functions as a substitute for comments makes the workings of a specific operation much harder to follow, as you have to jump around the source to understand its effects.
A long function is easier to understand than an exploded one.
Also tests should target specific operations (aka functional tests), not every single function in the program.
EDIT: Every function you add becomes part of your internal API. Any API, exported or not, should comprise a cohesive collection.
This is a pretty controversial position, and quite situational in my opinion. I absolutely agree that having to hop all over the source to understand something is frustrating, but that doesn't mean that very long functions are the right solution. Some combination of reasonably named helper methods and a function flow that makes the logic easy to parse should be the goal; either end of the spectrum is a problem.
Single-caller functions attract other callers over time, gain backwards-incompatible features, and result in regressions.
It’s a bummer that more languages don’t support them, though you can get there with lambdas too, sometimes at the cost of more syntax.
One of the (few) things I like about Javascript is the ability to define a closure anywhere in the containing function, so it appears in the order of operations:
function f() {
setTimeout(fDing, 2000);
g();
function fDing() {
...
}
}I am curious with this approach though...you're nesting behavior and/or logic, doesn't this further obscure the meaning of the code and contribute more to the need to jump around the source in order to figure it out?
function fDing() {
...
}
Honestly with modern JS I am not sure this feature is that great. Looks more like a code smell these days imo.Edit: Formatting
You write the test (where any is needed) for the outer function.
Edit: the start of the discussion was about using nested functions to decompose what otherwise would have been “unpartitioned” long functions. Such blocks of code nested within a long function would not be possible to unit test, either.
Unit tests, rather than integration tests, are usually bogus, anyway, though.
Your employer doesn’t want you testing getters, anyway, but rather features. Unit test fanatics need to stop.
I guess unit tests were useful for C++, when it was constantly crashing everything :-(
C++ and its legacy need to ride off into the sunset, already.
The Go Programming Language book (Kernighan and Donovan) has some examples of them.
Pascal, like Algol, had nested subroutines for decomposing longer operations without leaking the details.
Nested functions is one of the things I like about JavaScript as well.
For better or for worse, taking advantage of that forces you to keep source files fairly short and cohesive in functionality.
If you just call it from the enclosing function it's fine.
> If a function is only called from a single place, consider inlining it.
> If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that.
> If there are multiple versions of a function, consider making a single function with more, possibly defaulted, parameters.
> If the work is close to purely functional, with few references to global state, try to make it completely functional.
> Try to use const on both parameters and functions when the function really must be used in multiple places.
> Minimize control flow complexity and "area under ifs", favoring consistent execution paths and times over "optimally" avoiding unnecessary work.
As long as what are doing is approximately the same type of thing, it is fine to do a lot of things without breaking readability.
Well, that's extremely good, and standard, advice.
https://en.wikipedia.org/wiki/Single_responsibility_principl...
GetData()
FormatData()
UploadData()
Those could do one thing but could be called at several points within a larger program, which i think you are fine with. Or they could only be called once in which case i think you are saying it might make sense to just inline them.
If I understand you correctly, then I agree.
Though it also depends on whether I'm doing something familiar or something new, if I'm doing something new I might split things up more to help me conceptualize the problem. But when I'm just making a quick CLI tool, I might put it all in main first and only split it up as needed.
The function:
func ManyThing(i int) int {
fmt.Println(i)
return i+1
}
does two things, and it's two lines long. The function tcp_send_message_locked (https://github.com/torvalds/linux/blob/master/net/ipv4/tcp.c...) does one thing at it's 261 lines long.Shorter code is _indicative of_ orthogonality (what he asks you to break on)__but_ it is not the same thing.
Well-known abbreviations are fine, like "iter" and "prev", but single-letter variable names notoriously impede readability for me.
If the name is more than a few characters long, it starts to become non-instantaneous to recognize it. Things get much easier to follow with visually-instantly-recognizable symbols.
So in conditions where a variable is used over a short area in the code (or where it's used _constantly_ over a wide area), I prefer short variables.
id identifier
i loop counter
j inner loop counter
c general purpose non loop counter
a, b, comparator methods
k, v, key value setters, array iteration
e, event, error or exception. Contextual rule but rarely collides.
Other than those I pretty much always write entire full word names. If it's abbreviated or shorthand it likely sounds funny in my head, or it leads to ambiguation. If I need to even ask / recall for a split second it's not good enough.
Programmers incorrectly place far too much weight on time to type. I write like ten lines of code on a good day. It's just not important. I want readable code that is clear and concise as possible. Every branch, block or shorthand variable raises a question I have to think about before moving on.
Clever code is why I call ten lines of code a good day. It sure as shit isn't my typing speed.
If it's less "boilerplate" code, I'd go for more descriptive names. I think.
I like the sorta described "rule" in this document; the longer the variable is used, the more descriptive it should be.
As an extreme example, scalaz is similarly a very specialized DSL. Or in my personal experience, functional constructions like map, flatMap, foldLeft and reduce (which I never learned in school).
Yeah, I noticed that for myself, too. so instead of i I might use frame_index, or whatever it "actually is". Up to a certain length it seems faster to just read what is there, without an additional mental translation step. But to be honest, I just do it because I like it.
Up to four or five single letter variables is pretty trivial to remember. Especially when three of those are i, j, and k. More than 6 or 7 rapidly becomes painful. But if you are manipulating 6 or 7 variables _in the same scope_ you are doing to much.
I also agree with Pikes comments about typography here: http://doc.cat-v.org/bell_labs/pikestyle
Also names vary with there contextual scope. Larger scopes mean longer names generally. Russ Cox gives a succinct description here: https://research.swtch.com/names
A name's length should not exceed its information content.
For a local variable, the name i conveys as much information as
index or idx and is quicker to read. Similarly, i and j are a
better pair of names for index variables than i1 and i2
(or, worse, index1 and index2), because they are easier to
tell apart when skimming the program. Global names must convey
relatively more information, because they appear in a larger
variety of contexts. Even so, a short, precise name can say more
than a long-winded one: compare acquire and take_ownership.
Make every name tell.
Variables should generally have short scope (we don't want a lot of global, or even package level variables). So _variable names_ in particular should be short.This is only true because i is a specific, common abbreviation for index. When writing arbitrary glue code, a single letter variable would be a meaningless abbreviation without shared context. If you encounter "i" and it doesn't mean "index of a for loop", you're going to be taking additional time parsing meaning.
`usersList.stream().forEach(u -> someSet.add(u))`
It's immediately obvious that u is a user in usersList. I realize that it's debatable if u is really more readable than spelling out user, but I prefer it, and I don't think anyone is going to be confused by it. If the chain does get really long, also, I will spell it out explicitly.
For Javascript at least that would work.
`usersList.stream().forEach(someSet::add)`
Many times your function should be longer, rather than shorter.
Short methods and functions used just for the sake of being short just move the complexity of understanding in the interaction between them, making logic harder to follow an algorithm when it could have been all in the same place (for related functionality of course, I don't advocate having a function do 2 different irrelevant things).
configuration := NewConfiguration()
configuration[word] = parameters[values[line][column]] - parameters[values[column][line]]
vs conf := NewConfig()
conf[w] = params[vals[i][j]] - params[vals[j][i]]
It takes your brain more time to parse the first line. Now, there is an obvious limit, this is probably too much c[w] = p[v[i][j]] - p[v[j][i]]
unless maybe the scope of the vars is very limited. conf[w] = params[vals[line][column]] - params[vals[column][line]]
or even: conf[w] = params[vals[l][c]] - params[vals[c][l]]
if you really want to save a couple of characters. Confusing which loop index is indexing which dimension is far too easy.Take following functions, for example:
// Auth check if user credential is existed in bla, bla...
// ... (many more explanation)
Auth(user User, authProvider AuthProvider) bool
// AuthDefault check user against the default **AuthProvider**
AuthDefault(user User) bool {
return Auth(user, new(DefaultAuthProvider))
}
If only the AuthProvider in the second function's godoc be a link to the first one, we don't have to repeat the explanation for the second one. Dev will be able to discover the explanation easily via their IDE. This alone will be very helpful for the maintainability of any big projects.I get so much more done in Go, have fewer maintenance issues, and more frequently collaborate with other folks/contribute to other projects.
> I've used a good number of languages professionally at this point, and the Go orthodoxy is easily the most off-putting I've ever encountered.
I could -- and do -- say the same about the Java ecosystem.
I'm confused about your assertion that the Java ecosystem has a similar problem, though, because my sense is both that it has a much less identifiable orthodoxy by virtue of being so widely used across so many domains, and that the new guard, such as it is, is very much in favor of creating libraries and utilities that favor simpler implementations and interfaces contra its "architecture astronaut" baggage.
I guess my remarks could be rearranged as: In general, I find Java, and the JVM friend Scala, to worship Abstraction over simplicity. Complexity is constantly confused for convenience, and that makes me sad. The number of files I need to read to understand _any_ piece of Go, I could likely count on one hand, if even. For the JVM-based approach: dozens, if not more.
Abstractions _in theory_ are great: they reduce the cognitive burden, they simplify behavior, make it easier to rationalize and cast judgement; But in practice, that just isn't true. You _always_ need to peel away the abstraction.
In Go, countless times, I've found myself reading the standard library implementation. Is this good? No, of course not. Ideally, as a consumer, I never need to look under the curtain. Things should just work. But that tends to never be true, and looking under the curtain is an important aspect of computing (c. 1970-1999) that permeates everything we do.
Go makes it really easy to look under the hood, see what's happening, and more-or-less instantly achieve clarity. The only other languages I've ever used that came close were C and C++, and history has demonstrated how well that's worked out.
This code was written in really roundabout way in Java tradition where e.g. setName() would call getName() -> initName() -> initClassName() -> getDefaultClassName() -> initDefaultClassName() just to set one damn string value. And believe me this getName() function would get called grand total of one time in whole project.
And it was possible for me to do so because learning and writing Go made me confident that straightforward code is not a sign of junior/inexperienced developer. Because in Java it will be exactly that if you do not bury the actual logic in ten levels of abstract crap.
And I agree - it has become the prime example of "you're holding it wrong" school of programming language design and apologetics.
If someone thinks since they have developed feature and put lot of hardwork in it so language maintainers have to merge it. I am sure they would have no option but to invent their own language.
Language improvement proposals aren't taken in just with words on paper.
I'd have more respect if they just came out and said, this style cause hey we don't want conflicts over style and since our language our way or highway. And this feature is abused a lot in other languages (we think) so we omitted it.
[1] I hate that programmers habitually lie about technical issues, especially to management and others. Because those people are as dumb as programmers think. It's why your non technical manager doesn't trust you.
I disagree with them, but here we are.
I've been slightly tempted at times to make something with markdown but never gotten enthusiastic enough to put the time. Plus, markdown is technically a bit too powerful, so I'd want to cut it back down, and before you know it I'm inflicting the 3,124th custom markdown on the world. Really I just would like bold, italic, monospace, identifier linking as you say, and numbered and bulleted lists not in the unformatted text wrapper.
I find them distracting when I'm reading the code but I don't want them not to exist either.
EDIT: Another thing I'd love to be able to do is 'tag' a variable with a synonym and have that appear next to it eveywhere.
so $strCstAcctRec (I wish that was fake but it's pretty much straight out the codebase I inherited..) would show up as $strCstAcctRec (CustomerAccountRecord).
There are a bunch of ergonomic features like that I've considered over the years, I might at some point try implementing some of them as an intellij plugin.
Your criticisms re refactorings and links are on point though.
Trade-offs.
Documentation for most Go libraries I've come across has been pathetic compared to similar things in Python or PHP.
edit: Compare to something like Racket: https://docs.racket-lang.org/plot/intro.html?q=graph#%28part...
EDIT: Racket shares many of the same problems as Python. Dynamic languages need more documentation than static languages, yet they (or rather, their users) often fail to deliver.
http://flask.pocoo.org/docs/1.0/api/#api
to this:
https://golang.org/pkg/net/http/
If the source weren't hyperlinked, I'd be lost on the latter.
edit: I love Racket's documentation. I've been using it for so many in-house things because I can just read what the function does in English--instead of having to run experiments or dig through someone else's code.
The distinction I see is that someone put a lot more care into Flask's documentation (including the theme, which is actually a little hard to read since it renders certain things in small italic font), which is great but not indicative of the broader ecosystem. The most significant distinction wrt readability is that the Go page has types for _every_ parameter with links to the type definition. For all of the care put into the Flask doc, you still see things like this all over:
> view_func – the function to call when serving a request to the provided endpoint
I have no idea what the signature of that callback is. Meanwhile Go has https://godoc.org/net/http#HandleFunc which clearly shows the type for the handler callback (with links to types):
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
> I love Racket's documentation. I've been using it for so many in-house things because I can just read what the function does in English--instead of having to run experiments or dig through someone else's code.I tried to use Racket (it looks really neat), but I just spent so much time trying to figure out what to pass into each function. It was nearly impossible since types are often absent. That's just not how I want to spend my free time. :(
Ah yes, the nice godoc feature which only got added to the python stdlib in checks note 2001.
The manuals provided with Python are great when compared with quite a few alternatives, some of them require to go buy a book to properly learn them.
For example: https://github.com/airbnb/javascript
One thing I disagree with is the remark about having fewer, big packages. Though conceptually I agree that avoiding having too many public APIs that aren't widely used makes sense, in practice--at least on the types of projects I tend to work on--I find that directing people to split things into a few packages forces them to think about a decoupled design with good APIs between the components. This could certainly be done with discipline inside a single package, but unless everyone working on the codebase is very diligent about this it's easy for abstraction leaks to creep in.
Ultimately it's a judgment call, but I think an earlier paragraph (copied below) is far more important than optimizing on having fewer packages or fewer exported types and functions, especially (as is also pointed out in the doc) you can use `internal` subdirectories to make APIs project-private if you are writing a library that is consumed by other projects, as opposed a service.
> A good Go package should strive to have a low degree of source level coupling such that, as the project grows, changes to one package do not cascade across the code-base. These stop-the-world refactorings place a hard limit on the rate of change in a code base and thus the productivity of the members working in that code-base.
However, I feel myself to be in the minority on this one. To which I basically shrug and write my code with lots of relatively small packages. It really only affects code you're working on, or that your team is working on. Things you pull in as libraries and have no direct interaction with don't matter too much on this front.
In general I found larger packages tend to produce more direct / pragmatic code with less indirection which is usually easier to understand, even though it also feels wrong to me from a theoretical standpoint.
I solve that with describing the context of the package in the opening prose section. I think this section is underused in every language community I've seen, even though all the automated doc systems support a top-level summary/contextualization/etc.
I think that an advantage of small packages is precisely that it is easier to understand if you're not familiar with the structure yet, by isolating how much structure you have to understand. Large packages, or languages with loose barriers, force you to eat huge swathes of the project at once to understand the code. Small packages are both bite-sized on their own, and also the packages that use the small packages often allow you to gloss over the used package while you're learning that package.
I don't end up with much indirection caused by the package boundaries. (Where there are interfaces I would usually have them anyhow for testing purposes.)
This tends for me to be one of those places where I wonder if I'm just doing something really different than most people. Another example is all the many people over the years who have tried to convince me, with varying level of politeness, that testing code should only use the external interfaces, or dire consequences like having to rewrite all the testing code if I tweak the package will happen. All my testing code uses private interfaces unless there's a really good reason not to, and maybe once in ten years have I had a serious rewrite of the test code come up. The threatened problems don't seem to happen to me. (And I am pretty sure I'd notice them if they did, although I guess I can't completely discount the possibility that I'm just too oblivious somehow.)
Certainly, if they cause you trouble, either because your style is different, or your problem domain is different, or whatever reason, don't use small packages.
For example, a common convention is to avoid redundancy. Let's say you have a package "builder". Your encouraged to have "builder.New()" as a constructor, not "builder.NewBuilder()". Fine. Now let's say you need two types of builders: One for building "schemas", one for "objects". Your original constructor now needs to be something like "builder.NewSchemaBuilder()" ("NewSchema" would be confusing, since the function creates builders, not schemas). Or maybe turn it into a verb: "builder.BuildSchema()", "builder.BuildObject()". Part of this is due to the lack of statics, or we could've had "builder.Schema.Build()" or something.
You can also split the package up into two packages, one for schema building and one for objects. (Naming here can be tricky, too. Will it be "schemabuilder.New()", or "schemas.NewBuilder()"?) But if the two builders need to share types, you may end up refactoring the common types into a common package that exists only because of the split.
I have a concrete example of this right now for a small query language. The language has data types (common interface Type) and values (Value). Values can tell you their type. Types can construct values. But they're two distinct sets of declarations. The type implementations and the value implementations can't easily be split across separate packages without having a common package that exists only to hold the shared types. It'd be nice to have "types.String" (in types/string.go) and "values.String" (values/string.go), not "lang.StringType" (lang/string_type.go) and "lang.String" (lang/string.go).
Having a namespace option would help here. Everything could be in one package, but under separate namespaces.
I wonder though why there is so little emphasis on how important interfaces are in Go. I mean, section 4.5 talks about it a bit, but in my experience, this mistake is made far too often.
This is a real problem in Go these days, people use one and two letter vars quite a bit which makes reading code you’re not familiar with practically impossible. On one project we simply switched to semi-java length like names since our customers couldn’t read the code.
There is no such thing as "Java length like names", or whatever. There are good practices and bad practices and they are universal.
I happen to be learning Go coming from a C background. I find "The Go Programming Language" by Donovan and Kernighan exemplary and the decades of experience that went into the language really show.
If you look past the book, and directly at the STL, you'll find a common example: the "fmt" library, shortened to save three letters. Or compare the verbosity of these two examples from language docs, and the length and descriptiveness of variable names in them: https://docs.oracle.com/javase/tutorial/essential/io/cl.html to https://golang.org/pkg/bufio/#example_Scanner_emptyFinalToke...
I agree, it's a great language. These naming conventions are part of its developers and community.
There is no issue with having short names to describe well-known entities part of the language, or trivial code.
Your example from pkg.bufio is trivial and but still the variables names are meaningful. There is a difference between clearly and meaningful, and verbose.
Your example from Java is not very different.
I feel that the point is not being understood here.
If there was one true way to do it, everybody would adopt it (unless you consider some communities are just plain dumb).
> 2.1. Choose identifiers for clarity, not brevity
Cosmetic considerations like snake or camel case are irrelevant.
The advice above is standard good practice that is not dependent on the language used.
First off, OP is talking about a trend in the community to favor shorter and shorter names. He didn't say you need to code that way, but there is an increasing chance that the programmers you work with, or the libraries you depend on, will have adopted this convention.
Second, unless you are coding in a vacuum or starting a brand new project, you're usually expected to follow the conventions of an existing codebase. These were likely influenced by conventions in the community, which often are set by conventions in the standard library, which have much to do with the language.
What's the best way to handle a situation where you use two different types for the same data? e.g.
var usersMap map[string]*User
var usersList []*User var usersByUsername map[string]*User
var usersToEmail []*User type Users struct{
dict map[string]*User
list []*User
}
and attach methods to both access the data and coordinatedly update both forms, e.g. func (us *Users) byID(id string) *User { ... }
func (us *Users) sortedByID() []*User { ... }
func (us *Users) addUser(id string, nu *User) {
dict[id] = nu
list = append(list, nu)
...
}> 8.3. Never start a goroutine without [knowing] when it will stop.
100% agreed with the concept, but even the final example is flawed. That'll unblock and continue immediately after `close(stop)`, without being able to do two important things: it can't tell you when it's done shutting down, and it can't tell you if it encountered an error. Fixing this makes it even more complex.
Both of those are common and often necessary things to do - e.g. if you need to drain traffic, you need to wait until it's actually done before killing your process. Same goes for closing files (you might need to flush / sync first), OS resources that might survive your process, etc. This example will just kill your process as soon as everyone has been informed that it should stop, not when they're done.
---
Yeah it's a bit nitpicky, but it's even called out as "asking a http.Server to shut down is a little involved, so I’ve spun that logic out into a helper function". Except that the helper function can't ensure the involved server shutdown process actually shut down the server. That's a dangerous pattern to encourage, and it's an over-simplification that's absolutely rampant in go tutorials and code I encounter.
The workgroup lib he links doesn't (arguably?) have this flaw, but it has some strange / incomplete error handling details... E.g. if you Add 5 funcs, you'll only receive the error result of the first func to complete, not the first err or anything more predictable. If the first succeeds but all the rest fail, you'll see no error. Go's errgroup does "first non-nil error" at least, but you can still only get one: https://godoc.org/golang.org/x/sync/errgroup
The final example actually does both of these things already, but maybe you're confusing the purposes of some of the channels?
The loop at the end of main will wait until it receives one (possibly nil) error value from every task that was launched. Upon receiving the first error, it will close the done channel, which will then cause any remaining tasks to run their Shutdown function, which will attempt to gracefully shutdown the server. The error of each shutdown function is then returned and printed if non-nil.
There is no immediate termination, and all errors are communicated. It doesn't let the process die until every task has sent its error value.
Obviously, there are two semantics not enforced at compile time: each task must send exactly one error value, and each task is responsible for its own shutdown. If a task sends more than one error, there will be early termination. If a task doesn't shut itself down properly, that is its own fault.
>When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn't exit and waits instead for Shutdown to return.
So no, this writes to the "done" channel essentially immediately once `close(stop)` is called, and ends the process prematurely.
But one refreshing thing is how opinionated the language and the frameworks are refreshing as there’s only one acceptable way to do many things.
Go lets you write One True Code for the most part.
https://golang.org/doc/effective_go.html
Also see back issues of the Go team blog for insight into concrete implementation realities of slices, interface types, GC and more.
Once again, good collection if you have no time to read the book. Anyway, it does not cover other parts like contracts/interfaces, the absence of comments (which can be a good sign) etc. Would recommend checking both CC books if you want to write better and maintainable code.
PS. Anyway, Dave, thank you for the popularization of best practices.
If you've been writing C and Java for years, all simple/readable means is 'familiar'. There are other definitions of simplicity
> errWriter fulfils the io.Writer contract so it can be used to wrap an existing io.Writer. errWriter passes writes through to its underlying writer until an error is detected. From that point on, it discards any writes and returns the previous error.
this is a cute trick.
In some cases it might produce a great result. For example, floating point nan values work roughly like this: a special error values included in the type that can be computed upon without needing to write special control flow until you explicitly need to check the final computed result to see if you got an actual value or the nan value.
but I fear the applicability of this trick in go is limited as it relies on a heap of idiosyncracies belonging to this example:
The chain of potentially error-ing operations that are being performed all involve a value of the same type that can be wrapped. In a less contrived example there would be a variety of different types involved in the sequence of possibly error-ing operations.
The code that is processing the wrapped error-hiding type roughly must not perform side effects when processing an apparently non-erroring computation, since now the error wrapper type is stopping the code from aborting early. It's completely non obvious that this transformation will be correct (ie result in a program with equivalent behaviour to the original program) without reading exactly through the implementation of everything that touches the error wrapper. This isn't helped by go not having a way to tag functions as pure.
The new code now superficially looks like it is wrong as it isn't bothering to check error values of the functions it is calling in the usual way. This will probably cause a spray of linter warnings about failing to handle possible errors in your CI build.
In other languages a much more general way to address this kind of problem could be:
error monads ; or exception handling
Which can be used to short circuit the entire normal control flow.
errWriter is quite literally "let's do monadic error handling, except not just without monads but without generics or reified errors". And thus it can only handle a single source or error type.
Also FWIW monadic error handling != error monads. For instance Rust does the former, but its type system can't express the latter (so there is no monad or functor abstraction on top of Result).
> 5.1. Consider fewer, larger packages
> 5.2. Keep package main small as small as possible
If you have one package, which is the main package, then how are you supposed to keep it small, and at the same time not creating more packages because somehow that is the work of the devil? He is telling me to try to fit everything into one package, but at the same time keep it really small. It is not going to work. Maybe I misunderstood?
That's what it means.
If you want to be able to reuse code in different packages in the future or have more than one compiled binary, you won't be able to put everything into a single "main" package
Someone who has put the hours in will probably stray from all of this advice, and still end up with something beautiful. Meanwhile, for all of the other schmoes who haven't put the hours in, this is just more fuel for screeching about how this name isn't right or that comment is too long.
Dijkstra said GOTO was bad, and now we have callback hell and 10-layer inheritance hierarchies instead.
Throw away goto and globals, replace them with anonymous functions and closures, and the new thing looks an awful damn lot like the old thing.
All I know is that we used to have retrained housewives writing physics programs. Now we expect 10,000 hours to write a halfway decent web form, and halfway decent doesn't happen nearly as often as it should.
Use init() to compile any regular expressions and store them as variables within that package, so that you don't
Split out related code in a folder, remembering that the included code is done in alphabetical order - so shared functions within the same package you should alphabetically make it the first to be included.
You can register components of a larger system by using the init() function to call a function in the base package, and within the calling package (usually main) import the "plugin" using the underscore space prefix (sql database drivers use this method)
Use the new go modules system; its great. But sometimes it doesn't include the latest packages, just modify the go.mod file and anything after the package name remove it and just put master instead.
One last thing with error handling. Everybody does if err!=nil. I tend to go the opposite way, if err==nil and nest these, if err isn't nil i drop out and deal with the error. If it is ok, it will return ok.
Don't you get "staircase of doom"?
err := unsafe1()
if err == nil {
err = unsafe2()
if err == nil {
err = unsafe3()
if err == nil {
err = unsafe4()
if err == nil {
err = unsafe5()
if err == nil {
// ...
}
}
}
}
}No need to resort to init() for regexps, simply use an unexported package level variable:
var validFoo = regexp.MustCompile(...)
The go tool also supports a special package declaration, ending in test, ie., package http_test. This allows your test files to live alongside your code in the same package, however when those tests are compiled they are not part of your package’s code, they live in their own package. This allows you to write your tests as if you were another package calling into your code. This is known as an _external test.I took a lot of his advice when designing V [1].
It's very similar to Go, but it has
- No global state
- Only one declaration style (a := 0)
- No null
- No undefined values
- No err != nil checks (replaced by option types)
- Immutability by default
- Much stricter vfmt
- No runtime
- Cheaper interfaces without dynamic dispatch
[1] http://vlang.io
Where can I find code illustrating V Option types?
They are very simple and combine Rust's Option<T> and Result<T>.
// `http.get()` returns an optional string.
// V optionals combine the features of Rust's Option<T> and Result<T>.
// We must unwrap all optionals with `or`, otherwise V will complain.
s := http.get(API_URL) or {
// `err` is a reserved variable (not a global) that
// contains an error message if there is one
eprintln('Failed to fetch "users.json": $err')
// `or` blocks must end with `return`, `break`, or `continue`
return
}
This looks really handy. Is there somewhere else (outside of V) where I can read about Option types (in the real world, or in theory).How long was this language in development? In what language was the compiler originally written in?
About 2 months later I rewrote it in V. Bootstrapping is fun. Did it 4 times (V1 => V4).
The "Detailed comparison of V and other languages." link on the homepage doesn't seem to work.
No runtime and such a small language!
How does it do threads? Networking? Any story on cross-compiling?
It supports only traditional threads right now (but with automatic locking).
Networking is supported. It uses curl/windows api.
Cross compiling will be top notch, just like with Go.
BTW, if you're going for immutability by default, perhaps swap := and =? Reason being, if variables are immutable by default, assignments should be far less common, so the language should discourage them with more verbose syntax over initialization of immutables.
Besides, C originally used = instead of Algol's := for assignments for the exact opposite reason - because assignments were more common in C code than comparisons - this would be a nice opportunity to fix that mistake.
So yeah, Go will get you there. It will take you a lot longer to get there when you aren't going 600MPH, and you can carry a lot less people, but at least you aren't driving a car like the people using Python. Sure it's probably not going to be as rigorously inspected as the Boeing borrow checker, but Go is at least getting an inspection, whereas your Python code is just going to break down on the side of the road when the problems surface itself because you didn't check the oil light.
To me, Go is a better Python. It's easier to learn, faster, more scalable, and safer, and just as easy and quick to write. It's just not a language for big projects.
Python may leave you in the lurch when it comes to maintainability, but for some domains python is infinitely better than Go.
What the hell does "power level 9000" even mean for a programming language?
Big, successful projects have all been written in Go, Rust, C++, Python, and many more languages. The key is that they've been chosen in cases when it makes sense to choose them. And it has nothing to do with childish "Rust is fast jet. Python is slow car." analogies.
Power level 9000 is a reference to Dragon Ball Z. I haven't watched it but I gather it's up there on the power scale.
I think the analogy makes sense. Sometimes it is better to take a car than a Cessna or a jet, so it doesn't necessarily fail as you think it does.
As an example, let's take this quote:
> So yeah, Go will get you there. It will take you a lot longer to get there when you aren't going 600MPH, and you can carry a lot less people, but at least you aren't driving a car like the people using Python.
If Go was the name of a Cessna, C++ the name of a commercial airliner, and Python the name of a car, then that statement might make _some_ sense. But no, they're programming languages and you can't just throw this analogy out there and then try to have a serious discussion based on it.
Sometimes a Go program performs faster than a C++ or Rust program, and is written in less time. For certain domains, you might get something working (and performing faster) in less time in Python than writing it in Go. Python (and others) can call out to C libraries. Oh, I don't recall your car being able to summon a commercial airliner to pick it up in the middle of highway traffic. But that's how you'd have to explain FFI in this make-believe world of programming language cars.