はじめての Go 言語 ( golang ) 入門 研修コースに参加してみた
今回参加した研修コースは はじめてのGo言語 です。
Go言語、いよいよエンタープライズな開発でも使おうという機運があるのでしょうか、満員です!!
業務をやりながら、未経験の言語を入門書やネットで独学しようとすると、まとまった時間が取れずに困るケースが多いのですが、このコースは 1 日間コースで、
- 基本構文やライブラリ、並行処理 ( goroutine ) 、処理の書き方など他言語と比べて特徴的なところを講師がピックアップ -> 解説
- 受講者各自で気になるところを自由に触って Go言語 を体験する
他言語経験者向けに配慮された、いわゆる「もくもく会」っぽい学び方でした。
最終的には http サーバや API サーバを動かしたり改造したり、実践的なものまで踏み込んだので、自分なりに Go言語 を感触を確かめられたと思います。
では、どんなこのコースだったのかレポートします!!
もくじ
コース情報
想定している受講者 | JavaやC言語など、なんらかのプログラミング経験がある。 |
---|---|
受講目標 | Go言語で簡単なツールが作成できるようになる。 |
講師紹介
このコースで登壇されたのは 冨原 祐さん です。この参加してみたレポートでは初登場ですね。
現場の技術をどう学ぶか、工夫を凝らした演習をデザインできる講師
冨原さんはもともと三好 康之さんのお知り合いで、三好さんからの呼びかけで講師育成のプロジェクトに参加されて講師になられた方です。(現場開発も継続されています)
その縁から徐々に登壇いただき、弊社の DOJO という新人研修プログラムの Web アプリ開発演習コースでは毎年登壇頂いています。
SEカレッジでも幾つかプログラミングのコースを中心に登壇されていますので、ぜひ ↑ の講師紹介ページの実施講座をご覧ください。
ということで、冨原さんも研修では自己紹介がお名前だけのシンプルスタイルなので、このページで紹介させていただきました。
まずは冨原さんから受講者の方に質問です。
Q. Go言語 をすでにプロジェクトで使っていらっしゃいますか?
-> 5% ぐらいの方が手を挙げられました
これからという感じですね。また、今日の狙いとして、
「今日ははじめての方向けのものです。Go素晴らしい、という訳ではなく他と比べてどうか、という観点でコースをみて欲しい」
とコースをスタートされました。
Go言語の特徴とできること
まずは Go言語の特徴です。
- Google が開発したプログラミング言語
- Google が開発した言語でほかに話題なのは Dart があります
- ちなみに Dart はネイティブアプリのクロスプラットフォーム開発ができる Flutter ( Google が開発) で再注目されてます
- Google が開発した言語でほかに話題なのは Dart があります
- Go だとググらビリティが低い
- golang, Go言語 で検索する
- 静的言語
- Java や C と違う点は、実行ファイルを渡せば、実行エンジンが無くとも動く (!)
- 簡単な CLI ツールを作ったりする人が多い
- クロスコンパイル
- Mac, Linux, Windows でも動く
- 並行処理が得意
- ここが一番強い
- 100 万リクエスト / 1 min でも余裕
- 大量ユーザを抱えるアプリケーションで使うだろう
- コンテナのオーケストレーションツール Kubernetes も Go で書かれています
- なぜそんなに速いのか
- CPU のコアをフルに使い切ることができる
- 言語設計した 3 人が「 C++ が嫌い」から生まれている
- 「楽」たのしくラクできる
- 例えば型を厳密に書かなくてよい (あとで試してみます)
特に冨原さんのお気に入りの点はランタイムが無くてもアプリゲーションが動く、というところでした。
たしかにそんな言語は無い気がします。あとで実際、試してみます。
あとは、なぜ Golang とよく言われているのかが分かりました。ググラビリティ大事ですね。
ちょっとした注意点
- gofmt (ゴーフォーマット) がある
- コードを自動整形する
- 予め用意されている社内のコーディング規約のようなものは使いにくい
- GoDoc を使うとドキュメントも自動生成 (めっちゃ便利っぽい)
- 指定の方法でコメントを書くとドキュメントを自動生成する
- 参考: GoDocドキュメントで知っていると便利な機能
コーディング規約をいちいち作ったり、レビューコストが減るというメリットもあるので、Go に入れば Go に従え、ですね。
セリフの参考:
(Go Con 2014) Goに入ってはGoに従え
インストール環境構築とHello World
- 今日は Go, Git, MySQL を使います
- その他ライブラリのインストール
go get -u -v github.com/golang/lint/golint
go get -v golang.org/x/tools/cmd/goimports
go get -v github.com/nsf/gocode
go get -v github.com/rogpeppe/godef
go get github.com/go-sql-driver/mysql
- エディタは Atom を使います
- Atom にもライブラリをインストール
- go plus
- godef
ちなみに教室の仮想環境ではなく自分のPC ubuntu 16.04 で GOPATH を設定しようとすると詰まりました。。
Hello World
- hw.go を書く
$ go run hw.go
でコマンドプロンプト上に表示されます$ go build hw.go
で実行ファイル `hw.exe` が生成されます
package mainimport (
"fmt"
)
func main() {
fmt.Print("Hello World\n")
}
基本構文
環境構築したところで基本構文について解説いただきました。
パッケージ
- golang はオブジェクト指向言語ではない
- Java などのようなクラス、パッケージの概念はない
- GOPATH の /src から相対パスを作れば呼べる
- GOPATH/src/funcA.go
- ただし複数の golang のプロジェクトがあるとツラみがある
- Docker などで環境を作るのも一つの手
- なお main パッケージはプロジェクトに1つ
- main パッケージから実行される
- hw.go の
package main
はコレ - パッケージの注意点
- ディレクトリ名と同じパッケージ名をそろえる
- パッケージを呼び出すときは ディレクトリ名 で呼び出す
- GOPATH/src/directory_name
package directory_name
- 関数名では呼び出せずエラーになる
今日の演習では特に気にせず、自由にプロジェクトフォルダを作りましたが、実際に開発するときはハマりポイントかも知れません。
基本構文
というわけで、ここからは具体的な基本構文となります。
ここが冨原さんのうまいやり方で、特徴的なことだけザッと説明して、そのあとに色々自身で試してみる、という進め方をされました。(いわゆる もくもく会 のような進め方に近いです)
- 末尾のセミコロン
;
は省略できる - 変数宣言
- 型をあとに記載する
- 省略することも出来る
var (
a, b = 10, 20
c, d = 10.5, 20.5 //自動的にfloatと認識される
)
- 配列
- 値渡し ( call by value ) になる
- 固定長の配列に近い
var a [5] int // a[0]~a[4]
var b [3] int {1, 2, 3}
var c [...]
- スライス
- 他言語の Array に近い
- 配列の参照版
- こちらがよく使われる
- 型宣言無しでよい
a := [5]int{1, 2, 3, 4, 5}
b := make([]int, 3) //[0,0,0]
c := []int{1, 2, 3}
- 条件分岐
- if の ( ) がいらないぐらいしか他言語と変わらない
- if 文に初期処理を持てる
- switch 文もあまり変わらない
- break も省略可能
if a := 1 ; a == 1 {
// 処理
}
- 繰り返し
- for しかない
- do while などはない
for i = 0 ; i < 5 ; i++ {
// 処理
}
- 型
- int だけの宣言だと CPU に依存してしまう
- int64 のように指定する
- string が read-only の文字列型になっている
- 編集したいときは strings パッケージを使う
- 定数定義
- const を使う
- ポインタが使える
- C 言語と同じく
*
と&
を使う
- C 言語と同じく
- 関数
- func で宣言する
- 左が引数、戻り値が右
- 複数の戻り値を返せる (!!)
- 特にエラー情報を返すときによく使う
func test1(name string) (msg string) {
msg = name + "さん、こんにちは"
return
}
配列、スライスの扱い方など特徴的なところはありますが、やはり色々な言語の影響を受けていそうです。個人的には結構 C言語 に近い印象を持ちました。
皆さんの印象はいかがでしょうか?
実際試してみよう (もくもく Go)
紹介された構文の中で、気になったところを中心に書いてみながら確かめます。なお、冨原さんにサンプルコードを用意頂いていたので、コードを見ながら実行したり、かなり自由に取り組めてよい感じです。
個人的には配列とスライスを色々やってみました。
標準ライブラリ
次は標準ライブラリでよく使いそうなものを、こちらも特徴的なところのみをザックリ紹介いただき、そのあと、自分で「もくもく」します。
- ファイル入出力
- os パッケージ
- ファイル読み書きやファイル操作 (ディレクトリ作成や編集)
- C 言語のファイル操作に近い
- io/ioutil パッケージ
- ファイルの読み書きに使うユーティリティ関数がある
- bufio
- 1 行ずつ読み込みたいときなどに使う
func main() {
file, err := os.Open(`ファイルパス`)
defer file.Close() // defer をつけると処理の最後に実行される
sc := bufio.NewScanner(file) //Scanner構造体を利用
for i := 1; sc.Scan(); i++ {
if err := sc.Err(); err != nil {
// エラー処理
break
}
fmt.Printf("%4d行目: %s¥n", i, sc.Text())
}
}
なお Go言語 では null は nil と表現します。
- 日付/時刻など
- time パッケージがある
- Local や ロケーション (Asia/Tokyo) など指定できる
- フォーマットは実際の数値を使う
- yyyy/mm/dd ではなく 2018/11/28 Wed のように書く
fmt.Printf("%s¥n", now.Format("2006/01/02 Mon 15:04:05"))
- 日本ロケーションでの標準フォーマット (米国はまた別の書き方)
- 文字列操作
- strings パッケージ
- 文字列置換
- 引数で int を渡すと最大何個、置換するのか指定できる
- 文字列分離
- SplitN で分割指定ができる
- 文字列結合
- Join という関数を使える
data := []string{"山田", "24歳", "男性"}
fmt.Println(strings.Join(data, ","))
// "山田,24歳,男性"
- JSON 出力
- encoding/json パッケージを使う
- 要素ごとに型の定義が必要
type City struct {
ID int
Name string
CountryCode string
District string
Population sql.NullInt64
}
私はこの中で time パッケージと strings パッケージを中心にやってみました。
なお標準ライブラリは翻訳が遅れ気味なので、公式を見るのがオススメとのことでした。
Go言語 の特徴的な処理(メリット)
ここからはGo言語特有の処理の書き方を紹介いただきました。
エラーハンドリング
まずはエラー処理です。
- エラーを throw するのではなく、正常終了させてエラーを受け取る
- 関数の複数の戻り値をエラー情報に使う
- なので try catch を使わない
- ここがオブジェクト指向とは違うところ
- finally の代わりに使うのが defer
- Error 関数を使う
- fmt の エラーメッセージ を返すこともできる
- 例外に近いものをパニックという
- 例外 ( exception ) でなく panic 関数を使う
func myFunc() (result,error){
//処理
if isError {
//fmt パッケージを使うと 0,fmt.Errorf("ERR: %s",msg)
return 0,errors.New("エラーメッセージ")
}
}
func recoverPanic() {
defer func() {
if err := recover(); err != nil {//復帰させる
fmt.Println("recover:", err) //panic時の情報取得可能
}
}()
panic("panic発生")
//これ以下は実行されない
}
並行処理 goroutine
きましたマルチスレッド。... 難しいやつです。。以前に Java のマルチスレッドのコースでレポートしましたが、ほとんど理解ができなかった記憶が蘇ります。
- ユースケース
- 業務系システムにおける夜間バッチ (ex.複数店の売上処理とかを行うとき、店舗ごとに並行処理して集計する)
- ゴルーチン ( goroutine )
- 他の言語ではスレッドが残っていたり、同期非同期の処理が難しかったり
- スレッド間のデータ通信はチャネルという機構を使う
- 同期化 (待ち合わせ) にはチャネルより WaitGroup を使うとラク
- ゴルーチンの書き方
- 関数の前に go をつければ良い
func main() {
sample()
go sample
}
func sample() {
//処理
}
- チャネル
- 通信を使う側にチャネルを書く
ch1 := make(chan int)
- 受信は無限ループ + select case とすることが多い
val := <-cha1
- 通信を使う側にチャネルを書く
//並行処理からの通知を受け取る
func sample20() {
fmt.Println("開始")
//チャネルの生成
ch1 := make(chan int)
ch2 := make(chan string)
//終了用チャネル
chend := make(chan struct{})
//チャネルを送信するゴルーチン
//ここでは、別関数に切り出さずに中で処理を行っている
go func(chint chan<- int, chstr chan<- string, end chan<- struct{}) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
// 偶数回はint型チャネル、奇数回はstring型チャネルを送信する
if i%2 == 0 {
fmt.Println("intチャネルへ送信")
chint <- i
} else {
fmt.Println("文字列チャネルへ送信")
chstr <- "test" + strconv.Itoa(i)
}
}
close(end) // クローズして通知
}(ch1, ch2, chend)
//非同期処理なのでここまで一瞬で終了。下のループに入る
// 受信用の無限ループ
for {
select {
case val := <-ch1:
fmt.Println("ch1から受信:", val)
case str := <-ch2:
fmt.Println("ch2から受信:", str)
case <-chend:
fmt.Println("終了")
return
}
}
}
実行してみます。
- WaitGroup
- すべてのゴルーチンを完了するまで待機できる
- セマフォとかに近い
- add してカウントアップ
//並行処理の全終了を待つ
func sample21() {
// WaitGroup構造体を初期化
wg := new(sync.WaitGroup)
// 3つのゴルーチン(3タスク)を実行します
for i := 0; i < 3; i++ {
wg.Add(1) // WaitGroupの回数分Addする
go syncProc(wg)
}
// wg.Addで追加したすべてゴルーチンが、Doneで終了通知されるまで待機
fmt.Println(time.Now())
wg.Wait()
fmt.Println(time.Now())
}
func syncProc(wg *sync.WaitGroup) {
//処理A~Cまで、時間のかかる処理
fmt.Println("処理A")
time.Sleep(1 * time.Second)
fmt.Println("処理B")
time.Sleep(1 * time.Second)
fmt.Println("処理C")
// このタスクが終了したことを通知する
wg.Done()
}
実行してみます。
マルチスレッドがこれだけ制御できるというのは驚きです。また Java に比べて、コードも読みやすいですね。
Java などが何年もかけ、これまでの互換性も担保しながら進化したのに対して、後発言語はその影響を受けながら開発できるので、これだけシンプルになっているのかも知れません。
Goでアプリケーション開発してみよう
ここからは実際にアプリゲーション開発など業務寄りのお話です。冨原さんいわく、golang のスゴイところはここだと思う、とのこと。 (冒頭のランタイムが必要とせず実行できるというお話です)
ソケット通信
- net パッケージを使う
- リスナを作るのに2行
- 実行ファイルを渡せば、Web サーバなしで Web アプリを配布できる、ということも出来る
コードを解説いただきましたが、驚きの記述量で簡単に Web サーバを書けました。
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
)
//サーバが保持するメモのリスト
var memos []string
func main() {
fmt.Println(os.Getwd())
//メモの初期化
memos = make([]string, 0)
//memosにアクセスが来た場合、showMemo関数を呼び出す
http.HandleFunc("/memos", showMemo)
//memos/addにアクセスが来た場合、addMemo関数を呼び出す
http.HandleFunc("/memos/new", addMemo)
//リスナを起動する
http.ListenAndServe(":80", nil)
}
// memoへのアクセス時に応答する
func showMemo(w http.ResponseWriter, r *http.Request) {
//index.htmlファイルを読み込み
index, err := ioutil.ReadFile(`./index.html`)
if err != nil {
fmt.Print(err)
return
}
//メモのリストから、表示用HTMLを生成
memo := "" + strings.Join(memos, " ") + " "
// HTML中の[[LIST_AREA]]をmemoのHTMLに置換
ret := strings.Replace(string(index), "[[LIST_AREA]]", memo, 1)
//ResponseWriterにHTMLを書き込む
fmt.Fprintln(w, ret)
}
// memo/addへのアクセス時に応答する
func addMemo(w http.ResponseWriter, r *http.Request) {
//POSTされた"memo"の値を受け取る
receiveValue := r.FormValue("memo")
//保持しているメモリストに追加する
memos = append(memos, receiveValue)
//memosにリダイレクトさせる
http.Redirect(w, r, "/memos", 303)
}
ちなみにこのコードを実際に go build
して実行ファイルを作ると、そのファイルだけで動きますので、ぜひぜひ試してみてください。
mysql 連携
- 環境構築でインストールしたパッケージを使う
- 使ってないと削除されてしまうので、import 文に ↓ のように _ "" を書く
import _ "github.com/go-sql-driver/mysql"
- Rowbytes と interface の値を合わせるということをやってる
- 配列処理がややこしいので、先程のスライスを使います
- gorp という ORM をインポート
- ビジネスロジックを書かずともサクッと書ける ( ↓ のような感じ)
_, err2 := dbmap.Select(&list, "SELECT * FROM city WHERE CountryCode = ?", countryCode)
- _, でエラー情報を受け取らない、ということも出来る
SQL をベタ書きなので、もそっといい感じに書けるといいのに、と思って冨原さんに質問すると他に OR マッパーはたくさんあるそうです。
調べてみると下のライブラリが GitHub のスター数も 11481 (2018.12時点) で Pulse を見る限りアクティブなので、いい感じな気がします。
jinzhu/gorm: The fantastic ORM library for Golang, aims to be developer friendly
クエリの書き方も ORM っぽい感じですね。
// gorm の書き方
db.where("countrycode = ?", "?").Find(&countries)
// Laravel などのEloquent
App\Countries::where('countrycode', ?);
// Rails の ActiveRecord
Country.find_by countrycode: ?
ユニットテスト
- ファイル名が _test.go で終わるとテストコード
- Sample.go
- Sample_test.go
go test [パッケージ名]
でテストが実行できる- 関数名は test で始まらなければならない
Test_funcA()
- カバレッジは
go test -coverprofile=cover.txt
とオプションをつけると出せる
API サーバを書いてみよう
- RESTful を想定
- その中でも Get で /users/view/{user_id} のようなものを書いてみましょう
- ルータとして mux というパッケージを使う
router.Handlefunc("/users/view/{user_id}", viewUser)
と書くだけでよい
package main
func main() {
//httpパッケージでは、エイリアスをパラメータにすると手間がかかる
//muxパッケージを利用
router := mux.NewRouter().StrictSlash(true)
//ルータの設定
router.HandleFunc("/countries", Countries)
router.HandleFunc("/cities/{countrycode}", Cities)
//リスナを起動
log.Fatal(http.ListenAndServe(":8080", router))
}
//すでにmysqlとのコネクションは出来ている前提
//テーブル作成と定義も完了している前提
//実際には、データアクセスは別ファイルに切り出す
func Cities(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
countryCode := vars["countrycode"]
//mysql接続
db, err := sql.Open("mysql", "seplus:seplus@/world")
if err != nil {
panic(err.Error())
}
//gorpを利用し、Countryとデータのマッピングを楽にする
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{}}
defer db.Close()
//受け取るための変数を宣言
var list []City
//戻り値を受け取る必要がないのでアンダースコアにしている
_, err2 := dbmap.Select(&list, "SELECT * FROM city WHERE CountryCode = ?", countryCode)
if err2 != nil {
panic(err2.Error())
}
//構造体からjsonデータに変換
jsonBytes, err := json.Marshal(list)
fmt.Fprintln(w, string(jsonBytes))
}
こちらも実行してみました。
このあたりの書き方は PHP や Python などスクリプト系の言語と似たような感じですね。
このテストコードを書いてみたり、POST を作ってみるなど余力がある人は試しながら、このコースは修了しました。
まとめ
このコースでは Go言語 の基本構文やゴルーチン ( goroutine ) など特徴的なところをピックアップして見ながら、サンプルコードをもとに自身で動かしり改造したり、ということを自由にやってみて、最終的に RESTful API サーバを書いてみました。
他言語経験者向けの研修コースらしく、学び方も特に自由にできる時間を豊富に用意いただいたので、受講者の方もご自身が気になるところをやってみて、講師に質問することが多いコースでした。
なかなか 1 日がかりで未経験の言語をさわる機会は少なくなっているので、他言語の経験者で Go が気になるという方にはとても有意義なコースの進め方でした。 オススメです!
label SE カレッジの無料見学、資料請求などお問い合わせはこちらから!!
label SEカレッジを詳しく知りたいという方はこちらから !!
IT専門の定額制研修 月額28,000円 ~/ 1社 で IT研修 制度を導入できます。
年間 670 講座をほぼ毎日開催中!!
SEプラスにしかないコンテンツや、研修サービスの運営情報を発信しています。