cloud9_note

cloud9に限らないメモ

View on GitHub

Golang

モジュールの作成

mkdir ${module_name}
cd ${module_name}
go mod init ${module_name}

# モジュール実装

go mod tidy
go build

# 実行
./${module_name}

Githubに登録する場合

go mod init github.com/SampleUser0001/${reponame}

Githubに登録されているモジュールを使用する場合

go mod init ${project_name}
go get github.com/SampleUser0001/${reponame}

go install

GithubなどにソースがUPされている場合、go installコマンドで導入できる。

# $GOHOME/binのパスにインストールされる。
go install github.com/rakyll/hey@latest
hey https://www.golang.org

フォーマッタ

go install golang.org/x/tools/cmd/goimports@latest
goimports -l -w .
# または `go fmt`

lint(staticcheck)

go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck
# go.modがあるフォルダに対してチェックする
go vet

package mainでエラーになる

# プロジェクトのHOMEで実行すること。
go work init

go work use ${エラーを吐いているgoファイルがあるディレクトリ}

参考

リポジトリ、モジュール、パッケージ

モジュール

Goのソースコードの集合がモジュール。
ルートディレクトリにgo.modが配置されている。
下記のコマンドで生成する。

go mod init $MODULE_PATH

パッケージ

package句で指定する。ディレクトリ名とパッケージ名は一致させるのが一般的。

ローカルパッケージを使用する

下記でインポートする。

import $MODULE_PATH/$package名

外部パッケージを使用する

import github.com/$user/$repo

スライス

Javaで言うList。(配列もあるが、サイズが固定化される。)

append

	evenValues := []int{}
	for i := 0; i < 10; i++ {
		evenValues = append(evenValues, i*2)
	}

copy

package main

import "fmt"

func main() {
    x := []int{1, 2, 3, 4}
    z := make([]int, len(x))
    length := copy(z, x)
    z[1] = 10
    fmt.Printf("z : %d , length : %d, x : %d\n", z, length, x)
}
z : [1 10 3 4] , length : 4, x : [1 2 3 4]

map

package main

import "fmt"

func printFound(hashMap map[int]string, key int) bool {
	value, have := hashMap[key]
	if have {
		fmt.Println("value :", value)
	} else {
		fmt.Println("Not Found :", key)
	}
	return have
}

func main() {
	// LinkedHashMapはない。
	hashMap := map[int]string{
		2: "piyo",
		3: "fuga",
		1: "hoge",
	}
	fmt.Println(hashMap)

	for k := range hashMap {
		fmt.Printf("key : %d , value : %s\n", k, hashMap[k])
	}

	printFound(hashMap, 1)
	printFound(hashMap, 4)
}
map[1:hoge 2:piyo 3:fuga]
key : 3 , value : fuga
key : 1 , value : hoge
key : 2 , value : piyo
value : hoge
Not Found : 4

for

range

package main

import "fmt"

func main() {
	evenValues := []int{}
	for i := 0; i < 10; i++ {
		evenValues = append(evenValues, i*2)
	}

	for i, v := range evenValues {
		fmt.Println("i :", i, " v :", v)
	}
}
i : 0  v : 0
i : 1  v : 2
i : 2  v : 4
i : 3  v : 6
i : 4  v : 8

switch

goのswitchはbreak不要。

package main

import "fmt"

func main() {
	n := 0
	switch n {
	case 0:
		fmt.Println(n)
	case 1:
		fmt.Println(n)
	default:
		fmt.Println(n)
	}
}
0

関数型

package main

import "fmt"

func echo(v string) {
	fmt.Println(v)
}

func double(i int) int {
	return i * 2
}

func triple(i int) int {
	return i * 3
}

type strFuncType func(string)
type intFuncType func(int) int

func main() {
	var e strFuncType = echo
	var df intFuncType = double
	var tf intFuncType = triple
	e("hoge")
	fmt.Println("double :", df(5))
	fmt.Println("triple :", tf(5))
}

hoge
double : 10
triple : 15

関数を返す関数

0: 0, 0
1: 2, 3
2: 4, 6
3: 6, 9
4: 8, 12
5: 10, 15

型メソッド(構造体にメソッドを追加する)

package main

import "fmt"

type Model struct {
	id    int
	value string
	count int
}

// Modelで保持している値を返す
func (m Model) toString() string {
	return fmt.Sprintf("Model[id:%d, value:%s, count:%d]", m.id, m.value, m.count)
}

