Skip to content

Instantly share code, notes, and snippets.

@Jxck Jxck/README.md
Last active Aug 29, 2015

Embed
What would you like to do?
Go Kyoto(Go勉強会 そうだ京都、行こう) のハンズオン資料 (http://www.zusaar.com/event/4367004)

Go Kyoto (Go勉強会 そうだ京都、行こう)

About

実施概要

前提

  • 多言語で基本的なプログラミング知識(Web 系?) がある。
  • Go のインストール、開発環境(エディタとか)の準備済み。
  • http://go-tour-jp.appspot.com/ を一通り(24, 48, 60, 69, 70 は飛ばしてよし)

タスク管理ツール

  • タスク管理ツール的なものを作りながら、 Go のひと通りの機能を触る。
  • Go をひと通り触るのが目的なので、ツール自体の機能とかはこだわらない。
  • 他の言語での経験がある方が対象なので、プログラミング言語の基本的な話は省略。
  • 標準モジュールでできることのみ。
  • 環境構築は終わっている前提。
  • 1 ファイルで完結させる構成(main.go のみ)
  • 時間がないので色々省略している部分は最後にリンクを。
  • 時間があまったらテストの話。

Hands On

ハンズオン形式で進め、途中で課題をいくつか実施。

main

main() がいわゆる main 文。 main() は main パッケージにのみ作成可能。

package main

import (
  "fmt"
)

func main() {
    fmt.Println("Go Kyoto!")
}

フォーマット

$ go fmt main.go

実行

$ go run main.go
Go Kyoto!

フォーマッタは、コミットフックやエディタで保存時にやるのを推奨。

Document

電波がなくても、標準モジュールと自分が書いたモジュールのドキュメントはローカルで見られる。

$ godoc fmt

サーバも内蔵されている。

$ godoc -http=":4000" &

http://localhost:4000 で起動される。

Print Debug

fmt.Print() が stdout, log.Print() が stderr に出力。 init() は初期化処理が記述でき、 log.SetFlags() でフォーマットが変えられる。 フォーマットは http://golang.org/pkg/log/#pkg-constants 参照

import (
	"fmt"
	"log"
)

func init() { // 初期化処理
  // 日付ではなく行番号になるのでおすすめ
	log.SetFlags(log.Lshortfile)
}

func main() {
	fmt.Println("Go Kyoto!")
	fmt.Printf("%v", "Go Kyoto!\n")
	log.Println("Go Kyoto!")
	log.Printf("%v", "Go Kyoto!\n")
}

Variables

定義は変数名が先、型が後。

func main() {
	var id int = 1
	var detail string = "buy the milk"
	var done bool = false
	log.Println(id, detail, done)
}
func main() {
	var (
		id     int    = 1
		detail string = "buy the milk"
		done   bool   = false
	)
	log.Println(id, detail, done)
}

暗黙的型宣言

func main() {
	id := 1
	detail := "buy the milk"
	done := false
	log.Println(id, detail, done)
}

Struct

構造体。(オブジェクト的なもの) メンバは、大文字が Public、小文字が Private

type Task struct {
	Id     int
	Detail string
	Done   bool
}

インスタンスの生成。 Task{} はインスタンスの実体を返し、 &Task{} はポインタを返す。

func main() {
	// new 的なこと
	task := &Task{
		Id:     1,
		Detail: "buy the milk",
		Done:   false,
	}
	log.Printf("%+v", task) // 見やすい
}

インスタンス生成時に、値を明示的に指定しなかったら暗黙的に各型に応じた初期値が使用される。これを ゼロ値 という。

func main() {
	// bool のゼロ値は false なので Done は省略可
	task := &Task{
		Id:     1,
		Detail: "buy the milk",
	}
	log.Printf("%+v", task) // 見やすい
}

Function

Task を New する関数は NewTask という関数名にするのが通例。 (コンストラクタ相当)

func NewTask(id int, detail string) *Task {
	task := &Task{
		Id:     id,
		Detail: detail,
		Done:   false,
	}
	return task
}

func main() {
	task := NewTask(1, "buy the milk")
	log.Printf("%+v", task)
}

Method

struct にはメソッドが定義できる。 レシーバが、実体のものとポインタのもので定義方法が違う。

String() は、表示などで文字列化するときに使われる Stringer というインタフェースのメソッド。(toString() とか相当)

func (task Task) String() string { // 引数なし,戻り値あり
	return fmt.Sprintf("%d) %s (%t)", task.Id, task.Detail, task.Done)
}

func (task *Task) Finish() { // 引数,戻り値無し
	task.Done = true
}

課題1

Task.Detail を引数で受け取った detail string でまるっと上書きする Edit メソッドを実装しなさい。

IF

中括弧はいらない。 三項演算子はない。

func (task Task) String() string {
	var status string
	if task.Done {
		status = "done"
	} else {
		status = "not yet"
	}
	return fmt.Sprintf("%d) %s (%s)", task.Id, task.Detail, status)
}

Slice

可変長な配列だと思えば良い。 追加には組み込み関数の append() を使用する。 (追加結果を返すことに注意)

func main() {
	tasks := []*Task{
		NewTask(1, "buy the milk"),
		NewTask(2, "eat the pie"),
	}
	tasks = append(tasks, NewTask(3, "go to the bank"))
	log.Printf("%s", tasks)
}

実際は、固定長配列のビューとなっている。 固定長な配列は別にある。 今回は make() を用いた初期化は省略。 詳細は http://blog.golang.org/go-maps-in-action を参照のこと。

For

繰り返しを表現する方法は for のみ。While などない。 range を用いてイテレーションが可能、インデックスと要素を返す。 (イテレータ相当)

for i, task := range tasks {
	log.Printf("%d %s", i, task)
}

// 宣言して使わない変数は許されないので
// 使わないなら _ で宣言する
for _, task := range tasks {
	log.Printf("%s", task)
}

課題2

Tasks を以下のように定義し、 NewTasks 関数と Add メソッドを実装しなさい。

type Tasks struct {
	Owner string
	Tasks []*Task // ここは空のスライスで初期化
}

動作例

func main() {
	tasks := NewTasks("Jxck")
	tasks.Add(NewTask(1, "buy the milk"))
	tasks.Add(NewTask(2, "eat the pie"))
	tasks.Add(NewTask(3, "go to the bank"))

	log.Println(tasks.Owner)
	for i, task := range tasks.Tasks {
		log.Printf("%d %s", i, task)
	}
}

HTTP

タスク内容を net/http で公開するサーバを作成。 まず index ページとしてリンクのみを表示するハンドラを作成。 ハンドラを登録したサーバを port 3000 で起動。

func IndexHandler(w http.ResponseWriter, r *http.Request) {
	// heredoc
	html := `
	<!DOCTYPE html>
	<title>tasks</title>
	<h1>here is your <a href="%s">tasks</a></h1>
	`
	fmt.Fprintf(w, html, "/tasks")
}

func main() {
	http.HandleFunc("/", IndexHandler)
	http.ListenAndServe(":3000", nil)
}

http.ResponseWriter は Writer というインタフェースを実装しており、書き込み可能なオブジェクト。 fmt.Fprintf は、第一引数に Writer を受け、第二引数をそこに書き込む。

今回は使用していないが、 *http.Request の中にはリクエストに関する情報が全て入っている。

課題3

TasksHandler を作成し、とりあえず main でやっていたことを移して、 http://localhost:3000/tasks でタスクをリスト表示する。

実装は、 fmt.Fprintf を用いて ResponseWriter にガンガン書き込む感じで良い。

動作例(html)

<!DOCTYPE html>
<title>tasks</title>
<h1>Jxck's tasks</h1>
<ul>
  <li>1) buy the milk (not yet)</li>
  <li>2) eat the pie (not yet)</li>
  <li>3) go to the bank (not yet)</li>
