一つの機能修正、バグ修正に対してコードをいじる範囲、つまりdiffの行数は常に最小であるべきなのか?というお題ですね。
無関係な場所を触りまくって肥大化したPull requestが論外なのは当然です。
そのせいでその挙動に影響があった範囲だけど少しだけいじるようなPull requestが常に理想、と素朴に信じている人が多いように見受けられます。
しかしいじるコードが小さければ小さいほど良いという風潮には僕は反対です(デカければいいって主張はしてないからね)。
コーディングはプロジェクトに対するデザイン行為である
僕はコーディングそのものをデザインだと考えています。
木をみて森をみず、といいますがコードもそのときどきで視界にはいっているロジックや関数単体のみで考えてはいけません。
プロジェクトに含まれるコードは視野にある文字列よりもその外側にあるもののほうが大きいのです。だから常に脳内にあるそれと対話し、追加や修正や全体との調和を考えながらやらなければなりません。
いまの話はあまりに本質的すぎてうまく例にできるようなサンプルが見当たらないのですが、例えば
「ある関数Xに渡している引数aの形式が間違っていた」
というバグがあったとします。
これを修正するのは簡単です。引数aの形式を間違えている場所を開いて正しい形式にして渡せばいい。
ただ本質的な問題は、そもそも形式を間違えたら動作しないような関数になっているのが悪いわけです。こういうのはたいてい引数の型の宣言が弱かったり、引数aの想定してる形状が複雑すぎるのが原因です。
適切な型宣言を使うようにしたり、複数の引数に分解するよう誘導すれば良いのです。
ただしこの修正を取り入れると、その関数Xを使っている箇所全てにdiffが発生します。
本質的に正しい修正のはずでも、いじるコードを最小限にという原則には外れてしまいますね。
もちろん一度挙動の修正のみを行うPull requestを作り、その後リファクタリングタスクとして別のPull requestにするという方法もとれなくはないですが、手続きを煩雑にしているだけでやっていることは同じです。
言語仕様の高級さとコーディングスタイルの関連性
ここまで書いて気がついたことがあります。
僕の主張は最低でも静的な型検査がついている、あるいは結合テストの網羅性が高い状態にある(理想状態ってかんじ)ことを前提にしていました。
例えば僕が普段愛用しているTypeScriptは静的型検査付きとしてはユルいところの多い言語ですが、それでも strict: false
のような超人向き苦難厄憑きモードオプションやanyをそこらじゅうで使う野獣派スタイルコーディングを採用でもしていない限り、各インターフェースの変更は依存箇所全てに通知されます。
そうではない状態、つまりどこか一部分のインターフェースをいじると全然関係ない場所でなんかのロジックがぶっ壊れるようなコードを生み出しやすい言語、フレームワーク……*1で書き続けている場合、予測不可能性に対する防衛術として「コードの修正範囲は最小限にしろ!!」という鉄の掟が生まれるのかもしれないなと予想しました。
まぁ実際グローバルに書きかわっちゃう変数とか、特定のオブジェクトのプロパティを参照してメソッドが生成されまくるようなメタプログラミングとかそういうことをしてると予想外の壊れ方しますから、そういう環境であればいくらテストの網羅性を上げていたとしても予測不能な壊れ方をしてしまいがちなわけで。であれば必要なところ以外いじるんじゃねえというルールは理に適ったものになるわけです。
現実は厳しい。
あるいはGitやマージツール使いこなしの練度
"マージツール"と書きましたが、筆者はめんどくさがりなのでいつも端末上で筋肉式のコンフリクト解消してます。だってめんどいし……困ってないし……
それはともかく、Gitの練度が低い方はコンフリクトを異常に怖がる、避けたがる傾向にある気がします。
実際、度を超えた巨大なPRをマージしたときに生じたロジック的なコンフリクトの根本的解消は目視によるところが大きく、労力も難易度も危険度もマジでハンパねえので避けるべきですが、常識的な範囲のコンフリクトでさえも恐れてそれを避けようとする場合は先の「高級問題」とこのGit練度が影響しているのかもしれない。しれませんね?たぶんね。
結論
プロジェクトの状態、チームの方針に従え。
なんともひよった結論やね。
ちなみに今回のエントリの内容はメンバー間で合意をとれるプロジェクトを前提にしています。
自信がメンテナではないOSSにPRを送る場合、目的に対してdiffは最小限であるほうがたぶんいいんじゃないかなとおもいます。
*1:こう書くとなんだか馬鹿みたいだけど実際には存在しがち