著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

多くの最新のプログラミング言語では、開発者は他の人がプログラムで使用できるように既製のライブラリを配布できます。Goも例外ではありません。 一部の言語は中央リポジトリを使用してこれらのライブラリをインストールしますが、Goはライブラリの作成に使用されたのと同じバージョン管理リポジトリからそれらを配布します。 Goはまた、セマンティックバージョニングと呼ばれるバージョン管理システムを使用して、いつ、どのような種類の変更が行われたかをユーザーに示します。 これにより、ユーザーは、モジュールの新しいバージョンにすばやくアップグレードしても安全かどうかを知ることができ、ソフトウェアがモジュールで引き続き機能することを確認できます。

このチュートリアルでは、新しいモジュールを作成して公開し、セマンティックバージョニングの使用方法を学び、モジュールのセマンティックバージョンを公開します。

前提条件

公開するモジュールの作成

他の多くのプログラミング言語とは異なり、Goモジュールは、独立したパッケージリポジトリではなく、それが存在するソースコードリポジトリから直接配布されます。 これにより、ユーザーはコードで参照されているモジュールを見つけやすくなり、モジュールメンテナーはモジュールの新しいバージョンを公開しやすくなります。 このセクションでは、新しいモジュールを作成し、それを公開して他のユーザーが利用できるようにします。

モジュールの作成を開始するには、初期リポジトリをダウンロードするための前提条件の一部として作成した空のリポジトリでgit cloneを使用します。 このリポジトリは、コンピュータのどこにでも複製できますが、多くの開発者はプロジェクト用のディレクトリを持っている傾向があります。 このチュートリアルでは、projectsという名前のディレクトリを使用します。

projectsディレクトリを作成し、次の場所に移動します。

  1. mkdir projects
  2. cd projects

projectsディレクトリから、git cloneを実行して、リポジトリをコンピュータに複製します。

  1. git clone [email protected]:your_github_username/pubmodule.git

モジュールのクローンを作成すると、空のモジュールがprojectsディレクトリ内のpubmoduleディレクトリにダウンロードされます。 空のリポジトリのクローンを作成したという警告が表示される場合がありますが、これについて心配する必要はありません。

Output
Cloning into 'pubmodule'... warning: You appear to have cloned an empty repository.

次に、ダウンロードしたディレクトリに移動します。

  1. cd pubmodule

モジュールのディレクトリに移動したら、go mod initを使用して新しいモジュールを作成し、リポジトリの場所をモジュール名として渡します。 モジュール名がリポジトリの場所と一致することを確認することは重要です。これは、goツールが、他のプロジェクトで使用されるときにモジュールをダウンロードする場所を見つける方法だからです。

  1. go mod init github.com/your_github_username/pubmodule

Goは、go.modファイルが作成されたことを通知することにより、モジュールが作成されたことを確認します。

Output
go: creating new go.mod: module github.com/your_github_username/pubmodule

最後に、nanoなどのお気に入りのテキストエディタを使用して、リポジトリと同じ名前のファイルpubmodule.goを作成して開きます。

  1. nano pubmodule.go

このファイルの名前は何でもかまいませんが、パッケージと同じ名前を使用すると、なじみのないパッケージで作業するときにどこから始めればよいかがわかりやすくなります。 ただし、パッケージ名自体はリポジトリ名と同じである必要があります。 このように、誰かがパッケージからメソッドまたはタイプを参照すると、pubmodule.MyFunctionなどのリポジトリと一致します。 これにより、後で参照する必要がある場合に備えて、パッケージがどこから来たのかを簡単に知ることができます。

次に、文字列Hello, You!を返すHelloメソッドをパッケージに追加します。 これは、パッケージをインポートするすべての人が利用できる機能になります。

projects / pubmodule / pubmodule.go
package pubmodule

func Hello() string {
  return "Hello, You!"
}

これで、go mod initを使用して、リモートリポジトリ(github.com/your_github_username/pubmodule)と一致するモジュール名で新しいモジュールを作成しました。 また、モジュールのユーザーが呼び出すことができるHelloという関数を使用して、pubmodule.goという名前のファイルをモジュールに追加しました。 次に、モジュールを公開して他の人が利用できるようにします。

モジュールの公開

ローカルモジュールを作成し、それを他のユーザーが利用できるようにする準備ができたら、モジュールを公開します。 Goモジュールは、保存されているのと同じコードリポジトリから配布されるため、コードをローカルのGitリポジトリにコミットし、github.com/your_github_username/pubmoduleのリポジトリにプッシュします。

