Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.rootl.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{} 型なので任意の型を格納可能)を保持し、nextprev フィールドで前後の 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点に集約されます。

  1. lazyInit() メソッドの改善:

    • 変更前: lazyInit() は、リストが未初期化の場合に l.root.nextl.root.prev を直接 &l.root に設定していました。
    • 変更後: lazyInit() は、リストが未初期化の場合に l.Init() を呼び出すように変更されました。
    • 意図: List 型には既に Init() という初期化メソッドが存在します。このメソッドは、リストの root 要素の nextprev ポインタを自身に設定し、len を0にリセットする役割を担っています。lazyInit() がこの既存の Init() メソッドを呼び出すようにすることで、初期化ロジックの重複が解消され、一貫性が保たれます。これにより、リストの初期化に関する唯一の真実の場所が Init() メソッドになります。
  2. 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.nextnil の場合)ときに、リストの内部状態を適切に設定するためのものです。変更前は、root 要素の nextprev ポインタを直接自身に設定していました。これは 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言語の公式ドキュメント
  • ソフトウェアリファクタリングに関する一般的な情報(例: マーティン・ファウラー著『リファクタリング 既存のコードを安全に改善する』)
  • 二重リンクリストに関するデータ構造の知識
  • Go言語の標準ライブラリの設計原則に関する情報