前書き

Go in packageを作成する場合、最終目標は通常、他の開発者が使用できるようにパッケージをアクセス可能にすることです。高次パッケージまたはプログラム全体のいずれか。 https://www.digitalocean.com/community/tutorials/importing-packages-in-go [パッケージのインポート]により、コードは他のより複雑なツールのビルディングブロックとして機能できます。 ただし、インポートできるのは特定のパッケージのみです。 これは、パッケージの可視性によって決まります。

このコンテキストでの_Visibility_は、パッケージまたはその他の構造を参照できるファイルスペースを意味します。 たとえば、関数で変数を定義する場合、その変数の可視性(スコープ)は、それが定義された関数内にのみ存在します。 同様に、パッケージ内で変数を定義する場合、そのパッケージのみに変数を表示することも、パッケージの外部にも変数を表示することもできます。

人間工学に基づいたコードを記述する場合、特にパッケージに加えたい将来の変更を考慮する場合は、パッケージの可視性を慎重に制御することが重要です。 バグの修正、パフォーマンスの改善、または機能の変更が必要な場合は、パッケージを使用している人のコードを壊さない方法で変更を行う必要があります。 重大な変更を最小限に抑える方法の1つは、パッケージを適切に使用するために必要な部分のみにアクセスを許可することです。 アクセスを制限することにより、他の開発者がパッケージを使用する方法に影響を与える可能性を少なくして、パッケージを内部的に変更できます。

この記事では、パッケージの可視性を制御する方法と、パッケージ内でのみ使用する必要があるコードの部分を保護する方法を学習します。 これを行うために、アイテムの可視性の度合いが異なるパッケージを使用して、メッセージを記録およびデバッグする基本的なロガーを作成します。

前提条件

この記事の例を実行するには、次のものが必要です。

  • Goのインストール方法に従ってセットアップされたGoワークスペースローカルプログラミング環境のセットアップ]。 このチュートリアルでは、次のファイル構造を使用します。

.
├── bin
│
└── src
   └── github.com
       └── gopherguides

エクスポートされたアイテムとエクスポートされていないアイテム

Javaやhttps://www.digitalocean.com/community/tutorial_series/how-to-code-in-python-3[Python]のような他のプログラム言語とは異なり、 + public +、 `+ private `などの_access modifiers_を使用します、またはスコープを指定するための ` protected `の場合、Goはアイテムの宣言方法によって、アイテムが ` exported `および ` unexported `であるかどうかを判断します。 この場合、アイテムをエクスポートすると、そのアイテムは現在のパッケージ外で「 visible +」になります。 エクスポートされていない場合は、定義されたパッケージ内からのみ表示および使用できます。

この外部可視性は、宣言されたアイテムの最初の文字を大文字にすることで制御されます。 「+ Types」、「+ Variables」、「+ Constants」、「+ Functions」など、大文字で始まるすべての宣言は、現在のパッケージの外に表示されます。

次のコードを見て、大文字と小文字の区別に注意してください。

greet.go

package greet

import "fmt"

var reeting string

func ello(name string) string {
   return fmt.Sprintf(Greeting, name)
}

このコードは、それが `+ greet `パッケージにあることを宣言しています。 次に、2つのシンボル、「 Greeting 」という変数と、「 Hello 」という関数を宣言します。 どちらも大文字で始まるため、どちらも「 exported +」であり、外部プログラムで利用できます。 前述のように、アクセスを制限するパッケージを作成すると、APIの設計が改善され、パッケージに依存するコードを壊すことなくパッケージを内部で簡単に更新できるようになります。

パッケージの可視性の定義

プログラムでパッケージの可視性がどのように機能するかを詳しく見るために、パッケージの外に表示したいものと表示しないものを念頭に置いて、 + logging +`パッケージを作成しましょう。 このロギングパッケージは、プログラムメッセージをコンソールに記録する役割を果たします。 また、ログインしている_レベル_も調べます。 レベルはログのタイプを説明し、3つのステータスのいずれかになります: `+ info ++ warning +、または + error +

まず、 `+ src `ディレクトリ内で、 ` logging +`というディレクトリを作成して、ログファイルを配置します。

mkdir logging

次にそのディレクトリに移動します。

cd logging

次に、nanoなどのエディターを使用して、 `+ logging.go +`というファイルを作成します。

nano logging.go

作成したばかりの `+ logging.go +`ファイルに次のコードを配置します。

logging / logging.go

package logging

import (
   "fmt"
   "time"
)

var debug bool

func Debug(b bool) {
   debug = b
}

func Log(statement string) {
   if !debug {
       return
   }

   fmt.Printf("%s %s\n", time.Now().Format(time.RFC3339), statement)
}

