これはGoogleのGoチームのテクニカルリード兼マネージャーであるSameer Ajmani氏が共有したPPTで、JavaプログラマがGoをすぐに始められるように用意されたものです。

为 Java 程序员准备的 Go 语言入门 PPT

動画

このPPTは2015年4月23日にNYJavaSIGで使用されたものです。

YouTubeで動画を見る

主な内容

1. Goとは何か、誰が使っているのか?
2. GoとJavaの比較
3. コード例
4. 並行処理
5. ツール

Goとは何か?

「Goはオープンソースのプログラミング言語で、シンプルで信頼性が高く、効率的なソフトウェアを簡単に構築できます。」

golang.org

Goの歴史

2007年後半から設計が始まった

  • Robert Griesemer、Rob Pike、Ken Thompson。
  • Ian Lance Taylor と Russ Cox

2009年からオープンソース化され、非常に活発なコミュニティがある。

Go言語の安定版Go 1は2012年初めにリリースされた。

なぜGoなのか?

GoはGoogleの規模に対処するためのソリューションです。

システム規模

  • 計画上の規模は10⁶⁺台のマシン
  • 毎日数千台のマシン上でジョブが実行される
  • システム内で他のジョブと協調・対話する
  • 同時に大量の作業を行う

解決策:並行処理のサポートが非常に強力

2つ目の問題:エンジニアリング規模

2011年時点で

  • 40以上のオフィスにまたがる5000人以上の開発者
  • 毎分20以上の変更
  • 毎月コードベースの50%が変更される
  • 毎日5000万のテストケースが実行される
  • 単一のコードツリー

解決策:大規模なコードベースのために設計された言語

Googleで誰がGoを使っているか?

多数のプロジェクト、数千人のGoプログラマ、数百万行のGoコード。

公開されている例:

  • モバイル端末向けChrome SPDYプロキシ
  • Chrome、ChromeOS、Android SDK、Earthなどのダウンロードサーバー
  • YouTube Vitess MySQLバランサー

主な用途はネットワークサーバーだが、これは汎用言語です。

Google以外に誰がGoを使っているか?

golang.org/wiki/GoUsers

Apcera, Bitbucket, bitly, Canonical, CloudFlare, Core OS, Digital Ocean, Docker, Dropbox, Facebook, Getty Images, GitHub, Heroku, Iron.io, Kubernetes, Medium, MongoDB services, Mozilla services, New York Times, pool.ntp.org, Secret, SmugMug, SoundCloud, Stripe, Square, Thomson Reuters, Tumblr, ...

GoとJavaの比較

GoとJavaには多くの共通点がある

  • C系(強い型付け、括弧)
  • 静的型付け
  • ガベージコレクション
  • メモリ安全性(nil参照、実行時境界チェック)
  • 変数は常に初期化される(zero/nil/false)
  • メソッド
  • インターフェース
  • 型アサーション(インスタンス)
  • リフレクション

GoとJavaの相違点

  • コードは直接マシンコードにコンパイルされ、VMがない
  • 静的にリンクされたバイナリ
  • メモリレイアウトの制御
  • 関数値とレキシカルクロージャ
  • 組み込み文字列(UTF-8)
  • 組み込みの汎用マップと配列/スライス
  • 組み込みの並行処理

Goは意図的に多くの機能を省いている

  • クラスがない
  • コンストラクタがない
  • 継承がない
  • finalがない
  • 例外がない
  • アノテーションがない
  • ユーザー定義のジェネリックがない

なぜGoはそれらの機能を省いたのか?

コードの明確さが最優先である

コードを見たとき、プログラムが何をするのかが明確にわかる

コードを書くときも、プログラムにやりたいことを明確に実行させられる

時には、あいまいな関数を呼び出す代わりにループを書くことを意味する。

(退屈になりすぎないように)

詳細な設計の背景は以下を参照:

例

JavaプログラマならGoはとても見慣れたものだろう

Main.java

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

hello.go

package main
import "fmt"
func main() {
    fmt.Println("Hello, 世界!")
}

Hello, web server(こんにちは、Webサーバー)

package main

import (
    "fmt"
    "log"
    "net/http"
)
func main() {
    http.HandleFunc("/hello", handleHello)
    fmt.Println("serving on http://localhost:7777/hello")
    log.Fatal(http.ListenAndServe("localhost:7777", nil))
}
func handleHello(w http.ResponseWriter, req *http.Request) {
    log.Println("serving", req.URL)
    fmt.Fprintln(w, "Hello, 世界!")
}

(アクセス権限)型は変数名に基づいて宣言される。
公開変数の最初の文字は大文字、プライベート変数の最初の文字は小文字。

例:Googleフロントエンド


