0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

go初心者が1ヶ月で勉強してきた内容をまとめてみた

Last updated at Posted at 2024-09-27

この記事を書いた目的

大学2回生の終わりからプログラミングの勉強を始めたいわゆる「一般的な学生エンジニア」がどのような道筋で新しい言語の勉強をしたのか公開することで同じような状況の人に気づきを与えることができれば良いなと思い書き始めました。

僕自身が勉強を始めた時は記事などを見るとほとんどの人がjavaやrubyなどバックエンド言語を以前に勉強している状況で勉強を始めたという人が多かったので・・・。(僕はgoを勉強するまでフロントエンド分野しか勉強したことがありません)

またこの記事は勉強記録的な意味合いもあるのでところどころ読みづらいかもしれません、そこはすいません!

なぜGOが使われるのか

少し冗長ではあるが学んだことがある人には理解しやすく、シンプルであるから。
実装効率とパフォーマンスのバランスがいい。
コンパイル言語でネイティブコードに変換されるため、各種OS向けにビルド可能

信頼できる情報源

Standard Library: https://pkg.go.dev/std
Go Release Note: https://go.dev/doc/devel/release

Effective Go: https://go.dev/doc/effective_go
The Go Blog: https://go.dev/blog/
Go Wiki: https://go.dev/wiki/

Go by example: https://gobyexample.com/
プログラミング言語GO完全入門: https://docs.google.com/presentation/d/1RVx8oeIMAWxbB7ZP2IcgZXnbZokjCmTUca-AbIpORGk/edit#slide=id.g4f417182ce_0_0

やったこと

https://go.dev/tour/welcome/1

web上で実行できるのでまずは環境構築をせずにgoの感覚を掴みたい人はやってみるといいと思います。

https://go.dev/doc/tutorial/

公式チュートリアルです。以下の写真の3つのチュートリアルを行いました。

3つ目のチュートリアルをすることで簡単な感覚がつかめたと思います。ここからは実務や実際の個人開発などで使い方を学んでいこうと思います。

③Chat gptを使ってapiを作成した。またnext.jsと連携してボタンを押したら情報を取得するようにした。

④公式チュートリアルの4つ目をやった。

postgresqlと接続してクエリを取得したり、データの追加などを行うことができる関数を作成した。

⑤公式チュートリアルの5つ目をやった。

goでginフレームワークを使って簡単なrestful APIを作成した。

⑥公式チュートリアルの6つ目をやった。

genericsを使うことで複数の型を使用できる汎用性を得た。また、動的型付け言語とは異なりgenericsを利用しても型チェックはしてくれるので保守性も向上する。しかし、コードが複雑になったり学習コストが高いことが懸念点。

⑦書籍:goプログラミングエッセンスを読んだ。

⑧個人開発でバックエンドでgoを使用したアプリを作成中。

⑨書籍:go言語webアプリケーション開発を読み、todoアプリのハンズオンを行った。

いきなりハンズオンに取り組むと難しかった。次の書籍を先にやってその後にするといい。

⑩書籍:APIを作りながら進むGo中級者への道を読み、8章までハンズオンを行った。

この書籍は個人的にはかなりわかりやすかった。⑦の書籍を読んだ後に先にこの書籍をやっておけばと少し後悔。フォルダの構成なども参考になり、インターン先で僕が開発中のコードの改善に大きく貢献してくれた一冊。

11. インターン先でのバックエンド開発(Go)

実務経験に勝る勉強はないとよく言われるが今回それを実感した。適度な責任感や焦燥感があることで入社前に勉強していたフロントエンド分野よりはるかに早く、正確に理解できてきていると思う。自分の書いたコードが新規プロダクトに使用されるというワクワク感も相まってモチベーションが明らかに高い状況で勉強できている。
・関数をパッケージにして使用する
・環境変数をmain.goに直張り → ファイルから値を取得する
・Makefileを整理してSAMのビルドからデプロイまで一貫して行う
・dynamoDBへの接続
・ゴルーチンを使用した処理の実現
・複数のパッケージに使用される構造体はtypesフォルダにまとめる
・関数の命名をgoのベストプラクティスに則って変更
これらを1ヶ月ほどで経験させていただき、とても貴重な機会となった。もちろんまだまだ問題点が山積みなので勉強を続けます。

