日々常々

ふつうのプログラマがあたりまえにしたいこと。

「レガシーコードからの脱却」を読みました

レガシーコードからの脱却 ―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス

レガシーコードからの脱却 ―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス

頂きまして、ようやく一通り目を通したので書きます。ありがとうございます。 どこを切り取っても話題の中心に置ける、そう言う本でした。

ざっくりどんな本かと言うと

原著「Beyond Legacy Code」が2015年とそこそこ新し目の本ではありますが、目新しいことは書いていません。おそらく「全く知らない」と言うものはなかったと思います。プラクティス自体は目新しくはないですが、プラクティスの出自や必要とされた背景、実践における戦略などに焦点が置かれた本です。

この本の価値は様々なプラクティスから9つだけピックアップされていること。少ないことに価値があります。数を絞った上で個々の背景をしっかり書いたたものになっています。

タイトルの「脱却」はレガシーコード改善ガイドのような既存のレガシーコードとの戦い方を彷彿とさせますが、帯には「レガシーコードを初めから作らない」と新規プロジェクトの序盤のように取れることが書かれています。実態は レガシーコードを産み育てる土壌がターゲット で、フェーズに関係なく参考にできる内容となっています。

本書2章に「本物の虫と同じように、バグには繁殖に最適な条件がある。」とありましたが、レガシーコードも同じでしょう。土壌改善なので、別に序盤でしかできないものでも、既にレガシーコードが存在していないといけないものでもありません。流石にプラクティス9の「レガシーコードをリファクタリングする」はレガシーコードが存在しないとできませんが。

面白いのが「人が一度に記憶できるのは7プラスマイナス2個」という数へのこだわり。 副題にある「ソフトウェアの寿命を延ばし価値を高める9つのプラクティス」は当然9つなのですが、各プラクティスの「実践しよう」には7つの戦略が2セットぶら下げられています。例えば。

  • 5章 やり方より先に目的、理由、誰のためかを伝える
    • プロダクトオーナーのための7つの戦略
    • より良いストーリーを書くための7つの戦略
  • 6章 小さなバッチで作る
    • ソフトウェア開発を計測する7つの戦略
    • ストーリーを分割する7つの戦略

といった感じ。目次に無い物としては12章に6つの「変更を難しくするプラクティス」があるなど。まあ7±2個覚えられるからといって、9+(7*2)なんて覚えられない訳ですが、ややもすれば10を超えそうな列挙に数の制限がつけられています。(戦略の方は無理矢理増やした感のあるものがないとは言わないけれど。)

たぶん読まなくていい方

新しい知識が欲しい方で、ある程度の読書家な方。たとえば以下の本のほとんどを読了しかつ実践されている方が新しい手段を求めて読むと、イマイチという感想になると思います。

(参考文献に上がっているもののうち私が読んだことがあるもので、内容のリンクが強いと感じたものを選びました。オブジェクト指向入門やデザインパターンも参照されていますが、それらはそこまででもないかなって。)

あと、アジャイルって文字を見るだけアレルギー反応が出るような方も読まなくていいと思います。徹頭徹尾アジャイルラクティスです。

たぶん読んだらよさそうな方

伝聞や表層だけのなんちゃってアジャイルに騙されて(言い方が悪い)上手くいかず、アジャイルを仕事では役に立たないおもちゃのように感じている方。読めば誤解が解ける可能性があります。

知識や実践を通じた理解は十分でも、自分の考えの整理などを改めてしたい方。プラクティスと原則の話あたりを踏まえて、個々のプラクティスや戦略を読んでいくといい整理になるんじゃないでしょうかね。おさえてないところや捉え方の違いなどもあって、いい壁打ちになると思います。

あまり知識がなかったり、経験の浅い方。冒頭で書いたように様々なプラクティスから9つだけをピックアップした、新設の高速道路です。掘り下げの方向は違いますが、サイズや値段的にも達人プログラマーなどの古典・鈍器本に対するリーダブルコードのような印象。鈍器本に抵抗があるなら是非。

蛇足

タイトルを見た時の感想は「またレガシーシリーズか」でしたね。 とはいえ訳者の方々が方々なので期待せざるをえず、そればかりか頂けてしまったのでこうして読んだわけです。

