Slide 1

Slide 1 text

1 ソフトウェアを作る際に意識してほしい責務と依存性

Slide 2

Slide 2 text

質問:コードをキレイに保つこととは? • オタクのこだわり? 2 誰しもがコードが汚いと指摘されたり、キレイにしたいと思ったことがあるはず… それは何のためなのか? • 新人へのマウント? • 「良い」ソフトウェアを作るため!

Slide 3

Slide 3 text

「良い」ソフトウェアとは? • 価値のあるソフトウェアこそ継続的に利用され続ける。 • 継続利用されるからこそ、対応すべきニーズが増えていき変更を要することになる。 • 故に「良い」ソフトウェアとは「変更し続けられる」ソフトウェアとなる。 3 作りきりのソフトウェアは存在しない。 ソフトウェアは当初の機能要件を満たし将来のニーズに対応できるように変更しやすくすべきであ り、それが「良い」ソフトウェアとなる。

Slide 4

Slide 4 text

「良い」ソフトウェアがもたらすもの • 企業はビジネスで優位に立つために金をかける。 • 金をかける理由は将来儲けるため。 • ソフトウェア開発もお金を得るための手段。 • 多くのユーザが利用し、自社に利益をもたらす。 • クライアント企業が新たな事業で利益を得た。 • 企業のオペレーションが変わり、コストが削減された。 …等 4 お金です。 「良い」ソフトウェアはビジネスで価値を生む。

Slide 5

Slide 5 text

「変更を怠った」ソフトウェアの末路 • 価値を提供しないソフトウェア • ユーザからのFBや市場のニーズに対応できなかった。 • 運用コストが高く、価値のない機能を提供し続けていた。 • 価値を逃したソフトウェア • 特定のニーズに特化させることができなかった。 • ほとんどの価値が他のソフトウェアの劣化となってしまった。 • 対応したころには、そのニーズがなくなっていた。 • 対応に莫大なコストがかかり、費用対効果が薄い(ない)。 5 果たしてこんなソフトウェア使いたいですか?運用できますか? お金を生み出していますか?投資した分だけお金を取り戻せているのだろうか?

Slide 6

Slide 6 text

お金を得るためにどんな変更が発生するのか • 新たな価値を生むための機能の追加する。 • 市場やユーザのニーズに応じた機能の変更する。 • 価値を生まない機能を捨てる。 • 特定の価値を切り出して運用したい。 6 利益を生むために変更をするのは当然だが、コストを削減するための変更も発生する。

Slide 7

Slide 7 text

クリーンなコードは変更のしやすさを担保する • Cohesive:凝集性のあるコードは副作用を減らす • Loosely coupled :疎結合なコードはテストが容易である • Encapsulated :カプセル化されたコードは簡単に拡張できる • Assertive :断定的なコードによってソフトウェアがモジュール化される • Nonredundant :非冗長なコードは保守の問題を減らす 7 クリーンなコードを書くこと。 凝集性があり、疎結合で、カプセル化されており、断定的で、非冗長なコードのこと。 これらの要素は、どれも変更の容易さに収束する。

Slide 8

Slide 8 text

奇麗なものは俯瞰しやすく、問題に気づきやすい • 凝集性のあるコードは理解もバグを見つけるのも簡単だ。 それぞれのモジュールは1つの事しか扱っていないからだ。 • 疎結合なコードは呼び出し元を選ばず、再利用やテストが容易だ。 テストが容易であれば、その変更が正しいことが速く証明される • カプセル化されたコードは複雑さを管理し、呼び出し元が呼び出し先の実装の詳細を知らなくても維持ができる。 あとからの変更も簡単だ。 • 断定定期なコードは何ができて何ができないか、何をしないかがハッキリしている。 • 非冗長なコードはバグ修正や変更を1か所で1回やればいい。 8 変更すべき箇所や、問題となっているものがわかりやすい。 奇麗にすればするほどコードの内部品質が私たちを導いてくれる。 もう全人類読んでくれ

Slide 9

Slide 9 text

