行く:良い、悪い、怒っている


Goには、「良い」セクションで説明されている優れた機能がいくつかあります。 しかし、APIやネットワークサーバー(開発対象)の作成ではなく、ビジネスロジックの実装にこの言語を使用することになると、Goは厄介で不便だと思います。 ネットワークプログラミングのフレームワーク内でも、言語アーキテクチャと実装の両方に多くの落とし穴があります。


セカンダリプロジェクトの1つでGoを使用した後、この記事を書くことにしました。 SaaSサービスのプロキシ(HTTPおよびTCP)を作成するときに、以前のプロジェクトでこの言語を積極的に使用しました。 私はネットワーク部分での作業が好きでした(途中で言語を学びました)が、会計と請求の部分は私にとって大変でした。 私のマイナーなプロジェクトはシンプルなAPIでしたが、Goを使えばすばやく作成できるように思えました。 しかし、ご存じのとおり、結果として多くのプロジェクトが予想よりも複雑になっています。 統計を計算するためにデータ処理を実装する必要があり、再びGoの欠陥に遭遇しました。 この記事は私のトラブルについての物語です。


私自身について少し:私は静的に型付けされた言語が好きです。 私の最初の重要なプログラムはパスカルで書かれました。 1990年代の初めに、彼はAdaとC / C ++を使用し、次にJavaに切り替え、次にScalaに切り替え(それらの間に少しGoがありました)、最近Rustの勉強を始めました。 また、最近までブラウザでこの言語しか使用できなかったため、多くのJavaScriptコードを作成しました。 動的に型付けされた言語で作業するときは不快に感じ、その使用を単純なスクリプトに制限しようとします。 私は命令的、機能的、オブジェクト指向のアプローチが好きです。


記事は長いので、コンテンツに集中できます。



いいね


簡単に学べます


これは事実です。あらゆる種類のプログラミング言語に精通している場合は、「 Go of Tour 」を使用して、数時間でGo構文を学習し、数日で実際のプログラムの作成を開始できます。 Effective Goをチェックし、 標準ライブラリを調べ、 GorillaまたはGoキットの Webツールで遊んで、非常にまともなGo開発者になってください。


問題は、言語の包括的な単純さです。 Goを学習し始めたとき、Javaでの時間を思い出しました。それは、シンプルでリッチな言語であり、飾り気のない標準ライブラリです。 Learning Goは、今日の厳しいJava環境を背景にした楽しい経験になりました。 言語がシンプルであるため、エラー処理ブロックによってリストが多少複雑になっても、Goコードは非常に読みやすくなります(これについては後で詳しく説明します)。


しかし、この単純さは間違っている可能性があります。 ロブ・パイクが言ったように、「 単純さは複雑です 」と以下で、多くの落とし穴があなたを待っており、その単純さとミニマリズムがあなたがDRYコードを書くのを妨げることを見るでしょう。


ゴルーチンとチャネルを使用したシンプルなマルチスレッドプログラミング


おそらくゴルーチンはGoの最高の機能です。 これらは、コンピューティングOSのスレッドとは別に、コンピューティングの小さなスレッドです。


GoプログラムがブロッキングI / O操作のように見える場合、Goランタイムはゴルーチンを一時停止し、何らかの結果が得られることを知らせるイベントが発生するとゴルーチンに戻ります。 それまでの間、他のゴルーチンのラインナップが作成されています。 これにより、同期プログラミングの一部としての非同期プログラミングの特徴であるスケーラビリティが得られます。


また、ゴルーチンはほとんどリソースを消費しません。スタックは希望応じて増減できるため、問題なく何百、または何千ものゴルーチンを使用できます。


アプリケーションでgoroutineのリークが発生すると、終了する前にチャネルが閉じるのを待っていましたが、閉じませんでした(標準的なデッドロックの問題)。 理由もなく、プロセスはプロセッサのリソースの90%を消費しましたが、 expvarを調べたところ 、現在60万のゴルーチンがアイドル状態になっていることがわかりました! プロセッサはディスパッチャによって占有されていたと思います。


