0
0

初心者視点で読むGoチュートリアル

Last updated at Posted at 2024-05-05

はじめに

 個人開発でバックエンドにGoを使いたかったので、Goの勉強を始めました。
 疑問をその都度解決していく勉強法なため、横道にそれまくりますが、Goを勉強し始目た時に同じ壁にぶつかった人の助けになればと思っております。コードは本家様をご参照ください。

Goとは

 Goとは、Googleがサポートするオープンソースのプログラミング言語です。

環境構築編

 早速環境構築を始めます。
 まず、Go公式からGoのインストーラーをダウンロードします。homebrewを使わないのはbrew upgradeで意図せずバージョンアップさせそうだからです。
 インスターラーでのインストールが完了したら、.zshrcに下記(パス)を記述します。

export PATH=$PATH:/usr/local/go/bin

 次は、Goのバージョンを確認します。

% go version
go version go1.22.2 darwin/arm64

 問題なくインストールされていますね。
 ついでに、VScodeにGoの拡張機能をインストールします。

チュートリアル Get started with Go

 環境構築が終わったら、Go公式のチュートリアルに取り組みます。

# 適当なディレクトリに移動
% cd example
# 作業ディレクトリの作成
% mkdir hello
# 作業ディレクトリに移動
% cd hello
# チュートリアル用のファイルを作成
% touch hello.go
# hello.goを編集
% vim hello.go
# go.modファイルを生成
% go mod init example/hello
go: creating new go.mod: module example/hello

疑問1: go.modって何?

 go.modファイルは使用されるモジュールの依存関係を管理するファイルです。とのこと。つまり、Pythonでいうpip install requirements.txtrequirements.txtにあたるファイルのことです。
※go.modはgo本体のバージョンや設定を含むため、厳密には違います。また、go.modはGOPATHの外側で開発を行う際には必須なファイルなので、絶対作成しましょう。


 結論が出たところでチュートリアルに戻ります。

hello.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

 goはgo runで実行できます。

% go run hello.go
Hello, World!

 恒例の"Hello,World"はうまくいきました。ですが、ここで一つ疑問が生まれます。package mainとは何でしょうか?

疑問2 : packageとは?

 パッケージはGoのコードをまとめる基本単位です。Goでは全てのコードがパッケージの中に含まれています。
 以下のようにパッケージを読み込むことで、他パッケージ内にある識別子(関数,変数,etc)にアクセスすることができます。 この時、以下の要件を満たすようにします。

  • mainパッケージとmain()を準備する
    • Goではリポジトリ内に必ずmainパッケージが存在し、その中にmain()が必要です。このmian()がエントリーポイントになるからです。
  • パッケージはディレクトリの中に入れる
    • Goのコードはパッケージで纏められており、同一のパッケージはディレクトリで纏めて管理します。
  • パッケージ名とファイル名は同一のものにする
    • 理由は後述します。
  • 外部で使用する識別子の頭は大文字
    • Goは頭文字が大文字であることをキーにエクスポートします。
main.go
package main

import {
    "fmt"
    "/test/print"
}

func main(){
    fmt.Println(hello.PrintHello("World"))
}

print.go
package hello

func PrintHello(name string) string{
    return fmt.Sprintf("Hello, %s!!",name)
}

% go run main.go
Hello, World!!

 import {path}で他パッケージを読み込むことができます。しかし、実際に使用する際はファイル名ではなくパッケージ名が使用されるため、ファイル名とパッケージ名は同一のものにすることが推奨されています。よって上記のコードは推奨されていない書き方と言えます。

 ここまでパッケージの話をしてきましたが、多くのソースコードで以下のような記述を見かけます。

import (
  "fmt"

  "github.com/{}/{}"
)

 私は思いました。なぜ、ここでgithub?

疑問3 : packageに現れるgithub

 Go言語ではgithubといったリモートからパッケージをインポート使用することが、しばしばあります。他者が作成したパッケージを気軽に使用できるのは利点かもしれません。しかし、疑問が残ります。
 別にローカルにコピーして使っても良いのではなだろうか?
 特に、開発中に自分で作ったパッケージをgithubからインポートする意味がわかりません。「同じものローカルにあるじゃん!」叫びました。
 しかし、物事には理由があるわけで、これにもしっかり理由がありました。

  • 理由1:常に最新版のパッケージを使用できる
    • チーム開発を行う際、誰かがgithubのパッケージを更新したとします。そのことに自分が気がついていなくてもビルドした際に最新版がgithubからインポートされるため、問題なく開発を行えます。
  • 理由2:パッケージをバージョン管理できる
    • githubで管理するということはパッケージもバージョン管理の恩恵を受けることができます。つまりバージョンを指定して過去のパッケージを扱うことができます。

 他にもありそうですが、私が納得できたのは以上二つです。

チュートリアル Get started with Go(2)

 だいぶ話が逸れてしましたが、チュートリアルに話を戻します。
 次は先程触れた外部パッケージの活用に挑戦します。
 外部パッケージはここで検索でき、今回はrsc.io/quoteモジュールに含まれるquote パッケージを使用します。

 main.goを作成したら、整合性を確認します。ここで使用する% go mod tidyとは、ソースファイルを解析して必要なライブラリをダウンロードしたり不要になったファイルを削除してくれたりするコマンドです。