9 スピードか内部品質か スタートアップのように一刻も早くプロダクトを市場にローンチしなければいけないようなフェー ズもある。 その際、スピードと内部品質はトレードオフとされているが内部品質が悪いソフトウェアは、 結果としてスピードも失うことになるケースが多い Is High Quality Software Worth the Cost?

Slide 10

Slide 10 text

• 開発のゴールを勘違いしている。 • 開発にゴールはない。リリースしてからがスタート • そもそも後から直す暇なんかない。 • 市場やユーザのニーズは変わり続けるから • コードの品質を高く保っていた「にも関わらず」速くコード書けるエンジニアは存在しない。 • 品質を高く保っていたからこそ速いエンジニア 10 スピードか内部品質か ソフトウェアだから片方を犠牲にしてもいい良いというのは間違い。 ソフトウェアも飲食店などと変わらず、速く高品質なものがビジネスに価値を生む。 詳しくはt_wada氏の「質とスピード」を読んでほしい。てか読め。

Slide 11

Slide 11 text

クリーンなコードをどう書くか? 11 クリーンなコードを書くための様々な原則を理解する。 また、そのためにはモジュール間の依存関係を明確にする必要がある。 • SOLID、DRY、KISS、YAGNI、DbCといった原則や各種デザインパターンに従う。 • 原則に従ってできるコードの実態は、単純かつ振る舞いが明確なモジュールの集まりになる。 • これらを達成するための方法論として、近年ではクリーンアーキテクチャやドメイン駆動設計等が提唱されている。 • 方法論の中では、モジュールの責務や依存関係を明確化することが説かれている。 logmi.jp/tech/articles/310537

Slide 12

Slide 12 text

責務と依存関係の明確化 • 依存性関係が単純なソフトウェアは問題点を探す際や変更を加える際に、手を加えるべき箇所やテストをすべきモ ジュールがはっきりすやすい。 • 複雑なソフトウェアであるほど影響が把握しにくく、変更に要する時間がかかる。 • 依存関係を単純化にするには、どのモジュールが何の責務を果たしているのかを明確にする必要がある。 • 責務が明確だと冗長なコードも減っていきテストすべきことがらも明確になっていく。 12 依存関係を明確にするためには、意味のある単位でモジュールを切り離し、 関係性をできるだけ単純に保つ必要がある。 複雑で影響範囲の 大きいモジュール が存在する

Slide 13

Slide 13 text

依存性を意識したモジュール設計の例 • 以下は悪例。設計書の記述をそのまま一行一行ロジックに落とした方が、確かに直感的ではある 13 Customer customer = customerRepository.getCustomer(id); if(customer.getStatus() == ACCOUNT_LOCK || customer.getStatus() == TEM_LOCK || customer.getStatus() == ANTI_SOCIAL) { {無効な顧客用の処理} return; } Account account = accountRepository.getAccount(customer.getAccountId()); if(account.getStatus() == xxxx || …etc ) { {無効な口座用の処理} return; } If … 実装 • 実装の中で行われている顧客の状態の検証は、機能を満たすための検証とビジネス上のルールが混在している • この機能として必要な情報は、あくまで「無効な顧客であるかどうか」である。 • この機能は有効/無効な顧客に対して何を行うかに関心を持つべきであり、ビジネス上の無効な顧客の条件に関しては関心を持つべきでは ない。 • ルールの変更の都度、このモジュールに対しても変更が加えられ、テストも必要になる。 • ルール変更時に検証すべきは新たなルールに従い有効/無効な顧客を判定できているかであり、判定によって提供する機能ではない。 ・顧客のデータを取得する ・顧客の状態を確認する。※以下の場合は無効な顧客とする。 ・アカウントロック ・一時ロック ・反社 ・有効な顧客だった場合、口座情報を取得する。 ・口座の状態を確認する。 ・条件1 ・条件2 …etc ・口座の利用可能残高に応じてxxxxをする。 設計書 機能を満たすモジュールは様々なビジネスルールに依存している。 設計の段階で、機能として提供すべき処理とビジネスルールとして定義すべきものを識別する必要がある

