はじめに
本編はこちらから飛んでください
なぜこの記事を書こうと考えたか
「拡張性・品質の良いコードって何?」と思い、
言語化して見ようと考えました :)
この記事で解説すること
この記事を読んだ結果どうなるか
- 実装の際に気をつけると良い観点を身につける
- コードレビューの観点を身につける
- "ちょっと"良いエンジニアになった気になる
関数
小さいこと!
関数の第一原則は、「小さくせよ」。第二原則は、「さらに小さくせよ」。
小さな関数が優れている論文などはありませんが、
異論を唱える方は多くないはずです。
# メール送信風プログラム## メールを送信するために必要な処理を実行する#defmail# Toに入るアドレスをget_to_address関数から取得するto_address=get_to_adress# Fromに入るアドレスをget_from_address関数から取得するfrom_address=get_from_address# 本文に入るテキストをget_text関数から取得するtext=make_text# send関数に変数を渡し、メールを送信するsend(to: to_address,from: from_address,text: text)end# ここから下の関数は同じファイル内から使えませんよという宣言ですprivate## Toを取得する#defget_to_address# Toを取得end## Fromを取得する#defget_from_address# fromを取得end## 本文を取得する#defget_text# textを取得enddefsend(to:,from:,text:)# Utilityという便利なClassがある想定して、# インスタンスメソッドを作成しています:)# sendの引数をsend_mailに入れると送信してくれるUtility.new.send_mail(to,from,text)end
1つのことを言う
Clean codeでは関数について以下のように書かれています。
関数では1つのことを言うようにする。
その1つのことしっかりと行い、それ以外のことを行っては行けない。
ここで大切なことは2つあります。
1. 関数内で1つ以上のことをしていないか
2. 関数名がその1つと一致しているか
1-1.rb
の処理を御覧ください。
1の「関数内で1つ以上のことをしていないか」に関しては、
1つ1つの処理をprivateメソッドへ分割していることで、
「1つ以上のことをしていないか」は守れています。
さて、2の「関数名がその1つと一致しているか」はどうでしょうか?
関数名がmail
となっているので、1つのことかどうかが判断しずらいかもしれません。
では、mail
と同じ処理で関数名がsend_mail
だった場合はどうでしょうか?
send(送信)以外の処理も行っているので、
1つのこと(send mail)とはずれてしまうのでNGでしょう。
この様に、処理は「関数名」と「関数の処理」が1:1の関係となっているのが、
理想の状態となります。
関数の引数
関数の引数は理想的には0です。その次が1つ、続いて2つです。
3つ以上の引数は可能な限り避けるべきです。
関数に引数があることによって、実装詳細を知らなければならないケースが増えてしまいます。
また、引数が増えることによって、「さまざまなケースを網羅するテスト」を行う必要がでてきます。
引数オブジェクト
どうしても引数が2,3を超える必要がある場合は、引数のいくつかをクラスにラップすることも検討できます。
# 普通の書き方makeCircle(x,y,radius)# オブジェクトを渡した場合center=Center.new(x,y)makeCircle(center,radius)
クラス
クラスの第一も関数と同じく、「小さくせよ」です。
第二、第三も同じ様に「更に小さくせよ」「更に更に小さくせよ」と続いていきます。
しかし、関数と同じ文章を繰り返す訳では有りません。
まずは「どうやって小さくするか?」を考えて行きます。
小さくするには?
関数を小さくする際は物理的な「行数」に注目しました。
クラスの場合は何に注目すると良いのでしょう?
それは「責務」です。
# 悪い例classSuperDashboarddefgetLastFocusedComponent# 処理enddefsetLastFocused(lastFocused)# 処理enddefgetMajorVersionNumber# 処理enddefgetMinorVersionNumber# 処理enddefgetBuildNumber# 処理endend
一見5メソッドなら責務が多すぎるとは言えないさそうに見えます。
しかし、この場合、メソッド数は少ないのですが、「責務」は多すぎます。
クラス名は、そのクラスの責務を表すべきです。
実際に名前付けがクラスのサイズ(責務)の拠り所になります。
クラス名が曖昧なほど、その責務が大きくなりがちです。
SuperDashboardクラスは、「最後のフォーカスを保持していたコンポーネントへのアクセスができ、そしてビルドの番号とバージョンを追跡できる」クラスです。
ここで、「そして」が発生してしまう事が、責務を持ちすぎているサインなのです。
単一責務の原則(SRP:Single Responsibility Principle)
単一責務の原則によると、クラス、モジュールは「変更の原因」となるものが1つでなければなりません。
先程のクラスでは、変更の原因となるものが2つありました。
1つは「コンポーネントへのアクセス」で、もうひとつは「バージョンの追従」です。
責務を適切にするには、以下の粒度で良いでしょう。
classVersiondefgetMajorVersionNumber# 処理enddefgetMinorVersionNumber# 処理enddefgetBuildNumber# 処理endend
責務を探すことは、コードの抽象化を高めることに役立ちます。
SRPはOOPの概念として、重要なものの1つです。
また、その中でも理解が容易で、守るのが簡単な概念なのですが、
不思議なことにSRPは最も無視されがちな設計原則の1つと言えます。
多くの現場では「コードを洗練すること」<「動かすこと」となってしまいがちの様です。
また、多くの開発者は、1つの責務のみを持った小さなクラスが溢れかえることで、
全体の絵を見失ってしまうのではないかと危惧します。あるいは、1つの大きな作業を行う為にいくつものクラスを渡りあるかなければいけないのではないかと、心配します。
しかし、小さなクラスの集まりとして構成されたシステムというのは、大きな少数のクラスで構成されたシステムより可動部分が少ないのです。
そして結局、大きな少数のクラスで構成されたシステムと、覚えなければ行けないものの数は少ないのです。
つまり
『普段使用するツールが、適切にラベル付けされ、小さな引き出しに格納された大量のコンポーネントで構成されていることを望むか、何でも無造作に格納できる、大きな少数の引き出しで構成されていることを望むか』
ということになります。
凝集性
あるメソッドが操作する変数が多いほど、そのクラスのメソッドの凝集性は高いと言えます。
全変数が全メソッドに使用されるクラスは、凝集性が最大限に高いものといえます。
関数を小さくし、引数リストを短くする鉄則を守ることで、インスタンス変数を増やすことに繋がる場合があります。
この場合、大きいクラスから、小さいクラスに分離する事が可能であることを意味してます。
その場合、変数とメソッドを別のクラスに分離し、クラスの凝集性を高めるべきです。
変更の為に最適化する
多くのシステムは常に変更を受けます。
変更を行うときには、常にシステム内の別の部分が想定通りに動かなくなってしまう危険性が伴います。
洗練されたシステムでは、クラスは、変更に対するリスクが最小限となるように構成されています。
変更に強いクラスを作るには、各クラスを極端に単純化し独立化させます。
各クラスを理解する為に必要な時間を減らし、独立化されることで、テスタビリティを向上させることができます。
また、OCP(開放/閉鎖原則:Open Closed Princple)の観点で、拡張に対して開かれており、変更に対して閉じている事が重要になります。
updateステートメントが必要になった際に、既存ロジックへの修正は行わず、新たなupdataOOという名のサブクラスを実装すれば良いので、これによってシステムが壊れることはないのです。
Clean Codeのいいところ
Clean Codeには参考文献の引用があること
Clea codeの各章の最後には、本章の執筆の際に参考にした参考文献が記載されています。
その為、各章で理解を深めたいと感じた際に、参考にするべき文献が提示されているのは、本書の強みだと思います。