このコードの最初の行は、 `+ logging `というパッケージを宣言しました。 このパッケージには、「 Debug」と「+ Log」という2つの「+ exported」関数があります。 これらの関数は、 `+ logging `パッケージをインポートする他のパッケージから呼び出すことができます。 ` debug `というプライベート変数もあります。 この変数には、 ` logging `パッケージ内からのみアクセスできます。 関数 ` Debug `と変数 ` debug +`のスペルは同じですが、関数は大文字で、変数は大文字ではないことに注意してください。 これにより、スコープが異なる明確な宣言になります。

ファイルを保存して終了します。

コードの他の領域でこのパッケージを使用するには、https://www.digitalocean.com/community/tutorials/importing-packages-in-go [+ import + it into a new package]を使用します。 この新しいパッケージを作成しますが、最初にこれらのソースファイルを保存するための新しいディレクトリが必要です。

`+ logging `ディレクトリから移動して、 ` cmd +`という新しいディレクトリを作成し、その新しいディレクトリに移動しましょう。

cd ..
mkdir cmd
cd cmd

作成したばかりの `+ cmd `ディレクトリに ` main.go +`というファイルを作成します。

nano main.go

次のコードを追加できます。

cmd / main.go

package main

import "github.com/gopherguides/logging"

func main() {
   logging.Debug(true)

   logging.Log("This is a debug statement...")
}

これでプログラム全体が作成されました。 ただし、このプログラムを実行する前に、コードが適切に機能するための構成ファイルをいくつか作成する必要があります。 Goはhttps://blog.golang.org/using-go-modules[Go Modules]を使用して、リソースをインポートするためのパッケージの依存関係を構成します。 Goモジュールは、パッケージディレクトリに配置される構成ファイルであり、パッケージのインポート元をコンパイラに指示します。 モジュールについて学習することはこの記事の範囲外ですが、ほんの数行の設定を書いてこの例をローカルで動作させることができます。

`+ cmd `ディレクトリで次の ` go.mod +`ファイルを開きます:

nano go.mod

次に、ファイルに次の内容を配置します。

go.mod

module github.com/gopherguides/cmd

replace github.com/gopherguides/logging => ../logging

このファイルの最初の行は、コンパイラに `+ cmd `パッケージのファイルパスが ` github.com / gopherguides / cmd `であることを伝えます。 2行目は、パッケージ ` github.com / gopherguides / logging `がディスク上のローカルの ` .. / logging +`ディレクトリにあることをコンパイラに伝えます。

また、 `+ logging `パッケージ用の ` go.mod `ファイルも必要です。 ` logging `ディレクトリに戻り、 ` go.mod +`ファイルを作成しましょう:

cd ../logging
nano go.mod

ファイルに次の内容を追加します。

go.mod

module github.com/gopherguides/logging

これは、作成した `+ logging `パッケージが実際には ` github.com / gopherguides / logging `パッケージであることをコンパイラーに伝えます。 これにより、以前に記述した次の行を使用して、 ` main +`パッケージにパッケージをインポートできます。

cmd / main.go

package main



func main() {
   logging.Debug(true)

   logging.Log("This is a debug statement...")
}

これで、次のディレクトリ構造とファイルレイアウトができました。

├── cmd
│   ├── go.mod
│   └── main.go
└── logging
   ├── go.mod
   └── logging.go

すべての設定が完了したので、次のコマンドを使用して `+ cmd `パッケージから ` main +`プログラムを実行できます。

cd ../cmd
go run main.go

次のような出力が得られます。

Output This is a debug statement...

プログラムは、RFC 3339形式で現在時刻を出力し、その後にロガーに送信したステートメントを出力します。 RFC 3339は、インターネット上の時刻を表すように設計された時刻形式であり、ログファイルで一般的に使用されています。

+ Debug`および + Log`関数はロギングパッケージからエクスポートされるため、 `+ main `パッケージで使用できます。 ただし、 ` logging `パッケージの ` debug +`変数はエクスポートされません。 エクスポートされていない宣言を参照しようとすると、コンパイル時エラーが発生します。

次の強調表示された行を `+ main.go +`に追加します。

cmd / main.go

package main

import "github.com/gopherguides/logging"

func main() {
   logging.Debug(true)

   logging.Log("This is a debug statement...")


}

ファイルを保存して実行します。 次のようなエラーが表示されます。

Output. . .
./main.go:10:14: cannot refer to unexported name logging.debug

パッケージ内の「+ exported 」および「 unexported 」アイテムの動作を確認したので、次に、「 structs 」から「 fields 」および「 methods +」をエクスポートする方法を確認します。

構造内の可視性