コードをローカルのGitリポジトリにコミットする前に、コミットする予定のないファイルをコミットしないことを確認することをお勧めします。ファイルは、コードをGitHubにプッシュするときに公開されます。 pubmoduleディレクトリ内でgit statusコマンドを使用すると、コミットされるすべてのファイルと変更が表示されます。

  1. git status

出力は次のようになります。

Output
On branch main No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) go.mod pubmodule.go

で作成したgo mod initコマンドのgo.modファイルと、Hello関数を作成したpubmodule.goファイルが表示されます。 リポジトリの作成方法によっては、この出力とは異なるブランチ名が使用される場合があります。 最も一般的には、名前はmainまたはmasterのいずれかになります。

探しているファイルだけがあることが確実な場合は、git addを使用してファイルをステージングし、git commitを使用してリポジトリにコミットできます。

  1. git add .
  2. git commit -m "Initial Commit"

出力は次のようになります。

Output
[main (root-commit) 931071d] Initial Commit 2 files changed, 8 insertions(+) create mode 100644 go.mod create mode 100644 pubmodule.go

最後に、git pushコマンドを使用して、モジュールをGitHubリポジトリにプッシュします。

  1. git push

出力は次のようになります。

Output
Enumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (4/4), 367 bytes | 367.00 KiB/s, done. Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/pubmodule.git * [new branch] main -> main

git pushコマンドを実行すると、モジュールがリポジトリにプッシュされ、他のユーザーが使用できるようになります。 公開されているバージョンがない場合、Goはリポジトリのデフォルトブランチのコードをモジュールのコードとして使用します。 デフォルトのブランチの名前がmainmaster、またはその他の名前であるかどうかは関係ありません。リポジトリのデフォルトのブランチが設定されているものだけです。

このセクションでは、作成したローカルのGoモジュールを取得し、GitHubリポジトリに公開して、他のユーザーが使用できるようにしました。 これで公開モジュールができましたが、パブリックモジュールを維持するもう1つの部分は、モジュールのユーザーがその安定バージョンを使用できるようにすることです。 今後、モジュールに変更を加えて機能を追加したいと思うかもしれませんが、モジュールのバージョンを使用せずにこれらの変更を行うと、モジュールを使用している誰かのコードを誤って壊してしまう可能性があります。 この問題を解決するために、開発の新しいマイルストーンに到達したときにモジュールにバージョンを追加できます。 ただし、新しいバージョンを追加するときは、意味のあるバージョン番号を選択して、ユーザーがすぐにアップグレードしても安全かどうかを確認できるようにしてください。

セマンティックバージョニング

意味のあるバージョン番号は、ユーザーが対話するパブリックインターフェイス(API)がどれだけ変更されたかをユーザーに知らせます。 Goは、セマンティックバージョニング、または略して「SemVer」と呼ばれるバージョニングスキームを通じてこれらの変更を伝えます。 (セマンティックバージョニングは、バージョン文字列を使用してコード変更に関する意味を伝えます。これは、セマンティックバージョニングの名前の由来です。)Goのモジュールシステムは、SemVerに従って、現在使用しているバージョンよりも新しいバージョンと、新しいバージョンかどうかを判断します。モジュールのバージョンは、自動的にアップグレードしても安全です。

セマンティックバージョニングは、バージョン文字列の各番号に意味を与えます。 SemVerの一般的なバージョンには、メジャーバージョン、マイナーバージョン、およびパッチバージョンの3つの主要な番号が含まれています。 これらの各番号は、.と組み合わされて、1.2.3などのバージョンを形成します。 番号は、メジャーバージョンが最初、マイナーバージョンが2番目、パッチバージョンが最後の順に並べられています。 このように、バージョンを見ると、特定の場所の数が以前のバージョンよりも多いため、どちらが新しいかを確認できます。 たとえば、バージョン2.2.3は、メジャーバージョンが高いため、1.2.3よりも新しいです。 同様に、バージョン1.4.3は、マイナーバージョンが高いため、1.2.10よりも新しいです。 パッチバージョンでは103よりも高い場合でも、マイナーバージョン42よりも高いため、バージョンが優先されます。 バージョン文字列の数値が増えると、それに続くバージョンの他のすべての部分が0にリセットされます。 たとえば、1.3.10のマイナーバージョンを増やすと1.4.0になり、2.4.1のメジャーバージョンを増やすと3.0.0になります。

