Upgrade to Pro — share decks privately, control downloads, hide ads and more …

キレイなコードの書き方

 キレイなコードの書き方

Takuya Kitamura

December 23, 2016
Tweet

More Decks by Takuya Kitamura

Other Decks in Programming

Transcript

  1. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 今回のコンテキスト n  プログラマ n  Java •  オブジェクト指向 •  静的型付け⾔語(コンパイル⾔語) n  業務システム •  ⻑期間の保守・エンハンス 2
  2. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. キレイなコードとは何か? n  誰でも理解コード? n  変更しやすいコード? n  重複が少ないコード? n  無駄な処理が含まれていないコード? n  ちゃんとコメントが書かれているコード? n  会社の規約に沿ったコード? n  真の意味でオブジェクト指向を実践したコード? n  etc… 3
  3. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. コード品質の指標 4 指標 説明 可読性(解析性) 他者がソースコードを⾒て処理の内容を理解できる、または処理の内容を解析できること。 保守性 (修正容易性) 予想できなかった変更を、最低限の修正で実現できること。 重複がなく変更箇所を局所化し、他のコンポーネントへの影響を最⼩にすること。 例:不具合の修正やミドルウェアの変更の場合に、修正箇所が少ない 拡張性 事前に想定できる機能の追加や拡張を、既存コードに影響を与えず実現できること。 拡張が想定されるコンポーネントを、既存コードを修正する事無く、差し込みや差し替え可能 な構造になっていること。 例:認証⽅式をDBからLDAPに切り替える際に、LDAP版の認証モジュールをプラグインできる。 再利⽤性 コードの重複がなく、複数存在する同⼀ロジックの処理を1つのモジュールで実現しているこ と。 コードが再利⽤可能な粒度で汎⽤的なモジュールに分割されていること。 運⽤性 障害発⽣時にトラブルシューティングしやすい状態にあること。 環境依存のパラメータをプログラムを変更する事無く切り替えられる仕組みとなっていること。 その他の指標 上述以外の品質指標。 それぞれの品質指標を⾼い⽔準で満たしている事。 n  よく使われるコードの品質を測るための指標
  4. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. キレイなコードを書くためには… n  キレイなコードであると判断する基準はいくつもある n  個々の基準を満たすためにそれぞれのアプローチがある n  キレイなコードの定義を⼀⾔で⾔い表す事は難しく、実践 ⽅法も⼀筋縄でいかない n  この状況をわかりやすく表すために、キレイなコードを書 くためには「センス」が必要だと表現される n  このキレイさの「センス」を⾝につけるには、無数の⼩さ な⼿法を知識として蓄え、⼀つ⼀つ丁寧に実践していく必 要がある 5
  5. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. ⼀般的に⾏われているアプローチを学ぶ n  今⽇は以下の書籍の中で語られている「キレイなコードを 書く」ための代表的なアプローチを学ぶ •  時間が限られているので、あくまでも⼀部のみ 6
  6. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. オブジェクト指向エクササイズ n  オブジェクト指向の良さを⽣み出す9つのルール 1.  1つのメソッドにつきインデントは1段階までにすること 2.  else句を使⽤しないこと 3.  すべてのプリミティブ型と⽂字列型をラップすること 4.  1⾏につきドットは1つまでにすること 5.  名前を省略しないこと 6.  すべてのエンティティを⼩さくすること 7.  1つのクラスにつきインスタンス変数は2つまでにすること 8.  ファーストクラスコレクションを使⽤すること 9.  Getter/Setter/プロパティを使⽤しないこと 7
  7. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 意味のある名前をつける n  変数、関数、引数、クラス、パッケージの全てにおいて、注意深く、意図が 明確な名前を付ける n  偽や曖昧な名前を避け、コード中での意味が変わったら、速やかに意味に合 わせた名前に変える n  エンコーディングが必要な名前にしない n  クラス名には名詞を、メソッド名には動詞を使う n  システム内で同じ抽象概念、コンセプトには同じ命名規則を適⽤して⼀貫性 を持たせる n  ビジネスドメインの⽤語を使⽤する n  多少⻑くなっても意図を理解できる名前を付ける事を優先する            9 public List<int[]> getThem() { List<int[]> list1 = new ArrayList<>(); for (int[] x : theList) if (x[0] == 4) list.add(x); return list1; } public List<int[]> getFlaggedCells() { List<int[]> flaggedCells = new ArrayList<>(); for (int[] cell : gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; }
  8. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 関数を⼩さく保つ n  ⼀にも⼆にも関数は⼩さく保つこと n  1つの関数は1つの事だけをきちんと⾏い、それ以外の事 を⾏ってはならない n  1つの関数は1つの抽象レベルにする •  抽象度の異なる処理が1つの関数内に混在しないようにする n  DRY(Don't Repeat Yourself)原則を守る •  重複しているコードは、それだけで1つの役割を持っている 10
  9. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 11 public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean includeSuiteSetup) throws Exception { WikiPage wikiPage = pageData.getWikiPage(); StringBuilder buffer = new StringBuilder(); if (pageData.hasAttribute("Test")) { if (includeSuiteSetup) { WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(SUITE_SETUP_NAME, wikiPage); if (suiteSetup != null) { WikiPagePath pagePath = suiteSetup.getPageCrawler().getFullPath(suteSetup); String pagePathName = PathParser.render(pagePath); buffer.append("!include -setup .") .append("pagePathName") .append("\n"); } } WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage); if (setup != null) { WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setup); String setupPathName = PathParser.render(setupPath); buffer.append("!include -setup .") .append(setupPathName) .append("\n"); } } buffer.append(pageData.getContent()); if(pageData.hasAttribute("Test")) { WikiPage teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage); if (teardown != null) { WikiPagePath tearDownPath = suiteSetup.getPageCrawler().getFullPath(teardown); String tearDownPathName = PathParser.render(tearDownPath); buffer.append("\n") .append("!include -teardown .") .append("tearDownPathName") .append("\n"); } if (includeSuiteSetup) { WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(SUITE_TEARDOWN_NAME, wikiPage); if (suiteTeardown != null) { WikiPagePath pagePath = suiteTeardown.getPageCrawler().getFullPath(suiteTeardown); String pagePathName = PathParser.render(pagePath); buffer.append("!include -teardown .") .append("pagePathName") .append("\n"); } } } pageData.getContent(buffer.toString()); return pageData.getHtml(); } public static String renderPageWithSetupsAndTeardowns( PageData pagaData, boolean isSuite) throws Exception { boolean isTestPage = pageData.hasAttribute("Test"); if (isTestPage) { WikiPage testPage = pageData.getWikiPage(); StringBuilder newPageContent = new StringBuilder(); includeSetupPages(testPage, newPageContent, isSuite); newPageContent.append(pageData.getContent()); includeTeardownPages(testPage, newPageContent, isSuite); pageData.setContent(newPageContent.toString()); } return pageData.getHtml(); } public static String renderPageWithSetupsAndTeardowns( PageData pagaData, boolean isSuite) throws Exception { if (isTestPage(pageData)) { includeSetupAndTeardownPages(pageData, isSuite); } return pageData.getHtml(); } リファクタリング後 さらにリファクタリング後
  10. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 副作⽤を避ける n  副作⽤とは、関数が1つのことを⾏なうことを保証しつつ、隠れ て別のことを⾏なうこと n  誤解を招き、奇妙な時間軸の関連を産み、依存関係の順序を狂わ せる n  出⼒引数を避ける n  コマンド・照会の分離原則を守る •  関数では、何らかの処理を⾏なうか、何らかの応答を返すかのどちらかを⾏ うべきで、両⽅を⾏ってはならない 12 public class UserValidator { private Cryptographer cryptographer; public boolean checkPassword(String userName, String password) { User user = UserGateway.findByName(userName); if (user != User.NULL) { String codedPhrase = user.getPhraseEncodedByPassword(); String phrase = crytographer.decrypt(codedPhrase, password); if ("Valid Password".equals(phrase)) { Session.initialize(); return true; } } return false; } }
  11. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. コメント n  コメントは必要悪 •  あくまで、コードでうまく表現することに失敗したときに、それを補うのに使う n  コメントは間違っていても動いてしまう •  コードは正しく修正されても、コメントは置き去りになりがち •  コメントはコンパイルされず、テスト出来ないことを意識する n  コードの意図は、コード⾃⾝で伝える •  変数名や関数名を利⽤してコードに意図を込める n  よいコメント •  公開APIにおけるJavaDoc •  実装の背景や決定要因を伝えるコメント 13 // 従業員が、給与の完全給付をうけるかどうかをチェック if((employee.flags & HOURLY_FLAG) && (employee.age > 65)) if(emplyee.isEligibleForFullBenefits()) 悪い例 良い例
  12. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. オブジェクト指向編 14
  13. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 凝集度と結合度 n  凝集度 •  クラスやパッケージ内の機能要素と情報要素間の関連性の強さを表す指標 •  互いに関連する機能や情報があちこちに分散していると、仕様変更が⽣じた 場合の影響範囲が広くなってしまう •  凝集度を⾼めようとすると1つのクラスが持つデータの数がへり、⼩さなク ラスが⼤量にできるようになる n  結合度 •  クラスやパッケージ間で、呼び出し関係にあるメソッドの結び付きの強さを 表す指標 •  結合度が⾼くなると複数のクラスやパッケージ間で依存度が上がってしまう ため、保守やメンテナンス、仕様変更などの対応がしづらくなる n  凝集度を⾼め、結合度を低くする事で、保守性や拡張性を⾼める 事が必要 15
  14. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. データとロジックの置き場所を同じクラスにする n  オブジェクトは裏にあるデータを隠して抽象化し、データを操作 する機能を公開するためのもの •  全てのデータにsetter/getterを⽤意すると本末転倒 n  データ構造の変更が必要になった場合でも、⾃クラス内の修正に 留める事ができる n  デメテルの法則を守る・守らせる •  オブジェクトを使⽤する場合、そのオブジェクトの内部についてしるべきで はない •  データを隠蔽し、操作を公開する •  必然的に引数の受け渡しの数が少なくなる n  データ転送オブジェクトの利⽤を避ける •  レイヤ設計に伴って仕⽅がない場合にのみ利⽤ 16
  15. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 多態性を利⽤する n  多態性とは、実⾏される処理の実体が、コールされたメッセージ ではなく、メッセージを受けたオブジェクトによって決定される 性質 •  呼び出し側は、処理を依頼した側の実体を知らなくてよい n  多態性を活⽤する事により分岐処理を無くす •  多態性を使って分岐した場合は、新しい分岐候補が追加されても、呼び出し 側の修正が不要 •  保守性や拡張性を上げる事ができる 17 Shape float calcTotalArea(List<Shape> shapes) { float totalArea = 0.0; for (shape in shapes) { totalArea += shape.getArea(); } return totalArea; } + getArea() Rectangle + getArea() Circle + getArea() Shapeにのみ 依存する
  16. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. SOLID原則を⽤いた設計 n  オブジェクト指向の設計における5つの基本原則 •  単⼀責務の原則(SRP) •  オープン・クローズドの原則(OCP) •  リスコフの置換原則(LSP) •  インターフェース分離の原則(ISP) •  依存関係逆転の原則(DIP) 18
  17. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 単⼀責務の原則(SRP) n  「クラスを変更する理由は1つより多く存在してはならない」 n  複数の変更理由が存在するクラスは改修の過程で凝集度が下がっ てしまい、変更に弱くなる •  1つの役割の変更が、別の役割の部分にまで影響を及ぼしてしまう n  責務や役割と捉えると判断が難しいため、「変更の理由」と捉え て考えてみる 19 Modem + dial(String) + hangup() + send(char) + recv() DataChannel + send(char) + recv() Connection + dial(String) + hangup() Modem 責務の分割
  18. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. オープン・クローズドの原則(OCP) n  「ソフトウェアの構成要素は、拡張に対して開いていて、修正に対し て閉じていなければならない」 n  オープン:拡張に対して開かれている •  アプリの仕様が変更されても、モジュールに新たな振る舞いを追加するだけでその変 更に対処できる n  クローズド:修正に対して閉じている •  モジュールの振る舞いを拡張しても、他のモジュールには影響が出ない、コンパイル なども不要な状態 n  多態性をうまく活⽤する 20 Client Server Client <<interface>> Server ServerImpl ClientはServerを参照している ため、Serverが変更されると Clientも何らかの変更、または 再コンパイルが必要 Clientはinterfaceを参照してい るだけなので、ServerImplの 振る舞いが修正されても変更 もコンパイルも不要
  19. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. リスコフの置換原則(LSP) n  「派⽣型はその基本型と置換可能でなければならない」 •  Tから派⽣したSがあり、Tを参照している任意の処理をSに置き換えても問 題なく動作する事を保証しなければならない n  例えば、派⽣クラスで親クラスのメソッドをオーバーライドして 振る舞いを壊している、例外を投げているなど n  または、利⽤クラス側が多態性を無視して具象クラスにキャスト して利⽤している n  この原則はコンパイルの仕組だけでは保証できない n  親と⼦でメソッド毎に事前条件、事後条件を取り決めてそれを守 るようにする •  そもそも、できるだけ前後の処理に依存しない形でインターフェースを設計 する事 21
  20. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 依存関係逆転の原則(DIP) n  「上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュール も"抽象"に依存すべきである。」 n  「"抽象"は実装の詳細に依存してはならない。実装の詳細が"抽象"に依存すべきであ る。」 n  上位(⽅針を決めるレイヤ)が下位(具体的な実装レイヤ)に依存すると、実装の変更 が⽅針に影響を及ぼしてしまうため n  多態性をうまく活⽤する n  レイヤ毎に⾼い凝集性を保つ必要がある 22 Policy Layer Mechanism Layer Utility Layer Policy Layer <<interface>> Policy Service Mechanism Layer Mechanism Layer Utility Layer Policy Policy Policy 悪い例 良い例
  21. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. インターフェース分離の原則(ISP) n  「クライアントに、クライアントが利⽤しないメソッドへの依存を強 制してはならない」 n  太ったインターフェースを作らないこと n  様々な⽤途を持った1つのインターフェースを作ると、実装側が必要 としていない機能まで実装しなければならない n  役割毎に複数のインターフェースに分離すること n  関連性のあるインターフェースはグループ化して抽象クラスに実装を まとめること 23 ModemImplA <<interface>> DataChannel + send(char) + recv() <<interface>> Connection + dial(String) + hangup() DataChannelImpl ConnectionImpl <<abstract>> Modem + dial(String) + hangup() + send(char) + recv() Modemインターフェースは作 らず、DataChannelと Connectionに分離 関連性が強い機能 は抽象クラスとし てまとめる ModemImplB
  22. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. もう⼀度、オブジェクト指向エクササイズ n  オブジェクト指向の良さを⽣み出す9つのルール 1.  1つのメソッドにつきインデントは1段階までにすること 2.  else句を使⽤しないこと 3.  すべてのプリミティブ型と⽂字列型をラップすること 4.  1⾏につきドットは1つまでにすること 5.  名前を省略しないこと 6.  すべてのエンティティを⼩さくすること 7.  1つのクラスにつきインスタンス変数は2つまでにすること 8.  ファーストクラスコレクションを使⽤すること 9.  Getter/Setter/プロパティを使⽤しないこと 24
  23. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. もう⼀度、オブジェクト指向エクササイズ n  オブジェクト指向の良さを⽣み出す9つのルール 1.  1つのメソッドにつきインデントは1段階までにすること 2.  else句を使⽤しないこと 3.  すべてのプリミティブ型と⽂字列型をラップすること 4.  1⾏につきドットは1つまでにすること 5.  名前を省略しないこと 6.  すべてのエンティティを⼩さくすること 7.  1つのクラスにつきインスタンス変数は2つまでにすること 8.  ファーストクラスコレクションを使⽤すること 9.  Getter/Setter/プロパティを使⽤しないこと 25 全てのルールは以下のような⽬的を達成するための⼿段 ①コードの可読性を向上 ②⾼凝集と疎結合の促進による保守性・拡張性・再利⽤性を向上 全てのルールを守ることをゴールにするのではなく、⽬的を理解して 現実と折り合いを付けながら改善することが⼤切
  24. Copyright ©2016/12/23 , NC Design & Consulting Co., Ltd. All

    rights reserved. 参考⽂献 n  ThoughtWorksアンソロジー •  https://www.amazon.co.jp/ThoughtWorks%E3%82%A2%E3%83%B3%E3%82%BD%E3%83%AD %E3%82%B8%E3%83%BC-%E2%80%95%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB %E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C %87%E5%90%91%E3%81%AB%E3%82%88%E3%82%8B%E3%82%BD %E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A4%E3%83%8E %E3%83%99%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3-ThoughtWorks-Inc/dp/487311389X n  Clean Code •  https://www.amazon.co.jp/Clean-Code- %E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD %E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%81%94%E4%BA%BA %E3%81%AE%E6%8A%80-Robert-Martin/dp/4048676881 n  アジャイルソフトウェア開発の奥義 •  https://www.amazon.co.jp/%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB %E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B %E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9-%E7%AC%AC2%E7%89%88-%E3%82%AA %E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C %87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB %84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80-%E3%83%AD%E3%83%90%E3%83%BC %E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%81%E3%83%B3/dp/ 4797347783 26