GOにCIを導入(Github Actions)

※Githubに接続されていることを前提とします

①プロジェクトディレクトリ配下にディレクトリを作成

mkdir -p .github/workflows

②yamlファイルを作成
以下の内容をgo.ymlファイルに記述する

name: Go CI

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Check out the repository
      uses: actions/checkout@v2

    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: 1.x  # 使っているGoのバージョンを指定

    - name: Install dependencies
      run: go mod tidy

    - name: Run tests
      run: go test ./...

③githubに変更を反映
行った変更をリモートリポジトリに反映させて完了

go mod initの意味

パッケージ管理やバージョン管理のために必要。

go mod init 

を実行することでモジュールが初期化される。またこれにより依存関係を示すgo.modファイルが作成され、静的型付け言語であるgoはこれを参考に依存関係を把握する。これにはややこしさがあるものの異なる環境での再現性を向上させるという利点がある。

実務ではgraceful shutdownする

以下サンプルコード

ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt, os.Kill)
defer stop()

srv := &http.Server{
	Addr:    port,
	Handler: mux,
}

go srv.ListenAndServe()

<-ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

err = srv.Shutdown(ctx)
if err != nil {
	return err
}

文法

リテラル・変数宣言

関数外で宣言した値を再度関数内で宣言するとそれは関数の外側には反映されない。

またgoでは型変換をする際は明示的に行う必要がある

var num1 int = 123
num2 := 456 #これは関数内部でのみ使用可能、また未使用だとエラーが発生する

ゼロ値

変数宣言を行ったが値を設定していない変数はゼロ値で初期化される。
数値なら0、文字列なら空文字列が設定される。

スライス

goには可変長配列が存在しない。だがスライスを配列のような構造として使用できる。goは安全性を重視しているため、範囲外アクセスがあるとプログラムが終了する。以下のように使用可能。

var nums [3]int = [3]int{1, 2, 3}

	var nums1 []int

	// 1, 2, 3の要素を持つスライスを作成して代入
	nums2 := []int{1, 2, 3}

	// あるいは既存の配列やスライスからも範囲アクセスでスライス作成
	nums3 := nums[0:2]  // 配列から
	nums4 := nums2[1:3] // スライスから

	// 配列と同じようにブラケットで要素取得可能
	// 範囲外アクセスはパニック
	fmt.Println(nums2[1]) // 2

	// 要素の割り当ても可能
	nums2[0] = 100

	// 長さも取得可能
	fmt.Println(len(nums2)) // 3

	// スライスに要素を追加
	// 再代入が必要
	nums2 = append(nums2, 4)

	// use-slice
	fmt.Println(nums1, nums2, nums3, nums4)

マップ

型は map[キーの型]値の型 とする必要がある

hs := map[int]string{
		200: "OK",
		404: "Not Found",
	}

	// makeで作る
	authors := make(map[string][]string)

	// ブラケットで要素アクセス
	// 代入
	authors["Go"] = []string{"Robert Griesemer", "Rob Pike", "Ken Thompson"}

	// データ取得
	status := hs[200]
	fmt.Println(status)
	// "OK"

	// 存在しない要素にアクセスするとゼロ値
	fmt.Println(hs[0]) // panic

	// あるかどうかの情報も一緒に取得
	status, ok := hs[304]
	// status = ""
	// ok = false
	// use-map

	fmt.Println(hs, ok)

switch文

if, if elseのような動作をするが、1つのcaseに当てはまったらその処理を抜けるという特徴を持つ

switch s {
	case "running":
		fmt.Println("実行中")
	case "stop":
		fmt.Println("停止中")
	default:
		fmt.Println("その他")
	}

