Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

2018.11.22 レガシー化しても困らないための コーディング

2,016 views

Published on

Sansan×M3 Tech Night ~レガシーシステムに立ち向かえ!~
2018.11.22

Published in: Technology
  • Login to see the comments

  • Be the first to like this

2018.11.22 レガシー化しても困らないための コーディング

  1. 1. レガシー化しても困らないための コーディング Sansan×M3 Tech Night ~レガシーシステムに立ち向かえ!~ 2018.11.22 エムスリー 中村貴志
  2. 2. 自己紹介 中村貴志 ● エムスリー株式会社 インターネットを活用して健康で 楽しく長生きする人を増やし、 不必要な医療コストを削減する ○ 医療者向けポータル www.m3.com ○ 一般の方向け医療相談サイト AskDoctors.jp 2 ● 基盤開発チーム ○ m3.comの認証基盤APIの担当 ○ 社内の開発環境の改善 ○ 他のチームのヘルプ ○ など ● エムスリーテックブログ bashスクリプティング研修の資料を公開 します https://www.m3tech.blog/entry/2018/ 08/21/bash-scripting
  3. 3. 未来のレガシーを作っている レガシーなものに関わっていて辛さを感じることはある でも、いま自分たちが作っているものがいつかはレガシーになり、 後任の人を苦しめることになるのかもしれない 3
  4. 4. レガシーの難点 ● 仕様・意図 (どうしてそう動かしたいか) がわからない ○ どう動くかは実装コードを読めばいい ○ 書いてあることが意図通りかバグってるかがわからない ○ 経年変化で前提条件が変わっている場合もある ● 他との依存関係、影響範囲がわからない ○ 動作確認する対象範囲は? ○ 同時に修正が必要なことがあるか? ○ 変更してはいけないことがあるか? 4
  5. 5. コーディングでの対処 ● 意図を読み解きやすい実装にすること ● 適度にコメントを書くこと ○ どういったコメントを残すか? ○ どこにコメントを残すか? 5
  6. 6. 仕様、意図 をコメントに残す どう動くか、より、なぜ必要か・どういう意図かをコメントする コードから読み解けない意味や意図をコメントに残す レビュー時に意図と合っているのかをチェックできる // 空ならtrueを返す ← NG // ここでユーザがいないのは既に削除済みと同義 ← OK if (userList.isEmpty()) { return true; } 6 // ユーザが重複していたら最大値を使う ← NG // IDが最大のものが最新データである ← OK ID targetUserId = Math.max(userIdList);
  7. 7. コピペコードはコピー元を書く コピー&ペーストしたコードはコピー元を書く コピペで対処した事情があれば書く(「作業時間がなくて仕方なく」でもOK) 修正時に一緒に修正が必要な箇所はお互いにそのことを書いておく ■ countActiveUsersByName -- UserDao#findActiveUsersByNameに対する件数を取得 -- NOTE 条件部分をfindActiveUsersByNameと同じにすること select count(*) from users u where u.name like '%{{name}}%' and u.is_active = 1 7
  8. 8. 定石から外れた処理の理由 定石から外れた処理は、なぜ必要かを説明 する リファクタリング時にバグになりがち ● 最適化のための意図的な崩しなのか ● どうしても解決できなかったバグへの対処 なのか ● 最適解が他にありそうでも思いつかな かった場合 ループ変数に関係ない処理なのにループ内に あるのは奇妙では? ループの外に出した方が効率的かも? for (User user : userList) { // ループ内で他ユーザに変更があるため // ループのたびに同期が必要 userLogic.syncAllAttributes(); if (userLogic.isAllowed(user)) { 〜〜 } } 8
  9. 9. フレームワークのバグや制限への対処 フレームワークやライブラリへの対処で、無理やりな方法 や monkey patchを使う場合 は丁寧に理由を書く 将来フレームワークのバージョンアップで治るかもしれないので、ドキュメントやissueの URLを残しておく // FIXME SpringのHogeFilter#doSomething は〇〇が××になるバグがある // https://github.com/spring/spring/issue/9999 // 暫定での回避のために、ここで〜する 9
  10. 10. 依存モジュールの目的を書く 依存モジュールが、何の目的に入れているかを書いた方がいい バージョンアップ時に何に気をつけて確認するか、別のものに変えられるのかを判断で きるようにする 依存に含めるだけで暗黙的に作用するようなものは特にわからなくなりがち (文字化け回避のフィルタ機能、言語の次期仕様の先取り、etc...) 要らないと思って消したら思ってないところでバグに... 「依存モジュールが作用していること」というユニットテストを書くとよい 10
  11. 11. ファイルにコメントをつける 設定ファイルが何の用途かがわからなくなることがある ライブラリを除去するときに設定ファイルが残されがち 暗黙で読み込まれる場合があるので削除したときの影響がわからなくて怖い (ライブラリの利用箇所を検索するときに使いそうな単語を書いておくのがよい) また、設定値の意図をコメントすることも重要! なぜそういう設定にしたかったのか?がわからなくなると設定を変えづらい (自動生成された初期テンプレートを1度コミットし、次に修正を加えるのとGit差分で修正範囲がわかりや すい) 「設定が機能していること」というユニットテストを書くとよい 11
  12. 12. コメントが要らないコーディング ● コメントをつけるより、 メソッド化・定数化で名前を 割り当てて表現する ● テクニカルなコードより、平易なコード (Streamよりforがいい時もある) class User() { boolean hasNeverUsedSinceCreated() { return user.status == NORMAL && user.firstLoginedAt == null; } } if (user.hasNeverUsedSinceCreated()) { 〜〜 } 12
  13. 13. 要らないものは削除する そもそも、要らなくなった機能やクラスは勇気を持って消す いざとなったらGitの履歴から戻せる 積極的に Deprecated でマーキングする Deprecated の理由、どうすれば削除しきれるか、代替手段を書く /** * @Deprecated スペルミスしているので削除予定 * 代わりにsetGroup()を使うこと * キャッシュのデシリアライズ時の互換の為に残っている * キャッシュ内容が新しいモデルに置き換われば削除可能 */ public setGrop(String group) { 〜〜 } 13
  14. 14. コメントをつけるための習慣 実装の最初にこれから作ることを日本語で書く ● コントローラメソッドやビジネスロジックの主なメソッドには丁寧に ● 作業のTODOを洗い出すような感覚 コミット前にセルフレビュー ● (次ページで) 14
  15. 15. コミット前にセルフレビュー 実装中は試行錯誤になるので丁寧にコメントをつけていられない commit・pushの時にセルフレビューして清書する 修正内容のタイトルをつけるイメージでコミットコメントを書く ● デバッグコードが残っていないか ○ デバッグコードが視認しやすいようなマーカをつけると便利 ○ 決まったキーワードを書く/ 必ずインデントを無視して行頭から書く ● 作りかけで忘れていることがないか ○ 後からやろうとするところにTODOを書いておく(エラー処理とか) ○ テストを作り忘れていないか ● 書き方(悩んで頑張ったところ)が理解できるようになっているか 15
  16. 16. コメントをつける問題点 コメントがメンテナンスされず、実装から乖離する コメントだらけで実装が読みづらくなる ● コメント不要な読みやすい実装を目指す ● ソースへのコメントではなく、gitのコミットコメントで補足する ○ git-blameなどで実装時の背景を掘り起こすときに参考になる ● メソッド名がわかりにくい ○ 英語のボキャブラリが必要では? ● テストコードで重要な仕様・機能の使い方例を明示する ● コメントが多くなってしまう理由を掘り下げる ○ 全体的な文書で包括的に説明が必要では? 16
  17. 17. まとめ 以下のことは分からなくなりがち ● 色々な理由 ○ 機能、メソッド、条件などの仕様・意図 ○ 定石を外す特殊な処理の理由 ○ Deprecatedの理由 ○ 依存モジュールの導入理由 ● 依存・影響の範囲 ○ 処理の前提 ○ 暗黙で働く機能 ○ 設定ファイルの効果 ○ 連携する外部システム 対処 ● 適宜コメントを残す ● 実装で意味を表現する ● 要らないものは消す 17
  18. 18. We are hiring!! エムスリーでは、共に医療 × テクノロジーの 未来を切り拓いてくれる仲間を募集中です! https://m3.recruitment.jp/engineer/ 18
  19. 19. 付録: コメントをつける場所 追加分 19
  20. 20. 悩んだこと・暫定で残したこと 悩んだこと、最適解が他にありそうでも思いつかなかった場合に、どこまで考えて、どこ でつまづいたかを残す 通常と違う奇妙に見えるコードには、そうしなければいけなくなった理由を書く // FIXME 保存時にDBのトリガーで値が変更されうるので // 変更分を反映するためsave後に再取得が必要 // frameworkのafterリスナで綺麗にできそうだが、 // 既存のUserLogig#saveAllと噛み合わなかった userLogic.save(user); user = userLogic.findById(user.getId()); 20
  21. 21. SQLの条件判定はコメントを添える SQLは定数やメソッドなどで意味づけできない nullやコード値(1,2とか)の意味を説明した方がいい また、一度に複数の仕様の条件を書きがちなので全体でコメントをするより、 意味の塊ごとにコメントした方が読み解きやすい select u.* from user u join group g on u.group_id = g.id where -- 1度はログイン済みなVIPユーザ u.first_logined_at is not null and u.status = 'VIP' -- '新規作成'扱い(10日以内)のグループ所属 and g.created_at >= now() - 10 21
  22. 22. 前提条件・事後条件を明確にする コメントを書いたり、メソッド内で前提条件のチェックをする処理を記述することで前提を 明示する それらをラップしたメソッドを用意して間違った使い方できないようにする ● 前提条件 ○ あるメソッドを呼び出した後じゃないと使えないメソッド ○ 特定のデータの状態でなければ使えないメソッド ○ 引数のリストが決まったソート順序であること ● 事後条件 ○ 後続の処理を想定したリストのソート順序 22
  23. 23. コメントが要らないコーディング 追加分 処理の塊がわかるようにするコードの書き方をした方がいい 構文で意味の区切りを表現できる場合もある 1つの条件で明確な2択をする場合はelseをつかう if (text.startsWith("Hello")) { return text; } else { return "Hello" + text; } if (text.startsWith("Hello")) { return text; } // Helloがないので追記する return "Hello " + text; 23

×