これらのルールを使用すると、Goはgo getを実行するときに使用するモジュールのバージョンを決定できます。 例として、モジュールのバージョン1.4.3github.com/your_github_username/pubmoduleを使用しているプロジェクトがあるとします。 pubmoduleが安定していることに依存している場合は、パッチバージョン(.3)のみを自動的にアップグレードすることをお勧めします。 コマンドgo get -u=patch github.com/your_github_username/pubmoduleを実行すると、Goはモジュールのパッチバージョンをアップグレードすることを確認し、バージョンのメジャー部分とマイナー部分として1.4を含む新しいバージョンのみを検索します。

モジュールの新しいリリースを作成するときは、モジュールのパブリックAPIがどのように変更されたかを考慮することが重要です。 セマンティックバージョン文字列の各部分は、API変更の範囲をユーザーとユーザーの両方に伝えます。 これらのタイプの変更は通常、バージョンの各コンポーネントに合わせて3つの異なるカテゴリに分類されます。 最小の変更はパッチバージョンを増やし、中規模の変更はマイナーバージョンを増やし、最大の変更はメジャーバージョンを増やします。 これらのカテゴリを使用して、増やすバージョン番号を決定すると、自分のコードや、モジュールに依存している他の人のコードを壊さないようにするのに役立ちます。

メジャーバージョン番号

SemVerバージョンの最初の番号は、メジャーバージョン番号(1.4.3)です。 メジャーバージョン番号は、モジュールの新しいバージョンをリリースするときに考慮する最も重要な番号です。 メジャーバージョンの変更は、パブリックAPIに下位互換性のない変更を通知するために使用されます。 後方互換性のない変更とは、他の変更を行わずにアップグレードした場合に誰かのプログラムが破損する原因となるモジュールの変更です。 破損とは、関数名が変更されたためにビルドに失敗したことや、ライブラリの動作が変更されて同じメソッドが"1"ではなく"v1"を返すことなどを意味します。 ただし、これはパブリックAPI専用です。つまり、他の誰かが使用できるエクスポートされたタイプまたはメソッドを意味します。 ライブラリのユーザーが気付かないような改善のみがバージョンに含まれている場合は、バージョンを大幅に変更する必要はありません。 どの変更がこのカテゴリに当てはまるかを覚える方法は、「更新」または「削除」と見なされるものはすべてメジャーバージョンの増加になるということかもしれません。

注: SemVerの他のタイプの数値とは異なり、メジャーバージョン0には追加の特別な意味があります。 メジャーバージョン0は、「開発中」バージョンと見なされます。 メジャーバージョン0のSemVerは安定しているとは見なされず、APIでいつでも変更される可能性があります。 新しいモジュールを作成するときは、メジャーバージョン0から始めて、モジュールの初期開発が完了するまでマイナーバージョンとパッチバージョンのみを更新することをお勧めします。 モジュールのパブリックAPIの変更が完了し、ユーザーにとって安定していると見なされたら、バージョン1.0.0から始めます。

メジャーバージョンの変更がどのように見えるかの例として、次のコードを取り上げます。 UserAddressという関数があり、現在stringをパラメーターとして受け入れ、stringを返します。

func UserAddress(username string) string {
	// return user address as a string
}

関数は現在stringを返しますが、関数が*Addressのようなstructを返した方が、あなたとユーザーにとってより良いと判断するかもしれません。 このようにして、郵便番号など、すでに分割されている追加データを含めることができます。

type Address struct {
	Address    string
	PostalCode string
}

func UserAddress(username string) *Address {
	// return user address and postal code struct
}

これは、ユーザーが使用するために自分のコードに変更を加える必要があるため、メジャーバージョンの変更の例になります。 UserAddressを完全に削除することにした場合も同じことが言えます。これは、ユーザーが置換を使用するためにコードを更新する必要があるためです。

メジャーバージョン変更の別の例は、stringを返す場合でも、UserAddress関数に新しいパラメーターを追加することです。

func UserAddress(username string, uppercase bool) string {
	// return user address as a string, uppercase if bool is true
}

この変更では、ユーザーがUserAddress関数を使用している場合にもコードを更新する必要があるため、バージョンを大幅に増やす必要があります。

ただし、コードに加えるすべての変更がそれほど大幅なものになるわけではありません。 新しい関数や値を追加するパブリックAPIに変更を加えても、既存の関数や値は変更されない場合があります。

マイナーバージョン番号

SemVerバージョンの2番目の番号は、マイナーバージョン番号(1.4.3)です。 マイナーバージョンの変更は、パブリックAPIへの下位互換性のある変更を通知するために使用されます。 下位互換性のある変更とは、現在モジュールを使用しているコードやプロジェクトに影響を与えない変更のことです。 メジャーバージョン番号と同様に、これはパブリックAPIにのみ影響します。 このカテゴリに当てはまる変更を覚える方法は、「追加」と見なされるものであり、「更新」とは見なされないものです。