// countを加算する。
// 構造体の中で持っている値を直接更新する場合は、引数をポインタにする。
func (m *Model) countUp() {
	m.count = m.count + 1
}

// countを加算する。(実装ミス)
func (m Model) notCountUp() {
	m.count = m.count + 1
}

func main() {
	model := Model{
		id:    1,
		value: "hoge",
		count: 0,
	}

	fmt.Println(model.toString())
	model.countUp()
	fmt.Println(model.toString())
	model.notCountUp()
	fmt.Println(model.toString())
}
Model[id:1, value:hoge, count:0]
Model[id:1, value:hoge, count:1]
Model[id:1, value:hoge, count:1]

iota

enum(もどき)で使う。

package main

import "fmt"

type Category int

const (
	A Category = iota
	B
	C
)

func main() {
	fmt.Printf("A : %d\n", A)
	fmt.Printf("B : %d\n", B)
	fmt.Printf("C : %d\n", C)
}
A : 0
B : 1
C : 2

interface

関数をリストアップする

package main

import "fmt"

// TestInterface は3つのメソッドを持つインターフェースです
type TestInterface interface {
	func1()
	func2()
	func3()
}

// StructA は TestInterface を満たす構造体です
type StructA struct{}

func (a StructA) func1() { fmt.Println("StructA func1") }
func (a StructA) func2() { fmt.Println("StructA func2") }
func (a StructA) func3() { fmt.Println("StructA func3") }

// StructB は TestInterface を満たさない構造体です
type StructB struct{}

func (b StructB) func1() { fmt.Println("StructB func1") }
func (b StructB) func2() { fmt.Println("StructB func2") }

func main() {
	var test TestInterface
	test = StructA{} //OK
	test.func1()
	test.func2()
	test.func3()

	// 以下のコードはコンパイルエラーになります
	// test = StructB{} //NG
}

jsonファイルの読み込み

構造体に変換する

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
)

type Data struct {
	Id        string
	Name      string
	Image     Detail
	Thumbnail Detail
	// jsonと同じ項目を定義する。ただし、変数名は大文字。
}
type Detail struct {
	Url    string
	Width  int
	Height int
}

func main() {
	filePath := "data.json"

	// Read the JSON file
	data, err := os.ReadFile(filePath)
	if err != nil {
		log.Fatal(err)
	}

	// Unmarshal the JSON data into a struct
	var jsonData Data
	err = json.Unmarshal(data, &jsonData)
	if err != nil {
		log.Fatal(err)
	}

	// Print the data
	fmt.Println(jsonData)
}

data.json

{
    "id": "0001",
    "name": "Cake",
    "image": {
        "url": "images/pict0001.jpg",
        "width": 640,
        "height": 480
    },
    "thumbnail": {
        "url": "thumb/pict0001.jpg",
        "width": 64,
        "height": 64
    }
}
{0001 Cake {images/pict0001.jpg 640 480} {thumb/pict0001.jpg 64 64}}

配列の場合

	jsonData := []Data{}

	err = json.Unmarshal(data, &jsonData)
	if err != nil {
		fmt.Println("JSONデータのパースエラー:", err)
		return
	}

	// // データを表示する
	for _, item := range jsonData {
		fmt.Println(item.Id, item.Value)
	}

設定ファイルの読み込み

jsonとかを使うらしい。

構造体をjsonに変換する

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

func main() {
	person := Person{
		Id:   1,
		Name: "John Doe",
	}

	jsonData, err := json.Marshal(person)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}

	fmt.Println(string(jsonData))
}
$ go run app.go | jq
{
  "id": 1,
  "name": "John Doe"
}

起動引数を取得する

import (
	"fmt"
	"os"
	"strconv"
)

func main() { //liststart2
    if len(os.Args) != 3 {
		fmt.Println("引数の数が間違っています。")
		fmt.Println("使い方:ex0801 <被除数> <除数>")
		os.Exit(1)
	}
	var argsIndex int = 1

    // strconv.Atoiは文字列 -> 数値変換。
	numerator, _ := strconv.Atoi(os.Args[argsIndex])
	argsIndex++
	denominator, _ := strconv.Atoi(os.Args[argsIndex])
	argsIndex++
}

例外の扱い

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	_, error := hogehoge()
	if error != nil {
		fmt.Println(error)
		os.Exit(1)
	}
}

func hogehoge() (int, error) {
	return 0, errors.New("なんかのエラーが発生しました。")
}

