6.パッケージ - iruma-tea/go_programming GitHub Wiki

6. パッケージ

Goコードはパッケージという単位で構成される。
自分で作成した別パッケージのコードを利用したり、Goの標準パッケージやサードパーティ製の
パッケージを利用することで、よりさまざまなコードを書くことができる。

6.1 パッケージでコードを管理しよう

パッケージごとにファイルを分け、別のパッケージから関数や型を呼び出す方法について記載します。

6.1.1 パッケージ単位でコードを分けよう

Goのコードは、1または複数のファイルで構成されるパッケージという単位で管理する。

  • 「プロジェクト」フォルダの中に、新しくフォルダを作る。
  • 新しいフォルダは「mylib」という名前で作成する。
  • 新しいパッケージを作る際は、新しいフォルダを作る必要があり、パッケージ名とフォルダ名は対応させる必要がある。
  • math.goを作成する。
    • 1行目に「package mylib」というパッケージ名を書く
      • これでこのファイルはmylibというパッケージに属した
    • int型のスライスを引数として平均値を求めるAverage関数を作る
package mylib

func Average(s []int) int {
    total := 0
    for _, i := range s {
        total += i
    }
    return int(total/len(s))
}
package main

import (
    "awesomeProject/mylib"
    "fmt"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(mylib.Average(s))
}
// 実行結果
// 3
  • human.go
    • math.goと同じフォルダに「human.go」というファイルを作成する。
    • mylibパッケージに属する
    • 「Human!」という文字列を出力するSayという関数を作成する
package mylib

import "fmt"

func Say() {
    fmt.Println("Human!")
}
package main

import (
    "awesomeProject/mylib"
    "fmt"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(mylib.Average(s))

    fmt.Println(mylib.Say())
}
// 実行結果
// 3
// Human!
  • sub.go
    • mylibフォルダの中に「under」という名前のフォルダを作る。
    • その中にsub.goというファイルを作る。
    • sub.goではHelloという関数で「Hello!」という文字列を出力させる。
package under

import "fmt"

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

import (
    "awesomeProject/mylib"
    "awesomeProject/under"
    "fmt"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(mylib.Average(s))

    fmt.Println(mylib.Say())
    under.Hello()
}
// 実行結果
// 3
// Human!
// Hello!

6.1.2 関数や型をエクスポートしよう

  • human.goにPersonという構造体を作り、フィールドにstring型のNameとint型のAgeを書きます。
  • この構造体をmain.goから呼び出し、初期化し手表示する。
  • human.goの「type Person struct」やmain.goの「person := mylib.Person」などのPersonの最初のPは大文字で書いている。
  • これは、エクスポートという仕組みで関数や型の名前の大文字ではじめると、他のパッケージから呼び出せるようになる。
  • また、構造体PersonのフィールドのNameやAgeも、小文字にすると他のパッケージから呼び出せずエラーとなる。
  • varを使って変数を宣言する場合
    • 「var Public string = "Public"」→ 他のパッケージから呼び出すことが可能。
    • 「var private string = "private"」 → 他のパッケージから呼び出すことが不可能。
package mylib

import "fmt"

type Person struct {
    Name string
    Age int
}

func Say() {
    fmt.Println("Human!")
}
package main

import (
    "awesomeProject/mylib"
    "awesomeProject/under"
    "fmt"
)

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(mylib.Average(s))

    fmt.Println(mylib.Say())
    under.Hello()
    person := mylib.Person{Name: "Mike", Age: 20}
    fmt.Println(person)
}
// 実行結果
// 3
// Human!
// Hello!
// {Mike 20}

6.2 テストを実行しよう

作成したコードが正しく動作するかを確認するためには、テスト用コードを作成する。
テストを実行することで、コードに変更があった場合でも、テストが成功するコードの動作が問題ないことが確認できます。
ここでは、Goの標準パッケージであるtestingを使ってテストを作成し、実行する方法を説明。

6.2.1 testingパッケージでテストを作ろう

