【Go言語入門②】Go言語でモジュールを作成し、理解を深める

Go言語
目次

前回の内容

前回実施した内容を理解した前提なので、まずは下記をやってみてくださいね

今回実施する内容

このチュートリアルでは、2 つのモジュールを作成します。

実施するチュートリアルは下記になります

公式ドキュメントの冒頭の説明

1 つ目は、他のライブラリまたはアプリケーションによってインポートされることを意図したライブラリです。2 番目は、最初のアプリケーションを使用する呼び出し側アプリケーションです。

このチュートリアルのシーケンスには、それぞれ言語の異なる部分を説明する 7 つの簡単なトピックが含まれています。

  1. モジュールを作成する — 別のモジュールから呼び出すことができる関数を含む小さなモジュールを作成します。
  2. 別のモジュールからコードを呼び出す— 新しいモジュールをインポートして使用します。
  3. エラーを返して処理する— 簡単なエラー処理を追加します。
  4. ランダムな挨拶を返す— データをスライス (Go の動的サイズの配列) で処理します。
  5. 複数の人に挨拶を返す — キーと値のペアをマップに保存します。
  6. テストを追加— Go の組み込みの単体テスト機能を使用して、コードをテストします。
  7. アプリケーションをコンパイルしてインストールする — コードをローカルでコンパイルしてインストールします。

Go モジュールを作成することから始めます。モジュールでは、個別の便利な機能セットに関連する 1 つ以上のパッケージを収集します。たとえば、財務分析を行うための機能を備えたパッケージを含むモジュールを作成して、他の財務アプリケーションを作成しているユーザーが自分の作業を使用できるようにすることができます。モジュールの開発について詳しくは、モジュールの 開発と公開を参照してください。

ぎゅう

全体像が見えたところで、実際にやってみよう

モジュールを作成する

プロジェクトの作成するイメージでモジュールを開始します。

今回はgreentingというモジュールになります。

// ディレクトリの作成
$mkdir greeting
$cd greeting

// go.mod作成
$go mod init example.com/greeting
go: creating new go.mod: module example.com/greeting

前回同様にこれでgo.modの生成まで終えました

module example.com/greeting

go 1.19

greetings.goを作成して、型定義を理解する

greetings.goを作成して、挨拶する処理を作成していきましょう。

関数の定義は下記の構成になっております

func 関数名(引数 型定義) 返り値の型 {
  // 処理内容
}

実際に記述してみます

package greetings

import (
	"fmt"
)

// Hello は、指定された人に対する挨拶を返す
func Hello(name string) string {
    // メッセージに名前を埋め込んだグリーティングを返す。
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}
ぎゅう

型はJavaっぽいですね

これは、モジュールの最初のコードです。あいさつを求める発信者には、あいさつを返します。次の手順で、この関数を呼び出すコードを記述します。

この関数は、namestring型のパラメータをとり、stringを返します。Go では、名前が大文字で始まる関数は、同じパッケージにない関数から呼び出すことができます。これは、Go ではエクスポートされた名前として知られています。エクスポートされた名前の詳細について は、Go ツアーの エクスポートされた名前を参照してください。

Go公式
ぎゅう

どういうことだろう?

このモジュールをimportしても、小文字から始まる関数は実行できない仕様です。
大文字なら外部モジュールでも実行できます

この辺りは下記の記事を読んでみるとわかりやすいです。

ぎゅう

小文字だとprivateメソッドの扱いですね

この辺りは下記のページの説明をみてみるとよいでしょう

Goでは、最初の文字が大文字で始まる名前は、外部のパッケージから参照できるエクスポート(公開)された名前( exported name )です。 例えば、 Pi は math パッケージでエクスポートされています。

小文字ではじまる pi や hoge などはエクスポートされていない名前です。

パッケージをインポートすると、そのパッケージがエクスポートしている名前を参照することができます。 エクスポートされていない名前(小文字ではじまる名前)は、外部のパッケージからアクセスすることはできません。

https://go-tour-jp.appspot.com/basics/3

関数がエクスポートされないということなので、JavaScriptと似たイメージですね。

オンラインで動作確認できるので、ありがたいです

関数はmainから開始

A Tour of Goでも説明されていますが、モジュール内で作成されたファイルつまりパッケージは、main()から実行されます。

Goのプログラムは、パッケージ( package )で構成されます。


プログラムは main パッケージから開始されます。
このプログラムでは "fmt" と "math/rand" パッケージをインポート( import )しています。
規約で、パッケージ名はインポートパスの最後の要素と同じ名前になります。 例えば、インポートパスが "math/rand" のパッケージは、 package rand ステートメントで始まるファイル群で構成します。

main()から実行されるということは、まずはmain()から読めばいいので、可読性もよいですね

ぎゅう

言語で統一されているのはありがたいですね

変数の型定義は:=で省略可能

message := fmt.Sprintf("Hi, %v. Welcom!", name)
ぎゅう

変数定義時の:=はなんだろう?

:=で変数名を定義すると、変数の型を自動的に定義してくれます

=で定義すると下記のようになります

// 変数をstring型で宣言
var message string 

// 変数に値を割り当てる
message = fmt.Sprintf("Hi, %v. Welcome!", name)

このように、変数を宣言してから、値を割り当てます。

ぎゅう

JavaとかTypeScriptでよく使いますよね