センチネルエラー

mod/mod.go

package sentinelerror

import (
	"errors"
)

var SampleError = errors.New("なんかのエラーが発生しました。")

func SampleFunc() (int, error) {
	return 0, SampleError
}

package main

import (
	"errors"
	"fmt"
	"os"
	"sentinelerror"
)

func main() {
	_, err := sentinelerror.SampleFunc()
	if err != nil {
		if errors.Is(err, sentinelerror.SampleError) {
			fmt.Println("SampleErrorが発生しました。")
			os.Exit(1)
		} else {
			fmt.Println("その他のエラーが発生しました。")
			os.Exit(1)
		}
	} else {
		fmt.Println("エラーは発生しませんでした。")
		os.Exit(0)
	}
}

パッケージのインポート

go mod init ${package}

# ダウンロードするものがある場合実行
go mod tidy

main.go

package main

import "ittimfn/hoge"

func main() {
	hoge.PrintHoge()
}

hoge/hoge.go

package hoge

func PrintHoge() {
	println("hoge")
}
go mod init ittimfn

# 今回はローカルなので不要
# go mod tidy

go run main.go

go.mod

module ittimfn

go 1.21.5

ゴルーチン

doneチャネルパターン

package main

import (
	"fmt"
	"time"
)

func doSomething(i int, done <-chan bool) {
	for {
		select {
		case <-done:
			fmt.Printf("処理 %d を終了\n", i)
			return
		default:
			// 通常の処理
			fmt.Printf("処理 %d 実行中\n", i)
			time.Sleep(time.Second) // 何かの処理を想定
		}
	}
}

func main() {
	done := make(chan bool)
	for i := 0; i < 3; i++ {
		go doSomething(i, done)
	}

	time.Sleep(3 * time.Second) // 何かの処理を想定
	close(done)                 // すべてのゴルーチンに終了を通知
	time.Sleep(time.Second)     // ゴルーチンの終了を待機
}

tsv読み込み

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type Item struct {
	id    int
	value string
}

func main() {
	argsIndex := 1
	filePath := os.Args[argsIndex]
	argsIndex++

	fp, err := os.Open(filePath)
	if err != nil {
		fmt.Println("ファイルの読み込みエラー:", err)
	}
	defer fp.Close()

	scanner := bufio.NewScanner(fp) // 1行ずつ読み込む
	var itemList []Item
	for scanner.Scan() {
		splited := strings.Split(scanner.Text(), "\t")
		id, _ := strconv.Atoi(splited[0])
		item := Item{
			id:    id,
			value: splited[1],
		}
		itemList = append(itemList, item)
	}

	fmt.Println(itemList)
}
$ cat file.tsv
1       hoge
2       piyo
3       fuga
$ go run app.go file.tsv
[{1 hoge} {2 piyo} {3 fuga}]

別解

package main

import (
	"encoding/csv"
	"fmt"
	"os"
)

type Comment struct {
	Time    string
	Content string
}

func main() {
	// Open the comments.tsv file
	file, err := os.Open("comments.tsv")
	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}
	defer file.Close()

	// Read the file as a CSV
	reader := csv.NewReader(file)
	reader.Comma = '\t'

	// Read all the records
	records, err := reader.ReadAll()
	if err != nil {
		fmt.Println("Error reading file:", err)
		return
	}

	// Create a slice to store the comments
	comments := make([]Comment, 0)

	// Iterate over the records and create Comment objects
	for _, record := range records {
		comment := Comment{
			Time:    record[0],
			Content: record[1],
		}
		comments = append(comments, comment)
	}

	// Print the comments
	for _, comment := range comments {
		fmt.Println(comment)
	}
}

時間

time.Now()

package main

import (
	"fmt"
	"time"
)

func main() {
	currentTime := time.Now()
	// 2006-01-02 15:04:05でフォーマットする。
	fmt.Println(currentTime.Format("2006-01-02 15:04:05"))
}
2024-01-27 22:34:30

文字列 -> Time

package main

import (
	"fmt"
	"time"
)

func main() {
	// TimeZoneがないと、変換時にUTCとして扱われる。
	str := "2024/01/27 22:27:30 +0900"
	t, err := time.Parse("2006/01/02 15:04:05 -0700", str)
	if err != nil {
		fmt.Println("Error parsing time:", err)
		return
	}
	fmt.Println("Parsed time:", t)
}

Parsed time: 2024-01-27 22:27:30 +0900 JST