もちろん、Akkaのようなアクターシステムは何百万ものアクターを簡単に処理できます。これは、スタックがないためもあります。 しかし、ゴルーチンの助けを借りれば、要求/応答スキーム(HTTP APIなど)に従って動作する高度に並列化されたアプリケーションを作成するのがはるかに簡単になります。


チャネルは、ゴルーチンが相互作用するように設計されています。信頼性の低い低レベルの同期プリミティブに依存することなく、ゴルーチンによってデータを送受信するための便利なモデルを提供します。 チャネルには、独自の使用パターンのセットがあります


ただし、誤ったサイズの選択(定義によるチャネルはバッファリングされない) により、デッドロックが発生する可能性があるため、チャネルを慎重に使用する必要があります 。 以下では、Goには不変性がないため、チャネルを使用しても競合状態が防止されないことがわかります。


洗練された標準ライブラリ


Go 標準ライブラリは 、特にネットワークプロトコルまたはAPIの開発に関して非常に優れています。HTTPクライアントとサーバー、暗号化、アーカイブ形式、圧縮、電子メールの送信などを備えています。 HTMLパーサーとかなり強力なテンプレートエンジンもあり、自動エスケープを使用してテキストとHTMLを作成し、XSS(たとえば、 Hugoで使用)から保護できます。


さまざまなAPIは一般にシンプルで理解しやすいものです。 時には非常に単純に見えるかもしれませんが、一部はgoroutinモデルのためです。つまり、「同期」と思われる操作を処理する必要があります 。また、時間計算で最近発見したように 、いくつかのユニバーサル関数が多くの特殊な関数を置き換えることができるためです。


性能


Goをネイティブ実行可能ファイルにコンパイルします。 多くのプログラマーは、Python、Ruby、またはNode.jsからGoにアクセスします。 サーバーは膨大な数の同時リクエストを処理できるため、この可能性によって単純に吹き飛ばされます。 同じことは、並列化されていない(Node.js)インタープリター言語から、またはグローバルインタープリターロックを使用してインタープリター言語から切り替える場合にも言えます。 言語のシンプルさと組み合わせて、これはGoの人気に貢献しています。


しかし、Javaと比較すると、 パフォーマンスベンチマークの状況はそれほど明確ではありません。 ただし、メモリ使用量とガベージコレクションの点では、GoはJavaよりも優れています。


Goのガベージコレクターは、 待ち時間優先し 、サーバーで特に重要な大きな一時停止を回避するように設計されています。 より多くのプロセッサリソースを消費する可能性がありますが、水平方向にスケーラブルなアーキテクチャでは、これはマシンを追加することで簡単に解決できます。 GoがGoogleで作成されたことを忘れないでください。


Javaと比較して、Goのガベージコレクターの作業は少なくなります。構造体スライスは、Javaのようにポインター配列ではなく、構造体の連続した配列です。 Goのマップでは、 小さな配列をバケットブロックとして使用します 。 その結果、ガベージコレクターの作業が少なくなり、プロセッサキャッシュの局所性が向上します。


Goは、コマンドライン経由で使用する場合、Javaよりも優れています。実行可能ファイルのネイティブ性を考えると、Goプログラムは、バイトコードをダウンロードしてコンパイルする必要があるJavaとは異なり、実行コストがかかりません。


ソースコードの形式は言語によって決まります


. Go , . gofmt - .


, gofmt , Go, !



Go . , .


Go


Python, Ruby Node.js, — . , Docker, .


Go expvar, , . , — — HTTP- . Java JMX, .


defer


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
}

, , / .



Go


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 , .


:= / var


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, , - .


, , , -, . .


Go . … !


"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 . — .


, Google, . , , . , .


Go GOPATH. ? -, . ? . «».


, GOPATH, . , ? GOPATH .


. , , (lock files) Git sha1, .


, Go 1.6 vendor. , , . , .


: 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 CSP, , . « , ». , .


, 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 , .


nil-


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».


, . , - .


struct: runtime DSL


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 , - 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 :



, .


, . sort :


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 generate: , ...


Go 1.4 go:generate . , «» //go:generate : « // go:generate». , .


:



, , 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/J353790/


All Articles