% go mod tidy
go: finding module for package rsc.io/quote
go: downloading rsc.io/quote v1.5.2
go: found rsc.io/quote in rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

go.modを見ると内容が更新されています。

go.mod
module example/hello

go 1.22.2

require rsc.io/quote v1.5.2

require (
	golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
	rsc.io/sampler v1.3.0 // indirect
)
```bash

しかし、ここで一つ知らないファイルが増えていることに気がつきます。

% ls
go.mod  go.sum  main.go

 確かに、チュートリアルには"Goはquoteモジュールを要件として追加し、モジュールを認証するために使用する go.sum ファイルも追加します。"という記述がありました。ですが、よくわかりません。

疑問4 : go.sumとは?

 go.sumとは外部モジュールの不正な改竄を検出するためのハッシュ値が入ったファイルです。このファイルのおかげで、もし外部モジュールが不正に改竄されてもgo mod tidy時効時に異常を検出することができます。
 ※詳しくはこちら

チュートリアル Create a Go module

Start a module that others can use

 Goのコードはパッケージにグループ化され、パッケージはモジュールにグループ化されます。この章ではモジュールを作成して活用する練習を行います。

% mkdir greetings
% cd greetings
% go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings
% touch greetings.go

 チュートリアルではexample.comとなっていますが、example.comの箇所は自分のgituhubリポジトリ(モジュールがダウンロード可能なリポジトリ)に変えました。
 greetings.goではgreetingsパッケージで"Hi," +name+"Welcome!"を出力するHell関数を定義しています。変数messageは:=を使用することで型を宣言していませんが、型を宣言する場合は以下のように記述します。

greetings.go
var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)

 関数はfunc 関数名(引数 型) 戻りの型{return 戻り値}で定義されます。

Call your code from another module

"Create a Go module"では呼び出すgreetings.goを作成しました。
次はそれを呼び出すためのhellパッケージを作っていきます。

% mkdir hello
% cd hello
% go mod init example.com/hello
go: creating new go.mod: module example.com/hello

 しかし、これらのモジュールはまだgithubにpushしていないので、アクセスすることができません。そこで以下のコマンドを使用して、モジュールが見つからない際にローカルへリダイレクトするように設定します。

% go mod edit -replace example.com/greetings=../greetings

 さらに、まだ存在しませんが、main.goにgreetingsが必要である依存関係を記述します。この時、go.mod内のrequirev0.0.0-00010101000000-000000000000という記述が追加されますが、これはバージョンがない際に使われる疑似番号です。

% go mod tidy 

問題なく実行することもできました。

% go run main.go
Hi, Gladys. Welcome!

Return and handle an error

 エラーの扱いについてです。コードをgreetings.goにコピーします。コードにはnameが空である際にエラーを返す機能が追加されています。
 実際、実行してみたところ、エラーが出力されました。

% go run .
greetings: empty name
exit status 1

 ちなみに、ここでerrors.New("empty name")の代わりに使われているnilnullのことです。

Return a random greeting

 ここではGoのランダム関数とフォーマットに触れます。
 まず、greetings.goを以下のように書き換えます。
 Goはpythonでいうformat関数のようなものを使わなくても変数を代入できるのですね。驚きです。
 コードを実行してみると、毎回異なる出力が得られます。

% go run main.go
Great to see you, world!
% go run main.go
Hi, world. Welcome!

Return greetings for multiple people

 ここではマップとスライスを活用して複数の要素に対してHello関数の処理を実行します。
 実行すると、挨拶文のマップを出力として得られました。

% go run main.go
map[Darrin:Hail, Darrin! Well met! Gladys:Hail, Gladys! Well met! Samantha:Hail, Samantha! Well met!]

mapとslice

 ここで登場したmapsliceについて少し触れます。
 mapとは
  mapとはPythonでいう辞書にあたる要素です。
  m := make(map[string]string, 100)
  のようにmakeを使って型とサイズを指定して初期化できます。
 sliceとは
  sliceとはPythonでいうリストにあたる要素です。
  s := make([]int, 5, 10)
  のようにmakeを使って型とサイズを指定して初期化できます。

Add a test

 次はテストの実装を行います。Goでは、ファイル名を_test.goで終わらせるとことで、go testコマンドがそのファイルをテスト関数が含まれるファイルとして認識してくれます。
 まず、greetings関数のtestを作成します。テスト関数はTestから始まる名前を持ち、testing.T型のポインタをパラメータとして持ちます。
 一つ目のテスト、TestHelloNameはHello関数が有効な応答を返すことができるかのテストを行います。二つ目のテスト、TestHelloEmptyはHello関数にからの文字列が渡された際にエラーハンドリングが正しく機能するかを検証します。
 実際にgo testコマンドを実行してみます。

% go test
PASS
ok      github.com/{}/greetings    0.379s
% go test -v
=== RUN   TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN   TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok      github.com/{}/greetings    0.352s

 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