[インデックス 13984] ファイルの概要
このコミットは、Go言語の標準ライブラリである container/list
パッケージにおけるコードのファクタリング(リファクタリング)に関するものです。具体的には、リストの初期化処理と要素の挿入処理において、コードの重複を排除し、より簡潔で読みやすい実装を目指しています。
コミット
commit de782dd146ede31408b8212f8f5b72457c132387
Author: Robert Griesemer <gri@golang.org>
Date: Fri Sep 28 10:58:46 2012 -0700
container/list: slightly better code factoring
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/6569077
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/de782dd146ede31408b8212f8f5b72457c132387
元コミット内容
このコミットの目的は、container/list
パッケージ内のコードファクタリングを改善することです。具体的には、リストの遅延初期化 (lazyInit
) のロジックを Init()
メソッドに集約し、また新しい要素をリストに挿入する際の共通パターンを insertValue
というヘルパー関数に抽出することで、コードの重複を減らし、可読性と保守性を向上させています。
変更の背景
Go言語の container/list
パッケージは、標準ライブラリとして提供される汎用的な二重リンクリストの実装です。このパッケージは、要素の追加、削除、移動などの基本的なリスト操作を提供します。
このコミットが行われた背景には、既存のコードベースにおける重複の解消と、よりクリーンな設計への移行というリファクタリングの一般的な動機があります。特に、リストに新しい要素を挿入する多くのメソッド(PushFront
, PushBack
, InsertBefore
, InsertAfter
など)が、内部的に insert
メソッドを呼び出す際に、毎回 &Element{Value: v}
のように新しい Element
構造体を生成していました。このパターンが繰り返されることで、コードの冗長性が増し、将来的な変更やデバッグが困難になる可能性がありました。
また、リストがまだ初期化されていない場合に内部構造をセットアップする lazyInit
メソッドも、直接 l.root.next = &l.root
と l.root.prev = &l.root
を設定していました。これは List
型の Init()
メソッドが本来行うべき処理であり、重複した初期化ロジックが存在していました。
このコミットは、これらの重複を特定し、共通のロジックを再利用可能なヘルパー関数や既存のメソッドに委譲することで、コードベース全体の品質を向上させることを目的としています。これは、ソフトウェア開発における「DRY (Don't Repeat Yourself)」原則に則った典型的な改善です。
前提知識の解説
Go言語の container/list
パッケージ
container/list
はGo言語の標準ライブラリの一部であり、二重リンクリスト(doubly linked list)を実装しています。二重リンクリストは、各ノードが前のノードと次のノードの両方への参照(ポインタ)を持つデータ構造です。これにより、リスト内での前方および後方への効率的なトラバース(走査)が可能になります。
主要な構成要素は以下の通りです。
List
構造体: リスト全体を表す構造体で、リストの先頭(root
)と要素数(len
)を管理します。Element
構造体: リスト内の個々の要素を表す構造体です。Value
フィールドに実際のデータ(interface{}
型なので任意の型を格納可能)を保持し、next
とprev
フィールドで前後のElement
へのポインタを持ちます。また、自身が属するList
へのポインタも持ちます。
container/list
パッケージが提供する主な操作には以下のようなものがあります。
PushFront(v interface{}) *Element
: リストの先頭に要素を追加します。PushBack(v interface{}) *Element
: リストの末尾に要素を追加します。InsertBefore(v interface{}, mark *Element) *Element
: 指定された要素の前に新しい要素を挿入します。InsertAfter(v interface{}, mark *Element) *Element
: 指定された要素の後に新しい要素を挿入します。Remove(e *Element) interface{}
: 指定された要素をリストから削除します。Front() *Element
: リストの先頭の要素を返します。Back() *Element
: リストの末尾の要素を返します。Len() int
: リストの要素数を返します。Init() *List
: リストを初期化または再初期化します。
コードファクタリング(リファクタリング)
コードファクタリングとは、プログラムの外部から見た動作を変えずに、内部構造を改善するプロセスです。主な目的は、コードの可読性、保守性、拡張性を向上させることです。ファクタリングは、バグの修正や新機能の追加とは異なり、既存のコードをより良くすることに焦点を当てます。
一般的なファクタリングの手法には以下のようなものがあります。
- メソッドの抽出: 長いメソッドの一部を新しいメソッドとして切り出すことで、メソッドの責務を明確にし、再利用性を高めます。
- 重複コードの排除: 複数の場所に存在する同じ、または非常に似たコードを共通の関数やメソッドにまとめることで、コード量を減らし、一貫性を保ちます。
- 変数の名前変更: 変数や関数の名前をより意味のあるものに変更し、コードの意図を明確にします。
- クラスの抽出: 責務が多すぎるクラスを複数の小さなクラスに分割します。
- 条件式の簡略化: 複雑な条件ロジックをより理解しやすい形に書き換えます。
このコミットでは、「メソッドの抽出」と「重複コードの排除」が主なファクタリング手法として適用されています。
技術的詳細
このコミットにおける技術的な変更点は、主に以下の2点に集約されます。
-
lazyInit()
メソッドの改善:- 変更前:
lazyInit()
は、リストが未初期化の場合にl.root.next
とl.root.prev
を直接&l.root
に設定していました。 - 変更後:
lazyInit()
は、リストが未初期化の場合にl.Init()
を呼び出すように変更されました。 - 意図:
List
型には既にInit()
という初期化メソッドが存在します。このメソッドは、リストのroot
要素のnext
とprev
ポインタを自身に設定し、len
を0にリセットする役割を担っています。lazyInit()
がこの既存のInit()
メソッドを呼び出すようにすることで、初期化ロジックの重複が解消され、一貫性が保たれます。これにより、リストの初期化に関する唯一の真実の場所がInit()
メソッドになります。
- 変更前:
-
insertValue()
ヘルパー関数の導入と利用:- 変更前:
PushFront
,PushBack
,InsertBefore
,InsertAfter
,PushBackList
,PushFrontList
といった複数のメソッドが、新しい要素を挿入する際に、毎回l.insert(&Element{Value: v}, at)
のようにElement
構造体をその場で生成し、insert
メソッドに渡していました。 - 変更後: 新しいプライベートヘルパー関数
insertValue(v interface{}, at *Element) *Element
が導入されました。この関数は、&Element{Value: v}
を内部で生成し、それをl.insert()
に渡す処理をカプセル化します。そして、上記の各メソッドはl.insertValue(v, at)
を呼び出すように変更されました。 - 意図: これは典型的な「メソッドの抽出」による重複コードの排除です。
Element
の生成とinsert
の呼び出しという共通のパターンをinsertValue
にまとめることで、各挿入メソッドのコードが簡潔になり、読みやすさが向上します。また、もし将来的にElement
の生成方法やinsert
の呼び出し方に変更が必要になった場合でも、insertValue
の定義を修正するだけで済むため、保守性が大幅に向上します。
- 変更前:
これらの変更は、Go言語の標準ライブラリが、単に機能を提供するだけでなく、内部的なコード品質や設計原則にも配慮していることを示しています。
コアとなるコードの変更箇所
変更は src/pkg/container/list/list.go
ファイルに対して行われています。
--- a/src/pkg/container/list/list.go
+++ b/src/pkg/container/list/list.go
@@ -83,8 +83,7 @@ func (l *List) Back() *Element {
// lazyInit lazily initializes a zero List value.
func (l *List) lazyInit() {
if l.root.next == nil {
- l.root.next = &l.root
- l.root.prev = &l.root
+ l.Init()
}
}
@@ -100,6 +99,11 @@ func (l *List) insert(e, at *Element) *Element {
return e
}
+// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
+func (l *List) insertValue(v interface{}, at *Element) *Element {
+ return l.insert(&Element{Value: v}, at)
+}
+
// remove removes e from its list, decrements l.len, and returns e.
func (l *List) remove(e *Element) *Element {
e.prev.next = e.next
@@ -123,13 +127,13 @@ func (l *List) Remove(e *Element) interface{} {
// Pushfront inserts a new element e with value v at the front of list l and returns e.
func (l *List) PushFront(v interface{}) *Element {
l.lazyInit()
- return l.insert(&Element{Value: v}, &l.root)
+ return l.insertValue(v, &l.root)
}
// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List) PushBack(v interface{}) *Element {
l.lazyInit()
- return l.insert(&Element{Value: v}, l.root.prev)
+ return l.insertValue(v, l.root.prev)
}
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
@@ -139,17 +143,17 @@ func (l *List) InsertBefore(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
- return l.insert(&Element{Value: v}, mark.prev)
+ return l.insertValue(v, mark.prev)
}
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
-func (l *List) InsertAfter(value interface{}, mark *Element) *Element {
+func (l *List) InsertAfter(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
- return l.insert(&Element{Value: value}, mark)
+ return l.insertValue(v, mark)
}
// MoveToFront moves element e to the front of list l.
@@ -177,12 +181,12 @@ func (l *List) MoveToBack(e *Element) {
func (l *List) PushBackList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
- l.insert(&Element{Value: e.Value}, l.root.prev)
+ l.insertValue(e.Value, l.root.prev)
}
}
func (l *List) PushFrontList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
- l.insert(&Element{Value: e.Value}, &l.root)
+ l.insertValue(e.Value, &l.root)
}
}
コアとなるコードの解説
lazyInit()
の変更
// lazyInit lazily initializes a zero List value.
func (l *List) lazyInit() {
if l.root.next == nil {
- l.root.next = &l.root
- l.root.prev = &l.root
+ l.Init()
}
}
この変更は、List
構造体がまだ初期化されていない(l.root.next
が nil
の場合)ときに、リストの内部状態を適切に設定するためのものです。変更前は、root
要素の next
と prev
ポインタを直接自身に設定していました。これは List
型の Init()
メソッドが既に行っている処理と同じです。
変更後は、既存の l.Init()
メソッドを呼び出すように修正されました。これにより、初期化ロジックが一箇所に集約され、コードの重複が解消されます。Init()
メソッドは、リストを空の状態にリセットする役割も持っており、この変更によって lazyInit()
の責務がより明確になりました。
insertValue()
ヘルパー関数の導入
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List) insertValue(v interface{}, at *Element) *Element {
return l.insert(&Element{Value: v}, at)
}
この新しいプライベートメソッド insertValue
は、container/list
パッケージ内で頻繁に繰り返されていたパターンを抽象化するために導入されました。多くの挿入関連のメソッド(PushFront
, PushBack
など)は、まず新しい Element
を作成し、その Value
フィールドに渡された値を設定し、その後 l.insert()
メソッドを呼び出してリストに挿入していました。
insertValue
はこの共通の処理、すなわち &Element{Value: v}
を作成して l.insert()
に渡す部分をカプセル化します。これにより、各挿入メソッドは insertValue
を呼び出すだけでよくなり、コードがより簡潔で読みやすくなります。
各挿入メソッドでの insertValue()
の利用
PushFront
, PushBack
, InsertBefore
, InsertAfter
, PushBackList
, PushFrontList
の各メソッドは、以下のように insertValue
を利用するように変更されました。
例: PushFront
メソッド
// Pushfront inserts a new element e with value v at the front of list l and returns e.
func (l *List) PushFront(v interface{}) *Element {
l.lazyInit()
- return l.insert(&Element{Value: v}, &l.root)
+ return l.insertValue(v, &l.root)
}
他の挿入メソッドも同様に、l.insert(&Element{Value: ...}, ...)
の形式から l.insertValue(..., ...)
の形式に書き換えられています。
この変更により、各メソッドの内部ロジックが簡素化され、そのメソッド本来の目的(どこに要素を挿入するか)がより明確になります。また、Element
の生成ロジックが insertValue
に集約されたことで、将来的に Element
の構造や生成方法に変更があった場合でも、insertValue
のみを修正すればよくなり、保守性が向上します。
関連リンク
- Go言語
container/list
パッケージのドキュメント: https://pkg.go.dev/container/list - Go言語のコードレビューツール Gerrit の変更リスト: https://golang.org/cl/6569077
参考にした情報源リンク
- Go言語の公式ドキュメント
- ソフトウェアリファクタリングに関する一般的な情報(例: マーティン・ファウラー著『リファクタリング 既存のコードを安全に改善する』)
- 二重リンクリストに関するデータ構造の知識
- Go言語の標準ライブラリの設計原則に関する情報