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

[インデックス 18921] ファイルの概要

src/pkg/regexp/syntax/parse.goは、Go言語の標準ライブラリであるregexpパッケージの一部であり、正規表現文字列を解析して抽象構文木(Abstract Syntax Tree: AST)に変換する役割を担っています。このファイルは、正規表現の構文解析ロジックを実装しており、より高レベルなregexpパッケージが内部的に利用する低レベルなコンポーネントです。具体的には、Parse関数が正規表現文字列を受け取り、それを*syntax.Regexpオブジェクト(正規表現の構文木ノード)として表現します。

コミット

このコミットは、src/pkg/regexp/syntax/parse.goファイルから未使用の変数minmaxを削除するものです。これらの変数は、case '{'(繰り返し指定)の処理ブロック内で定義されていましたが、外側のスコープで定義された同名の変数が常に0の値しか持たず、実際の計算には使用されていなかったため、コードの整理と最適化のために削除されました。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ed97788af9d3c38e48a7f34dd18f05ae80a48c9e

元コミット内容

regexp: remove unused variables

"min" and "max" in "case '{'" clause are fresh variables.
The variables defined in the outer scope never get value
other than 0.

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/78750044

変更の背景

この変更の背景には、コードの品質向上と保守性の維持があります。Go言語では、未使用の変数はコンパイルエラーとなることが一般的ですが、特定のケース(例えば、異なるスコープで同名の変数が定義されている場合など)では、未使用と判断されずに残ってしまうことがあります。

このコミットでは、src/pkg/regexp/syntax/parse.go内の正規表現の繰り返し指定(例: {n,m})を処理する部分において、minmaxという変数が定義されていましたが、これらが実際に使用されていなかったことが判明しました。具体的には、case '{'ブロック内で新たに宣言されたminmaxは使用されていましたが、その外側のスコープで宣言されていた同名のminmaxは、常に初期値の0のままであり、正規表現の繰り返し回数の計算には寄与していませんでした。

このような未使用の変数は、コードの可読性を低下させ、将来的なバグの原因となる可能性を秘めています。また、コンパイラが不要なコードを最適化する手間を省くためにも、明示的に削除することが推奨されます。このコミットは、このようなコードの「お掃除」の一環として行われました。

前提知識の解説

Go言語の正規表現パッケージ (regexp)

Go言語の標準ライブラリには、正規表現を扱うためのregexpパッケージが用意されています。このパッケージは、RE2という正規表現エンジンに基づいています。RE2は、線形時間でのマッチングを保証し、バックトラッキングによる指数関数的な時間計算量を避けるように設計されています。

regexpパッケージは、大きく分けて以下の2つのサブパッケージで構成されています。

  • regexp: ユーザーが直接利用する高レベルなAPIを提供します。Compile関数で正規表現をコンパイルしたり、MatchString関数で文字列とのマッチングを行ったりします。
  • regexp/syntax: regexpパッケージが内部的に利用する低レベルなパッケージです。正規表現文字列を解析して構文木を構築し、それを実行可能なプログラムにコンパイルする役割を担います。今回のコミットで変更されたparse.goはこのサブパッケージに属します。

正規表現の繰り返し指定 ({n,m})

正規表現において、{n,m}は直前の要素がn回以上m回以下繰り返されることを意味します。例えば、a{2,4}は"aa"、"aaa"、"aaaa"にマッチします。{n}はちょうどn回、{n,}n回以上繰り返されることを意味します。

Go言語の変数スコープ

Go言語では、変数のスコープはブロック({}で囲まれた領域)によって決定されます。あるブロック内で宣言された変数は、そのブロック内でのみ有効です。同じ名前の変数が異なるスコープで宣言された場合、それらは別々の変数として扱われます。内側のスコープで宣言された変数は、外側のスコープの同名変数を「シャドウイング(shadowing)」します。つまり、内側のスコープからは内側の変数が参照され、外側の変数は直接参照できなくなります。