testingパッケージを使って、テストを書いていきます。testingパッケージのドキュメントはこちらから確認できる。

  • math.goのテストを作成する
    • テストを作る際は
      • テストする対象と同じフォルダにテストようのコードを記述するファイルを入れる。
      • testingパッケージは、ファイル名の末尾「_test.go」のファイルを読み込んでテストを行う。
      • ファイル名を「math_test.go」とする。
      • テストを行う関数は「Test」からはじまる関数名で、*testing.T型を引数を受け取る必要がある。
    • テストの実行
      • ターミナルに「go test ./..」と入力して実行させる
        • 現在のディレクトリとその下にあるすべての「_test.go」がついたファイルを実行する。
package mylib

import "testing"

func TestAverage(t *testing.T) {
    v := Average([]int{1, 2, 3, 4, 5})
    if v != 3 {
        t.Error("Expected 3, got ", v)
    }
}
  • testingのメソッド
    • t.Errorメソッドを使ってテストを行いましたが、他にもテストに利用できるメソッドがある。
      • t.Fail テストが失敗し、かつテストの実行を続けたいときに使う。
      • t.Skip テストが必要ないときに使う。
package mylib

import "testing"

var Debug bool = true

func TestAverage(t *testing.T) {
  if Debug {
    t.Skip("Skip Reason")
  }
  v := Average([]int{1, 2, 3, 4, 5})
  if v != 3 {
    t.Error("Expected 3, got ", v)
  }
}
// 実行結果
// kip Reason
// --- SKIP: TestAverage (0.00s)

6.3 コードの形式を整えよう

コードを読みやすくするために、形式を整えることは重要です。
ただし、手動でコードを修正していくのは非常に大変かつ修正漏れが発生する恐れもあるため、
ツールを使ってある程度修正することが一般的です。ここでは、gofmtというコードを修正するための
ツールについて記載する。

6.3.1 gofmtでコードの形式を整えよう

gofmtは、Goのコードの書き方を修正するためのツールです。
VSCodeなどのコードエディタの機能によっては、保存するたびに自動でgofmtを実行してくれる場合もある。

  • gofmtの実行方法
    • gofmt math.go → 整形されたコードが表示される。
    • gofmt -w math.go → 整形されたコードで上書きされる。

6.4 サードパーティのパッケージを利用しよう

Goにはさまざまな機能を持つ標準パッケージがありますが、開発したい機能によっては、サードパーティのパッケージを活用する場合もある。
ここでは、サードパーティのパッケージをインストールして使用する方法について記載する。

6.4.1 サードパーティのパッケージのインストール

  • 第三者(サードパーティ)が公開されているパッケージは、こちらで検索ができる。
  • talibという株価を分析するサードパーティのパッケージをインストールする。
  • Goでは、go getというコマンドで、サードパーティのパッケージをインストールする。
    • go get github.com/markcheno/go-talib (詳細)
  • quoteというパッケージもインストールする
    • go get github.com/markcheno/go-quote/quote@latest (詳細)
package main

import (
	"fmt"

	"github.com/markcheno/go-quote"
	"github.com/markcheno/go-talib"
)

func main() {
	spy, _ := quote.NewQuoteFromYahoo("spy", "2016-01-01", "2016-04-01", quote.Daily, true)
	fmt.Print(spy.CSV())
	rsi2 := talib.Rsi(spy.Close, 2)
	fmt.Println(rsi2)
}
  • インポートしたパッケージに別名をつける
package main

import (
	"fmt"

	"github.com/markcheno/go-quote"
	a "github.com/markcheno/go-talib" // 別名 a をつける。
)
  • インポートしたパッケージを使用しない場合
    • 通常、インポートしたパッケージをコード内で使用しないとエラーとなる。
    • _を前に書くとコードの中で使わなくてもエラーが出ない。
package main

import (
	"fmt"

	"github.com/markcheno/go-quote"
	_ "github.com/markcheno/go-talib" // 別名 a をつける。
)

6.5 ドキュメントを作成しよう

コードを作成したら、ドキュメントを整備することも大切です。
Goには、コードをドキュメント化するツールが用意されている。ドキュメントを書く際には、
いくつかのルールがある。ここではドキュメントの書き方と、その内容を確認する方法について記載する。

6.5.1 go docでコードの説明を確認しよう

go docは、Goのドキュメントを確認するためのコマンドです。
go docでfmtのドキュメントを見てみましょう。ターミナルに「go doc fmt.Println」とコマンドを入力すると、Printlnの説明文がでました。

package fmt // import "fmt"

func Println(a ...any) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline
    is appended. It returns the number of bytes written and any write error
    encountered.