で、読んでみたらこれが面白くて仕方がない。第一部の導入が良すぎて、そのノリで第二部のプラクティスを読むとどこまでも潜っていくことになりました。時間かかるの仕方ない。 本当はもう少し早くブログ書きたかったんですが、いちいち考えちゃってツイートしたりしちゃって、いつ読み終わるかわからない感じになってしまった。いやページ数の割に考えられるネタ多過ぎるんですよ……

あと、Beyondの意味とか一通りみて考えてみたのですが、脱却以上にしっくりくるものは思いつかず……レガシーコードを超えて?レガシーコードの向こう側に??うーむ、やっぱ脱却が合ってそうだなぁ。

いくつかは私のこれまでのブログやセッションで話したことがほぼそのまま(少なくとも私にはそう見える)書かれていたり、今書いてる途中のブログとネタ被りしてるものとかもあって(近いうちに公開)。参考文献の傾向とか見ればそれはそうなんだろうって感じではあるんですが、なんか勝手にシンパシー感じたりしてました。

とりあえずもう一回読みます。

プルリクエストとマージリクエストと。

short answer

プルリクエストとマージリクエストに違いはありません。 同等の機能が別の呼び方をされているだけです。

名前の由来を考えてみる

名前の由来を考えてみるのも面白いと思うんです。 ここに書いているのは私の予想ですが、大外しはしていないはず。 仮に違ったとしても別に良くて、名前の由来を考えてみることは、名付けのいい訓練になります。なるはず。たぶん。

f:id:irof:20190929195255p:plain
こんな感じ。たぶんpullは使われてない。

プルリクエスト、通称プルリクは元々GitHubの言葉です。Gitの機能ではありません。 テキストではPRと略されることもあるけど、ピーアールって読むと何を宣伝したいんだろう(Public Relationsとは意味変わっちゃってますね)ってなるからか、発音はプルリクなことが多いと思います。

Gitのpullfetch+mergeなのだけど、このGitHubでForkしたリポジトリがフォーク元に対してpullを要求するのがプルリクエストと言う名付けの元。ちなみにForkはGitの機能でもGitHubの造語でもなく、元々OSS界隈で使われてた言葉。

GitHubは重々しく考えられていたForkを軽くできることに注力して、そのためにForkしたリポジトリからのフィードバックを扱いやすくすることを重視した結果、プルリクエストと言う名前を全面に出すことにしたんだと思います。 pullがボタン一発でできるならForkもしやすい……そんな感じでしょうかね。 なんのためにForkを軽くしたかったか、とかも妄想していくのも楽しいです。プルリクエストって名前からボトムアップでビジョンにたどり着けるかって感じで。

で、pullfetchは裏で走れば十分で、ユーザーに重視されるのはmergeの方。 単なるmergeをプルリクエストで扱うのは機能的に問題ないし、ユーザーとしても分ける必要はありません。 なので同じリポジトリ内の単なるmergeもプルリクエストで扱われています。

f:id:irof:20190929185249p:plain
Gitの文脈で捉えるとmergeなの?pullなの?ってなりそうなボタン

GitLabだとマージリクエスト。これはForkが重視されていないからだと思う。 fetchが要らないなら機能的にはこれが正しいと思います。 機能を重視してのネーミングだとしたら、fetchが必要なものもマージリクエストで扱うので、これはこれでどうなんだろうとなります。 操作として区別する必要のない 歴史的経緯は知りませんが(調べたらわかる話ですが)、Forkを後からできるようにしたのか、元々Forkを重視していなかった、とかでしょうかね。

Backlogだとプルリクエスト。 BacklogはクローズなリポジトリでそもそもForkは扱わないけれど、これは機能よりもよく知られてる言葉を採用したんだと思います。 マージリクエストよりもプルリクエストの方が知名度はありますしね。

他のGitサーバー(って呼び方でいいのかな)も、作成された時期や思想によってどちらかの名前がついていると思います。他の名前もあるのかもしれませんが、知りません。patchを押し出した名前があっても不思議ではないんだけど。

同等の機能に異なる名前がついているのは面白いのですが、混乱している方もいらっしゃるわけで、自身の知っている方の言葉で押し通している方(GitLabを使ってるのにプルリクと言い続けるとか)もあり、なんと言うか。そこまで悪影響を与えるものではないのですが、名前を重視する派を自称する身としてはちょっと悩ましいところですね。

どうでもいいんですが、マージリクエストがマジリクと略されてるのはみたことはありません。 マジなリクエストとか面白そうなんだけど、意味はわからないので使われなくていいと思います。 なおMRと略されることもありますが、この略称はMedical Representativeが最初に出てきちゃってちょっと戸惑ってしまう私です。