文字列 -> Duration

func convertToDuration(timeString string) (time.Duration, error) {
	splited := strings.Split(timeString, ":")
	var hour, minute, second int = 0, 0, 0
	var err error
	if len(splited) == 2 {
		// 分秒
		hour = 0
		minute, err = strconv.Atoi(splited[0])
		if err != nil {
			return 0, fmt.Errorf("minute is not number")
		}
		second, err = strconv.Atoi(splited[1])
		if err != nil {
			return 0, fmt.Errorf("second is not number")
		}
	} else if len(splited) == 3 {
		// 時分秒
		hour, err = strconv.Atoi(splited[0])
		if err != nil {
			return 0, fmt.Errorf("hour is not number")
		}
		minute, err = strconv.Atoi(splited[1])
		if err != nil {
			return 0, fmt.Errorf("minute is not number")
		}
		second, err = strconv.Atoi(splited[2])
		if err != nil {
			return 0, fmt.Errorf("second is not number")
		}
	}

	duration := time.Duration(hour)*time.Hour + time.Duration(minute)*time.Minute + time.Duration(second)*time.Second
	return duration, nil
}

http.Client

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

type User struct {
	UserId    int    `json:"userId"`
	Id        int    `json:"id"`
	Title     string `json:"title"`
	Completed bool   `json:"completed"`
}

func (u User) String() string {
	return fmt.Sprintf("User{UserId: %d, Id: %d, Title: %s, Completed: %t}", u.UserId, u.Id, u.Title, u.Completed)
}

func main() {
	client := http.Client{
		Timeout: 10 * time.Second,
	}

	url := "https://jsonplaceholder.typicode.com/todos/1"
	req, err := http.NewRequestWithContext(
		context.Background(),
		http.MethodGet,
		url,
		nil,
	)

	if err != nil {
		panic(err)
	}

	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}

	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		panic(fmt.Sprintf("status : %v", res.Status))
	}

	fmt.Println("status : ", res.Status)
	var data User
	err = json.NewDecoder(res.Body).Decode(&data)
	if err != nil {
		panic(err)
	}

	fmt.Println("data : ", data)
}

http.Server

package main

import (
	"net/http"
	"time"
)

type HelloHandler struct{}

func (handler HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

func main() {
	server := http.Server{
		Addr:         ":8080",
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  30 * time.Second,
		Handler:      HelloHandler{},
	}

	err := server.ListenAndServe()
	if err != nil {
		if err == http.ErrServerClosed {
			panic(err)
		}
	}
}

ルーティング

package main

import (
	"log"
	"net/http"
)

func generateMux(message string) (*http.ServeMux, error) {
	mux := http.NewServeMux()
	mux.HandleFunc("/greet",
		func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte(message + "\n"))
		})
	return mux, nil
}

func main() {
	person, _ := generateMux("Hello Person")
	cat, _ := generateMux("Hello Cat!!")

	mux := http.NewServeMux()
	mux.Handle("/person/", http.StripPrefix("/person", person))
	mux.Handle("/cat/", http.StripPrefix("/cat", cat))

	log.Fatal(http.ListenAndServe(":8080", mux))

	// http://localhost:8080/person/greet
	// http://localhost:8080/cat/greet
}

jsonを返す

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type Greeting struct {
	Message string
}

func generateMux(message string) (*http.ServeMux, error) {

	greeting := Greeting{Message: message}
	jsonData, err := json.Marshal(greeting)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return nil, err
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/greet",
		func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte(jsonData))
		})
	return mux, nil
}
func main() {
	person, _ := generateMux("Hello Person")
	cat, _ := generateMux("Hello Cat!!")

	mux := http.NewServeMux()
	mux.Handle("/person/", http.StripPrefix("/person", person))
	mux.Handle("/cat/", http.StripPrefix("/cat", cat))

	log.Fatal(http.ListenAndServe(":8080", mux))

	// http://localhost:8080/person/greet
	// http://localhost:8080/cat/greet
}
 $ curl http://localhost:8080/cat/greet | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    25  100    25    0     0  97276      0 --:--:-- --:--:-- --:--:-- 25000
{
  "message": "Hello Cat!!"
}

POST

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type Greeting struct {
	Message string
}

type Tension struct {
	Message string
	Level   int
}

var tensionMap map[string]Tension

func (t Tension) getMessage(key string) string {
	message := "Hello " + t.Message
	for i := 0; i < t.Level; i++ {
		message += "!"
	}
	return message
}