前のセクションで作成したロガーの可視性スキームは単純なプログラムで機能する可能性がありますが、複数のパッケージ内から使用するにはあまりにも多くの状態を共有します。 これは、エクスポートされた変数は、変数を矛盾した状態に変更する可能性のある複数のパッケージからアクセスできるためです。 この方法でパッケージの状態を変更できると、プログラムの動作を予測するのが難しくなります。 たとえば、現在のデザインでは、1つのパッケージで変数「+ Debug 」を「 true 」に設定し、別のパッケージで同じインスタンス内で変数を「 false 」に設定できます。 ` logging +`パッケージをインポートしている両方のパッケージが影響を受けるため、これにより問題が発生します。

ロガーを分離するには、構造体を作成し、そのメソッドからメソッドを切り離します。 これにより、ロガーの「+インスタンス」を作成して、それを使用する各パッケージで個別に使用できるようになります。

`+ logging +`パッケージを次のように変更して、コードをリファクタリングし、ロガーを分離します。

logging / logging.go

package logging

import (
   "fmt"
   "time"
)

type Logger struct {
   timeFormat string
   debug      bool
}

func New(timeFormat string, debug bool) *Logger {
   return &Logger{
       timeFormat: timeFormat,
       debug:      debug,
   }
}

func (l *Logger) Log(s string) {
   if !l.debug {
       return
   }
   fmt.Printf("%s %s\n", time.Now().Format(l.timeFormat), s)
}

このコードでは、 `+ Logger `構造体を作成しました。 この構造体は、出力する時間形式や、「 true 」または「 false 」の変数設定「 debug 」など、エクスポートされていない状態を格納します。 ` New `関数は、時間形式やデバッグ状態など、ロガーを作成するための初期状態を設定します。 次に、エクスポートされていない変数 ` timeFormat `および ` debug `に内部的に与えた値を保存します。 また、出力したいステートメントをとる ` Logger `型に ` Log `というメソッドを作成しました。 ` Log `メソッド内には、ローカルメソッド変数 ` l `への参照があり、 ` l.timeFormat `や ` l.debug +`などの内部フィールドにアクセスできます。

このアプローチにより、多くの異なるパッケージで「+ Logger +」を作成し、他のパッケージがどのようにそれを使用しているかに関係なくそれを使用できます。

別のパッケージで使用するには、 `+ cmd / main.go +`を次のように変更します。

cmd / main.go

package main

import (
   "time"

   "github.com/gopherguides/logging"
)

func main() {
   logger := logging.New(time.RFC3339, true)

   logger.Log("This is a debug statement...")
}

このプログラムを実行すると、次の出力が得られます。

Output This is a debug statement...

このコードでは、エクスポートされた関数 `+ New `を呼び出してロガーのインスタンスを作成しました。 このインスタンスへの参照を ` logger `変数に保存しました。 ステートメントを出力するために、 ` logging.Log +`を呼び出すことができます。

`+ timeFormat `フィールドなどの ` Logger `からエクスポートされていないフィールドを参照しようとすると、コンパイルエラーが発生します。 次の強調表示された行を追加して、 ` cmd / main.go +`を実行してみてください。

cmd / main.go

package main

import (
   "time"

   "github.com/gopherguides/logging"
)

func main() {
   logger := logging.New(time.RFC3339, true)

   logger.Log("This is a debug statement...")


}

これにより、次のエラーが発生します。

Output. . .
cmd/main.go:14:20: logger.timeFormat undefined (cannot refer to unexported field or method timeFormat)

コンパイラは、「+ logger.timeFormat 」がエクスポートされていないことを認識しているため、「 logging +」パッケージから取得できません。

メソッド内の可視性

構造体フィールドと同様に、メソッドもエクスポートまたはアンエクスポートできます。

これを説明するために、ロガーに_leveled_ロギングを追加しましょう。 レベルログは、特定の種類のイベントについてログを検索できるようにログを分類する手段です。 ロガーに入れるレベルは次のとおりです。

  • `+ info `レベルは、ユーザーにアクションを通知する情報タイプのイベントを表します。たとえば、 ` Program started `や ` Email sent +`などです。 これらは、プログラムの一部をデバッグおよび追跡して、予想される動作が発生しているかどうかを確認するのに役立ちます。

  • `+ warning `レベル。 これらのタイプのイベントは、「 Email failed to send、retrying +」のように、予期しない何かがエラーではないときに発生することを識別します。 彼らは私たちが期待したほどスムーズに進まないプログラムの部分を見るのに役立ちます。

  • 「+エラー」レベル。これは、「+ファイルが見つかりません」など、プログラムで問題が発生したことを意味します。 これにより、多くの場合、プログラムの操作が失敗します。

また、特にプログラムが期待どおりに実行されず、プログラムをデバッグする場合は、特定のレベルのログをオンまたはオフにすることもできます。 この機能を追加するには、プログラムを変更して、「+ debug 」が「 true 」に設定されている場合に、すべてのレベルのメッセージが出力されるようにします。 それ以外の場合、「 false +」の場合、エラーメッセージのみが出力されます。

