Go has some great properties that the “Good” section is about. But when it comes to using this language not for creating APIs or network servers (for which it was developed), but for implementing business logic, I consider Go too clumsy and inconvenient. Although even within the framework of network programming, there are quite a few pitfalls in the language architecture as well as in the implementation, which makes Go dangerous, despite its apparent simplicity.
I decided to write this article after applying Go in one of the minor projects. I actively used this language in a previous project when writing a proxy (HTTP and TCP) for the SaaS service. I liked working on the network part (I studied the language along the way), but the accounting and billing parts were hard for me. My minor project was a simple API, and it seemed to me that using Go I could quickly write it. But, as you know, many projects are more difficult as a result than expected. I had to implement data processing for calculating statistics, and again I ran into the disadvantages of Go. This article is a story about the troubles I have experienced.
About me: I like statically typed languages. My first significant programs were written in Pascal. In the early 1990s, I used Ada and C / C ++, then switched to Java, then to Scala (there was a bit of Go between them), and recently began to learn Rust. I also wrote a large amount of JavaScript code, because until recently, only this language was available in browsers. I feel uncomfortable when working with dynamically typed languages and I try to limit their use to simple scripts. I like the imperative, functional and object-oriented approaches.
The article is long, so you can navigate by content:
This is a fact: if you know all kinds of programming languages, you can use the Tour of Go to learn the Go syntax for a couple of hours, and after a couple of days, start writing real programs. Check out Effective Go , learn the standard library , play around with the Gorilla web tools or the Go kit , and become a very decent Go developer.
It's all about the comprehensive simplicity of the language. When I started to learn Go, it reminded me of the times of my acquaintance with Java : it’s also a simple and rich language, a standard library with no frills. Learning Go was an enjoyable experience against the backdrop of today's heavy Java environment. Due to the simplicity of the language, the code on Go is very easy to read, even if the error-handling blocks somewhat complicate the listing (see below).
But this simplicity may be false. As Rob Pike said, “ simplicity is complicated, ” and below we will see that a large number of pitfalls await you, and that simplicity and minimalism impede writing a DRY code.
Perhaps the gorutinas are the best feature of Go. These are small calculation threads separated from the OS calculation threads.
When a Go program executes what looks like a blocking I / O operation, the Go runtime suspends the gorutin and returns to it when an event occurs that signals the availability of some result. In the meantime, a queue of other gorutin execution is being drawn up. This gives us the scaling capabilities that are characteristic of asynchronous programming, as part of synchronous programming.
Gorthinas also consume few resources: their stack can grow and shrink at your will , so you can easily have hundreds, and even thousands of them.
Once I ran into a leak of gorutin in the application: before they were completed, they were expecting the closure of the channel, but that did not close (standard deadlock problem). The process, without any reason, consumed 90% of the processor's resources, and when studying expvars, it turned out that 600,000 gorutines are standing still! I guess the processor was occupied by their dispatcher.
Of course, a system of actors like Akka can process millions of actors without any effort, in part because they do not have a stack. But with the help of Gorutin it is much easier to create highly parallelized applications that operate according to the request-response scheme (for example, HTTP API).
Channels are designed for interaction between gorutin: they provide a convenient model for sending and receiving data with gorutins, without relying on unreliable low-level synchronization primitives. Channels have their own set of usage patterns .
However, the channels should be used deliberately, because if the size is chosen incorrectly (the channels are not buffered by definition) deadlocks can occur . Below, we will see that due to the lack of immutability in Go, the use of channels does not prevent the race condition.
The standard Go library is really great, especially when it comes to the development of network protocols or APIs: it has an HTTP client and server, encryption, archiving formats, compression, sending letters, and so on. There is even an HTML parser and a fairly powerful template engine that allows you to create text and HTML with automatic escaping to protect against XSS (for example, used in Hugo ).
The various APIs are generally simple and easy to understand. Although sometimes they may look overly simplistic: partly because of the gorutin model, that is, we need to take care of operations that seem synchronous, and partly because several universal functions can replace many specialized ones, as I recently found out in time calculations .
Go is compiled into native executables. Many programmers come to Go from Python, Ruby or Node.js. They are simply blown away by this possibility, since the server is capable of handling a huge number of simultaneous requests. The same can be said about those who switch from interpreted languages without parallelization (Node.js) or with global interpreter locking. Combined with the simplicity of the language, this contributes to Go's popularity.
But compared to Java, the situation in performance benchmarks is not so straightforward. But Go is better than Java in terms of memory usage and garbage collection.
The garbage collector in Go is designed to take into account the priority of delay and avoidance of large pauses, which is especially important for servers. It may consume more processor resources, but in a horizontally scalable architecture this is easily solved by adding machines. Do not forget that Go was created in Google, which barely have enough resources!
Compared to Java, the garbage collector in Go does less work: structure slices are contiguous arrays of structures, not arrays of pointers, as in Java. Maps in Go also use small arrays as buckets. As a result, the garbage collector has to do less work, which improves the locality of the processor cache.
Also, Go is better than Java when used via the command line: given the native nature of the executable files, the Go program has no startup cost, unlike Java, which first has to load and compile bytecode.
. Go , . gofmt
- .
, gofmt
, Go, !
Python, Ruby Node.js, — . , Docker, .
Go expvar
, , . , — — HTTP- . Java JMX, .
defer , finally
Java: , , . , defer
. , , :
file, err := os.Open(fileName)
if err != nil {
return
}
defer file.Close()
// use file, we don't have to think about closing it anymore
, try-with-resource Java , Rust , , Go , .
, , , , (persisted object identifiers) string
long
. , , , .
Go — , , . , . :
type UserId string // <-- new type
type ProductId string
func AddProduct(userId UserId, productId ProductId) {}
func main() {
userId := UserId("some-user-id")
productId := ProductId("some-product-id")
// Right order: all fine
AddProduct(userId, productId)
// Wrong order: would compile with raw strings
AddProduct(productId, userId)
// Compilation errors:
// cannot use productId (type ProductId) as type UserId in argument to AddProduct
// cannot use userId (type UserId) as type ProductId in argument to AddProduct
}
, , / .
Less is exponentially more , Google Go ++, Newsqueak, 1980-. Go Plan9, , Go Bell Labs 1980-.
Go Plan9. LLVM, ? , - , ? , , ?
Go , , ( Plan9?), , 1990- 2000-. Go , .
? . ? , , - ++! , , map , .
Go ++, , . . , . - , , . , . , Rust .
, Go Python Ruby. //. , . Go Docker, DevOps. Kubernetes .
Go Java Scala Rust: , ( «»).
, Java Scala Rust, , : , . Go .
, , , , Scala Kotlin, Rust. : , , .
Go — , , :
: , « nil-».
Go , .
iota
, , , . , iota , . , .
, , switch
, .
Go : var x = "foo"
x := "foo"
. ?
, var
( ), var x string
, :=
. , :=
:
var:
var x, err1 = SomeFunction()
if (err1 != nil) {
return nil
}
var y, err2 = SomeOtherFunction()
if (err2 != nil) {
return nil
}
C:=:
x, err := SomeFunction()
if (err != nil) {
return nil
}
y, err := SomeOtherFunction()
if (err != nil) {
return nil
}
:=
«» . , :=
( ) =
():
foo := "bar"
if someCondition {
foo := "baz"
doSomething(foo)
}
// foo == "bar" even if "someCondition" is true
Go . , " " . , , (language implementors).
, . io.File
, Effective Go:
type File struct {
*file // os specific
}
func (f *File) Name() string {
return f.name
}
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
func (f *File) checkValid(op string) error {
if f == nil {
return ErrInvalid
}
return nil
}
?
, , File
, . - Open
Create
. — , .
, , - . html.Template
: .
map
: , - , :
var m1 = map[string]string{} // empty map
var m0 map[string]string // zero map (nil)
println(len(m1)) // outputs '0'
println(len(m0)) // outputs '0'
println(m1["foo"]) // outputs ''
println(m0["foo"]) // outputs ''
m1["foo"] = "bar" // ok
m0["foo"] = "bar" // panics!
, map
, , - .
, , , -, . .
"Why Go gets exceptions right" , Go, error
. , , Java ( , Go , ). , panic
« , ».
"Defer, panic and recover" , ( ), : « JSON- Go».
, JSON- , . unmarshal
, , « », ( ).
Java- try / catch (DecodingException ex)
. Go , , .
: JSON-, .
(Jaana Dogan, aka JBD), Google, Twitter:
, Go . .
— JBD (@rakyll) March 21, 2018
: Go . — .
Go GOPATH
. ? -, . ? . «».
, GOPATH
, . , ? GOPATH
.
. , , (lock files) Git sha1, .
: dep
, . (git-) (version solver), . , . , GOPATH
.
dep
, vgo
, Google, Go .
Go . , , GOPATH
...
.
Go : struct
, const
. Go , , , .
, . (map, ) , , , .
:
type S struct {
A string
B []string
}
func main() {
x := S{"x-A", []string{"x-B"}}
y := x // copy the struct
y.A = "y-A"
y.B[0] = "y-B"
fmt.Println(x, y)
// Outputs "{x-A [y-B]} {y-A [y-B]}" -- x was modified!
}
, , .
, () (reflection), , . , . Go Clone
, .
. "Go slices: usage and internals", , . , , - (view), . copy()
, .
copy()
, append:
, . append
, . , .
, , , :
func doStuff(value []string) {
fmt.Printf("value=%v\n", value)
value2 := value[:]
value2 = append(value2, "b")
fmt.Printf("value=%v, value2=%v\n", value, value2)
value2[0] = "z"
fmt.Printf("value=%v, value2=%v\n", value, value2)
}
func main() {
slice1 := []string{"a"} // length 1, capacity 1
doStuff(slice1)
// Output:
// value=[a] -- ok
// value=[a], value2=[a b] -- ok: value unchanged, value2 updated
// value=[a], value2=[z b] -- ok: value unchanged, value2 updated
slice10 := make([]string, 1, 10) // length 1, capacity 10
slice10[0] = "a"
doStuff(slice10)
// Output:
// value=[a] -- ok
// value=[a], value2=[a b] -- ok: value unchanged, value2 updated
// value=[z], value2=[z b] -- WTF?!? value changed???
}
, Go . , : . , ( ) , , , , map, . struct
: , , , .
, , . - map.
: Go , . , , , . production - runtime-, , .
Go , :
someData, err := SomeFunction()
if err != nil {
return err;
}
Go , ( ), , , error
. , -, Go .
«, », , .
: , , , io.Reader
:
len, err := reader.Read(bytes)
if err != nil {
if err == io.EOF {
// All good, end of file
} else {
return err
}
}
"Error has values" . :
type errWriter struct {
w io.Writer
err error
}
func (ew *errWriter) write(buf []byte) {
if ew.err != nil {
return // Write nothing if we already errored-out
}
_, ew.err = ew.w.Write(buf)
}
func doIt(fd io.Writer) {
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
return ew.err
}
}
, , , . , , , , , . , ? - Go.
Rust : ( , Go), , , Result<T, Error>
. Rust 1.0 try!
, , . .
Rust Go, , , Go , .
Reddit jmickeyd nil
, . :
type Explodes interface {
Bang()
Boom()
}
// Type Bomb implements Explodes
type Bomb struct {}
func (*Bomb) Bang() {}
func (Bomb) Boom() {}
func main() {
var bomb *Bomb = nil
var explodes Explodes = bomb
println(bomb, explodes) // '0x0 (0x10a7060,0x0)'
if explodes != nil {
explodes.Bang() // works fine
explodes.Boom() // panic: value method main.Bomb.Boom called using nil *Bomb pointer
}
}
, explodes
nil
, Boom
, Bang
. ? println:
bomb
0x0
, — nil
, explodes
nil (0x10a7060,0x0)
.
— (method dispatch table) Bomb
Explodes
, — Explodes
, nil
.
Bang
, Bomb:
. Boom
, , .
, var explodes Explodes = nil
, != nil
.
? nil
, nil
, … , !
if explodes != nil && !reflect.ValueOf(explodes).IsNil() {
explodes.Bang() // works fine
explodes.Boom() // works fine
}
? Tour of Go
, : « , , nil-, nil».
, . , - .
JSON Go, - :
type User struct {
Id string `json:"id"`
Email string `json:"email"`
Name string `json:"name,omitempty"`
}
struct, . « (reflection interface) struct’, ». , runtime . runtime, .
Go , DSL, runtime?
, . Go:
type Test struct {
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
}
: JSON? Go UpperCamelCase
, , JSON lowerCamelCase snake_case
. .
/ JSON , Jackson Java. , , Docker API UpperCamelCase
: API.
, , Go: … , , . , , .
, map, . map [string]MyStruct
. , .
Go . , , . interface{}
. runtime . Java- JSE 5.0 2004 .
"Less is exponentially more" - « » , , . , ( Scala ), : .
, «» — Go.
Go , - slice map. Go , . : interface{}
, .
sync.Map
— map (thread contention) map :
type MetricValue struct {
Value float64
Time time.Time
}
func main() {
metric := MetricValue{
Value: 1.0,
Time: time.Now(),
}
// Store a value
m0 := map[string]MetricValue{}
m0["foo"] = metric
m1 := sync.Map{}
m1.Store("foo", metric) // not type-checked
// Load a value and print its square
foo0 := m0["foo"].Value // rely on zero-value hack if not present
fmt.Printf("Foo square = %f\n", math.Pow(foo0, 2))
foo1 := 0.0
if x, ok := m1.Load("foo"); ok { // have to make sure it's present (not bad, actually)
foo1 = x.(MetricValue).Value // cast interface{} value
}
fmt.Printf("Foo square = %f\n", math.Pow(foo1, 2))
// Sum all elements
sum0 := 0.0
for _, v := range m0 { // built-in range iteration on map
sum0 += v.Value
}
fmt.Printf("Sum = %f\n", sum0)
sum1 := 0.0
m1.Range(func(key, value interface{}) bool { // no 'range' for you! Provide a function
sum1 += value.(MetricValue).Value // with untyped interface{} parameters
return true // continue iteration
})
fmt.Printf("Sum = %f\n", sum1)
}
, Go : map. — Go :
range
,, .
import "sort"
type Person struct {
Name string
Age int
}
// ByAge implements sort.Interface for []Person based on the Age field.
type ByAge []Person
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func SortPeople(people []Person) {
sort.Sort(ByAge(people))
}
… ? ByAge
, , ( « ») .
, , , — Less
, - (domain-dependent). — , , Go . , . .
: sort.Slice. , (!) -, .
, Go , « Go», , (downcasting) interface{}
...
. , , ?
Go 1.4 go:generate
. , «» //go:generate
: « //
go:generate
». , .
:
String()
., , Makefile‘
, , .
, , Scala Rust, ( ), AST . Stringer Go AST. Java , .
, , «» , , , , .
, , Go / , ?
, - Go. , , , , .
Go API , . , -, .
, Go: , C C++. Rust , , . , Rust — , , .
, , , Rust Go , Rust — , , . , . Rust - ORM’. , « , , , ».
/service mesh , Sozu, Rust. Buoyant ( Linkerd) Kubernetes-service mesh Conduit, Go (, Kubernetes-), Rust, , — .
Swift C C++. Apple-, Linux API Netty.
, — . . , Go, , .
: . Hackernews ( ) /r/programming ( ), Twitter.
, , ( /r/golang/), . , /r/rust Rust. - : « , — . ».
. , , , , , , , .
, golang.org, Go, «, , ».
, . , ( ). !
Source: https://habr.com/ru/post/353790/
All Articles