func main() {
    http.HandleFunc("/search", handleSearch)
    fmt.Println("serving on http://localhost:8080/search")
    log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
// handleSearch handles URLs like "/search?q=golang" by running a
// Google search for "golang" and writing the results as HTML to w.
func handleSearch(w http.ResponseWriter, req *http.Request) {

リクエスト検証

func handleSearch(w http.ResponseWriter, req *http.Request) {
    log.Println("serving", req.URL)
    // Check the search query.
    query := req.FormValue("q")
    if query == "" {
        http.Error(w, `missing "q" URL parameter`, http.StatusBadRequest)
        return
    }

FormValueは *http.Request のメソッドです:

package http
type Request struct {...}
func (r *Request) FormValue(key string) string {...}

query := req.FormValue("q") は変数 query を初期化し、その型は右辺の式の結果、ここでは string 型です。

検索結果を取得

// Run the Google search.
    start := time.Now()
    results, err := Search(query)
    elapsed := time.Since(start)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

Searchメソッドには2つの戻り値があり、それぞれ結果(results)とエラー(error)です。

func Search(query string) ([]Result, error) {...}

error の値が nil の場合、results は有効です。

type error interface {
    Error() string // a useful human-readable error message
}

error 型は追加情報を含む可能性があり、型アサーションでアクセスできます。

検索結果をレンダリング

// Render the results.
    type templateData struct {
        Results []Result
        Elapsed time.Duration
    }
    if err := resultsTemplate.Execute(w, templateData{
        Results: results,
        Elapsed: elapsed,
    }); err != nil {
        log.Print(err)
        return
    }

結果 results は Template.Execute を使用して HTML を生成し、io.Writer に書き込みます:

type Writer interface {
        Write(p []byte) (n int, err error)
}

http.ResponseWriterはio.Writerインターフェースを実装しています。

Go変数でHTMLテンプレートを操作

// A Result contains the title and URL of a search result.
type Result struct {
    Title, URL string
}
var resultsTemplate = template.Must(template.New("results").Parse(`
<html>
<head/>
<body>
  <ol>
  {{range .Results}}
    <li>{{.Title}} - <a href="{{.URL}}.html">{{.URL}}</a></li>
  {{end}}
  </ol>
  <p>{{len .Results}} results in {{.Elapsed}}</p>
</body>
</html>
`))

Google検索APIへのリクエスト

func Search(query string) ([]Result, error) {
    // Prepare the Google Search API request.
    u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0")
    if err != nil {
        return nil, err
    }
    q := u.Query()
    q.Set("q", query)
    u.RawQuery = q.Encode()
    // Issue the HTTP request and handle the response.
    resp, err := http.Get(u.String())
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

defer宣言により、resp.Body.Close は Search メソッドが戻る時に実行されます。

返されたJSONデータをGoのstruct型にパース

developers.google.com/web-search/docs/#fonje

var jsonResponse struct {
        ResponseData struct {
            Results []struct {
                TitleNoFormatting, URL string
            }
        }
    }
    if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil {
        return nil, err
    }
    // Extract the Results from jsonResponse and return them.
    var results []Result
    for _, r := range jsonResponse.ResponseData.Results {
        results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL})
    }
    return results, nil
}

これがそのフロントエンドです

参照されるすべてのパッケージは標準ライブラリからのものです:

import (
    "encoding/json"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "net/url"
    "time"
)

Goサーバーのスケーラビリティ:各リクエストがそれぞれのgoroutine内で実行されます。

並行処理について話しましょう。

通信順次プロセス(Hoare, 1978)

並行プログラムは、独立したプロセスとして、メッセージ通信によって順次実行されます。

逐次実行は理解しやすいが、非同期はそうではない。

「共有メモリで通信するな。通信でメモリを共有せよ。」

Goの原則:goroutine、チャネル、select文。

Goroutines

Goroutines は軽量スレッドのようなものです。

それらは小さなスタック(tiny stacks)と必要に応じた調整で実行されます。

Go プログラムは何千何万もの(goroutines)インスタンスを持つことができます。

go文を使用してgoroutineを起動します:

go f(args)

GoランタイムはgoroutineをOSスレッドに配置します。

スレッドでgoroutineをブロックしないでください。

Channels

Channels は goroutine 間の通信のために定義されています。

c := make(chan string)
 
// goroutine 1
c <- "hello!"
 
// goroutine 2
s := <-c
fmt.Println(s) // "hello!"

Select

select は、実行を判断するステートメントブロックを宣言します。

select {
case n := <-in:
  fmt.Println("received", n)
case out <- v:
  fmt.Println("sent", v)
}

条件が成立したcaseブロックだけが実行されます。

例:Google検索(バックエンド)

質問:Google検索は何ができますか?

回答:質問を出すと、検索結果のページ(と広告)を返します。

質問:どのようにして検索結果を得るのでしょうか?

回答:質問をウェブ検索、画像検索、YouTube(ビデオ)、マップ、ニュースに送信し、少し待ってから結果を取得します。

それをどのように実装するのでしょうか?

Google検索:疑似フレームワーク

検索関数を模擬し、ランダムに0〜100ミリ秒のタイムアウトを発生させます。

var (
    Web   = fakeSearch("web")
    Image = fakeSearch("image")
    Video = fakeSearch("video")
)
type Search func(query string) Result
func fakeSearch(kind string) Search {
    return func(query string) Result {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        return Result(fmt.Sprintf("%s result for %q\n", kind, query))
    }
}

Google検索:テストフレームワーク

func main() {
    start := time.Now()
    results := Google("golang")
    elapsed := time.Since(start)
    fmt.Println(results)
    fmt.Println(elapsed)
}

Google検索(逐次)

Google関数はクエリを受け取り、結果セット(必ずしも文字列ではない)を返します。

GoogleはWeb(ウェブ)、Image(画像)、Video(ビデオ)を順番に呼び出し、返り値を結果セットに追加します。

func Google(query string) (results []Result) {
    results = append(results, Web(query))
    results = append(results, Image(query))
    results = append(results, Video(query))
    return
}

Google検索(並列)

Web、Image、Videoの検索を同時に実行し、すべての結果を待ちます。

func関数はqueryとcの場所でクロージャとして閉じられています。

func Google(query string) (results []Result) {
    c := make(chan Result)
    go func() { c <- Web(query) }()
    go func() { c <- Image(query) }()
    go func() { c <- Video(query) }()
    for i := 0; i < 3; i++ {
        result := <-c
        results = append(results, result)
    }
    return
}

Google検索(タイムアウト)

遅いサーバーを待ちます。

ロックも、条件変数も、戻り値もありません。

    c := make(chan Result, 3)
    go func() { c <- Web(query) }()
    go func() { c <- Image(query) }()
    go func() { c <- Video(query) }()
    timeout := time.After(80 * time.Millisecond)
    for i := 0; i < 3; i++ {
        select {
        case result := <-c:
            results = append(results, result)
        case <-timeout:
            fmt.Println("timed out")
            return
        }
    }
    return

タイムアウト防止

質問:遅いサービスの結果を失わないようにするには?

回答:サービスを複製し、複製した複数のサービスにリクエストを送り、最初に応答した結果を使用します。

func First(query string, replicas ...Search) Result {
    c := make(chan Result, len(replicas))
    searchReplica := func(i int) { c <- replicas[i](query) }
    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

First関数を使用

func main() {
    start := time.Now()
    result := First("golang",
        fakeSearch("replica 1"),
        fakeSearch("replica 2"))
    elapsed := time.Since(start)
    fmt.Println(result)
    fmt.Println(elapsed)
}

Google検索(レプリケーション)

複製したサービスを使用して、余分な遅延を減らします。

    c := make(chan Result, 3)
    go func() { c <- First(query, Web1, Web2) }()
    go func() { c <- First(query, Image1, Image2) }()
    go func() { c <- First(query, Video1, Video2) }()
    timeout := time.After(80 * time.Millisecond)
    for i := 0; i < 3; i++ {
        select {
        case result := <-c:
            results = append(results, result)
        case <-timeout:
            fmt.Println("timed out")
            return
        }
    }
    return

その他

ロックも、条件変数も、呼び出しもありません。

まとめ

いくつかの簡単な変換により、Goの並行性プリミティブを使って

  • 慢
  • 逐次的で
  • 障害に敏感な

プログラムを

  • 快
  • 並行な
  • 再利用可能な
  • 堅牢な

ツール

Goには強力なツールがたくさんある

  • gofmt と goimports
  • The go tool
  • godoc
  • IDEとエディタのサポート

この言語はまさにツールチェーンのために設計されています。

gofmt と goimports

Gofmtはオプションなしでコードを自動フォーマットできます。

Goimports はワークスペースに基づいて import 宣言を更新します。

ほとんどの人はこれらのツールを安全に使用できます。

play.golang.org/p/GPqra77cBK

The go tool

The go tool は従来のディレクトリレイアウトでソースコードからGoプログラムを構築できます。Makefilesやその他の設定は必要ありません。

これらのツールとその依存関係を解決し、ビルド、インストールします:

% go get golang.org/x/tools/cmd/present

実行:

% present

godoc

世界中のすべてのオープンソースGoコードのドキュメントを生成します:

godoc.org

IDEとエディタのサポート

Eclipse、IntelliJ、emacs、vim など:

  • gofmt
  • goimports
  • godoclookups
  • code completion
  • code navigation

しかし「Go IDE」はありません。

Goツールはどこにでもあります。

Goの今後の計画

Goのロードマップはオンラインで確認できます。

tour.golang.org

豊富な学習資料

golang.org/wiki/Learn

素晴らしいコミュニティ

golang.org/project

Thank you

Sameer Ajmani

Tech Lead Manager, Go team

Google

@Sajma

sameer@golang.org