`+ logging / logging.go +`に次の変更を加えることにより、レベル化されたログを追加します。

logging / logging.go

package logging

import (
   "fmt"
   "strings"
   "time"
)

type Logger struct {
   timeFormat string
   debug      bool
}

func New(timeFormat string, debug bool) *Logger {
   return &Logger{
       timeFormat: timeFormat,
       debug:      debug,
   }
}

func (l *Logger) Log(level string, s string) {
   level = strings.ToLower(level)
   switch level {
   case "info", "warning":
       if l.debug {
           l.write(level, s)
       }
   default:
       l.write(level, s)
   }
}

func (l *Logger) write(level string, s string) {
   fmt.Printf("[%s] %s %s\n", level, time.Now().Format(l.timeFormat), s)
}

この例では、 `+ Log `メソッドに新しい引数を導入しました。 これで、ログメッセージの ` level `を渡すことができます。 ` Log `メソッドは、メッセージのレベルを決定します。 「 info」または「+ warning」メッセージであり、「+ debug」フィールドが「+ true」の場合、メッセージを書き込みます。 それ以外の場合、メッセージは無視されます。 `+ error +`のような他のレベルの場合、それは関係なくメッセージを書き出します。

メッセージが出力されるかどうかを判断するためのロジックのほとんどは、 `+ Log `メソッドに存在します。 また、 ` write `と呼ばれる非エクスポートメソッドを導入しました。 ` write +`メソッドは実際にログメッセージを出力するものです。

`+ cmd / main.go +`を次のように変更することで、他のパッケージでこのレベルのログを使用できます。

cmd / main.go

package main

import (
   "time"

   "github.com/gopherguides/logging"
)

func main() {
   logger := logging.New(time.RFC3339, true)

   logger.Log("info", "starting up service")
   logger.Log("warning", "no tasks found")
   logger.Log("error", "exiting: no work performed")

}

これを実行すると以下が得られます:

Output[info]  starting up service
[warning]  no tasks found
[error]  exiting: no work performed

この例では、 `+ cmd / main.go `はエクスポートされた ` Log +`メソッドを正常に使用しました。

+ debug`を + false`に切り替えることで、各メッセージの `+ level`を渡すことができます。

main.go

package main

import (
   "time"

   "github.com/gopherguides/logging"
)

func main() {
   logger := logging.New(time.RFC3339, )

   logger.Log("info", "starting up service")
   logger.Log("warning", "no tasks found")
   logger.Log("error", "exiting: no work performed")

}

これで、 `+ error`レベルのメッセージのみが出力されることがわかります。

Output[error]  exiting: no work performed

`+ logging `パッケージの外部から ` write +`メソッドを呼び出そうとすると、コンパイルエラーが発生します。

main.go

package main

import (
   "time"

   "github.com/gopherguides/logging"
)

func main() {
   logger := logging.New(time.RFC3339, true)

   logger.Log("info", "starting up service")
   logger.Log("warning", "no tasks found")
   logger.Log("error", "exiting: no work performed")


}
Outputcmd/main.go:16:8: logger.write undefined (cannot refer to unexported field or method logging.(*Logger).write)

コンパイラは、小文字で始まる別のパッケージから何かを参照しようとしていることを認識すると、エクスポートされていないことを認識し、コンパイラエラーをスローします。

このチュートリアルのロガーは、他のパッケージに使用させたい部分のみを公開するコードを作成する方法を示しています。 パッケージのどの部分がパッケージの外部に表示されるかを制御するため、パッケージに依存するコードに影響を与えることなく将来の変更を行うことができます。 たとえば、 + debug`がfalseのときに + info`レベルのメッセージのみをオフにしたい場合、APIの他の部分に影響を与えずにこの変更を行うことができます。 また、ログメッセージを安全に変更して、プログラムの実行元ディレクトリなどの詳細情報を含めることもできます。

結論

この記事では、パッケージ間の実装の詳細を保護しながら、パッケージ間でコードを共有する方法を示しました。 これにより、後方互換性のためにめったに変更されないシンプルなAPIをエクスポートできますが、将来の動作を改善するために必要に応じてパッケージのプライベートな変更が可能になります。 これは、パッケージとそれに対応するAPIを作成する際のベストプラクティスと見なされます。

Goのパッケージの詳細については、https://www.digitalocean.com/community/tutorials/importing-packages-in-go [Goでのパッケージのインポート]およびhttps://www.digitalocean.com/communityをご覧ください。 / tutorials / how-to-write-packages-in-go [Goでパッケージを作成する方法]の記事、またはhttps://www.digitalocean.com/community/tutorial_series/how-to-code-in-全体を調べるgo [Goシリーズのコーディング方法]。