Goは静的型付きのcompile言語です。この早見表は現在のstable Goで基本programと最小HTTP serverを作る範囲へ絞ります。
最頻出10項目
| 操作 | Command / syntax |
|---|---|
| 実行 | go run . |
| build | go build ./... |
| test | go test ./... |
| format | go fmt ./... |
| module作成 | go mod init example.com/app |
| 依存整理 | go mod tidy |
| 変数 | name := "Ada" |
| 条件 | if err != nil |
| loop | for _, value := range values |
| error | return fmt.Errorf("...: %w", err) |
Project開始
mkdir hello
cd hello
go mod init example.com/hello
go run .
go test ./...
go fmt ./...
go vet ./...
go mod tidyは不要dependencyを削除し不足分を追加するため、実行後のgo.modとgo.sum差分をreviewします。
package main
import "fmt"
func main() {
fmt.Println("Hello, Go")
}
変数と基本型
var name string = "Ada"
age := 18
active := true
const maxRetries = 3
主な型:
| 種類 | 例 |
|---|---|
| boolean | bool |
| string | string |
| integer | int、int64 |
| floating point | float64 |
| byte | byte(uint8のalias) |
| Unicode code point | rune(int32のalias) |
zero valueはnumberが0、booleanがfalse、stringが""、pointer・slice・mapなどがnilです。
条件とloop
if score >= 80 {
fmt.Println("good")
} else {
fmt.Println("retry")
}
for i := 0; i < 3; i++ {
fmt.Println(i)
}
names := []string{"Ada", "Grace"}
for index, name := range names {
fmt.Println(index, name)
}
Goにwhile keywordはなく、conditionだけのforを使います。
for count < 10 {
count++
}
Function
func add(left, right int) int {
return left + right
}
func divide(left, right float64) (float64, error) {
if right == 0 {
return 0, errors.New("division by zero")
}
return left / right, nil
}
複数return valueを受け取れます。未使用値は_で捨てられますが、errorを安易に捨てないでください。
Error
data, err := os.ReadFile("config.json")
if err != nil {
return fmt.Errorf("read config: %w", err)
}
%wでwrapするとerrors.Is・errors.Asで元errorを調べられます。
if errors.Is(err, os.ErrNotExist) {
fmt.Println("config not found")
}
予測可能な失敗にpanicを使わずerrorを返します。logとreturnを両方行うと重複logになりやすいため、どのlayerで記録するか決めます。
Structとmethod
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func (u User) DisplayName() string {
return u.Name
}
func (u *User) Rename(name string) {
u.Name = name
}
値を変更するmethodはpointer receiverを使います。exportする名前は大文字で始めます。JSON tagは外部形式との契約なのでrename時に注意します。
Interface
type Writer interface {
Write([]byte) (int, error)
}
func writeGreeting(w Writer) error {
_, err := w.Write([]byte("hello"))
return err
}
型は必要なmethodを持てば明示宣言なしでinterfaceを満たします。利用側が必要な小さなinterfaceを定義します。
Slice
values := []int{1, 2, 3}
values = append(values, 4)
copyOfValues := append([]int(nil), values...)
for _, value := range values {
fmt.Println(value)
}
sliceはbacking arrayを共有する場合があります。独立copyが必要ならcopyまたは上記のように新sliceへappendします。
selected := values[:2]
index・slice範囲外はpanicになるため、外部入力のindexを検証します。
Map
scores := map[string]int{
"Ada": 90,
}
score, exists := scores["Ada"]
if exists {
fmt.Println(score)
}
delete(scores, "Ada")
nil mapからreadはできますがwriteはpanicします。make(map[string]int)で初期化します。通常のmapは複数goroutineからの同時writeに安全ではありません。
Pointer
value := 10
pointer := &value
*pointer = 20
pointer arithmeticはありません。nil pointerをdereferenceするとpanicします。
JSON
user := User{ID: 1, Name: "Ada"}
data, err := json.Marshal(user)
if err != nil {
return err
}
var decoded User
if err := json.Unmarshal(data, &decoded); err != nil {
return err
}
untrusted JSONにはsize上限、required field、値域のvalidationが別に必要です。unknown fieldを拒否したい場合はjson.DecoderのDisallowUnknownFieldsを検討します。
File
data, err := os.ReadFile("input.txt")
if err != nil {
return err
}
if err := os.WriteFile("output.txt", data, 0o600); err != nil {
return err
}
os.WriteFileは既存fileをtruncateして上書きします。重要dataでは一時file、rename、backupを検討します。外部入力からpathを作る場合は許可directory外へ出ないよう検証します。
Context
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
request, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://example.com/",
nil,
)
request scopeのdeadline・cancelを下位functionへ渡します。context.Contextをstruct fieldへ保存せず、通常は第1引数にします。
最小HTTP server
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(
w http.ResponseWriter,
r *http.Request,
) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
})
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
Internet公開にはTLS、request body上限、timeout、validation、graceful shutdown、proxy headerの扱いが必要です。http.ListenAndServeだけの例をproduction完成形にしません。
Test
package calculator
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Fatalf("got %d, want %d", got, want)
}
}
go test ./...
go test -race ./...
go test -cover ./...
race detectorは対応platformで利用します。test dataをproduction DBへ向けないよう環境を分けます。
Goroutine最小
results := make(chan int)
go func() {
results <- 42
}()
value := <-results
goroutineを開始したら、終了・cancel・error回収方法を決めます。channelのcloseは通常sender側が行い、close済みchannelへのsendはpanicします。
よく使うpackage
| Package | 用途 |
|---|---|
fmt | format・表示 |
errors | error検査 |
os | file・環境変数 |
io | stream |
encoding/json | JSON |
net/http | HTTP |
context | cancel・deadline |
time | 時刻・duration |
strings | 文字列 |
strconv | 型変換 |
最終確認
go fmt ./...
go vet ./...
go test ./...
go mod tidy
go build ./...
並行処理、benchmark、cross compile、embed、構造化logの詳細は必要になった時点でGo公式package documentへ進んでください。