しかし、:=を利用すること変数の宣言と同時に、値を割り当てることができます

message := fmt.Sprintf("Hi, %v. Welcom!", name)

2行だった箇所が1行にまとまり、スッキリしました

別のモジュールからコードを呼び出す

構成

<HelloPkgs>/
 |-- greetings/
 |-- hello/

helloモジュールを作成する

$cd ../
$mkdir hello
$cd hello
$go mod init example.com/hello
go: creating new go.mod: module example.com/hello
package main

import (
	"fmt" 
    "example.com/greetings"
)

func main() {
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}

go mod tidyを実行して、モジュールをimportしてみます

$ go mod tidy
go: finding module for package example.com/greetings
example.com/hello imports
	example.com/greetings: cannot find module providing package example.com/greetings: unrecognized import path "example.com/greetings": reading https://example.com/greetings?go-get=1: 404 Not Found
ぎゅう

404 Not Foundが表示されて、importに失敗しましたね

これは先ほど作成したgreetingsのmodが公開されていないためgo mod tidyを実行してもモジュールを見つからずエラーになるのです。

そこで、まだモジュールが公開されていない場合は、ローカルのディレクトリパスを指定します。

ローカルのモジュールをimportする

go.mod内でreplaceを利用してローカルパスを指定し、require時の仮バージョンを設定します。

これまではモジュールとバージョンを指定する形式でした。

require モジュール vバージョン

requireを利用しているのが特徴です。

前回やった例だと下記ですね

module example/hello

go 1.19

require rsc.io/quote v1.5.2 // バージョンを指定

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

しかし、今回は未公開のローカルにあるモジュールを利用するため、replaceで指定してあげます

replace モジュール => パス

実際にやってみましょう。

hello/go.modにgreetingsディレクトリを指定します。

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

するとreplaceが記述されました

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

依存関係を記載できたので、go mod tidyを実行してみましょう

$go mod tidy
go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000
ぎゅう

仮のバージョンが指定されていますね

hello/go.modのexample.com/greetingsにも仮のバージョンが指定されています。

module example.com/hello

go 1.19

replace example.com/greetings => ../greetings

require example.com/greetings v0.0.0-00010101000000-000000000000 // 記載される

公式を確認すると、下記の内容が記載されています

最後に実行して、動作確認をしてみましょう!

$ go run .
Hi, Gladys. Welcom!

無事に動作されました!!

エラーを出してみる

実はここまでの作業でエラーが出て、困っていたところがあったので、

あえてエラーを出して理解を深めていきたいと思います

ローカルのモジュールが見つからない

先ほどローカルのモジュールをimportして利用しました。

しかし、下記のエラーが表示されていたらどうでしょうか?

could not import example.com/greetings (no required module provides package "example.com/greetings")
// example.com/greetings をインポートできませんでした (パッケージ "example.com/greetings" を提供する必須モジュールがありません)

helloモジュールを実行してみると下記が表示されます

$ go run . 
# example.com/hello
./hello.go:5:5: imported and not used: "example.com/greetings" as greeting
./hello.go:9:16: undefined: greetings

example.com/greetingsはimportされていますが、利用されていないと表示されています。

そして、undefined: greetingsと表示され、greetingsが未定義であるとわかります。

ぎゅう

どこでバグったかな?

ヒントは"example.com/greetings" as greetingです。

module example.com/greeting // greetingsになっていない

go 1.19
ぎゅう

あ、
greetingsをgreetingと間違えている

greetings/go.modを修正して、実行します。

$go run .
# example.com/hello
./hello.go:6:2: imported and not used: "example.com/greetings" as greeting
./hello.go:10:13: undefined: greetings

ですが、改善されていません。

ぎゅう

他も修正しないといけないのかな?

そこでpackageを確認します。

package greeting

import (
	"fmt"
)

func Hello(name string) string {
	message := fmt.Sprintf("Hi, %v. Welcom!", name)
	return message
}

package名がgreetingsではなく、greetingになっていました。

greetingsに修正すると、正しく実行されました。

$ go run .
Hi, Gladys. Welcom!
ぎゅう

どっちが問題だったのだろうか?

先ほどgo.modgreetings.goを修正しました。

go.modでgreetingに変更してみても問題なく実行することができました。

module example.com/greeting

go 1.19
$ go run .   
Hi, Gladys. Welcom!

一方で、greetings.goのパッケージを間違えると失敗します

package greeting // 複数形にしない

import (
	"fmt"
)

func Hello(name string) string {
	message := fmt.Sprintf("Hi, %v. Welcom!", name)
	return message
}
$go run .
# example.com/hello
./hello.go:6:2: imported and not used: "example.com/greetings" as greeting
./hello.go:10:13: undefined: greetings

エラーが再現できました。

そのため、上記のエラーが表示された場合は、packageから探っていくとよいでしょう。

ぎゅう

エラーを出すと理解が深まっていいですね

次の記事

ここまでお疲れ様でした

次の記事ではエラーハンドリングを学習していきましょう

ぎゅう
WEBエンジニア
渋谷でWEBエンジニアとして働く。
LaravelとVue.jsをよく取り扱い、誰でも仕様が伝わるコードを書くことを得意とする。
先輩だろうがプルリクにコメントをして、リファクタしまくる仕様伝わるコード書くマン
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次
閉じる