</ul>

Template

標準で text/templatehtml/template があり、インタフェースは基本的に同じだが、 html/template の方はエスケープ処理などを標準で行ってくれるため Web 用途ではこちらを使う。

func TasksHandler(w http.ResponseWriter, r *http.Request) {
	tasks := NewTasks("Jxck")
	tasks.Add(NewTask(1, "buy the milk"))
	tasks.Add(NewTask(2, "eat the pie"))

	// こういうのがあっても大丈夫
	tasks.Add(NewTask(3, "<b>go to the bank</b>"))

	html := `
  <!DOCTYPE html>
	<title>tasks</title>
	<h1>{{.Owner}}'s tasks</h1>
	<ul>
		{{range .Tasks}}
		<li>{{.}}</li>
		{{end}}
	</ul>
	`

	// 第二引数がエラーを返すイディオム
	TasksList, err := template.New("tasklist").Parse(html)
	if err != nil {
		log.Fatal(err)
	}
	TasksList.Execute(w, tasks)
}

Go の関数は多値を返せる、 error が発生する場合は、引数の最後に返すのがイディオム。 実際はこれを処理したり、呼び出し元に返したりするが今回は log.Fatal() でプロセスを落としている。 (今回は扱わないが、 panic/recover というエラーの制御方法もある http://blog.golang.org/defer-panic-and-recover)

Closure

クロージャを用いて関数を返す関数にする。(Go では関数は First Class) tasks を受け取り http.Handler を返す関数にし、 main() から tasks を渡せるようにする。

func TasksHandler(tasks *Tasks) func(w http.ResponseWriter, r *http.Request) {
	html := `<!DOCTYPE html>
	<title>tasks</title>
	<h1>{{.Owner}}'s tasks</h1>
	<ul>
		{{range .Tasks}}
		<li>{{.}}</li>
		{{end}}
	</ul>
	`
	TasksList, err := template.New("tasklist").Parse(html)
	if err != nil {
		log.Fatal(err)
	}
	return func(w http.ResponseWriter, r *http.Request) {
		TasksList.Execute(w, tasks)
	}
}

func main() {
	// snip
	http.HandleFunc("/tasks", TasksHandler(tasks))
	// snip
}

JSON

Tasks を encoding/json を用いてシリアライズする。 基本的には Marshal() か MarshalIndent() に渡すだけ。 戻り値は []byte というバイトのスライスなので、文字列にする場合は変換が必要。

// b, err := json.Marshal(tasks) // インデントなし
b, err := json.MarshalIndent(tasks, "", "  ")  // プレフィックス、インデントあり
if err != nil {
	log.Fatal(err)
}
log.Println(string(b)) // 文字列化
{
  "Owner": "Jxck",
  "Tasks": [
    {
      "id": 1,
      "detail": "buy the milk",
      "done": false
    }
  ]
}

対応する key 名を変更したい場合はタグで指定する。

type Task struct {
	Id     int    `json:"id"`
	Detail string `json:"detail"`
	Done   bool   `json:"done"`
}
type Tasks struct {
	Owner string  `json:"owner"`
	Tasks []*Task `json:"tasks"`
}

課題4

http://localhost:3000/tasks.json でタスクを JSON で取得できる API を作成するため TasksJSONHandler を作成し、サーバに登録しなさい。

送信は fmt.Fprint() で良いでしょう。 Content-Type とか細かいことは気にしない!

おまけ

当日時間があったので Slice の内部構造について解説し、 それを利用して Tasks.Del() を実装した。

func (tasks *Tasks) Del(id int) {
	for i, task := range tasks.Tasks {
		if task.Id == id {
			copy(tasks.Tasks[i:], tasks.Tasks[i+1:])
			tasks.Tasks[len(tasks.Tasks)-1] = nil
			tasks.Tasks = tasks.Tasks[:len(tasks.Tasks)-1]
			break
		}
	}
}

詳細は、この辺を参照してほしい。 https://code.google.com/p/go-wiki/wiki/SliceTricks

終わり

時間が余ったら、 tasks のモジュール分けとテストを書きます。

話せなかったこと

  • interface
  • new()
  • channel, goroutine
  • module 分割
  • その他たくさん

参考資料

Documents

Resources

WAF

事例

事例集 からピックアップ

解答例

課題1

func (task *Task) Edit(detail string) {
	ttask.Detail = detail
}

課題2

func NewTasks(owner string) *Tasks {
	tasks := &Tasks{
		Owner: owner,
	}
	return tasks
}

func (tasks *Tasks) Add(task *Task) {
	tasks.Tasks = append(tasks.Tasks, task)
}

課題3

func TasksHandler(w http.ResponseWriter, r *http.Request) {
	tasks := NewTasks("Jxck")
	tasks.Add(NewTask(1, "buy the milk"))
	tasks.Add(NewTask(2, "eat the pie"))
	tasks.Add(NewTask(3, "go to the bank"))

	fmt.Fprintf(w, "<!DOCTYPE html><title>tasks</title>")
	fmt.Fprintf(w, "<h1>%s's tasks</h1>", tasks.Owner)
	fmt.Fprintf(w, "<ul>")
	for _, task := range tasks.Tasks {
		fmt.Fprintf(w, "<li>%s</li>", task)
	}
	fmt.Fprintf(w, "</ul>")
}

課題4

func TasksJSONHandler(tasks *Tasks) func(w http.ResponseWriter, r *http.Request) {
	b, err := json.MarshalIndent(tasks, "", "  ")
	if err != nil {
		log.Fatal(err)
	}
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, string(b))
	}
}

