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

2022年度新卒技術研修「良いコードの書き方」講義

 2022年度新卒技術研修「良いコードの書き方」講義

良いコードの定義を「読みやすいこと、拡張しやすいこと、安全なこと」の3つにしぼって解説したコーディングに関する講義です。

094e424062f60b011a0d0b294fbc17e4?s=128

excitejp
PRO

June 17, 2022
Tweet

More Decks by excitejp

Other Decks in Technology

Transcript

  1. 良いコードの書き方 エキサイト株式会社 新卒研修 2022.4.19

  2. 良いコードとは 本日の議題は良いコードの書き方ですが, 「良いコード」とは何でしょう? 2

  3. 良いコードとは 本日の議題は良いコードの書き方ですが, 「良いコード」とは何でしょう? このスライドでは - 読みやすいこと - 拡張しやすいこと - 安全なこと

    の3つとしてみます。 3
  4. 内容 2部構成となっています。 1. Principle プログラミングにおける普遍的な原則や思想, 視点を説明します。 2. Cording プリンシプルを実行するための手法や手段を説明します。 4

  5. Principle 原則, 思想, 視点 5

  6. なぜプリンシプル? プリンシプルとは - プログラミングの指針となる原則や思想のこと - 歴史の審査を受けたプログラミングのためのエッセンスでもある プリンシプルは指針なので手段ではなく方向性を示します。 プログラミング技術の大半はプリンシプルを意識したものです。 後半に紹介する手段やテクニックはプリンシプルを満たすためのものです。 プリンシプルを意識すれば手段を間違えても悪いコードにはなりません。

    6
  7. 原則 Keep It Simple! Stupid. (KISS) コードを書くとき最優先の価値を「単純性」「簡潔性」に置きます。 - 新しく覚えた技術を使いたい -

    将来の必要に備えたい(YAGNI) - 勝手に用件を加えてしまう といったことを避けましょう。 機能が同じであれば, 余計なコードを含まないコードの方が優れています。 7
  8. 原則 Don’t Repeat Yourself. (DRY) 同じ知識(コード)を重複して書いてはいけません。 重複はコード量を増やし, 理解を妨げ, 変更の難易度をあげます。 知識(コード)を抽象化しましょう。

    単にコードをそのまま説明しているコメントも重複です。 オブジェクト指向や設計, デザインパターンといった技術はコードの重複の排除を目的の 1つにしています。 8
  9. 原則 You Aren’t Going to Need It. (YAGNI) コードは「多分必要になるだろう」で書いてはいけません。 色々な事態に備えてコードを盛り込んでおいても使われないことがほとんどです。逆に

    時間が経つとなぜ使われないコードがあるのかわからなくなります。 汎用性よりも単純性を考え, コードは「今」必要なものに留めましょう。 仮に要件が増えて拡張することになっても, 単純なコードを変更する方が, 汎用的で複雑 なコードを変更するよりも簡単です。 9
  10. 原則 Open Closed Principle (OCP) コードの振る舞いを拡張してもその他のコード(クラスやモジュール)には全く影響を受け ないようにしましょう。 ソフトウェアの寿命は想定よりも長くなる傾向にあります。柔らかい設計を意識しましょ う。 そのためにはポリモーフィズムが重要です。

    オブジェクト指向と手続き型の違いはここへの対処と言い切ってる書籍もあるくらいで す。(Robert C. Martin著, 角 征典・高木 正弘訳, Clean Archetecture 達人に学ぶソフトウェアの構造と設計, 株式会社ドワンゴ, 2018) 10
  11. 思想 テスト容易性 コードはテストをしやすいように書きましょう。 ポイントはモジュール間の依存関係の排除です。 テストのしやすさを意識することで - 他クラスへの影響が減り - 責務がわかりやすく -

    呼び出し側が使いやすい コードになります。 11
  12. 思想 線形原理 処理の流れは直線にこだわりましょう。 ある機能はいくつかの機能の重ね合わせによって実現されるべきです。 スイッチでコードを制御したり, 状態の数を無闇に増やしたりするとコードがわかりにくく なります。 条件分岐を避けましょう。 12

  13. 視点 凝集度 凝集度とはモジュールに含まれている機能の純粋さを表す尺度です。 同じ機能に関する手続きは同じモジュールに含まれている方が好まれます。 なぜならば, 変更箇所の特定が容易であり, 変更の影響をモジュール内に限定できるた めです。 手順が近いもの, 共通する参照データは同じモジュールに含めましょう。

    13
  14. 視点 結合度 目指すべきモジュールの状態は, 他モジュールの改修によって影響を与えられることな く, また他モジュールにロジックを知られていない状況です。 これによって, 変更の影響を大きくしない効果が生まれます。 以下のことに気をつけましょう。 -

    public宣言したデータの参照 - 使う側がロジックを知っているような実装 - 不必要なデータを含むパラメータ 14
  15. 視点 冪等性 何度同じ処理をしても同じ状態を保つべきということです。 例えばコードは次のようなことを満たすべきでしょう。 - 途中で落ちたプログラムを再実行しても正常終了の場合と同等の結果になる - 何度APIへ同じリクエストをしても同じリソース状態となる - 同じバッチを何度実行してもDBやS3は同一の状態に保たれる

    これによって安全性が上がります。安全なコードは気軽に実行できます。 気軽に実行できるとリファクタリングがしやすくなり, コードは綺麗に保てます。 15
  16. まとめ 今回紹介したプリンシプルを簡単に意訳すると - シンプルにする - 重複させない - 将来の予測をしない - 他へ追加変更の影響を及ぼさないようインターフェースを使う

    - 処理を分岐させない - テストしやすいように書く - クラスは機能単位でまとめて各機能は他機能を参照しない - どのタイミングで何度実行しても同じ状態となるようにする 16
  17. もっと知りたい方は 3年目まで(入社2年間)で身につけたい原理原則を テーマとした本です。 今回の内容はここから独断と偏見で採用していま す。(歴は近いので的外れではないはず) おすすめの本なのでぜひ。 17

  18. Cording 手法, 手段, テクニック 18

  19. 命名 なぜ命名? コードは変数名, 関数名の連続です。 コードは書いてる時間よりも読んでる時間が長いです。(1:9くらい) 命名がよくなれば可読性が上がり, コーディングは格段に楽になります。 19

  20. 命名 意識すべきこと 変数, 関数, クラス名は次の大命題に応える必要があります。 - なぜそれをするのか - 何をするのか -

    どのように使用するのか もし名前に解説が必要なら, その名前は意図が明確とは言えません。 20
  21. 命名 品詞について 21 コーディング規約に近いですが - クラス名, 名前空間は名詞 - 関数名は名詞+動詞 とすると自然な命名になります。

  22. 命名 ニュアンス ニュアンスや意味に気を遣いましょ う。 「取得する」だけでも, get, find, factory, popなど複数の言い回しが あります。

    DataやInfoなど意味のない語彙は避 けましょう。 22
  23. 命名 疑問系 疑問系の変数や関数はbool値を 返す文化があります。 文化に乗っ取ることで, よりわか りやすい命名が可能です。 23

  24. 命名 長い名前 推奨はされませんが, あやふやな短い名前よりも優れています。 名前が長くなってしまう時は以下を見直してみましょう。 - 処理を名前にしていないか?-> 抽象的な名前をつけましょう。 - コンテキストは適切か?->名前空間やクラス名を見直しましょう。

    - 多くのことを関数が行っていないか?-> 関数を分割してみましょう。 24
  25. 命名 ユビキタス言語 プロジェクト独自の概念には積極的に名前をつけて 使用します。 日本語/英語の両方用意すると便利で良いです。 前項の長い名前やニュアンスの的確さ, 表記揺れに 強くなります。 25

  26. コメント なぜコメント? コードは実際に動いているからこそ仕様書としても機能します。 コメントは動かないので変更から取り残されることが頻発します。 余計なコメントは画面領域を奪い可読性を下げます。 適切なコメントへの姿勢を学びましょう。 26

  27. コメント 種類 ブロックコメント /** */で括られたコメント クラスや関数の挙動を示すDocコメントとして使われることが多い インラインコメント //で始まるコメント 関数や処理の説明として使われることが多い 27

  28. コメント 常に失敗 適切なコメントの使用方法とは, コードでうまく表現することに失敗したときに, それを補う のに使うことです。 コメントとは常に失敗なのです。 28

  29. コメント Docコメントの無くし方 29 /** * idのユーザーを DBから取得する * * @access

    public * @params int $id 検索用のユニークな ID * @return User $user ユーザーオブジェクト **/ public function get($id) { } public function findById(UserId $user_id) : User { }
  30. コメント インラインコメントの無くし方 30 // 名前は20文字以内であるべき if (length($name) <= 20) {

    return false; } // 生年月日の月は 1~12のうちのいずれかであるべき if ($month >= 1 && $month < 12) { return false; } if (isInvalidNameLenght($name)) { return false; } if (isInvalidMonth($month)) { return false; }
  31. コメント 許されるコメント 以下のようなコメントは許されます。 - 意図を示す - なぜそう書かなければならなかったのかを示す - 警告する というのは英語圏の書籍の主張です。

    基本的には賛成ですが, 英語のコードを読むのは基本的に辛いです。 コメントすることで理解の負担や時間を減らせるのであればヨシです。 31
  32. 条件分岐 なぜ条件分岐? プリンシプルでみた通り, 条件分岐は線形原理に反します。 条件分岐は区分や種別が要件に入っている場合に起こります。 区分や種別が入っても条件分岐を使わないことはインターフェースを使うことで可能にな ります。 32

  33. 条件分岐 条件の関数化 if ($user.type === 'child') { } if ($user.isChild())

    { } 33 まずは条件を関数化してみましょう。 例として大人と子供という区分を作って関数化してみます。 これはuser.typeをprivateにすることで防げるという見方もできます。 カプセル化を緩めるとこのように外部にロジックが漏れる危険性があります。 ※とは言え, getterを全く持たないことも不可能です。
  34. 条件分岐 早期リターン else句はコードを複雑にします。早期リターンを使って無くしましょう。 大人と子供の料金を返す関数を例としてみます。 private function fee(User $user) : int

    { if ($user.isChild()) { return 100; } else { return 200; } } private function fee(User $user) : int { if ($user.isChild()) { return 100; } return 200; } 34
  35. 条件分岐 ポリモーフィズム interfaceを使ってif文を無くします。 これで線形原理が実現できました。 interface User { public function fee()

    : int; } class Child implements User { public function fee() : int { return 100; } } class Adult implements User { public function fee() : int { return 200; } } 35 private function fee(User $user) : int { if ($user.isChild()) { return 100; } return 200; } $user.fee();
  36. 条件分岐 ポリモーフィズム ポリモーフィズムを使えば新しい区分の追加もクラスの追加のみです。 ちなみに, 生成はFactoryクラスで分岐させるようにします。 これはOCPも満たしています。 36 class Baby implements

    User { public function fee(): int { return 0; } } public static function factory(string $type) : User { $types = [ 'baby' => new Baby(), 'child' => new Child(), 'adult' => new Adult(), ]; return $types[$type]; } 生成してもらう
  37. 変数 スコープを絞る スコープとはアクセスを受け付ける範囲のことです。 アクセス範囲が大きいと, - どこで書き変わったかわからない - どこで参照されているかわからず変更できない といった事態になります。 基本的にprivateにしてクラス内,

    関数内, 宣言した場所から数行の利用に留めましょう。 37
  38. 変数 再代入をしない 基本的に変数の書き換えはデバッグを難しくします。 書き換えの場合は新しい変数を作成しましょう。 破壊的変更も受け付けてはいけません。(Immutable, mutable) これは変数の観点からみた冪等性とも言えます。 38

  39. 関数 小さくする 関数に関してはこれが全てです。 そのためには - 関数内では1つのことを行う - switch文をなるべく避ける - 引数は0もしくは1,

    最高でも3つまで - フラグ引数を避ける 39
  40. 関数 1つのことを行う 関数が1つのことを行っているかは「1つの関数に1つの抽象レベル」になっているかでわ かります。 コメントでセクション分けするのではなくコメントを英訳した関数を作り呼び出しましょう。 40

  41. クラス 小さくする 関数の小ささの指標は物理的な数でした。対してクラスの場合は責務の数です。 クラスは変更の原因となるものが1つでなければなりません。 これを単一責任の原則と呼びます。 クラス名が曖昧なほどその責務が大きくなりがちです。 41

  42. 境界 サードパーティーへの依存に対抗するためにインターフェースを使用します。 境界を設けることで, 次のメリットがあります。 - インターフェースの実装部を変えるだけでサードパーティーを交換できる - テストがしやすくなる 42

  43. オブジェクト指向 多くの技術がプリンシプルを達成することを目的に作られたと書きましたが, オブジェクト 指向もその1つです。 オブジェクト指向をコンセプト通りに使うことで, 多くのプリンシプルを達成する手助けに なります。 特にポリモーフィズムとカプセル化は非常に強力なためぜひ活用しましょう。 43

  44. オブジェクト指向 ポリモーフィズム ポリモーフィズムとは条件分岐部分でみたように, 様々な区分を同じ型で扱え, 同一型で の関数の存在を保証したものです。 インターフェースを使うことで実現できます。 これはOCPと線形原理を可能にすることを説明しました。 もう一つ, ポリモーフィズムはテスト容易性にも深く関わっています。

    これを説明するには依存とDIを説明しないといけないのですが, それはClean Architectureの講義でやると思うのでこのスライドではパスします。 44
  45. オブジェクト指向 カプセル化 カプセル化とはデータとロジックを1クラス内に隠蔽することです。 このようにすることである概念を1つのクラスで表すことができます。 ある概念に対するデータとロジックが1つのクラスにまとまる。つまり, これは凝集性をあ げて, 結合度を下げることに大きくつながります。 publicなインスタンス変数をなるべく避けましょう。 45

  46. オブジェクト指向 継承は? もう1つの要素である継承は少し危険な機能です。 多くの依存を生み出したり, 神クラスと呼ばれるなんでもクラスを作る原因になったりしま す。 必要な場合は1度にとどめるよう心がけましょう。 46

  47. 書式化 諸説ありすぎるので言及しません。 基本的にはLinterやFormmatterなどによって自動的に整形します。 47

  48. 参考文献 [1] Dustin Boswell Trevor Foucher著 角 征典訳, リーダブルコード より良いコードを書くためのシンプルで実践的なテクニック

    , O’REILLY, 2012 [2] Robert C. Martin著 花井 志生訳, Clean Code アジャイルソフトウェア達人の技 , ASCII DWANGO, 2017 [3] 上田 勲, The Principles Of Programming 3年目までに身につけたい一生役立つ 101原理原則, 秀和システム, 2016 [4] 増田 亨, 現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の原則技法 , 技術評論社, 2017 48