pullだのmergeだのってそもそもなんだって方へ

レビューさせてもらった本です。

finalを付けるのをやめてみた

Javaの話ね。バージョンは8以降の実質的final(effectively final)があるものとします。7以前は匿名クラス(この呼び方は 匿名クラスとかローカルクラスとか参照)でローカル変数を使うにはfinalが必要なので文脈変わります。

前提の整理

final は色々なところにつけられます。 例えばこんな感じ。

final class FooClass {

    final Object barField = new Object();

    final void bazMethod(final Object quxParameter) {
        final Object corgeLocalVariable;
    }
}

このエントリで対象にするのは変数。フィールド barField 、パラメータ quxParameter、ローカル変数 corgeLocalVariable です。

以下を前提にします。

  • 自分たちだけが使用するコードです。不特定多数から使用されるライブラリなどではありません。
  • クラスやメソッドの final は対象外です。意味も違うし。
  • アクセス修飾子も今回は存在自体を忘れてください。
  • XXX(某JVM言語や某ライブラリ)使えば? は堪えてください。言いたくなる気持ちはわかるつもりですが、そっちに脱線すると帰ってこれなくなりますので。
  • Java 11の var も別の話。

final をつける意義と実際

再代入不可になり、コンパイルレベルでチェックしてくれます。これにより再代入が原因となる不具合が防げます。

再代入を許容すると状態が変わるため「この時点ではどうなるんだっけ?」と悩むことになります。 無用な状態遷移に悩まされないように final をつけることを推奨するコーディング規約や、静的解析ツールも存在します。

で、実際のとこどうなのよと言うと、こんな感じになります。

  • フィールド: 可能なら final にする。
  • パラメータ: まちまち。 周囲が付けてたら合わせて付ける。
  • ローカル変数: わざわざ付けない。そう言う規約なら従う。けど付いてるのをわざわざ外したりもしない。

新しくプロジェクトに参画して、借りてきた猫モードしてる時はだいたいこうします。

フィールドを final にするのはイミュータブルが好ましいと言う最近の傾向や、完全コンストラクタに対応するバインディングライブラリなどの後押しもあります。 こんなクラスですね。

// 完全コンストラクタの例(イミュータブルの例ではない)
class Hoge {

    final Object field;

    Hoge(Object field) {
        this.field = field;
    }
}

フィールドはわかりやすいのですが、パラメータやローカル変数は様々です。 「パラメータは絶対上書きなんてしてはいけない。だから final を付ける。」と言うのは、前半は完全に同意。後半もまあいいかな、と言う感じです。付ける派のほうが強い意思を持つものなので、わざわざ喧嘩する必要もないかなって。

ローカル変数は、まあ、スコープが広い(数千行に及ぶ)とかなら、付けるのもわかるかも……。

ともかくコンテキストを絞らずに驚き最小の法則を適用するなら、先に挙げたような感じになると思います。

この辺りの話は ふつうのJavaコーディングの初段と四段 あたりが該当します。 で、次が九段の「強い意思でルールを破る」です。

final を付けるのをやめた

最近は final を付けません。

だからと言っても、再代入を推奨するわけではありません。 finalなんて付けなくても、 どうせ再代入なんてしない のです。 あってもなくても一緒ならあるだけノイズです。

もし final によりうっかり再代入してしまうことを防いでるのだとしたら、うっかり再代入をする方が問題です。 うっかり再代入を検出して、なぜ再代入をすべきでないかをチームで共有するべきでしょう。そんなうっかりさんがいたらどんなコード書いてくるか気になって仕方ないので、しばらくペアプロしたい。 検出に静的解析ツールを使ってもいいと思いますが、それは再代入を検出するべきで、finalが付いていないことを対象にはしません。

フィールドが final 付いてないからって再代入するようなのは、それをしちゃう考え方の方が危うい感じがします。 また、再代入を行うフィールドが存在した場合、選択的に final を外すのも手間です。一度外したのを付け直すとかも面倒だし、そう言うときはコンパイルチェックがかからないから付け直し忘れとかが起こりうる。「なんでこれ final 付いてないんだっけ……」のような、答えGitのログにしか無いようなことに悩みたく無いです。