func tensionMapSetUp(name string, level int) {
	if tensionMap == nil {
		tensionMap = make(map[string]Tension)
	}
	if _, ok := tensionMap[name]; ok {
		return // already set up
	} else {
		tensionMap[name] = Tension{Message: name, Level: level}
	}

}

func httpGet(w http.ResponseWriter, key string) {
	tension := tensionMap[key]
	greeting := Greeting{Message: tension.getMessage(key)}
	jsonData, err := json.Marshal(greeting)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
	}
	w.Write([]byte(jsonData))
}

func httpPost(w http.ResponseWriter, r *http.Request, key string) {
	type PostTension struct {
		Level int `json:"level"`
	}

	var postTension PostTension

	// POSTで渡ってきた形式が変換できるかチェック
	if err := json.NewDecoder(r.Body).Decode(&postTension); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// 変換
	response, err := json.Marshal(postTension)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 更新
	tension := tensionMap[key]
	tension.Level = postTension.Level
	tensionMap[key] = tension

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(response)
}

func generateMux(name string, initLevel int) (*http.ServeMux, error) {

	// 普通は事前にDBとかを準備しておくもの。今回はmapなので、初期化処理を行う
	tensionMapSetUp(name, initLevel)

	mux := http.NewServeMux()
	mux.HandleFunc("/greet",
		func(w http.ResponseWriter, r *http.Request) {
			if r.Method == "GET" {
				httpGet(w, name)
			} else if r.Method == "POST" {
				httpPost(w, r, name)
			}
		})
	return mux, nil
}
func main() {
	person, _ := generateMux("Person", 0)
	cat, _ := generateMux("Cat", 2)

	mux := http.NewServeMux()
	mux.Handle("/person/", http.StripPrefix("/person", person))
	mux.Handle("/cat/", http.StripPrefix("/cat", cat))

	log.Fatal(http.ListenAndServe(":8080", mux))

	// http://localhost:8080/person/greet
	// http://localhost:8080/cat/greet
}
# 実行してみる
#!/bin/bash

curl -s http://localhost:8080/person/greet | jq '.'
curl -s http://localhost:8080/cat/greet | jq '.'

curl -X POST -H "Content-Type: application/json" -d '{"level":100}' http://localhost:8080/cat/greet 

curl -s http://localhost:8080/person/greet | jq '.'
curl -s http://localhost:8080/cat/greet | jq '.'

{
  "Message": "Hello Person"
}
{
  "Message": "Hello Cat!!"
}
{"level":100}{
  "Message": "Hello Person"
}
{
  "Message": "Hello Cat!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
}

test

package main

func main() {

}

func greeting(name string) string {
	return "Hello, a " + name
}

package main

import "testing"

func Test_greeting(t *testing.T) {
    // テーブルテストの実装例
	tests := []struct {
		name string
		want string
	}{
		{"Alice", "Hello, Alice"},
		{"Bob", "Hello, Bob"},
		{"", "Hello, "},
	}
	for _, tt := range tests {
		if got := greeting(tt.name); got != tt.want {
			t.Errorf("greeting(%v) = %v; want %v", tt.name, got, tt.want)
		}
	}
}
go test

テスト用データを保存する

testdataディレクトリ配下に配置する。

logrus(logging)

準備

go mod init ${dirname}
go get github.com/sirupsen/logrus
mkdir log

実装

logconf/logconf.go

package logconf

import (
	"os"

	"github.com/sirupsen/logrus"
)

// ログインスタンス生成
var Log = logrus.New()

func LogConf() {
	//
	Log.SetReportCaller(true)

	// ファイル出力の場合の例
	file, err := os.OpenFile("./log/app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err == nil {
		Log.Out = file
	} else {
		Log.Info("Failed to log to file, using default stderr")
	}

}

app.go

package main

import (
	"logging/logconf"

	"github.com/sirupsen/logrus"
)

func main() {
	logconf.LogConf()

	// ログ出力例
	logconf.Log.WithFields(logrus.Fields{
		"animal": "walrus",
		"size":   10,
	}).Info("A group of walrus emerges from the ocean")

}

log/app.log

time="2024-02-05T00:25:15+09:00" level=info msg="A group of walrus emerges from the ocean" func=main.main file="/home/ubuntuuser/environment/Practice_go/logging/app.go:16" animal=walrus size=10

sqlite3に接続する

初めてのGo言語