メジャーバージョン番号の同じ例を使用して、stringを返すUserAddressという名前のメソッドがあるとします。

func UserAddress(username string) string {
	// return user address as a string
}

ただし、今回は、UserAddressを更新して*Addressを返す代わりに、UserAddressDetailという名前のまったく新しいメソッドを追加することにしました。

type Address struct {
	Address    string
	PostalCode string
}

func UserAddress(username string) string {
	// return user address as a string
}

func UserAddressDetail(username string) *Address {
	// return user address and postal code struct
}

この新しいUserAddressDetail関数を追加する場合、ユーザーがこのバージョンのモジュールに更新する場合、ユーザーによる変更は必要ないため、バージョン番号のマイナーな増加と見なされます。 UserAddressを引き続き使用でき、UserAddressDetailからの追加情報を含める場合にのみコードを更新する必要があります。

ただし、モジュールの新しいバージョンをリリースするのは、パブリックAPIの変更だけではない可能性があります。 バグはソフトウェア開発の必然的な部分であり、パッチのバージョン番号はそれらの穴を埋めるためにあります。

パッチのバージョン番号

パッチバージョン番号は、SemVerバージョン(1.4.3)の最後の番号です。 パッチバージョンの変更とは、モジュールのパブリックAPI影響を与えない変更のことです。 モジュールのパブリックAPIに影響を与えない変更は、バグ修正やセキュリティ修正などの傾向があります。 前の例のUserAddress関数を再度使用して、モジュールのリリースで、関数が返すstringのアドレスの一部が欠落しているとします。 そのバグを修正するためにモジュールの新しいバージョンをリリースした場合、パッチバージョンが増えるだけです。 このリリースには、ユーザーがUserAddressパブリックAPIを使用する方法の変更は含まれず、返されるデータの正確性のみが含まれます。

このセクションで見たように、新しいバージョン番号を慎重に選択することは、ユーザーの信頼を得るための重要な方法です。 セマンティックバージョニングを使用すると、ユーザーは新しいバージョンに更新するために必要な作業量がわかり、プログラムを壊すような更新で誤って驚かされることはありません。 モジュールに加えた変更を検討し、使用する次のバージョン番号を決定したら、新しいバージョンを公開して、ユーザーが利用できるようにすることができます。

新しいモジュールバージョンの公開

モジュールの新しいバージョンを公開する前に、計画している変更でモジュールを更新する必要があります。 変更を加えないと、セマンティックバージョンのどの部分を増やすかを決定できません。 このチュートリアルのモジュールでは、Helloメソッドを補完する新しいGoodbyeメソッドを追加してから、ユーザーが使用できるようにその新しいバージョンを公開します。

まず、pubmodule.goファイルを開き、新しいGoodbyeメソッドをパブリックAPIに追加します。

pubmodule / pubmodule.go
package pubmodule

func Hello() string {
  return "Hello, You!"
}

func Goodbye() string {
  return "Goodbye for now!"
}

変更を保存したら、git statusを実行して、どの変更がコミットされると予想されるかを確認する必要があります。

  1. git status

出力は次のようになり、モジュールの唯一の変更はpubmodule.goに追加したメソッドであることを示しています。

Output
On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: pubmodule.go no changes added to commit (use "git add" and/or "git commit -a")

次に、ステージングされたファイルに変更を追加し、git addおよびgit commitを使用してローカルリポジトリに変更をコミットします。

  1. git add .
  2. git commit -m "Add Goodbye method"

出力は次のようになります。

Output
[main 3235010] Add Goodbye method 1 file changed, 4 insertions(+)

変更がコミットされたら、それらをGitHubリポジトリにプッシュする必要があります。 大規模なソフトウェアプロジェクトの場合、またはプロジェクトで他の開発者と協力する場合、この手順は通常、わずかに異なります。 新機能の開発を行う場合、開発者はGitブランチを作成して、新機能が安定してリリースの準備ができるまで変更を加えます。 それが発生すると、別の開発者がブランチの変更を確認して、最初の開発者が見逃した可能性のある問題をキャッチする可能性のある2番目の目を追加します。 レビューが終了すると、ブランチはデフォルトのブランチ(mastermainなど)にマージされます。 リリース間では、デフォルトのブランチは、新しいリリースを公開するときまで、これらのタイプの変更を蓄積します。

ここのモジュールはこのプロセスを経ていないため、リポジトリに加えた変更をプッシュすると、代わりに変更の蓄積がシミュレートされます。

  1. git push

