この記事は、10年前に書いた記事なんですが、今年の新人の人たちにも、やってみてほしくて、古いblog記事から掘り起こして転載しています。
linterを使ったコーディングスタイルのチェックだけでは、スパゲッティなコードを直すことができません。 よくできたコードは、パッと見で、”なんか違う”と感じさせるところがあり、あぁ、このコードを書いた人はデキるって思わせるものです。
読みやすくて、きれいなコードを書くためには、どうしたらよいんだろうか?さらには、それを人に教えるには、どうしたら?
そのヒントが「オブジェクト指向エクササイズ」にあります。
この本を読んだときに、"これだ!"と感じたことを覚えています。
以下、10年前に書いた勉強会後記です。
今回の勉強会は「ThoughtWorksアンソロジー」の第5章、「オブジェクト指向エクササイズ」の読み合わせをやりました。
エクササイズは、1000行程度の小さなプロジェクトを準備して、次の9つのルールを厳密に適用させてみるという内容です。ルールを強制することで、いままでの自分流のコーディングとは違ったアプローチを発見することを目的としています。
- ルール1: 1 つのメソッドにつきインデントは1段階までにすること
- ルール2: else 句を使用しないこと
- ルール3: すべてのプリミティブ型と文字列型をラップすること
- ルール4: 1 行につきドットは1 つまでにすること
- ルール5: 名前を省略しないこと
- ルール6: すべてのエンティティを小さくすること
- ルール7: 1 つのクラスにつきインスタンス変数は2 つまでにすること
- ルール8: ファーストクラスコレクションを使用すること
- ルール9: Getter 、Setter 、プロパティを使用しないこと
参加したメンバーには、事前に1000行程度のプログラムを準備するように伝えてあったんですが、読み合わせが終わった時点で時間がなくなってしまい、実際のプログラムのリファクタリングは宿題となりました。残念ながら、エクササイズのビフォーアフターを載せることはできないのですが、ルールの要約と、読み合わせ中に話題となったことを少し書こうと思います。
ルール1
1つのメソッドにつきインデントは1段階までにすること
インデントの深さを抑えることで、メソッドをコンパクトにすることが目的。
メソッドが小さくなると、そのメソッドの再利用性が高まるし、さらに小さなコード片ならバグを発見するのも簡単になる。
さて、読み合わせ中に、こんな話がありました。「これを実際に適用したら、プログラムを追うときに、あちこちに飛ばないといけないので、実際には読みにくいプログラムになるんじゃないか」と。
しかし、この問いは9つのルールを全部適用してみれば、なるほど解決するだろうと思います。小さくなったメソッドの役割はどんどん鮮明になっていき、クラス分割や再配置が行われ、全般的に洗練されると期待できるからです。
これは良いエクササイズになりそうです。
ルール2
else 句を使用しないこと
ネストした条件分岐は、重複したコードを作りやすいので、else 句を使わずにプログラムを作っていくとよい。
else 句の代替策としては、ガード節と早期 return を使うようにする。あるいはポリモフィズムを使う。
これも良いエクササイズになりそうです。
ルール3
すべてのプリミティブ型と文字列型をラップすること
例えば、int型を引数にしたメソッドがあったら、int型ではなくて、クラスでラップすること。
もし、間違った変数をメソッドの引数に指定したとしても、静的型付け言語なら、コンパイラがエラーであることを教えてくれる。
面倒なんですよね。こういうのは、でもやった方がいいです。
ルール4
1行につきドットは1つまでにすること
複数のドットを使っているコードが何行かあれば、責務の配置を間違っている箇所がたくさん見つかる。
責務の配置とは、どのオブジェクトに、どんな仕事をさせるかということで、例えば、開発部のAという人に仕事の指示をするのに、総務部からAに作業依頼している状態はおかしいと言っている。
この状態をプログラムコードにすると、総務部からAにアクセスするために、人事にアクセスしてAを取り出して、Aの抱えている仕事を取り出して、その仕事のステータスを着手に変えて、という風にドットが複数つながると、その兆候が出てくる。(ちょっと例題が無理過ぎてわかり辛くなったかもしれない)
これは、なるほどなぁと 思いました。こういう矯正方法があるんですね。
ルール5
名前を省略しないこと
省略は紛らわしくなるし、もっと重大な問題を隠してしまいがち。他にも名前を省略したくなる理由の1つとして、ここでも責務の配置ミスがあげられている。
正しく配置された責務は、order.ship() のように文脈にピッタリのはずだ。エクササイズでは、すべてのエンティティの名前には、1つか2つの単語だけを使って省略しないことを、より具体的なルールとしている。
そうそう、文脈に無理のないコードというのは、見やすくて良いコードだと思います。そのためにも、ネーミングが重要です。また別の機会に、ネーミングに関する勉強会をしようと思います。
ルール6
すべてのエンティティを小さくすること
50行を超えるクラス、10ファイルを超えるパッケージは作らないというルール。
これは実際には難しいだろうなぁ。パッケージのデザインは決まっていることが多いし、50行以内で1クラスを作るのは難しそうです。
おそらくは、プログラムが洗練されていくと、コードはどんどん短くなっていくから、50行以内にまとめることも可能だということなのでしょう。
ルール7
1つのクラスにつきインスタンス変数は2 つまでにすること
インスタンス変数を2つ以上作りたくなったら、最も重要な1つと、それ以外のグループの2つに分類するとよい。重要な意味を持つインスタンス変数が10個も20個もあると、その変数が、すべての振る舞いの中でどのように変化するのかを、分析するのは大変である。
この章は、first、middle、lastを変数として持つNameクラスが例題に使われています。このNameクラスをリファクタリングして、familyとgivenの2つのインスタンス変数を持つクラスになっているんですが、日本人にはわかりにくい例題だったようです。
勉強会に参加したうちの2人が、この例題が示すことの有効性が感じられないと言っていました。
まぁ例題は置いといて、インスタンスを2つだけに制限したときに、どうなるか興味深いですね。
ルール8
ファーストクラスコレクションを使用すること
javaで言うなら、ListやSet、さらにはMapも。これらを操作する必要があるなら、それは1つのクラスとして独立させて、コレクションを操作する振る舞いに徹すること。
徹するために、このクラスにはコレクション以外の他のメンバー変数を持たせない方がよい。
これも実際にやろうとすると、面倒な場面の方が多いんですが、オブジェクトということを意識づけるための処方箋となりそうです。ついつい怠ってしまうので、ガイドラインで強制するといいですね。
ルール9
Getter 、Setter 、プロパティを使用しないこと
この内容は、ちょっとわかりづらかったです。参加した全員が1回読んだだけでは理解できなかったのですが、欄外の訳注で理解できました。
例えば、クラスAにインスタンス変数 a があり、クラスBに a を2倍にした値が必要だとする。このとき、getterなどによってクラスBで簡単に a の値が取得できるようになっていると、a を2倍にする処理はクラスBにかかれてしまう可能性がある。
やはり、a の値に関する振る舞いは、クラスAに書いた方がよいですね。
まとめ
このルールは、エクササイズのためのものであって、常にこのルールに沿ってプログラムを作成しなさいということではありません。
1度このルールでエクササイズしたら、「ルールを緩めてガイドラインとして使うとよい」と書いてあるので、しばらくは、少し緩まったルールを社内のガイドラインとして、常に意識して仕事することにしようと確認しました。