[インデックス 14500] ファイルの概要
このコミットは、Go言語の標準ライブラリ os/user
パッケージにおけるユーザー情報ルックアップ機能の内部構造を整理し、パブリックAPIとプラットフォーム固有の実装を分離することを目的としています。具体的には、Current()
, Lookup()
, LookupId()
といったユーザー情報を取得する関数が、新たに作成された src/pkg/os/user/lookup.go
ファイルでパブリックAPIとして定義され、実際の処理は各プラットフォーム(Unix、Windows、および未実装のプラットフォーム向けのスタブ)に特化した内部関数 (current()
, lookup()
, lookupId()
) に委譲されるようになりました。これにより、コードの可読性と保守性が向上し、将来的な拡張が容易になります。
コミット
commit 178c8578d5794aee6b111c6831e9a04e8a9d51ae
Author: Anthony Martin <ality@pbrane.org>
Date: Mon Nov 26 16:02:08 2012 -0800
os/user: update stub documentation
R=golang-dev, minux.ma, bradfitz
CC=golang-dev
https://golang.org/cl/6844088
---
src/pkg/os/user/lookup.go | 22 ++++++++++++++++++++++
src/pkg/os/user/lookup_stubs.go | 6 +++---
src/pkg/os/user/lookup_unix.go | 19 +++++++------------
src/pkg/os/user/lookup_windows.go | 9 +++------
4 files changed, 35 insertions(+), 21 deletions(-)
diff --git a/src/pkg/os/user/lookup.go b/src/pkg/os/user/lookup.go
new file mode 100644
index 0000000000..09f00c7bdb
--- /dev/null
+++ b/src/pkg/os/user/lookup.go
@@ -0,0 +1,22 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+// Current returns the current user.
+func Current() (*User, error) {
+ return current()
+}
+
+// Lookup looks up a user by username. If the user cannot be found, the
+// returned error is of type UnknownUserError.
+func Lookup(username string) (*User, error) {
+ return lookup(username)
+}
+
+// LookupId looks up a user by userid. If the user cannot be found, the
+// returned error is of type UnknownUserIdError.
+func LookupId(uid string) (*User, error) {
+ return lookupId(uid)
+}
diff --git a/src/pkg/os/user/lookup_stubs.go b/src/pkg/os/user/lookup_stubs.go
index 415f869f22..ad06907b5d 100644
--- a/src/pkg/os/user/lookup_stubs.go
+++ b/src/pkg/os/user/lookup_stubs.go
@@ -15,14 +15,14 @@ func init() {
implemented = false
}
-func Current() (*User, error) {
+func current() (*User, error) {
return nil, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
-func Lookup(username string) (*User, error) {
+func lookup(username string) (*User, error) {
return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
-func LookupId(string) (*User, error) {
+func lookupId(uid string) (*User, error) {
return nil, fmt.Errorf("user: LookupId not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
diff --git a/src/pkg/os/user/lookup_unix.go b/src/pkg/os/user/lookup_unix.go
index 1102e5bb1b..05c34b66e6 100644
--- a/src/pkg/os/user/lookup_unix.go
+++ b/src/pkg/os/user/lookup_unix.go
@@ -29,28 +29,23 @@ static int mygetpwuid_r(int uid, struct passwd *pwd,
*/
import "C"
-// Current returns the current user.\n-func Current() (*User, error) {\n-\treturn lookup(syscall.Getuid(), \"\", false)\n+func current() (*User, error) {\n+\treturn lookupUnix(syscall.Getuid(), \"\", false)\n }\n
-// Lookup looks up a user by username. If the user cannot be found,\n-// the returned error is of type UnknownUserError.\n-func Lookup(username string) (*User, error) {\n-\treturn lookup(-1, username, true)\n+func lookup(username string) (*User, error) {\n+\treturn lookupUnix(-1, username, true)\n }\n
-// LookupId looks up a user by userid. If the user cannot be found,\n-// the returned error is of type UnknownUserIdError.\n-func LookupId(uid string) (*User, error) {\n+func lookupId(uid string) (*User, error) {\n \ti, e := strconv.Atoi(uid)\n \tif e != nil {\n \t\treturn nil, e\n \t}\n-\treturn lookup(i, \"\", false)\n+\treturn lookupUnix(i, \"\", false)\n }\n
-func lookup(uid int, username string, lookupByName bool) (*User, error) {\n+func lookupUnix(uid int, username string, lookupByName bool) (*User, error) {\n \tvar pwd C.struct_passwd\n \tvar result *C.struct_passwd\n \ndiff --git a/src/pkg/os/user/lookup_windows.go b/src/pkg/os/user/lookup_windows.go
index 3626a4e9f0..a0a8a4ec10 100644
--- a/src/pkg/os/user/lookup_windows.go
+++ b/src/pkg/os/user/lookup_windows.go
@@ -68,8 +68,7 @@ func newUser(usid *syscall.SID, gid, dir string) (*User, error) {\n \treturn u, nil\n }\n \n-// Current returns the current user.\n-func Current() (*User, error) {\n+func current() (*User, error) {\n \tt, e := syscall.OpenCurrentProcessToken()\n \tif e != nil {\n \t\treturn nil, e\n@@ -103,8 +102,7 @@ func newUserFromSid(usid *syscall.SID) (*User, error) {\n \treturn newUser(usid, gid, dir)\n }\n \n-// Lookup looks up a user by username.\n-func Lookup(username string) (*User, error) {\n+func lookup(username string) (*User, error) {\n \tsid, _, t, e := syscall.LookupSID(\"\", username)\n \tif e != nil {\n \t\treturn nil, e\n@@ -115,8 +113,7 @@ func Lookup(username string) (*User, error) {\n \treturn newUserFromSid(sid)\n }\n \n-// LookupId looks up a user by userid.\n-func LookupId(uid string) (*User, error) {\n+func lookupId(uid string) (*User, error) {\n \tsid, e := syscall.StringToSid(uid)\n \tif e != nil {\n \t\treturn nil, e
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/178c8578d5794aee6b111c6831e9a04e8a9d51ae
元コミット内容
このコミットの元のメッセージは「os/user: update stub documentation」です。これは、os/user
パッケージにおけるスタブ(未実装のプラットフォーム向けのプレースホルダー)のドキュメントを更新するという意図を示唆しています。しかし、実際の変更内容は単なるドキュメントの更新に留まらず、パッケージの内部構造を大きく変更し、パブリックAPIと内部実装を分離するリファクタリングを含んでいます。このコミットメッセージは、変更の全体像を完全に捉えているわけではありませんが、スタブの実装がより明確になるという側面を強調していると考えられます。
変更の背景
Go言語はクロスプラットフォーム開発を強く意識しており、多くの標準ライブラリが異なるオペレーティングシステム(OS)で動作するように設計されています。os/user
パッケージもその一つで、ユーザー情報の取得(現在のユーザー、ユーザー名やIDによるルックアップ)をOSに依存しない形で提供することを目指しています。
このコミットが行われた背景には、以下のような課題があったと考えられます。
- コードの重複と複雑性: 以前の
os/user
パッケージでは、Current()
,Lookup()
,LookupId()
といったパブリック関数が、各プラットフォーム固有のファイル(例:lookup_unix.go
,lookup_windows.go
)で直接定義されていました。これにより、各ファイルで同じ関数シグネチャを持つ関数が複数存在し、コードの重複や、パブリックAPIとしての振る舞いを一元的に管理しにくいという問題がありました。 - スタブの実装の不明瞭さ: 特定のOSでユーザー情報ルックアップ機能が未実装の場合(スタブ)、そのエラーハンドリングやドキュメントが各プラットフォームファイルに散在していました。これにより、どのプラットフォームで機能が利用可能で、どのプラットフォームでスタブが使用されるのかが不明瞭になる可能性がありました。
- 保守性と拡張性: パブリックAPIと内部実装が密結合していると、将来的に新しいプラットフォームのサポートを追加したり、既存のプラットフォーム実装を改善したりする際に、パブリックAPIの変更を伴う可能性があり、保守性や拡張性が低下します。
このコミットは、これらの課題を解決するために、パブリックAPIを lookup.go
に集約し、プラットフォーム固有の実装を内部関数として隠蔽することで、コードベースの整理とモジュール化を進めました。これにより、os/user
パッケージの設計がより堅牢になり、クロスプラットフォーム対応がより明確になりました。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびOSに関する基本的な知識が必要です。
-
Go言語のパッケージとエクスポートルール:
- Go言語では、パッケージ内の識別子(関数名、変数名など)が大文字で始まる場合、その識別子はパッケージ外からアクセス可能なエクスポートされた(パブリックな)識別子となります。
- 一方、識別子が小文字で始まる場合、それはパッケージ内でのみアクセス可能なエクスポートされていない(プライベートな、または内部の)識別子となります。
- このコミットでは、既存のパブリック関数
Current
,Lookup
,LookupId
を小文字始まりのcurrent
,lookup
,lookupId
に変更し、これらを内部関数とすることで、プラットフォーム固有の実装を隠蔽しています。
-
Go言語のビルドタグとプラットフォーム固有のコード:
- Go言語では、ファイル名の末尾に
_GOOS
や_GOARCH
を付けることで、特定のOSやアーキテクチャでのみコンパイルされるコードを記述できます(例:lookup_unix.go
はUnix系OSでのみ、lookup_windows.go
はWindowsでのみコンパイルされます)。 - また、ファイルの先頭に
// +build
ディレクティブを使用することでも、より複雑な条件でコンパイルを制御できます。 os/user
パッケージは、このメカニズムを利用して、各OSに合わせたユーザー情報ルックアップの実装を提供しています。
- Go言語では、ファイル名の末尾に
-
スタブ (Stub):
- ソフトウェア開発において、スタブとは、まだ完全に実装されていない機能や、特定の環境で利用できない機能の代わりに、一時的に置かれるダミーのコードやプレースホルダーのことです。
- この文脈では、
lookup_stubs.go
は、ユーザー情報ルックアップ機能が実装されていないOS(例えば、Goがサポートするが、ユーザー管理システムが特殊な組み込みシステムなど)向けに、エラーを返すだけのプレースホルダー実装を提供しています。
-
OSにおけるユーザー情報ルックアップ:
- Unix系OS (Linux, macOSなど): ユーザー情報は通常、
/etc/passwd
や/etc/group
といったファイル、またはNIS (Network Information Service) やLDAP (Lightweight Directory Access Protocol) などのディレクトリサービスによって管理されます。C言語の標準ライブラリにはgetpwnam()
(ユーザー名からユーザー情報を取得),getpwuid()
(UIDからユーザー情報を取得) といった関数が提供されており、Goのsyscall
パッケージを通じてこれらのシステムコールを呼び出すことでユーザー情報を取得します。 - Windows OS: Windowsでは、ユーザー情報はSecurity Account Manager (SAM) データベースやActive Directoryによって管理されます。ユーザーやグループの情報を取得するためには、Windows API(例:
LookupAccountName
,LookupAccountSid
)を使用します。
- Unix系OS (Linux, macOSなど): ユーザー情報は通常、
技術的詳細
このコミットの技術的な核心は、os/user
パッケージのユーザー情報ルックアップ機能におけるパブリックAPIと内部実装の分離です。
-
src/pkg/os/user/lookup.go
の新規作成:- このファイルは、
os/user
パッケージのパブリックAPIであるCurrent()
,Lookup()
,LookupId()
を定義するために新しく作成されました。 - これらのパブリック関数は、それぞれ対応する小文字始まりの内部関数
current()
,lookup()
,lookupId()
を呼び出すだけのシンプルなラッパーとなっています。 - これにより、パッケージの外部から見えるインターフェースは
lookup.go
に集約され、どのプラットフォームで実行されても同じ関数シグネチャでユーザー情報を取得できるようになります。
- このファイルは、
-
既存のプラットフォーム固有ファイルの変更:
src/pkg/os/user/lookup_stubs.go
(スタブ実装)src/pkg/os/user/lookup_unix.go
(Unix系OS実装)src/pkg/os/user/lookup_windows.go
(Windows OS実装)- これらのファイルでは、これまでパブリック関数として定義されていた
Current()
,Lookup()
,LookupId()
が、それぞれ小文字始まりのcurrent()
,lookup()
,lookupId()
に名称変更されました。 - これにより、これらの関数はパッケージ内部でのみアクセス可能となり、外部からは
lookup.go
で定義されたパブリック関数を通じてのみ利用されるようになります。 - 特に
lookup_unix.go
では、内部関数lookup
がlookupUnix
にリネームされ、current
,lookup
,lookupId
がlookupUnix
を呼び出す形に変更されています。これは、Unix固有のルックアップロジックをさらに抽象化し、内部での再利用性を高めるための措置と考えられます。
この変更により、os/user
パッケージの設計は、以下のような利点を持つようになりました。
- 明確な責任分担:
lookup.go
はパブリックAPIの定義とドキュメントの提供に専念し、各プラットフォーム固有のファイルは実際のルックアップロジックの実装に専念します。 - カプセル化: プラットフォーム固有の実装の詳細がパッケージ内部に隠蔽され、外部からは見えなくなります。これにより、内部実装の変更が外部APIに影響を与えるリスクが低減します。
- 保守性の向上: 新しいプラットフォームのサポートを追加する場合や、既存のプラットフォーム実装を修正する場合でも、
lookup.go
のパブリックAPIを変更することなく、対応するプラットフォーム固有の内部関数を実装または修正するだけで済みます。 - テストの容易性: パブリックAPIと内部実装が分離されることで、それぞれのコンポーネントを独立してテストしやすくなります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下の通りです。
-
src/pkg/os/user/lookup.go
の新規追加:// Current returns the current user. func Current() (*User, error) { return current() } // Lookup looks up a user by username. If the user cannot be found, the // returned error is of type UnknownUserError. func Lookup(username string) (*User, error) { return lookup(username) } // LookupId looks up a user by userid. If the user cannot be found, the // returned error is of type UnknownUserIdError. func LookupId(uid string) (*User, error) { return lookupId(uid) }
-
src/pkg/os/user/lookup_stubs.go
の関数名変更:--- a/src/pkg/os/user/lookup_stubs.go +++ b/src/pkg/os/user/lookup_stubs.go @@ -15,14 +15,14 @@ func init() { implemented = false } -func Current() (*User, error) { +func current() (*User, error) { return nil, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) } -func Lookup(username string) (*User, error) { +func lookup(username string) (*User, error) { return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) } -func LookupId(string) (*User, error) { +func lookupId(uid string) (*User, error) { return nil, fmt.Errorf("user: LookupId not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) }
-
src/pkg/os/user/lookup_unix.go
の関数名変更と内部関数のリネーム:--- a/src/pkg/os/user/lookup_unix.go +++ b/src/pkg/os/user/lookup_unix.go @@ -29,28 +29,23 @@ static int mygetpwuid_r(int uid, struct passwd *pwd, */ import "C" -// Current returns the current user.\n-func Current() (*User, error) {\n-\treturn lookup(syscall.Getuid(), \"\", false)\n +func current() (*User, error) {\n +\treturn lookupUnix(syscall.Getuid(), \"\", false)\n } -// Lookup looks up a user by username. If the user cannot be found,\n-// the returned error is of type UnknownUserError.\n-func Lookup(username string) (*User, error) {\n-\treturn lookup(-1, username, true)\n +func lookup(username string) (*User, error) {\n +\treturn lookupUnix(-1, username, true)\n } -// LookupId looks up a user by userid. If the user cannot be found,\n-// the returned error is of type UnknownUserIdError.\n-func LookupId(uid string) (*User, error) {\n +func lookupId(uid string) (*User, error) {\n \ti, e := strconv.Atoi(uid)\n \tif e != nil {\n \t\treturn nil, e\n \t}\n -\treturn lookup(i, \"\", false)\n +\treturn lookupUnix(i, \"\", false)\n } -func lookup(uid int, username string, lookupByName bool) (*User, error) {\n +func lookupUnix(uid int, username string, lookupByName bool) (*User, error) {\n \tvar pwd C.struct_passwd\n \tvar result *C.struct_passwd\n ```
-
src/pkg/os/user/lookup_windows.go
の関数名変更:--- a/src/pkg/os/user/lookup_windows.go +++ b/src/pkg/os/user/lookup_windows.go @@ -68,8 +68,7 @@ func newUser(usid *syscall.SID, gid, dir string) (*User, error) {\n return u, nil\n } -// Current returns the current user.\n-func Current() (*User, error) {\n +func current() (*User, error) {\n \tt, e := syscall.OpenCurrentProcessToken()\n \tif e != nil {\n \t\treturn nil, e\n @@ -103,8 +102,7 @@ func newUserFromSid(usid *syscall.SID) (*User, error) {\n return newUser(usid, gid, dir)\n } -// Lookup looks up a user by username.\n-func Lookup(username string) (*User, error) {\n +func lookup(username string) (*User, error) {\n \tsid, _, t, e := syscall.LookupSID(\"\", username)\n \tif e != nil {\n \t\treturn nil, e\n @@ -115,8 +113,7 @@ func Lookup(username string) (*User, error) {\n return newUserFromSid(sid)\n } -// LookupId looks up a user by userid.\n-func LookupId(uid string) (*User, error) {\n +func lookupId(uid string) (*User, error) {\n \tsid, e := syscall.StringToSid(uid)\n \tif e != nil {\n \t\treturn nil, e
コアとなるコードの解説
このコミットの核となる変更は、Go言語のエクスポートルールを巧みに利用して、os/user
パッケージのAPI設計を改善した点にあります。
-
lookup.go
の役割: このファイルは、os/user
パッケージの「顔」となります。Current()
,Lookup()
,LookupId()
といった大文字で始まる関数は、Goのエクスポートルールによりパッケージ外部からアクセス可能です。これらの関数は、ユーザーがos/user
パッケージを利用する際に直接呼び出すAPIとなります。重要なのは、これらの関数が具体的なOS固有のロジックを一切持たず、単に小文字で始まる内部関数を呼び出しているだけであるという点です。これにより、APIの安定性と一貫性が保証されます。 -
プラットフォーム固有ファイルの役割の変更:
lookup_stubs.go
,lookup_unix.go
,lookup_windows.go
といったファイルは、それぞれ特定のプラットフォームにおけるユーザー情報ルックアップの具体的な実装を含んでいます。このコミット以前は、これらのファイルが直接パブリック関数を定義していましたが、変更後はcurrent()
,lookup()
,lookupId()
のように小文字で始まる関数名に変更されました。これにより、これらの関数はパッケージ内部でのみ利用可能な「プライベートな」実装となり、外部からは直接呼び出せなくなります。 -
lookupUnix
へのリネーム:lookup_unix.go
において、以前のlookup
関数がlookupUnix
にリネームされたのは、Unix系OSに特化したルックアップロジックであることをより明確にするためです。そして、current()
,lookup()
,lookupId()
のUnix実装が、このlookupUnix
を呼び出す形に統一されました。これは、コードの重複を避け、Unix固有の共通ロジックを一つの関数にまとめるという、さらなるリファクタリングを示しています。
この設計パターンは、Go言語におけるクロスプラットフォーム開発や、複雑なライブラリのモジュール化において非常に一般的で効果的な手法です。パブリックAPIをシンプルに保ちつつ、内部ではプラットフォームや実装の詳細を隠蔽することで、コードの保守性、拡張性、そして理解しやすさが大幅に向上します。
関連リンク
- Go言語の
os/user
パッケージのドキュメント: https://pkg.go.dev/os/user - Go言語のクロスコンパイルとビルドタグに関する公式ドキュメント (Go Command Documentation): https://go.dev/cmd/go/#hdr-Build_constraints
- Go言語のパッケージとエクスポートルールに関する解説 (Effective Go): https://go.dev/doc/effective_go#names
参考にした情報源リンク
- Go言語の公式ドキュメント (上記「関連リンク」に記載のURL)
- Go言語のソースコード (GitHub): https://github.com/golang/go
- 一般的なソフトウェア開発における「スタブ」の概念に関する情報