出力は次のようになります。

Output
numerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 369 bytes | 369.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/pubmodule.git 931071d..3235010 main -> main

出力は、ユーザーがデフォルトのブランチで使用できる新しいコードの準備ができていることを示しています。

この時点まで、これまでに行ったことはすべて、モジュールを最初に公開したときと同じです。 ただし、新しいバージョンをリリースする際の重要な部分が浮かび上がります。それは、新しいバージョン番号を選択することです。

モジュールに加えた変更を見ると、パブリックAPIへの唯一の変更(または実際の変更)は、モジュールにGoodbyeメソッドを追加することです。 ユーザーは、Hello機能しか備えていなかった以前のバージョンから、自分の側で変更を加えることなく更新できるため、この変更は下位互換性のある変更になります。 セマンティックバージョニングでは、パブリックAPIに対する下位互換性のある変更は、マイナーバージョン番号の増加を意味します。 ただし、これは公開されているモジュールの最初のバージョンであるため、以前のバージョンを増やす必要はありません。 0.0.0を「バージョンなし」と見なす場合、マイナーバージョンをインクリメントすると、モジュールの次のバージョンであるバージョン0.1.0になります。

モジュールのリリースに与えるバージョン番号がわかったので、それをGitタグと組み合わせて使用して、新しいバージョンを公開できます。 開発者がGitを使用してソースコードを追跡する場合、Go以外の言語でも、一般的な規則は、Gitのタグを使用して、特定のバージョンでリリースされたコードを追跡することです。 このようにして、古いバージョンに変更を加える必要がある場合は、タグを使用できます。 Goはすでにソースリポジトリからモジュールをダウンロードしているので、同じバージョンタグを使用してこの方法を利用します。

これらのタグを使用して独自のモジュールの新しいバージョンを公開するには、リリースするコードにgit tagコマンドでタグを付けます。 git tagコマンドの引数として、バージョンタグも指定する必要があります。 バージョンタグを作成するには、バージョンのプレフィックスvで開始し、その直後にSemVerを追加します。 モジュールの場合、最終的なバージョンタグはv0.1.0になります。 次に、git tagを実行して、モジュールにバージョンタグを付けます。

  1. git tag v0.1.0

バージョンタグがローカルに追加された後も、タグをGitHubリポジトリにプッシュする必要があります。これは、git pushoriginを使用して実行できます。

  1. git push origin v0.1.0

git pushコマンドが成功すると、新しいタグv0.1.0が作成されたことがわかります。

Output
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/pubmodule.git * [new tag] v0.1.0 -> v0.1.0

上記の出力は、タグがプッシュされ、GitHubリポジトリにモジュールのユーザーが参照できる新しいv0.1.0タグがあることを示しています。

git tagを使用してモジュールの新しいバージョンを公開したので、ユーザーがgo getを実行してモジュールの最新バージョンを取得するたびに、最新バージョンに基づくバージョンがダウンロードされなくなります。デフォルトのブランチからハッシュをコミットします。 モジュールにリリースされたバージョンがあると、goツールはそれらのバージョンの使用を開始して、モジュールを更新するための最良の方法を決定します。 セマンティックバージョニングと組み合わせることで、モジュールを反復および改善すると同時に、ユーザーに一貫性のある安定したエクスペリエンスを提供できます。

結論

このチュートリアルでは、パブリックGoモジュールを作成し、他の人が使用できるようにGitHubリポジトリに公開しました。 また、セマンティックバージョニングを使用して、モジュールに最適なバージョン番号を決定しました。 最後に、モジュールの機能を拡張し、セマンティックバージョニングを使用して、それに依存するプログラムを壊さないという自信を持って新しいバージョンを公開しました。

バージョンに数字以外の情報を追加する方法など、セマンティックバージョニングの詳細については、セマンティックバージョニングのWebサイトで詳しく説明しています。 Goのドキュメントには、GoがSemVerを具体的にどのように使用するかを説明するモジュールのバージョン番号ページもあります。

Goモジュールの詳細については、Goプロジェクトに一連のブログ投稿があり、Goツールがモジュールとどのように相互作用して理解するかを詳しく説明しています。 Goプロジェクトには、GoモジュールリファレンスにGoモジュールに関する非常に詳細で技術的なリファレンスもあります。

このチュートリアルは、 DigitalOcean How to Code inGoシリーズの一部でもあります。 このシリーズでは、Goの初めてのインストールから、言語自体の使用方法まで、Goに関する多くのトピックを取り上げています。