この説明文と同じように自分で作成したmath.goのAverage関数についても説明を書いて行きます。
説明文はコメントアウトして書きます。先頭に//を書く方法と、/**/で囲む方法があります。どちらで書いても問題ない。
また、文の最初を関数名と同じ(ここでは「Average」)にする必要がある。

パッケージの説明を書く。mylibパッケージの説明は、/**/で囲ってファイルの先頭に次のように書いてみよう

/*
mylib is my special lib.
*/
package mylib

// Average returns the average of a series of numbers
func Average(s []int) int {
	total := 0
	for _, i := range s {
		total += i
	}
	return int(total / len(s))
}

human.goの構造体Personのフィールドに説明文を追加するには、次のように書く。

type Person struct {
	// Name
	Name string
	// Age
	Age int
}

6.5.2 godocでブラウザ上のドキュメントを確認しましょう

go docと似たコマンドに、godocがあります。godocは説明文を書いたコードをローカルのWebページで実行することができる。

  • godocのインストール
    • godocが実行できない場合、「go install golang.org/x/tools/cmd/godoc@latest」
  • godocの実行
  • コードの例を作成する
    • math_test.goで関数名の頭に「Example」をつけた関数を作成し、その中にコードの例を書く
  • パッケージ全体のコードの例
    • パッケージ全体の使い方は、Exampleという名前の関数を作成し、その中にコードの例を書く
  • メソッドのコード例
    • 「Example」の後に、構造体名とメソッド名を_でつなげて書く

6.6 便利な標準パッケージを活用しよう

標準パッケージの中からよく使う2つのパッケージを紹介していきます。
timeは時間を扱うパッケージ、regexは正規表現についてのパッケージでどちらもよく
使うので実際のコードとともに使い方を見て行こう。

6.6.1 timeで時間に関数コードを書こう

timeは、時間に関する機能を提供するGoの標準パッケージです。
時間関係のコードを書く際に、time.Nowやtime.Secondを利用しました。
timeのgodocは「こちら」から確認できます。

6.6.2 regexpで正規表現のコードを書こう

正規表現を扱うパッケージであるregexpについて記載する。正規表現とは、条件にあてはまる文字列を検索するときに使われる
表現で例えば次のようのな正規表現がある。

正規表現の例 意味
[a-z] aからzまでの任意も1文字
a+ aという文字が1文字以上連続する部分
a b
^a 先頭がaで始まる文字列
a$ 末尾がaで終わる文字列
  • MatchString関数
    • MatchString関数は、正規表現が文字列にに一致していれば、true、そうでなければfalseを返す。
      • 引数に「a([a-z]+)e」という正規表現
      • 「apple」という文字列
import (
  "fmt"
  "regexp"
)

func main() {
  match, _ := regexp.MatchString("a([a-z]+)e", "apple")
  fmt.Println(match)
}
// 実行結果
// true
  • MustCompile関数
    • MustCompile関数は正規表現にしようするregexpオブジェクトを返します。
    • 同じ正規表現を繰り返し使用する場合、MatchString関数よりもMustCompile関数の方が高速な処理となる。
    • MustCompile関数の引数に正規表現を書き、regexpオブジェクトのメソッドであるMatchStringを使う。
import (
  "fmt"
  "regexp"
)

func main() {
  r := regexp.MustCompile("a([a-z]+)e")
  ms := r.MatchString("apple")
  fmt.Println(ms)
}
// 実行結果
// true
  • FindStringメソッド
    • FindStringメソッドは、正規表現に一致する最も左側の文字列を返す。
import (
  "fmt"
  "regexp"
)

func main() {
  r2 := regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
  fs := r2.FindString("/view/test")
  fmt.Println(fs)
}
// 実行結果
// /view/test
  • FindStringSubmatchメソッド
    • FindStringSubmatchメソッドは、正規表現に一致する文字列とともに、正規表現の()で囲まれた部分に一致する部部も合わせてスライスで返す。
import (
  "fmt"
  "regexp"
)

func main() {
  r2 := regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
  fss := r2.FindStringSubmatch("/view/test")
  fmt.Println(fss, fss[0], fss[1], fss[2])
}

// 実行結果
// [/view/test view test] /view/test view test
⚠️ **GitHub.com Fallback** ⚠️