今回のコミットでは、このスコープの概念が重要です。外側のスコープで宣言されたminmaxは、case '{'ブロック内で新たにminmaxが宣言されたため、シャドウイングされていました。そして、外側のminmaxは初期値の0のまま使用されることがなかった、というのが変更の背景にあります。

技術的詳細

このコミットは、src/pkg/regexp/syntax/parse.goファイル内のParse関数における、正規表現の繰り返し指定({})を処理する部分のコードを修正しています。

元のコードでは、Parse関数の冒頭でmin, max intという変数が宣言されていました。

func Parse(s string, flags Flags) (*Regexp, error) {
	// ...
	c          rune
	op         Op
	lastRepeat string
	min, max   int // ここで宣言されている
	)
	// ...
}

そして、case '{'の処理ブロック内で、正規表現の繰り返し回数を解析した後、p.repeat関数を呼び出す際に、この外側のスコープのminmaxが引数として渡されていました。

// 変更前
if after, err = p.repeat(op, min, max, before, after, lastRepeat); err != nil {
    return nil, err
}

しかし、コミットメッセージが示唆するように、case '{'ブロック内で正規表現の繰り返し回数を解析する際に、minmaxという新しい変数が宣言され、そこに解析結果が格納されていました。この新しいminmaxは、外側のスコープのminmaxとは別物であり、外側のminmaxは常に初期値の0のままでした。

このコミットでは、この冗長性を解消するために、以下の変更が行われました。

  1. Parse関数の冒頭で宣言されていた未使用のmin, max int変数を削除しました。
  2. p.repeat関数を呼び出す際に、常に0を直接引数として渡すように変更しました。これは、外側のminmaxが常に0であったため、その値を渡すことと実質的に同じ意味になります。

この変更により、コードはより簡潔になり、未使用の変数が存在しないため、可読性と保守性が向上します。機能的な変更は一切なく、正規表現の解析動作に影響はありません。

コアとなるコードの変更箇所

--- a/src/pkg/regexp/syntax/parse.go
+++ b/src/pkg/regexp/syntax/parse.go
@@ -668,7 +668,6 @@ func Parse(s string, flags Flags) (*Regexp, error) {
 		c          rune
 		op         Op
 		lastRepeat string
-		min, max   int
 	)
 	p.flags = flags
 	p.wholeRegexp = s
@@ -740,7 +739,7 @@ func Parse(s string, flags Flags) (*Regexp, error) {\n 			op = OpQuest
 			}\n 			after := t[1:]
-			if after, err = p.repeat(op, min, max, before, after, lastRepeat); err != nil {\n+\t\t\tif after, err = p.repeat(op, 0, 0, before, after, lastRepeat); err != nil {\n \t\t\t\treturn nil, err
 			}\n 			repeat = before

コアとなるコードの解説

上記の差分は、以下の2つの主要な変更を示しています。

  1. - min, max int: Parse関数の冒頭で宣言されていたminmax変数の宣言が削除されました。これは、これらの変数が外側のスコープで定義されていても、case '{'ブロック内で同名の新しい変数が宣言され、そちらが使用されていたため、この外側のminmaxは常に初期値の0のままで未使用であったことを意味します。未使用の変数を削除することで、コードの冗長性が排除され、コンパイラによる最適化の機会が増えます。

  2. if after, err = p.repeat(op, 0, 0, before, after, lastRepeat); err != nil {: case '{'ブロック内でp.repeat関数を呼び出す際の引数が変更されました。変更前は、削除された外側のスコープのminmaxが引数として渡されていましたが、これらが常に0であったため、直接0, 0を渡すように修正されました。この変更は、機能的には全く同じ結果をもたらしますが、コードの意図をより明確にし、未使用の変数を排除することで、コードベース全体の健全性を向上させます。

この変更は、Go言語のコードレビュープロセスにおいて、コードの品質と効率性を維持するための典型的な改善例と言えます。

関連リンク

参考にした情報源リンク