勉強日記

チラ裏

Clean Code ch3 Functions

www.oreilly.com


Functions

Small!

  • 関数はめっちゃ短くしろ
    • エディタの1画面に収まるのはもちろんのこと
    • 数行程度まで

Block and Indenting

  • ネストの深さは高々2重くらいまで
    • 可読性・理解しやすさのため
  • extract functionリファクタリングを適用し、説明的な名前をつける

Do One Thing

関数は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箇所に絞るのが理想

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
    • ...で締めくくる