Slide 14

Slide 14 text

• ルールと機能を意識した実装に変更した場合。 14 Customer customer = customerRepository.getCustomer(id); if(customerRule.isInvalidCustomer(customer)) { {無効な顧客用の処理} return; } Account account = accountRepository.getAccount(customer.getAccountId()); if(accountRule.isInvalidAccount(account)) { {無効な口座用の処理} return; } If … 実装 ・顧客のデータを取得する ・顧客の状態を確認する。※以下の場合は無効な顧客とする。 ・アカウントロック ・一時ロック ・反社 ・有効な顧客だった場合、口座情報を取得する。 ・口座の状態を確認する。 ・条件1 ・条件2 …etc ・口座の利用可能残高に応じてxxxxをする。 設計書 • 機能を提供するか否かを判断するのにルールの状態を意識する必要がなくなった。 • 他の機能でもルールを使用することができる。 • モジュールに対してテストすべきことが明確になる。 • ルールに変更があっても、機能を再検証する必要がなくなる。 • ルールに則った実装か否かはルールを満たすモジュールのテストコードが担保する。 依存性を意識したモジュール設計の例 機能とビジネスルールの責務を明確にしモジュールを分割することで、 ルールの変更や、機能として参照するルールの差し替えの際に影響を最小限にできる。

Slide 15

Slide 15 text

• 責務の切り分けにはレイヤ→モジュールといった感じで段階を踏みながら最終的な立ち位置を決めるとよい。 • 上位/同位レイヤ間の依存関係を排除したうえで、依存の方向性が一方向になるようにする。 • 矢印が先へ進むほど処理が単純かつ具体的になっていく。 15 依存関係を意識した設計のポイント 誰が何をするか、どう使われるか、依存の方向性の3つがポイントとなる。 Facebook/fluxドキュメント

Slide 16

Slide 16 text

本当にあった怖い話 • とあるアプリはユーザの認証が必須となる。 • 認証はログインだけでなく、お金の送金など重要な処理の際にも必須となる。 • それ故にあらゆる導線から認証を行うパターンや、その導線の中での再認証がユースケースとして存在していた。 16 複雑でな依存関係がもたらした実話を紹介する。 この実話はテスト段階での話なのでユーザまで被害は波及しなかったが、 開発者からいろいろなものを奪っていった。 OTP service DB 認証回数 service DB ユーザ service DB controller controller controller controller controller

Slide 17

Slide 17 text

本当にあった怖い話 • 責務過多ということは当然、テストコードの量も膨大(groovyで1000行超え) • 一つのユースケースで発生した障害の対応で、別のユースケースのテストコードも修正することもザラ。 • 多数の開発者/テスターの残業とスケジュールの後ろ倒しを招き、時間と金が溶けていった。(もし本番稼働だったら…) 17 明らかに責務過多なモジュールが存在し、障害もそこに集中していた。 障害対応の度にユースケース単位に状態を検証するようなロジックが追加されていき、 設計書と対になるだけの複雑なコードとなった。 OTP service DB 認証回数 service DB ユーザ service DB controller controller controller controller controller

Slide 18

Slide 18 text

ここまでのまとめ • 常に「このコードは変更しやすいのか?」に注意を払う。 • 変更のしやすいコードは、責務と依存関係の明確化が肝である。 • 変更のしやすいコードは、お金を生み出す。 • スピードと内部品質はトレードオフではない。 18 ソフトウェアの「変更」の重要性や、そのための手段としてのクリーンなコードは、 ビジネスに価値のあるソフトウェアという目的のためにある。

Slide 19

Slide 19 text

クリーンなコード以外に変更に耐えうる手段はないか? • 仕様を担保するテスト • ソフトウェアの価値を最大化するアーキテクチャ • ソフトウェアを開発するチーム 19 コードを書くことだけがソフトウェア開発ではない。 コード以外の様々な要素でも責務と依存関係の考え方は応用できないのか?

Slide 20

Slide 20 text

