Clean Code ch3 Functions

Clean Code勉強メモ

出典: 

Functions

  • リファクタリング前後の比較

Small!

  • 関数はめっちゃ短くしろ

    • エディタの1画面に収まるのはもちろんのこと
    • 数行程度まで

Block and Indenting

  • ネストの深さは高々2重くらいまで

    • 可読性・理解しやすさのため
  • extract functionリファクタリングを適用し、説明的な名前をつける

Do One Thing

  • 30年来で培われたアドバイス:

関数は1つのことだけをうまくやれ

  • 「1つのこと」って何
  • 同じ抽象度のステップに分解できていれば、それは1つのことをできている
  • 異なる抽象度が混ざっていれば、それは1つのことをできていない

Sections within Functions

  • 関数を複数のセクションに分解できるということは、1つのことをできていない兆候
  • 【補】そのセクション名の関数群に分割しろってこと

    • セクションの中身が実装に直書きされているのは、抽象度が揃っていないということ

One Level of Abstraction per Function

  • 割れ窓

    • 本質的な関心事と枝葉末節な詳細とがひとたび混ざると、どんどん混ざるようになる

Reading Code from Top to Bottom: The Stepdown Rule

  • TO paragraphでトップダウンに記述できること

    • 【補】TO paragraph: …するには…

      • 関数は動詞ないし動詞句なので To doSomething, ...という英文として書き下せるよという話

Switch Statements

  • switch文は…

    • 大きくなりがち
    • N個のことをしがち
  • 同じようなswitch文があちこちにあるのは最悪
  • 1箇所に絞るのが理想

    • Abstract Factoryの生成など
    • 他の部分はポリモーフィズム

Use Descriptive Names

  • よい命名はとても価値がある

    • 評価してもしすぎることはない
  • 1つのことだけをする小さな関数には名前も付けやすい
  • 長い名前を恐れるな

    • 「長くて説明的な名前」は「短いが謎掛けのような名前」より良い
    • 「長くて説明的な名前」は「長くて説明的なコメント」より良い
  • 命名に時間をかけるのを恐れるな

    • いくつかの名前を試して、コードを読んでみるべき

      • モダンなIDEならば容易くリネームできる
  • よい命名を検討した結果、コードの構造改善に繋がることも珍しくない
  • 一貫性を

Function Arguments

  • 個数

    • 0個が理想
    • 1個、ついで2個くらいまでが望ましい
    • 3個は避けたい
    • 4個以上は、相応の正当性が必要
  • 関数名と抽象度の異なる引数は良くない
  • テスタビリティ

    • 引数が2つ以上あると、組み合わせをテストしなければならない
    • 【所感】メンバ変数も同じことだと思いますけど
  • out引数は普通の引数よりも認知の負荷になる

Common Monadic Forms

  • 1引数関数
  • good

    • 関数(入力を加工して出力する)

      • 例: StringBuffer transform(StringBuffer in)
    • イベントディスパッチ

      • 例: void passwordAttemptFailedNTimes(int attempts)
  • bad

    • out引数

      • void transform(StringBuffer out)

Flag Arguments

  • 関数にフラグを渡すのは悪い習わし

    • シグネチャがすぐとっちらかる
    • 明らかに1つよりも多くのことをしている
  • true/falseそれぞれに対応する別の関数を用意せよ

Dyadic Functions

  • 2引数

    • 例: writeField(outputStream, name)
  • 自然なまとまりではなく、自然な順番もない場合は厄介

    • 例: assertEquals(expected, actual)

      • 引数を逆に渡したことは何回ありますか?(幾度となくありますよね)
  • 1引数形への変形を検討する

      • OutputStreamクラスのメソッドにする

        • outputStream.writeField(name)
      • outputStreamをメンバ変数に持ち、引数として渡さなくていいようにする

        • writeField(name)
      • FieldWriterクラスを作る

        • 【補】Method Object Pattern
        • fieldWriter = new FieldWriter(ouputStream);
        • fieldWriter.write(name);

Triads

  • 引数順の問題や認知の負荷は2引数の比ではないので十分に注意せよ
  • 3引数でも気にならない例

    • assertEquals(1.0, amount, .001)

      • 二度見不可避ではあるが、むしろそれがいい
      • 浮動小数点数の比較は相対的なものであることを想起させる

Argument Objects

