命名のプロセス
Creative Commons Attribution 3.0 Unported License.
多くの人が、1回で最高の命名をしようとする。これは難しく、うまく行くことなんて滅多にない。問題はネーミングというのは設計であるということだ。あらゆるものに収まりの良い場所を与え、正しい抽象化をしなくてはならない。これを最初の1回で完璧にこなせる可能性は低い。だから進化的ネーミングについて話をしよう。
良い名前を見つけるためには、一連の手順にそってやるのが最も簡単なアプローチだと、今のところは考えている。ステップは各々、
https://gyazo.com/4c01cacc124cfd76f01d7b1bd3d29158
1. 名前が無い(Missing)
2. 無意味 (Nonsense)
3. 実直 (Honest)
4. 実直かつ完全 (Honest and Complete)
5. 正しいことをする (Does the Right Thing)
6. 意図を表す (Intent)
7. ドメイン抽象 (Domain Abstraction)
それぞれの手順では、コードの一部を見て、発生していることの種類を理解し、何かを感じ取り、それを書き留める。このリストに沿ってこれを繰り返す。私の目的にあう十分良い名前になるまでやり続ける。
負債のあるコードに効率的に作用する
これを読んだ人の中には、なぜそんなに名前に固執するのか不思議に思うものもいるだろう。名前をつけるのは面倒で多分これで簡単になるかもしれない。でもそれは本当に問題なんだろうか?
この問いに対する答えは、技術的負債を理解したり、負債化を防いだり、返済したりすることの中心にある。
すべての技術的負債の元
負債のあるコードとは、追うのが難しいすべてのコードである。技術的負債とは、コードを読むのを難しくしているすべてのものである。
私がコードを読むことにフォーカスしていることに、違和感を覚える人も多いだろう。所詮、私たちはプログラマなのだ。複雑なコードを読むのは得意だし、そんなコードを修正するのが私たちの仕事だ。技術的負債の定義が、コードの変更コストやリスクについてのものじゃダメなことってあるだろうか?
私はそうであるべきだと思う。開発者が費やす時間の大部分はコードを読むことなのは明らかだ。設計よりも、コードを書くことよりも、検査するよりも、(たぶんだけど😜)ミーティングの時間よりも多い。私が見たEclipseデータの分析によると、プログラマはプログラミング全体の時間の60-70%をコードを読むことに費やしている。
だからプログラマがより効率的に働けるようにするには、コードを読む力を改善する必要があるんだ。
さらには、新しいコードにバグが埋め込まれる確率は、循環的複雑度に関して超線形になる。長いメソッドや深いネストのメソッドがあると、修正時のバグが増えるということだ。興味深いことに、構文的な可読性とも関係している。インデントが統一されてないと、プログラマはより多くのバグを書いてしまう。コードをより複雑にしたとき以上にね。
これは、バグが不完全な理解から発生するために起こる。私たちが一度に頭に入れておけること以上に理解するのが難しいシステムだと、不完全な理解が発生する。どれだけ頭に入れておけるかを決める大きな要素が、どれだけ読みやすいかということだ。詳細を記憶して扱う必要がないように、それが何をしているか、ラベル付けされたものを、どれだけ早く特定できるか。
どんなコードにも効率的に作用する
どんなコードにも情報が含まれる。この情報の一部は、あなたに向けられている。これはクイックスキャンで見ることができ、読む必要すらない。情報の中には、それを探すのに注意深く読む必要があるものもある。間にあるものもある。読む必要があるが、ほとんどの読み手は一度見れば情報を発見できるだろう。
コードをもっとスキャンしやすくしたければ、関連する情報の割合を増やす必要がある。これはまた、無関係な情報を隠すことを意味する。
負債を減らすプロセスは単純だ。
1. なにか見る。
2. 気付きをえる。
3. 書きとめる。
4. チェックインする。
なにか見る 調査するものを選ぶ。すべてを理解しようとはしないことだ。これは長すぎる道のりかもしれないし、あなたの脳がオーバーフローしてしまうかもしれない。そう、あなたの脳でさえも。
気付きをえる 何でも構わない。完璧な気付きを求めてはいけない。まだ明白ではない有用そうなものを見つけるのだ。
書き下す ある名前。プログラミング言語において、何でも書ける場所はごくわずかしかない。名前は最高だ。
コメントが使えるかもしれないが、コメントはコードの一部ではない。コードと重複するので、重複にまつわる問題の原因となりうる。
コメント以外で任意のものを記録できる場所は、名前とアサーションだけだ。もし気付きが構造的なものであれば、それは名前に従属したものだ。これが実行時のものであれば、アサーションを使う。
アサーションは見つけやすくなくてはならない。だからコアコードの周りにアサーションをまき散らさないようにしよう。気付きを用例として表現し、それをテストに書こう。そして、気付きに関するテストに名前を付けるのだ(どんなコードが実行されるのかではなく)。
だから、実行時の気付きですら(テスト名として)名付けられて保存される。特定の用例と評価は、テストのボディやアサーションに保存される。気付きは名前に従属するのだ。
チェックインする 今すぐチェックインしよう。コードが良くなったはずだ。完璧ではない。だが、レガシーコードのマントラを思い出そう。「Goodは金がかかりすぎる、やりたいこと全てがより(早く)良くなった」。世界を少しでもより良くした今、利益を確定させよう。チェックインだ。
気付きのループがここにある全て
ところでこのループが、現代ソフトウェア開発の全てだ。
レガシーコードをリファクタリングすると、このループが実行され、名前がつけられる。
レガシーコードを理解することは、このループを実行し、テストの用例として何かを書くことだ。
TDDはこのループを3回繰り返す。
最初に、顧客インタビューを確認し、それをテストの一例として記録する。
次に、テストをよく見て、コードの中に名前を記述する。
3番めは、(新しい)レガシーコードをリファクタリングする。
設計とは、自分が見ている場所が「このテストを書くのがどれほど大変だったか」で、名前を変えることでその気付きを書きとめるループである。(通常これは「正しいことをする」名前に修正するステップである)
このループのどこにいるかを常に把握し、それを素早くおこなう方法を習得することで、マスタークラスのソフトウェアエンジニアになれる。
問題はもちろん、それを素早くやる方法を知ることにある。マスターは、このループが本当に速い。2~45秒の間で定常的にこれを実行できる。顧客インタビュー、既存のコード、既存のテストコードなど、気付きを求めている場所に関係なく、このスピードを実現できる。そして、新しい名前、名前の変更、抽象化の変更、新しいテスト、テストの変更など、どこに気付きを書く必要があるかに関わらず、それができる。さらに速い人も知っている。
名前の話に戻ろう
名前は私たちの気付きを記録する場所だ。そして有用に気付きをえる可能性が最も高いのはどこか、を知る良い手段が必要だ。面白いことに、既に名付けられたものの質を見ることが、どこを見るべきか知るための優れた方法であるように思う。これが、この記事の冒頭で述べた、進化的ネーミングにつながる。
要するに、ソフトウェア開発のマスターとは、これを素早く繰り返すことを意味する。コアループを柔軟に使って。
1. なにか見る。現在の名前付けの質をもとに、どこをより見るべきかを決める。
2. 気付きをえる。通常は名前付けを見ることで行う。
3. 書き下す。リファクタリングツールを使って、気付きを名前に表現する。または可読性の高いアサーションを使って、テストにアサーションとして表現する。
4. チェックインする。メッセージを使用してコミットに名前を付けることで、意図を表す。
MissingからNonsenseへ
ひどいネーミングに共通していえるのは、以下の2つだ。
誤解を招く名前 (Misleading)
他の何かの重要な部分となっていて、それに名前が付いていない (Missing)
プロセスとしての命名の精神にのっとり、これを解決する手軽な方法を求めている。何十万回とやることなので、些細なことでも速く軽くする必要がある。
まずは、MissingとMisleadingをNonsenseに変える。
明らかにナンセンスなものは良くないが、少なくとも誤解を招くようなものではない!これでチームの誰にとっても、その名前はナンセンスだということが明らかになって、名前に騙されてバグを生んでしまうということはなくなる 。
名前が付いていないものを修正する
コードを読みやすくすることが、究極の目的であるが、コードには同じメソッド、クラス、フィールド、変数に異なる概念を持つものがよくある。そこでそれぞれの概念を抽出し、名前を付けたい。ある概念が他のものの中に埋め込まれていると、その名前が欠落しているということだ。
Part1: 長いものに着目する
長いメソッド、長いクラス、長いファイル、長い引数リスト、長い式など、目の前のタスクに関する「長いもの」に着目する。これらはすべて、1つずつの概念をもつ断片に分割したほうが、読みやすく消化しやすい。
Part2: 1つの塊を探す
一緒くたになってそうなものを探す。例えば、
ひとまとまり文
明確でない式 (通常計算や論理式を含む)
よく一緒に渡されるパラメータの集合
メソッドで一緒に使われるパラメータの集合 (別の変数が検証済みかどうかを示すbool値のようなもの)
私は、上記のような一緒くたになっているコード片を選ぶ。どれだけ良い部分を見つけたかは問題ではない。よりよいコード片を見つけて、理解を早めるテクニックはいくつかあるが、どのコード片を選んでも一歩前進はするだろう。あとで洗練することはできる。良いものはコストがかかるので、ここで欲しいのは、現状よりはちょっと良いもの(速いもの)だけだ。
Part3: Applesauceとして、それを抽出する
私たちはこのコード片を理解したいのだ。最初のステップは、名前を付けれるものを作ることだ。よくある言語では、異なる5つのものいずれかに名前を付けることができるので、どれか1つを作る必要がある。そのためには以下のリファクタリングのどれかを行う。
メソッド抽出(Extract method): 文の塊に名前を付ける
変数、パラメータ、フィールドの導入: 式に名前を付ける
パラメータオブジェクトの導入: 一緒に渡されるパラメータの集合に名前を付ける
明らかにナンセンスだと分かる名前を付けたい。なので、Applesauceを使う。チームの誰もが即座に「ナンセンスなやつだ」と分かるだろう。ナンセンスな名前はこのApplesauce1つだけなので、同じスコープでさらにナンセンスな名前を付ける必要があれば、まず現状のApplesauceをHonestなものにリネームする必要がある。これは次のステップだ。
Applesauceだって? 本当に!?
明らかにナンセンスなことは、プロっぽくないと感じるだろう。自分の作品をどう表現したいか分からない。でも、バグを作るほうが、もっとプロらしくないだろう? 名前のない概念を多く含むものは、推測するのが難しく、開発者がバグを埋め込んでしまう原因となる。
このプロセスのための自己管理およびコーチングによる習慣の変更は、良い名前を作るために時間をかけたいという衝動などの、一般的な抵抗に対処するものだ。
長いメソッドに良い塊を見つける
塊を見つける技術を磨くには、いくつかの方法がある。長いメソッドの中で、名前がついてない概念の可能性なことが分かったら、これらの戦略を使って突破するのに最適なピースを見つけよう。
下から見る
長いメソッドは次のようなものから構成される傾向がある。
1. ガード条件
2. メソッド引数から、メソッドが実際に必要とするデータをローカル変数に読み込む
3. 何らかの処理
4. 結果の計算
5. なにかに計算を書き込んだり、戻り値にしたりする
これは、より重要なものがメソッドの終わり近くにあることを意味する。メソッドの上からでなく下から塊を探すようにしよう。
また多くの言語では、関数は複数のパラメータを受け付けるが、戻り値は1つしか返せないので、抽出は簡単になる。またくさんのローカル変数のような複雑な情報は、関数の中に書くほうが、関数の外に受け渡すよりも簡単だ。これはパラメータリストと戻り値が同じカージナリティを持てる言語では問題にならない。
読む量を少なくするためバイナリサーチする
ふつうは、楽しむためにコードを読むわけではなく、何か成し遂げたいから読むのだ。なので読むのが億劫なものを、その指針にしよう。
バイナリサーチを使って、関連性の高い塊を素早く見つける。探しているものが含まれていない大きな塊か、含まれている小さな塊を探す。どちらを選んでも、探索空間はすぐに小さくなる。
次のステップの伏線として、関係ないとわかっているコードの大きな塊を見つけたら、Bare Honestな名前を付けておいて、先へ進もう。例えば、 probably_DoStuffUnrelatedTo{whatever}()] のような。
制御構造を追う
長いメソッドは、(ガード節とローカル変数へのデータ代入の後) 単一の制御構造からなることがままある。その制御構造の本体は、良いターゲットになる。もし複数の制御構造が続いているなら、代わりに最後のの制御構造全体を選ぶ。
例えば、BecomeFroglike()メソッドが巨大なforeachループを1つ含んでいる場合、ループの本体を取り出して、MakeOne{制御変数がどんなものであれ}BecomeFroglike()として抽出して名前を付ける。
また、あるメソッドがforeachループ、ifブロック、別のforeachループを含んでいる場合、それらを3つのメソッドに抽出し、それぞれが制御構造全体を受け取るようにする。そうすれば、外側のメソッドは3つのメソッドを順番に呼び出すだけになる。
例外処理は少し特殊だ。tryブロックの本体は、抽出の良いターゲットになりうる。私はよく抽出した関数に*_impl()と名付ける。複雑なcatchブロックは、抽出する価値は無いことが多い。(例外はあるけれども)
誤解を招く名前を修正する
コードを読む際に、よくあるもう一つの課題は、その名前が本当にしていることと異なるものを表すことだ。ある概念が名前によって正確に表現されてないと、その名前はMisleadingだ、という。
Part1: 使われている名前に着目しよう
読んでいるコードで出くわす名前は、どんなものであれ注意する。
Part2: 情報不足なものや間違った情報を探す
嘘が含まれていないかを見て回る。例えば、次のようなものだ。
ライフサイクルの中でいつ呼び出されるかで名前が決まるメソッド (ex. PageLoad, PreInit)
型と同じ名前の変数 (ex. GridSquare gridSquare)
重要な情報が省かれているメソッド名 (ex. composeNameという名前のメソッドで、データベースにnameを書き込むこともある)。
-erや-Utilで終わる名前 (ex. DocumentManager, CalculationUnit)
Part3: Applesauceにリネームする
この段階で、何をするものか、良い名前はないか、わざわざ考える必要はない。Applesauceと名付け、コーディングを進めればよい。我々には、常に良いものがあるべき、という社会規範がある。だが、誤解を招く名前であれば、たとえそれが良いものに見えても、本当は良いものなんかじゃない。誤解を招くような名前は、誰かがバグを埋め込む原因になる。
チェックインしよう
以前は、この時点でチェックインするかどうか検討したこともあった。このステップの作業はとても小さく感じるし、Applesauceなんて「酷い名前だな」と。
だが、4年以上実践した結果、もう考えなくなった。常にチェックインするようにしている。
この2つの変更によって、コードがより良くなった。大きくは改善されないが、より良いものだ。速く、簡単かつ安全で、何も悪化はさせない。チェックインすることで、まっさらな状態に戻って、次にどんなことにも対応できるようになる。
名前がない概念に名前を付けることは、コードに関するより多くの気付きをもたらす傾向にあるので、このコミットと、次のコミットの両方が大きくなる可能性が高い。これは、それと分けて考えていきたい。紛らわしい名前を置き換えることは、たいていの場合、将来のバグを1つ減らすことができる。次のタスクで失敗したときに、これをロールバックする可能性がないように、すぐにそれをチェックインする勝ちがある。いずれにせよ、今すぐチェックインしよう。
NonsenseからHonestへ
私たちは"何か"に名前を付けたが、意味のない名前だ。私がFAAシステムとやりとりするシステム構築に携わったときの経験では、抜き出したメソッドにPreLoad()といつ呼び出すかは分かるが、それで何が起こるかは分からない名前をまず付けた。そこでPreLoad()の本体コードを抜き出し、これにApplesauce()と名前を付けた。これでPreLoad時に、Applesauce()することが明示される。
ここから、Applesauceを実直な名前にしてみよう。
名前をHonestにする
名前を実直なものにしたい。まずは完璧に実直である必要はない。実直なものの1つが伝わればよいのだ。メソッドの実行の鍵となっている1つのものがを理解するために、メソッドを見よう。メソッドの全体を通して読み、中心となっているように見えるものを探すのだ。
Part1: コードの中身に着目しよう
メソッドの中身を見よう。メソッド内部や他のメソッドとの間で繰り返されているパターンを見つけよう。
("database", "screen", "network", "service"といった)システムコンポーネントである変数を探すとよい。戻り値がどこから来てるか、何度も使われているものがないかを探そう。
Part2: コードがしていることに着目しよう
本体内のコードが何をするものなのか、1つだけでなく、作業がしやすいか、安全か、などの判断も大切にする。
例えば、多く使われているグローバルな名前dbに気づいたとしよう。いくつかの箇所で、レコードセットのオブジェクトが生成され、値をセットし、dbを使ってそれらを読み書きしている。読み書きが一緒になっているので、メソッドには probably_doSomethingEvilToTheDatabase_AndStuff() と名付ける。
名前としてはdoSomethingEvilToTheDatabaseでも十分に思えるが、なぜ、probably_や_AndStuffも付け足したのか? このメソッドが何をするのかある程度わかっているが100%でないことや、他にも何かやっていそうだがもう少し踏み込んでみる必要があることを、チームの他の人に伝えるためである。実直だが完全ではない。
実直にはいくつかの方法がある。実際、doSomethingEvilToTheDatabaseにも複数の気付きが含まれている。
1. このメソッドの主な作用は、データベースに関することのようだ。
2. データベースに対して何をしているかはまだ分からない。
3. 貧弱なデータベースの扱い方に、私は批判的になっている。私はそれが気に食わない。
4. このメソッドは私が全く理解できていないことを、もっとやってそうだ。
最後の3つの気付きは、1つめと同じくらい重要だ。たとえ私がこの時点でどこかへ行ってしまったとしても、コードの未来の読者に何らかの気付きを与えられる。このメソッドが何をするのかわからなくても、このメソッドはヤバそうだと警戒させることができる。
それが何をするのか誰も正確に知らないこと、中央のデータベースに何かしているということ、最後に触った人がそのメソッドが何か悪さをしていると感じたことが分かる。よろしい。私の持っている全ての知識を伝達できている。このメソッドに取り組むための適切な心構えは出来た。
Part3: よりよい名前に変更して気づきを書き留める
気付きを書き出すには、もう1つリファクタリングを実施する。「リネーム」だ。重要なのは以下の2点だ。
正直なところ、それは何をするものなのか?
まだ分かっていないことを明確にする
Renameリファクタリングを使って、安全にリネームしよう。名前を編集するだけだと、誤って何らかのふるまいを変えてしまう恐れがある。Renameリファクタリングを使えば、安全性が確保される。
より具体的に!
このフェーズで失敗しがちなのは、名前を汎用的なものにしてしまうことだ。具体的なものが良いよ!
命名の目的は、読み手がコードの詳細を見ることなく、それに付けられた名前を見るだけで、それが何をしているか分かるようにすることだ。具体的で詳細な知識が読み手には必要だ。したがって、名前はより具体的なものでなくてはならない。
例えば、私の例で関数名をhandleFlightInfoSomehow()にすることもできる。実直なものかもしれない。だが、システムの非常に重要な部分に対してなされた、潜在的に危険なアクションを隠してしまっている。doSomethingEvilToTheDatabaseの方が、そのリスクに関してより具体的である。
少し先に目を向けると、コードが実行しているすべてのことを名前が伝えるようにすることが次のステップである(Honest and Complete)。ここで具体的にしておくと、完全性への道が、名前に付け加えていくことになる。精度はそのままに、記述を増やしていくことになる。
私があまりにも汎用的な考えをしたり、不正確だったりしたら、関数が行うことのほとんどまたは全ては、すでに名前で大まかに説明できてると考えてしまう。そうすると、まだ名前では伝えきれていないことが見えにくくなり、その背後にあるコードを完全に説明した名前を作るのが難しくなる。
関数やNonsenseな名前だけでなく
このステップはクラスや変数に名前を付けるのにも適用される。一般的にクラスのNonsenseな名前というのは、-Managerや-Operationで終わるものだ。Nonsense変数にはたいてい、型と同じ名前が付けられている。
いずれにしても、このことについて、考えておくことがある。クラスの重要な責務は何か? 変数の場合は、このFooインスタンスは、他のFooインスタンスと何が違うのか? 変数を使ったコードが、この1つをどう考えるのか? 気づいたことは何であれ、その名前として書き留めておこう。
HonestからHonest and Completeへ
このメソッドがしている全てを全部表す名前にする必要がある。より多くの気付きを含むように名前を付けて、次に読む人がより簡単にコードを読めるようにしておくのだ。
このレベルでは次に読む人がメソッドの中身を読まなくても、信頼できる名前で中身が分かるようにすることが、このステップでの目的である。このHonest and Completeは難しいので、1ステップでやらなきゃいけない、というものじゃなく、段階を踏んでやっていこう。
分かっていることを広げる: もう1つ、メソッドやクラスがしていることを見つけたらそれを名前に追加する。
分かっていない範囲を狭める: 分かっていない具体的なことを1つ見つけ、名前の_AndStuffの部分に追加する。
見るべきところ: 名前を付けるものの中身
分かっていることを広げる
名前を実直にするための繰り返し作業の最初の選択肢は、単純にもう一つの新たな役割を見つけることだ。
Part1: コード本体の中身を見よう
もうお分かりだと思うが、段落や制御構造を探すとよいだろう。
Part2: コードがやっていることが名前に含まれていないものを探す
1. コードがしている1つのこと(作用、計算、結果、状態の変更)を探す。
2. 既に付いてる名前に含まれていないかを確認する。
3. 含まれていなければ、その名前を追加する。
注意: 名前の一部を広くしすぎないように。目的はメソッドがやっていること全てを表現できるような1つの抽象概念を見つけることではない。目的は正確さだ。読み手はメソッド名を見て、メソッドがしていることを正確に知れるようにするべきだ。
Part3: 名前に付け加えよう
命名規約に関して教わったことはみな忘れよう。このステップでは長い名前は良いことだ。接続詞を使ってもよいし、繰り返しがあってもよい。名前に全てのことが表現されることが目的なのだから。したがって、誰もが名前を信頼できるようになる。
メソッド名を誰かにメールで送ることを考えてみよう。あくまでも名前だけだ。メソッドの中でやっている全てのことを正確に生成できるだろうか? 何かを付け加えても欠いてもいけない。
名前の第2セクション(真ん中)の目的は、既知のものをシンプルに表すことだ。前ステップで、「probably_」は不確実なことが残っていることをチームに示すフラグだった、ということを思い出してほしい。そしてサフィックス「_AndStuff」に含むわからないことを調査し、中央のパートに全てをリストアップして記録するのだ。
分からないことを狭める
第2の選択肢は、分かっていないことを具体的に1つ見つけ出すことだ。これも3つのパートから成る。
Part1: コード本体を見るか、名前を見るか
コードの中身を見るなら、まだ解析していないデータやパラメータに注目しよう。そうすれば、未知数の集合に名前を与えられる。
もう1つ次のような戦術も考えられる。
1. メソッド名を見る
2. 名前の未知のパートから絞り込みたいものを1つ選ぶ。
3. メソッド本体でそのパートに関連する部分を調査する。
Part2: コードがしてないことのカテゴリを調査する
これはタイトルが示すように、コードがしないことのカテゴリを探すだけでなく、コードがすることの部分的な真実を探すことになる。
してないこと
_AndDoSomethingWIthGraphicsContextAndStuff() という名前があるとする。調査の結果、このメソッドがGCに書き込んだり、なにか描画することはない、Read Onlyであると分かったとする。そこで、_AndDoSomethingReadOnlyToGraphicsContextAndStuff() とリネームする。やっていることはまだ分からないが、分からないことは減らせている。
やっていることの部分的な真実
名前に _AndDoSomethingToDatabaseAndStuff() が含まれるとする。コードが書き込みカーソルをオープンするが、読み込みはしないことに気づいたとする。そこで _AndWriteSomethingToDatabaseAndStuff() にリネームする。まだ何が書き込まれているのかは分からないが、部分的な真実を明らかにし、未知の部分を減らすことに成功している。
Part3: 名前に追記する
名前に含まれる「_AndStuff」セクションの目的は、メソッドの未知の挙動を記録し、定量化することだ。そのために、最初は_AndStuffとしておき、Honestな名前からCompletely Honestな名前に移行する際に、_AndStuffよりも高い解像度でそれを表現する。
Completely Honestに向けたイテレーションを終了させる
Completely Honestな名前は、Honestな名前の中間セクションのみを含むことになる。
3番めのセクションを無くすには、未知の部分をなくす必要がある。そのメソッドやクラスが、付けられた名前以上のことを何もしていないことが事実として分かるように読みやすいものにする必要があるだろう。たいていは、影響を受けるデータを全て特定し、第3セクションにより解像度の高い名前を加えていくのが最も簡単だろう。すべてのデータを追跡できたら、他に影響がないことが分かるので、一般的な句の_AndStuff()を削除できる。あとは、3番めのセクションがなくなるまで、不明な部分を1つずつ追いかけていけばよいのだ。
最初のセクションを取り除くには、名前の残りの部分に確信を持つ必要がある。probably_は、まだ確信が持てていないことを表現している。完全に自信を持つには、2つのことが必要だ。まず、このメソッドが中央のセクションで説明した通りのことを行うかを確認するテストが要る。もう1つは、メソッドやクラスが読みやすいものであることで、すべての未知の部分が第3のセクションの範疇になっていることを確認できることだ。この2つが成立すれば、probably_を取り除くことができる。だが、その後コードを編集する人は、自分の命名手順に自信がなければ、probably_を再び付け加えることになるだろう。そのため、probably_はよくステップの最後に行うことになる。
本当に大きなこと
場合によっては、サブコンポーネントを理解するために、サブコンポーネントを切り出して、より適切な名前を付ける必要がある。これは特に長いメソッドでよく行われる。これをやる際には、メインの名前に集中するようにしよう。メインのやっていることに名前を付けることが重要かどうか分かるまで、塊を抽出し、気付きを記録する。
ガード条件は良い例だ。通常は簡単に識別できるが、スペースを大量に消費する。それを名前で使うことはしないが、後の部分が読みにくくなることがある。
ガード条件と思われるものをいくつか抽出して、抽出されたコンポーネントをHonest and Completeなものにして、本当に全てがガード条件かどうかを確認してみよう。もしそうであったら、それらはそのままにしておいて、元のメソッドに戻ろう。
ガード条件以外になるまで、抽出を続ける。残ったメソッドの中をよこり深く追って、すべてをそのメソッドの名前に入れるようにする。作業が完了したら、ガード条件をインライン化するか、_ValidateAllInputs()のような名前で抽出したメソッドとして残しておくかを決めよう。
この例では、メソッドが行っているすべてのことを見つけるために、いくつかのステップを踏む必要があった。私は最終的に4つの主要な機能を特定したが、それぞれが多くのステップで行われていた。そのため私のメソッドは、parseXmlAndStoreFlightToDatabaseAndLocalCacheAndBeginBackgroundProcessing()という名前になった。
メソッドではないもの
このステップは変数やクラス名にも適用される。
クラスはすべての個々の責務を名前に含まなくてはならない。
変数はすべての変数の使用用途を名前に含まなくてはならない。
レガシーコードでは、変数は驚くほど色んな使い方をされているかもしれない。私はかつてイン・アウトに使われ過ぎているパラメータに、
searchHintThenBestMatchSoFarUntilItBecomesReturnValueOrReasonNoMatchWasFound
と名付けたこともある。
「何であるか」ではなく「何をしているか」でネーミングしよう
このステップでは、名付けようとしているものの重要な特徴をあらわす全てが、名前に含まれる。それが何であるかではなく、それが何をするのかによって命名している。この違いは神クラスと長いメソッドを排除するために重要だ。
あるものが、それが何であるかによって命名するとき、それは同一性になんとなく関連のありそうなものを全部含みたくなる。一方、それが何をするかによって命名するとき、短い名前を好む私たちは、命名するものがやることを少なくするモチベーションが産まれる。
もし機能を集めて大きなものが欲しいのであれば、「それが何であるか」で命名しよう。分割し小さくしたいのであれば、「それが何をするか」で命名しよう。
これはWhole Valueパターンの反対になる。コードにPrimitive Obsessionがある場合、すなわちコードが行うことのすべての部分が、コードベースの周りに散らばっていて、それを収集したい場合にはWhole Valueを使う。何かを作り、それに基づく名前を付けると、プログラマは自然とそれら全てを集めてしまう。神クラスや長いメソッドは、それを分割したいと思う。それが何をしてるかで命名すれば、プログラマは自然とクラスやメソッドを分割するようになるものだ。
Honest and CompleteからDoes the Right Thingへ
Completely Honestなネーミングが出来たことをお祝いいたします! コードが何をするのかを正確に表現することは大きな一歩だ。これまでの全てのステップは、名前に情報を集めることだった。名前を完全なものにすると、その実装の詳細を読まなくても、クラスやメソッド、変数の責務を推測できるようになる。これによって責務の変更の準備が整った。名前を使って、コードを整理していこう。
Part1: 名前だけに着目する
このステップでは、作業対象の名前のみに着目し、呼んでいるコードやコード本体は無視する。「この名前には意味がある?」ということが、ここでの関心事だ。
Part2: 単一責務に分割する
分離する責務を見つける最も簡単な方法は、排除したい名前の一部を探すことだ。通常、排除したい部分は2通りある。それらを見つけるための質問を以下に示す。
この部分は名前に含まれる他の節と無関係だろうか?
この部分はカプセル化したい関心事だろうか?
Part3: 構造的リファクタリングを行う
それを書き出すには、そのものの1つのふるまいを抽出したり、カプセル化するという、構造的なリファクタリングが要求される。そしてHonest and Completeな名前をキープする必要がある。もし名前が気に入らないなら、別の名前を使えるように、そのもののふるまいを変えなければならない。
「構造的リファクタリング」をもっと具体的に言えない?
コードパターンやデスティネーションパターンは何百種類もあり、それぞれが異なるリファクタリングを要求している。
特によくある目標は、1) メソッドから安全に責務を切り出すこと、2) カプセル化すること、の2つだ。Code by Refactoringは、Fix Complex Methods Changes Seriesでこの2つの目標を解決している。この一連の習慣は、複雑なメソッドを同一のふるまいをする新しいシステムにリファクタリングするのに役立つが、生前とした読みやすい方法だ。
Fix Complex Methodsで習慣を身に着けた開発者は、ある事例に対して、次のようなアプローチで対応することがある。
メソッドから責務を安全に切り出す
メソッドから、ある責務を切り出したいとき、私は次の手順にしたがってやるようにしている。
1. 切り出したい部分の前にある全てをメソッドとして抽出する。
2. 切り出したい部分をメソッドとして抽出する。
3. 切り出したい部分の後ろにある全てをメソッドとして抽出する。
4. メソッドの名前を、抽出した3つのメソッドそれぞれに分配する。
5. 名前を完全なものにするために必要な、欠けている節を追加する。例えば、分割によって、ある計算をどこかに書き出す部分から切り離せるかもしれない。外部のメソッドは、...AndRecordYield...()と名付けられるし、内部の名前は、...AndCalculateYield...()と...AndRecordYieldToService()と名付けることができる。メソッドが小さくなるので、名前を限定的にする方法がわかりやすくなる。
6. これですべての呼び出し元が3つのメソッドを直接使うようになり、機能が分割された。
カプセル化
もう1つのよくあるのは、何かカプセル化したいケースである。このとき、名前から節を消すだけでなく、実際には呼び出し側が影響を受けないように、動作をカプセル化する必要がある。問題はふるまいがパラメータによって評価されることだ。
その一例がparseXmlAndStoreFlightToDatabaseAndLocalCacheAndBeginBackgroundProcessing()でのローカルキャッシュの扱いだ。このキャッシュをカプセル化して、性能のためだけにリードスルーキャッシュとして使いたいとしよう。
この問題を解決するには、リファクタリングできそうな一連の処理を見つけて、パラメータをフィールドに移すことだ。場合によっては、新たにクラスを作成したり、クラスを分割したり、使っているメソッドを別のクラスに移動したりする必要がある。また、オブジェクトの生存期間を変更したり、メソッドのすべての呼び出し元からの値への参照を消したりするのを含むこともある。
こうするためには、メソッドがパラメータではなく、フィールドを介して全ての値を受け取るようにリファクタリングし、オートフィクスを使って、未使用のパラメータを削除し、コールスタックを登って、出来る限り未使用のパラメータを削除し続けることが、もっとも簡単な方法となる。
ここに、staticなメソッドから始めるときに使う一般的な手順を示す。
1. パラメータオブジェクトを導入する。カプセル化したい1つのパラメータを選び、クラスFoo
2. staticメソッドをインスタンスメソッドに変える。導入したパラメータを選択する。
3. クラス名Fooを、少なくともHonestレベルの名前に改善する。
4. そのメソッドを呼び出している箇所に行き、新しい型の生成をする。そのパラメータを呼び出し元の呼び出し元にまで押し上げる。
5. カプセル化するパラメータの使い方を、新しいクラスのフィールドを使うように変更する。
6. 使っているところが無くなったら、未使用のパラメータを削除するためにオートフィクスを使って、今カプセル化されたフィールドを削除する。
7. 呼び出し元のメソッドでpublicフィールドを使っているところを探し、関連する文や式をメソッドに切り出す。
8. 抽出したメソッドをstaticに変換し、それを新しい型に移動するためにインスタンスメソッドに変換する。
9. 7〜8をカプセル化したフィールドを使う呼び出しメソッドがなくなるまで繰り返す。
10. このメソッドの呼び出し元へとスタックを登り、ステップ4から繰り返す。値の初期化まで到達したら止める。
11. フェッチや生成を新しい型のファクトリメソッドに移す。(ExtractやMake Static, Convert to Instanceを使う)
12. 元のメソッドの他の呼び出し元に対して、ステップ4から繰り返す。
13. この時点で、カプセル化する値への参照は、新しいクラス内だけになる。この値をとるコンストラクタの利用者は、ファクトリメソッドまたは他のコンストラクタだけである。
14. コンストラクタをprivateにする。
15. publicプロパティをインライン化して、カプセル化した値にアクセスするようにする。これですべてのクラスメソッドが、フィールドを使うようになる。
この手順は長いが、適切なツールを使えば各ステップを1〜2秒でこなせる。自分でコードを編集する必要はない。よく使われている値でも、練習すれば2〜10分でカプセル化できるようになる。
複数の関連する値を一度にカプセル化することもできる。またクラスを分割したり、似たケースを扱いたいときも、この応用でいけるようになる。
クラスと変数
クラスをDoes the Right Thingへリファクタリングするには、1回以上のSplit Classのリファクタリングを実行することになる。それぞれの分割で1つの責務を切り出す。
変数のリファクタリングでは、新しいローカル変数の導入しどれか1つをメソッドで使うように変更する。
Does the Right ThingからIntentへ
クラスやメソッド、変数がしていることにフォーカスできるところまで、私たちのネーミングは改善されてきた。実直な名前にし、正確にやっていることを表すようになった。名前は正しく明瞭なものになった。
だが、不格好である。。
この名前を使ってメソッドの呼び方を理解可能にしたいのだが、その目的はまだ果たせては居ない。名前は「やっていること」を表していて、なぜ注意しなきゃいけないのかが分からない。
これでは呼び出し元のコードを読むときにつまずいてしまう。各サブコンポーネントの目的を示す名前が必要だ。だが現時点では、名前によって何をしているかは分かる。
呼び出し元のコードを読むときはいつも、その実装が意図したものであるかチェックしなければならない。これには精神的なエネルギーを要する。
私たちは気付きを得るための対象を見極めるところまで来た。そのコンテキストからの気付きを含める必要がある。
見るべきところ: 呼び出しているメソッドや使っている場所
使われている場所全部を読もう。他のクラスやメソッド呼び出しの流れでその果たす役割を理解しよう。メソッドの前後で何が起きるのか?
おそらく、この名前の周辺にある他の名前もイケてないと思われる。気付きを得る前に、それらも改善する必要がある。他の部分がHonestであれば気付きが得られることもあるが、少なくともHonest and Completeであった方が良い。
Honest and Completeレベルで、コンテキストのすべての項目を見ようとすると、その対象が実はDoes the Right Thingでないと分かるかもしれない。あまりないケースかもしれないが、そうだったとしたら修正しよう。
使っているコンテキストを調べると、ある流れが見えてくる。その対象は大きなオーケストレーションの中の1ステップだろう。そのステップには目的がある。この目的は、より抽象度の高いレベルにあることが多い。
気付き: コードの存在の目的。大きなオーケストレーションで使われているのはなぜ?
「やっていること」から「目的」を言い表すように名前を変えよう。よくある目的とは、以下のようなものだ。
メソッド
最初の状態やプロセスに関係なく、それが何を達成するのか (事後条件)
始めや終わりの状態に関係なく、それがやっている変換
それを置き換えるビジネスプロセス
メソッドの中心となる責務
クラス
メソッドたちに共通の責務
現実世界におけるクラスとはなんであるか (Whole Value)
呼び出すコンテキストがクラスに委譲し、クラスが果たす責務。呼び出し側が無視したいものは何か?
変数
このインスタンスが同じ型の他のインスタンスと違うのは何か?
このインスタンスでコンテキストが持つ目的とは何か? それはどう使われるのか?
書きとめておくこと: 目的に表す新しい名前
私たちの例では、storeFlightToDatabaseAndStartProcessing() は、 beginTrackingFlight() になる。最初の名前は、メソッドが何をするかが分かる。後の名前は、そのメソッドの目的が分かるようになっている。
私たちはフライトの追跡に何が必要かを知る必要はない。私たちはこのメソッドが追跡プロセスを始めることになる、ということを知る必要はある。
近くにstopTrackingFlight()があることを期待するだろうし、追跡されているフライトについての情報を得るためにいくつか検索クエリのメソッドもあるかもしれない。
別のところでもやってみる
別の呼び出しコンテキストでこのステップを繰り返してみよう。同じ気付きを得るかもしれないし、別のコンテキストだと付けた名前がしっくりこないかもしれない。複数の呼び出し元で同じコードを、目的が何かではなく、そのメソッドを呼ぶと何が起こるかとして使っているかもしれない。
こうなると、実装を共有する2つのメソッドを作ったほうがよい。異なるものを意図した表現は、異なるものとして外側の世界に見えたほうが良い。コンピュータに対しては、「今のところ」同じことをしているかもしれないけど。
実装を共有するメソッドのリファクタリング
次の手順を行う。
1. メソッドの全体を別のメソッドとして切り出す。
2. 切り出した(内部用になる)メソッドを、以前使っていたDoes the Right Thingな名前に変更する。
3. 外側のメソッドをコピーし、その名前を確実に一意になるように編集する。(ここではNonsenseでよい)
4. Nonsenseメソッドの名前を新しい目的に変更する。
5. 新たな目的でメソッドを使うように、呼び出し側を修正する。
一旦Nonsenseにしてから、手作業で名前を変更するのは、ツールがエラーをキャッチできるようにするためだ。普段は手動でコードを編集するのは危険なので避けるようにしているのだが。
名前が被ったり、別の名前を付けてしまったりすることがある。特にインポートされたネームスペースを持つ継承された関数やグローバル関数があると、その可能性が高まる。だが、これらは全部リネームリファクタリングツールが検出すべき問題だ。独自のNonsenseなものに手作業で変えることで、間違いを犯す可能性を減らしている。その後、名前を最終的なものに変えることによって、その一時的な名前が残ったままにならないことを保証している。
実装を共有するクラスのリファクタリング
クラスの場合は、使っているIDEがSplit Classをどうやるかに依存する。
もし、Split Classが委譲をサポートするのであれば、以下のようになる。
1. Split Classして、すべてを選択する。古い型を使っている
2. 内部の型の名前を、Does the Right Thingの名前に変更する。
3. 外部の型のためにファイルをコピーする (ファイル単位のクラスを想定)
4. コピーしたファイルのクラス名とコンストラクタを、一意性が保たれるように手動で変更する。ここではNonsenseなものが良い。
5. コピーしたクラスの名前を新たに目的を表すものに変更する。
6. コンストラクタを使っている箇所を、新しいクラスの方をインスタンス生成するように修正する。
7. コンパイルエラーに従い、毎回オートフィクスを適用して、変数の型を変更する。
Split Classが移譲をサポートしなければ以下の手順だ。
1. 新しいクラスをNonsenseな一意な名前で作る。古い型のprivateな1つのフィールドと古い型のコンストラクタを持つ。以上するメッンバを作るために、auto generateを使う。
2. コンフリクトを起こさないために、古いクラスをDo the Right Thingな名前にリネームする。
3. リネームをundoする。
4. 外側のクラスの名前を内部クラスの名前に手動で編集する。
5. 内部クラスをDoes the Right Thingの名前に手動で編集する。
6. 正しい型を使うように、外側のクラスのフィールドの型と構造を更新する。
7. この時点で、すべての呼び出し側は、目的に基づく名前を介して外部クラスを使うようになる。
8. 移譲をサポートするSplit Classを前提とした手順の3を参照。
切り出したものに新しい名前を導入したいが、すべての利用者には外部用のものを使わせたい。内部用のものを切り出せない場合は、shim(くさび)として新らに外向けのものを作り、名前を手動で編集して配置することで同じ結果を得ることができる。
また、一般的なリファクタリングのテスト戦略も使う。リファクタリングの最も役に立つ部分は、コードを変更することではなく、どのようなコードの変更が安全であるかを示す子だ。場合によっては、リファクタリングが意図したとおりに実行されないことがあるが、その分析を利用して、実行したいことが安全であることが確認できる。
Renameリファクタリングを実行し、Unduすると、名前の変更を手動で実行して”くさび”を配置できる場合でも、新しい名前でコンフリクトが起こるかどうかをテストできる。
注意: Nonsenseに逆戻りしないように
このステップは失敗しやすいところだ。失敗するとNonsenseな名前になってしまう。そして、それに気付かない。コンテキストが整っている名付け直後であれば、意味のある名前だと感じるかもしれないが、次に使おうとしたときにはNonsenseかもしれない。
問題はこのステップが意図的に情報を減らすことに起因する。何をするかについての情報の一部を、なぜそれを使うのか、その目的に置き換えているからだ。私たちはそのクラス/メソッドの潜在的な利用者に、信頼してほしいとお願いしていることになる。信頼を得なければならないのだ。それには、一貫して明確な名前とDoes the Right Thingsなコードが必要だ。
Nonsenseになる方法
なぜ使われるかではなく、いつ使われるかを表す名前を付けるとNonsenseなものになりやすい。初期条件で名前を付けても同じくNonsenseになりやすい。メソッド名では初期条件を無視しよう。そうしないと抽象概念が壊れて終わる。
他では決して使われない新しい概念を作り出すと、Nonsenseになりやすい。単発の抽象化はノイズでしかない。多くの名付けられたエンティティを参照し、抽象概念を作り一貫してそれを使わなければならない。
コンピュータ・サイエンスの用語を使うのはNonsenseなものになる最良の名前付けだ。プログラミング言語を書かない人には、ソフトウェアエンジニアの領域の名前は馴染みがない。だがソフトウェアエンジニアとしても、ドメイン固有の言語の方が読みやすいはずだ。
Nonsenseを避けるためには、コンテキストに特有なものになるようにしなくてはならない。そのクラス/メソッドがやることが、なぜそれをやりたいと思う人がいるのか、その理由を明確に名前に表現する。Honest and Completeの名前のうち、より明確にしてくれるものがあれば残しておいてもよい。残りは捨てよう。
IntentからDomain Abstractionへ
ようやく、ネーミングにおける最重要なステップへの準備が整った。進化的設計のすべてが「良い名前を付けよう。継続的に。」ということに行き着く理由が、このステップにある。ドメイン抽象化を正しくし、名前付けを調整する時がきた。
今、私たちは一緒に使われている名前の集合を手にしている。それぞれ個別に意図を表現している。責務が正しい場所に配置されている。だが、これらの名前を総合すると、アイデアの寄せ集めにすぎない。それぞれが自分自身とそのコンテキストを、うまく表現しているが、共有コンテキストは形成されない。
ドメイン抽象化とは、あるコードの集合のための共有コンテキストにすぎない
この時点では、私たちの探し求めている気付きは、全体の価値である。生成ドメインパターンを見つけたい。一度設定したら、すでに書かれているコード同様に、足りないコードが明らかになる概念である。
足りないコードはまだ書く必要はない(ドメイン抽象化を誤るかもしれないので)が、このコードをどこにどのように追加すればよいのかは、明確な状態のままにしておく。
このステップで行う基本的なプロセスは、「基本データ型への執着」を見つけ、欠けているValue Object(Whole Value)に関する気付きをえて、それを記録することである。これを機械的に行えば、統一されたコンセプトがまだ見えてないコードについても、気付きをえることができるようになる。
機械的に「基本データ型への執着」を探す
見るべきところ: いくつか共通点をもつメソッドやクラスの集合
探すべき共通のパターンを示す。
クラスの集合に関して:
クラスの外との相互依存している
1つのクラスでいくつかのメソッド
似た名前を持つクラス
Thing / ThingIsValid
接頭辞/接尾辞が繰り返されている。〜Transform,〜Traversal,Tree〜, Tail〜
寄せ集めの名前のクラス
〜Manager
〜Transform
名前が〜erで終わる。結果やオブジェクトよりもアクションを意味している。
Feature Envy: 自分よりも他のオブジェクトのメソッドやプロパティに多くアクセスしている
メソッドの集合に関して:
複数のメソッドに対して、一緒に渡される複数のパラメータ
毎度同じ組のオブジェクトのフィールドを使うメソッドの集合
似た名前をもつメソッド
begin / end / see progress / poll for data
create / read / update /destroy
source / sink / transform
類義語を含む名前
同じデータに対する多数の操作。
一緒に呼ばれるメソッド (特にエラー処理や生成ロジックで一般的)
システム内で一致する名詞がない名前の定型句
フィールドとパラメータ
変数の型名ではなく、変数の名前でそれが何であるかを狭める
firstName: String
lastName: String
socialSecurityNumber: String
許容される値を狭めたり解釈が必要となる名前
rangeInMeters: int
hardwareModeFlags: int
これらはそれぞれ、ドメイン抽象化の集合に何かが欠けていることを示している。他のコードは抽象化が欠落しているのを甘やかしている。
w
これらは、より明確で単一の目的を持てるものに、行き過ぎた一般化概念を適用しているコードがあることを示している。
それに関してやること
上記の1つを見れば、何かが欠けていることが分かるだろう。目的は何が欠けているかを理解し、それを作ることだ。これは2つのステップからなる。1つめは、プリミティブに名前を付け、代わりに使うべきものに名前を付けることだ。
気付き: 置き換えられるプリミティブとドメインの概念
コードにおけるプリミティブの名前を変える必要はない。それは置き換えてしまうからだ。代わりにノートやホワイトボードに記録しよう。見つけたプリミティブと、コードがそれに対して固執しているふるまいを正確に記述しよう。
気付きをコードに表現したいことだろう。それには、次のようにして行う必要がある。
1. 固有の概念を一般的な概念から切り離す
2. 甘やかしているコードのそれぞれの塊を調べる
3. そのコンテキストから新しい概念に関する部分を切り離す
4. 新しい概念に関連する部分を移動する
変更を実施する
記録すること: 私たちのドメインへ導入する新たなValueObject(Whole Value)と、それを使うための既存のコードの修正
最初の3ステップは、前のフェーズで使ったリファクタリングを利用する。
1. コンセプトをMissingからNonsenseへと変え、必要に応じて命名レベルをあげる。
1. メソッド: メソッド抽出を使う
2. クラス: クラス抽出を使う
3. パラメータおよびフィールド: パラメータオブジェクトを使う(問題は型だ)
2. Find usagesを使う
3. また分離する。上記参照。
ステップ4では、以下を使う。
メソッド/クラスの移動
インスタンスメソッドへの変換
staticへの変換
(もしC#だったら) 拡張メソッドへの変換
リネーム
リネームの最も一般的な形式は、意図的にコード片を結合することだ。プリミティブなものがシステムでしばらく動き続けていると、複数のコードがそのプリミティブなものに対して、同じ操作を実行したいと考えるようになる。
ステップ3では、これら重複したコード片を小さなメソッドに切り出した。それぞれのコンテキストに基づき、適切な名前(Intentレベルの名前)を付けたのだ。
4では私たちは、
1. 新しいクラスに全てを移動する。
2. 重複しない名前を使い、ふるまいの変更が無いようにする。
3. 同じ実装が近くになるように並べ替える
4. 同じ実装のペアを調べて、意図を共有しているかどうか確認する。(特に欠けた抽象概念を加える場合、すべての重複が同じ目的を持つわけではない)
5. 同じ実装と意図を持つペアを見つけたら、どちらか一方のあまり目立たない方の名前を、もう一方の名前と同じものに変更する。
6. 次に片方を削除し、呼び出し元はコードを共有したままにする。
7. 実装を共有する各ペアが、意図を共有しなくなるまで繰り返す。
を行う。
実装ではなく、意図を共有するペアを見つけることが共通している。これらはバグに繋がるかもしれないし、機能になるかもしれない。
同じ目的を持っているが実装が異なる2つのコード片を見つけたら、以下の手順を実施する。
1. それらをよく似た名前にリネームする。
共通のプレフィクスに違いを表す何かを付け加える
違いは実装であって、意図やドメインに関するものではない。それはスメルであり、以前よりはよい。
後でもう一度名前を付けることで、そのスメルに対処できる。まずこの手順を完了させよう。
2. それぞれについてテストを書く。アサーションメソッドを共有した共通部分と、それぞれ個別のアサーションを使った違いの部分の両方。
3. (オプション) 区分値を使って2つのメソッドを1つにまとめる
最後のステップは、多かれ少なかれ何かを明確にする。コードスメルを隠し修正の可能性を減らすかもしれないが、妥当なドメイン抽象概念へと成長しうる新しい概念を作り出すことになるかもしれない。
やり方の詳細は、私のgist Combine 2methods into one with a discriminator variable を見てほしい。(ここでは、少し異なる2つのCar.Drive()の実装を組み合わせている)
gistの履歴には、安全性を損なうことなく再設計するための一連(17ステップ)のリファクタリングがある。
私は呼び出し側がどうなるかを示すためだけにテストを書いている。各ステップを検証するためにテストに依存しているのではない。
呼び出し側の何千のコードを完全にはテストされてないコードでこれを実行するのも、(安全で)同じくらい快適である。
そして、それは…
私たちはドメイン抽象概念を識別し、書きとめた。これで命名は完了だ。
現実の世界では、私はこのプロセス全体を完了するのに一週間ほどかかった(トータル時間は30分ほど)。Missingの名前を1つずつ見つけて、それらに名前を付け、Honestレベルにあげていく。それを1つの領域で4〜6回ほどやった後、それぞれの名前をDoes the Right Thingにアップグレードする。それを2〜3回繰り返すと、目的がハッキリするので名前をさらにアップグレードできるようになる。
そして、同じ変数を操作するステートメントの基本要素や重複した手続きを頻繁に目にするようになる。あたりの全ての名前がその意図を表すようになると、欠けているWhole Valueが明らかになる。多くの場合、領域全体の意図が明らかになる前に、Intentな命名を2〜5回行うことになるだろう。
新しいドメイン抽象概念を導入する。実際、私はいつも3〜5個の抽象概念を一気に導入している。適用可能なケースの約60%で、新しい抽象概念を使うために、コードはプロジェクト全体で急速に変化する。その後、抽象概念が重要であることが分かってくると、1〜2週間かけてゆっくりと変わっていく。それは新しいオペレーションを引き起こし、他の適用可能なケースをキレイにするのだ。