私の観測上、ローカル変数の使い回しが行われる主因は「汎用的な変数名」です。例えば str とか。 適切な名前をつければ使い回しをする気なんて起きません。名付けの問題なのに「 final を付けてないから使い回しちゃうんだ、使い回せないように final をつけるルールにしよう」なんてするのは、臭いものの蓋ではないでしょうか。ルール敷いたところで str1 str2 とかが生まれるだけです。蓋の中でどんどん増殖しちゃう。

また、ローカル変数に final を付ける要因として先に挙げた「スコープが広い」がありますが、これは「狭くしましょう」としか言いようがありません。 変数のスコープを狭くする。わかりやすくはメソッドの分割ですね。十分にスコープが狭ければ、「このローカル変数は再代入されたりしてないだろうか?」と疑う必要はありません。数行手前に目を走らせれば良いだけです。これも final を付けて対策としてはいけないものだと思うのです。

パラメータのfinal 不要も、メソッドが十分小さければ再代入なんてしたらすぐ違和感に気づくはず。 そしてメソッドが十分に小さい、つまり文字数が少ない時に、パラメータやローカル変数に final が付いてたら、意味のない final の全体の文字数に対する割合が大きくなってしまう。

まあそんなわけで、実質的 final 、実質的イミュータブルで十分かなーって感じです。

それでもfinal にする場面

と言うのもあります。

フィールドは、そのクラスのインスタンスを自分の与り知らぬ誰かが使用する場合ですね。 この記事の前提に反しますが、例えばライブラリなどを開発している場合です。 まあ final にしたところで無理矢理変えることはできるんだけど、気休めは気が休まるので重要です。

パラメータやローカル変数は final 付いていようがなかろうがメソッドが十分小さくできてたら要らないと思う。対レガシーコードで手が出せなかったら事故防止のために付けるのを許容する、かも。

あとはチーム規模が大きくて(大きくするなよ)、誰がどんなコード書くかわからない(それもどうなの)……なんて時でしょうか。

まとめ

final を付けるルール(暗黙含む)」によって、本来の問題が隠されてはいませんか? 一回やめてみてもいいのではないでしょうか。私は今の所 final がないことによる問題には遭遇しておらず、コードの見通しがよくなったくらいです。 final を付けることで定数ならコンパイル時にインライン化されるなどのメリットはありますが、そういうのを考慮してもまあ要らないかなって。(とはいえ定数なら final 付けるけど、定数自体最近ほとんど使わないなぁ。)

最後になりますが、デフォルトで final がいいです。わざわざ書きたくない。再代入可能の方にだけ宣言したい。と言う願望で締めとします。

追記: finalを付けるべきだと言う話 (2019-09-28T09:25)

私自身も final を付けるべきだと思ってたりもします。以下のようなのは大いに同意しますし、なんならそれを理由に「 final を付けるべきだ!」と言う役目を負ってもいいくらい。

読み手に変更する意思がないことを明示する

わかります。コードに意図を込めることは重要です。 レビュアーや未来の自分を含め今後変更する際のコードを読む負荷を下げられるなら付けます。 でも「よほどのことがない限り再代入なんてしない」なら、付けるほうが読む量増えるのに情報量が増えないので、やめてみました。

IDEが自動でつけるから書く手間はかからない

わかります。わざわざ final(スペース含め6文字)をタイプすることはないですし、変数宣言をIDEに生成させた時に自動でつくオプションもあります。とても便利。 手間をすっ飛ばして残るのはやはり可読性。「 final をつけることでコードを読む速度が上がるか?」で、大体の場合答えはNO。なので、やめてみました。

ちなみに最近の私は、コードを書く手間は殆ど考慮していません。コードは読み物なんで、書く時の一瞬の負荷に振り回されるべきではないです。 ふつうのJavaコーディングコードをどまんなかになどをご参照ください。

再代入する必要のある変数を目立たせたいというのがあるんですよね……。🍥Reassignable みたいなアノテーションが有ればいいのかな……?

がくぞさんのツイートより。 私が本当に欲しいもの。情報量が増えて読む時間の短縮、使い方も誤りにくくなると期待できます。 まあでも使ってみたら案外役に立たないかもしれないし、導入するほどでもないんですよね……。 言語の標準機能であるならいいんですが、このために他のものを積む気にはならないんです。

まあでもフィールド用で、パラメータやローカル変数には使いませんね。後者はやっぱ小さいメソッドで片付けられると思うのです。

作るプロダクトや組織体制とかを踏まえて、どうするか考えるのがいいと思うのですよ。finalを付けるべきかどうか自体は枝葉に過ぎないかな。