Circle makeCircle(double x, double y, double radius)
Circle makeCircle(Point center, double radius)
  • 後者は、中心点のx/y座標であるということが伝わる

Argument Lists

  • 可変引数はListを渡しているのと同じ
  • public String format(String format, Object... args) は実質2引数

    • public String format(String format, List<Object> args) と同じだから

Verbs and Keywords

assertEquals(expected, actual)
assertExpectedEqualsActual(expected, actual)
  • 後者は引数順を間違えない

Have No Side Effects

  • 副作用は、1つよりも多くのことを隠れて行っているということ
  • 時間的結合を生み出してしまう

    • temporal coupling

      • 【所感】「一時的結合」なんて訳もあるようだが、「時間的結合」のほうが適切でしょう
  • 1つよりも多くのことをしているということなので、分割すべき

Output Arguments

  • OO言語では避けるべき

    • thisがoutput先

Command Query Separation

  • getter/isserとsetterを混ぜない
  • 混ぜると、動詞と形容詞/過去分詞が紛らわしくなったりする
if(set('username', 'unclebob')) { ... }
  • setして、成功したらtrue/失敗したらfalseを返すのか
  • exists的な意味なのか

Prefer Exceptions to Returning Error Codes

  • 例外を使おう

    • エラーコードを返すのはcommand query separation違反
    • 例外を使うと、正常フローと例外フローを綺麗に分離できる

      • cf. エラーコードは即座に捕まえて処理しないといけないので、正常フローがエラー処理でとっ散らかる

Extract Try/Catch Blocks

  • try/catch句の中に実装を直接つらつらと書かない
  • 関数に抽出する

Error Handling Is One Thing

  • エラーハンドリングは「1つのこと」
  • エラーハンドリングを行う関数は、tryで始まらないといけないし、catch/finally句の後に何も書いてはいけない

The Error.java Dependency Magnet

  • エラーコードを返す方式だと、エラーコードが定数クラスやenumで定義されている
  • こうしたクラスは、依存のマグネット(dependency magnet)と呼ばれる代物

    • あらゆるクラスからuseやらimportやらされる
    • エラーコードクラスに変更を加えると、依存する全クラスで再コンパイル・再デプロイが必要になる
    • ので、エラーコードの追加が億劫になる
  • 例外のススメ

    • エラー定義を追加するときは、Exception派生クラスを追加する
    • エラーハンドリング側では再コンパイル・再デプロイは一切必要ない

Don’t Repeat Yourself

  • 重複は諸悪の根源
  • それを御する・排除するために多くの原理原則や実践が培われてきた

    • リレーショナル理論の正規化
    • OOPの実装継承
    • AOP
    • もろもろ

Structured Programming

  • エドガー・ダイクストラ提唱
  • one-entry one-exit

    • 1つのreturn文のみ
    • ループのbreak, continueなし
    • gotoダメ絶対
    • 【補】ガード節の早期リターンなし
    • 【補】例外はone-exit違反ですね
  • 大きな関数向けのルール
  • 関数が小さければ、構造化プログラミングにしたがうメリットは少ない

    • むしろ、破っても害がないばかりか、表現力が上がるまである
    • ただし、この場合もgotoはやめよう

      • 小さな関数では役に立たないし

How Do You Write Functions Like This?

  • 文章を推敲するのと同じ
  • 最初は不細工なコードでよい

    • 深いインデント
    • 多重ループ
    • 引数いっぱい
    • 名前に一貫性がない
    • 重複
  • 単体テストで全行カバーして、きれいにしていく

    • 関数分割
    • リネーム
    • 重複をなくす

Conclusion

  • システムはドメイン特化言語で構築される
  • 「プログラムを書く」というよりは、「物語を語る」
  • そのための語彙にフィットする関数やクラスをつくる

    • 関数は動詞
    • クラスは名詞

英語

  • arcane

    • 秘密の
  • accrete

    • 固まる、融合する
  • a host of

    • たくさんの

      • a lot ofとかの仲間?
  • double-take

    • 少ししてから驚く

      • 「ふ〜ん…えっ」ってやつ
  • proclaim

    • であることを明白に表す
  • monadic

    • 引数が1つの
  • dyadic

    • 引数が2つの
  • triadic

    • 引数が3つの
  • come at a costs

    • 高くつく
  • evocative

    • 喚起する、示唆に富む
  • devious

    • 遠回りの、曲がりくねった
  • wind up with

    • …で締めくくる