deferによる後処理

関数の処理を抜けるタイミングで後処理をするメカニズムである。ファイルを閉じるなどの処理を行う。

defer.res.Body.Close()

終了処理

os.exit()は0以外でプログラムが強制終了する

構造体の定義

以下のように階層構造のAPIも定義できる

type NippoApiResponse struct {
	Results struct {
		MovementDetail struct {
			LoadedDistance      int    `json:"loadedDistance"`
			WorkStartTime       string `json:"workStartTime"`
			WorkEndTime         string `json:"workEndTime"`
			RoadRunningDuration int    `json:"roadRunningDuration"`
			WorkDuration        int    `json:"workDuration"`
		} `json:"movementDetail"`
		MovementEnd struct {
			EndTime string `json:"endtime"`
		} `json:"movementEnd"`
		MovementStart struct {
			StartTime string `json:"startTime"`
		} `json:"movementStart"`
	} `json:"results"`
}

変数の宣言

このようにスライスを定義することもできる

var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

エラー文

nilは「何もない」ということを示す。javascriptでいうnullのようなもの。

if err != nil {
		log.Fatal("Failed to connect to the database: ", err)
	}

このようにerrがnilではない場合〇〇を表示するといったエラー文がよくある。

postgreSQLとの接続

connStr := "user=aaa password=aaa dbname=mydb sslmode=disable"
	var err error
	db, err = sql.Open("postgres", connStr)
	if err != nil {
		log.Fatal("Failed to open the database: ", err)
	}
	defer db.Close()

	// データベースへの接続を確認
	err = db.Ping()
	if err != nil {
		log.Fatal("Failed to connect to the database: ", err)
	}

	fmt.Println("Connected to PostgreSQL!")

golangのfmt.Printf関数

%s:文字列(string)
%d:符号付き整数(int, int8など)
%t:論理値(bool)
%T:型

%v:デフォルトのフォーマット
%d:符号なし整数(uint, uint8など)
%g:浮動小数点数(float64など)
%g:複素数(complex128など)
%p:チャネル(chan)
%p:ポインタ(pointer)
%%:%を出力
%c:文字
%b:2進数

Goで作成するAPI(HelloWorld)

フレームワークを使用せずにgoのAPIを作成すると以下のようになる。

package main

import (
	"io"
	"log"
	"net/http"
)

func main() {
	helloHandler := func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "Hello, World!\n")
	}
	http.HandleFunc("/", helloHandler)
	log.Println("Listening on port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

ginを使ったAPI作成

GETメソッド

localhost:8080/albumsにアクセスされた時にgetAlbums関数が呼び出され、albums(宣言してある変数)が表示される。

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)

    router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

POSTメソッド

受け取ったデータをnewAlbumに格納。albumsにappendする。
c.IndentedJSON(http.StatusCreated, newAlbum)ではcurlコマンドでPOSTした時にステータスコードと追加したデータであるnewalbumを表示するようにしている。

func postAlbums(c *gin.Context) {
	//newAlbumにクライアントから受け取ったJSON(データ)を格納
    var newAlbum album

    // Call BindJSON to bind the received JSON to
    // newAlbum.
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Add the new album to the slice.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

GETメソッド(ID指定)

cにはリクエストパラメータの情報が入っているので、IDを取得してidに格納する。
for文を回し、albumの一つの塊であるaのIDがidと同じだった場合ステータスとaを表示する。

func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Loop over the list of albums, looking for
    // an album whose ID value matches the parameter.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

Generics

本来は以下のように型が違うことによって同じような処理でも2つの関数を作成する必要があるが、genericsを使用すると1つで済む。

Genericsを使用していないコード

func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

Genericsを使用したコード

func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

fuzzing

ルール

関数名の最初がFuzzである必要がある。またファイル名も
〇〇_test.goである必要がある。以下のコマンドで実行できる。

go test
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?