テストはどこに依存しているのか? • 単体テストは実装に依存してはいない。 • 設計の時点でモジュールの振る舞いを定義することで、そのモジュールが果たす責務を担保する単体テストが行える。 • コードをテストするのはなく、テスト通りに動くコードを作るのが理想。(TDD) • 究極のテストコードはそれそのものが仕様書の役割を担うことができる。 20 テストは依存するフェーズによって観点が異なる。 テストコードはプログラムが意図したとおりに動くことを担保するものではない。 https://hnavi.co.jp/knowledge/blog/system_v-model/

Slide 21

Slide 21 text

変更を受け入れるシステムアーキテクチャ • システムはスケールアウトまたはスケールアップすることで対応できるユーザ数を増やすことができる。 • モノリス → マイクロサービスも期待される価値は前述したコードの話と変わらない。 • MSAの本質は「ビジネスとして必要な単位で構成を変えられるアーキテクチャ」 • リアルタイムにビジネスを支えているモジュールをオートスケールさせる仕組み(K8Sやコンテナ)を活用できる。 • 意味のない塊になっていれば、不要なスケーリングやコストがかかる。 • エコシステムの中で最も価値を生み出しているサービスそのものを利用できる。 • 責務過多による単一障害点を作りこまないためにも、システム間でも依存関係は大事な要素となる。 21 アーキテクチャも単純かつ振る舞いが明確なモジュールの集まりにすることが、近年のトレンドと なっている やるべきことや考えるべきことの本質はクリーンなコードと変わらないが、それを果たすことによ るビジネスの価値はクリーンなコードに比べると計り知れない単位になる。

Slide 22

Slide 22 text

誰でもできるチーム → 誰かに任せられるチーム • チームにも変更はつきもの。 • フェーズによって必要となる人数も人材も変わる。 • 他にもライフプランやキャリアプランに応じてメンバーがチームを離れる等、人の出入りがあるのは当たり前。 • 個々のスペシャリティが明確なら、出入りによる変更に対して耐性のあるチームが生まれる。 • 全員が何でもやるチームは出入りの都度、教育コストがかかってしまう。 • メンバーのやることが明確ならば、 チームとしてやるべきことや、やるべきでないこと(他のチームに任せるべきこと)も明確になる。 • 人ではなく、ツールに一任することもできる。 22 皆が浅く広く知を共有しているチームは個々人の価値が最大化しにくい。 なぜならば、知を共有している状態なので、課題解決に際に合意を得るなどのプロセスが生じ、同じ知識 を共有していても知識量や過去の経験などへの暗黙的な依存関係が生まれる。 個々が責任を果たすものを明確にすることで、知識やスキルの依存性をスペシャリスト1人に閉じること ができる。

Slide 23

Slide 23 text

まとめ 23 ソフトウェア以外にも責務と依存関係を明確にする考え方の応用は可能。 また役割が明確なものは俯瞰してみたときに、問題に気づきやすい。 問題に気づきやすいと、それを解決する判断がしやすい。 • 何を責務として全うするのか。 • 何と協働すべきか(しないべきか) この2つを考えるだけでソフトウェア開発がクリエイティブになり、楽しくなる。 変更に強いコード、テスト、アーキテクチャ、チームは見栄えがいいだけではなく、ビジネスに価値を生み出す。 こんな簡単なことを放棄する人にビジネスもエンジニアリングも語る資格はない。 本当に必要なのは、変更に強い人間なのかもしれない。

Slide 24

Slide 24 text

24 APPENDIX

Slide 25

Slide 25 text

25 • 参考文献 – David Scott Bernstein. レガシーコードからの脱却 ―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス – Eric Evans. ドメイン駆動設計 – Fowler, Martin. リファクタリング(第2版): 既存のコードを安全に改善する – Robert C.Marti. Clean Architecture 達人に学ぶソフトウェアの構造と設計 – 質とスピード(2020秋100分拡大版) / Quality and Speed 2020 Autumn Edition https://speakerdeck.com/twada/quality-and-speed-2020-autumn-edition