License

The MIT License (MIT) Copyright (c) 2013 Jxck

package main
import (
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
)
func init() {
log.SetFlags(log.Lshortfile)
}
type Task struct {
Id int `json:"id"`
Detail string `json:"detail"`
Done bool `json:"done"`
}
func NewTask(id int, detail string) *Task {
task := &Task{
Id: id,
Detail: detail,
}
return task
}
func (task *Task) Finish() {
task.Done = true
}
func (task *Task) String() string {
var status string
if task.Done {
status = "done"
} else {
status = "not yet"
}
str := fmt.Sprintf(
"%d) %s (%s)", task.Id, task.Detail, status)
return str
}
func (task *Task) Edit(detail string) {
task.Detail = detail
}
type Tasks struct {
Owner string `json:"owner"`
Tasks []*Task `json:"tasks"`
}
func NewTasks(owner string) *Tasks {
tasks := &Tasks{
Owner: owner,
Tasks: make([]*Task, 0, 16),
}
return tasks
}
func (tasks *Tasks) Add(task *Task) {
tasks.Tasks = append(tasks.Tasks, task)
}
func (tasks *Tasks) Del(id int) {
for i, task := range tasks.Tasks {
if task.Id == id {
copy(tasks.Tasks[i:], tasks.Tasks[i+1:])
tasks.Tasks[len(tasks.Tasks)-1] = nil
tasks.Tasks = tasks.Tasks[:len(tasks.Tasks)-1]
break
}
}
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
// heredoc
html := `
<!DOCTYPE html>
<title>tasks</title>
<h1>here is your <a href="%s">tasks</a></h1>
`
fmt.Fprintf(w, html, "/tasks")
}
func TasksHandler(tasks *Tasks) func(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<title>tasks</title>
<h1>{{.Owner}}'s tasks</h1>
<ul>
{{range .Tasks}}
<li>{{.}}</li>
{{end}}
</ul>
`
TasksList, err := template.New("tasklist").Parse(html)
if err != nil {
log.Fatal(err)
}
return func(w http.ResponseWriter, r *http.Request) {
TasksList.Execute(w, tasks)
}
}
func TasksJSONHandler(tasks *Tasks) func(w http.ResponseWriter, r *http.Request) {
b, err := json.MarshalIndent(tasks, "", " ")
if err != nil {
log.Fatal(err)
}
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, string(b))
}
}
func main() {
tasks := NewTasks("Jxck")
tasks.Add(NewTask(1, "buy the milk"))
tasks.Add(NewTask(2, "eat the pie"))
tasks.Add(NewTask(3, "<b>go to the bank</b>"))
http.HandleFunc("/", IndexHandler)
http.HandleFunc("/tasks", TasksHandler(tasks))
http.HandleFunc("/tasks.json", TasksJSONHandler(tasks))
http.ListenAndServe(":3000", nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.