# 08. Coding ________________________________________ リーダブルコード 5刷 https://www.oreilly.co.jp/books/9784873115658/ 新装版 達人プログラマー 職人から名匠への道 1刷 https://www.ohmsha.co.jp/book/9784274219337/ ※ 達プロは2020年に第2版が出ました。そちらの方が良いでしょう !!注意!! ```text 本記事で紹介する方式は、一般論とは離れているものがある これは以下を前提としているため ・リソースに比較的余裕がある ・Xunit testing可能かどうかを基準に考える - Xunit testingは用法・用量を守って行う必要がある ・ハンドラ/コントローラアクションルートがtry-catchする - ほぼGC対象リソースのみを扱っていて、アンマネージドリソースが主戦場でない - 中負荷以下のWebAPI全般 - 俺たちは、業務ロジックinvalidに対して、業務エラー例外(エラーコードを通知する脱出goto)が欲しいのだ ・「副作用のない純粋な写像算出であり、基本型に対してMix-inする」かつ「ドメインに依存しない」Utilityクラスは肯定派 - Utilityクラスの名称は拡張メソッドする前提なので、○○Extension - 基本型でない場合は、値オブジェクトを用意するのが正解で、これはほとんどの場合ドメインなのでUtilityクラスにしてはならない - 言い換え:Utilityクラスとオレオレフレームワークは表裏一体あるいは同義でなければならず、別プロジェクトでも利用出来る必要がある ``` !!異論や疑問が出てくる可能性あり!! ```text あくまでこの記事全般は独自解釈が色々と含まれている この記事を読むより、上記原書と以下の人たちの意見に耳を傾けたほうが大体いい (コーディングプラクティス~アーキテクチャ~UI理論に絞ってます。 コンピュータサイエンス&Javaより低レベル言語以下、マネジメント/ビジネスモデル以上は非考慮。セキュリティ等も非考慮) - Alan Cooper - Erich Gamma - Martin Fowler - Anders Hejlsberg - 和田卓人(@t_wada) - 松本行弘(Matz) ``` ________________________________________ ## 1. はじめに ________________________________________ 良いコードは ```text 1. 理解しやすい 2. 変数や関数の名前が明確である 3. インデントと整形が統一されている 4. コメントは、「要約」「hack理由」のどちらかだ 5. 制御の書き方がすっきりしている 6. 手続きの塊が明確で、一塊ずつ行われている 7. ライブラリを適切に使い、コード量が少ない ``` 良いコーディングは ```text 当たり前のこと 1. 他の関数を呼び出すとき、契約による設計されていないこと、ドキュメントに記載されていない仕様を期待しないこと - 明示されていない挙動がアップデートにより変わることは普通に起こる 2. デバッグ完了で必要なのは、仮定ではなく証明 計算量オーダーを見積もる - O(k) < O(log(n)) < O(n) < O(n*log(n)) << O(n^2) - nやmの量が推定済み        : O(k) - 二分木探索、二分割探索 : O(log(n)) - 単純なループ : O(n) - ネストしたループ、相関サブクエリ : O(n^2) - 順列調査(P-NPのNP分野) : 膨大 Xunit testingしやすいコードを目指す - 設計とテスト設計を同時に行えればベスト - 同じ引数から同じ結果になるようにする - 副作用がない(イミュータブル)実装 - コンストラクタDIを積極的に活用する - テストコードは以下の2つのどちらかに置くのが良い - 通常方式。テスト対象に対して.Test名前空間 - 補足1:VSで「単体テストの作成」で作った場合、上記の名前空間にしてくれる - 補足2:VSのテストプロジェクト自体の規定の名前空間は上記と異なりプロジェクト名Testsになっている - Unity方式。テスト対象に対してTests/Editor/ - 作りやすい対象、作るべき対象は例えば以下 - 契約による設計:事前条件、事後条件、オブジェクト不変条件 - Validationロジック ウィザードや自動スクリプトは、理解した上で使う - Formデザイナ - Scaffolding - npm installやdotnet restore(パッケージの復元) ``` 良いプログラマーは ```text 1. 無知を認める 2. 責任を負う。過剰な責任を負わないものも明確にする 3. 責任を負ったものの問題に対して、弁解より解決策を提示する 4. 割れ窓を放置しない 5. 周囲の触媒になる 6. 周囲の変化を見落とさない 7. 十分な品質をユーザーと共に定める 8. ソフトウェアに完璧はなく、十分がある事を知っている 9. 知識の投資を戦略的に行う 10. 言いたいことは事前に明確する 11. 聞き手の興味と注目に合ったを内容を話す 12. ドキュメントの見栄えをよくする 13. ドキュメントに対して草稿段階から意見をもらう 14. すぐの回答が難しいことをすぐに伝える ``` 良いナレッジや表現は ```text 1. 単一である - ある業務仕様は単一の資料がマスタである - コードを日本語化しただけのコメントは記載しない - DB正規化不備による2重持ちをしない 2. 異なる表現は、自動生成される - DBスキーマに対するレコードクラス - テストコードに対する仕様リファレンス - APIに対するAPIリファレンス 3. キャッシュやmaterialized viewを初めから採用しない - 問題が明確になってから採用する - 記憶域プロパティ、計算プロパティ、キャッシュプロパティで局所化する 4. 直交している(ソフトウェア直交性。独立すべきものが独立している) - 関係ない者同士の影響が皆無である - チームメンバの役割や権限が重複しない。打ち合わせが少人数でできる - 3layerアーキテクチャなどを採用している 5. 柔軟で、後戻りできる - DRY原則、結合度の最小化、メタデータ化(パラメータ化、ミニ言語実装)の徹底 - ドメインlayer(ビジネスロジックlayer)が独立している - ドメインlayerが特定のGUIやDBベンダーやハードウェアに依存しない - 依存してはならないものには標準ライブラリさえも含むが、トレードオフである - メリット:標準ライブラリのシグネチャが変わった時のインパクトを最小限に抑えられる - デメリット:過剰・余計なインターフェース(疎結合)が増え煩雑化する ``` 良いプロジェクトアプローチは ```text 1. 曳光弾(捨てない)とプロトタイプ(捨てる)のどちらを行うのかが明確だ 2. ドメイン(問題領域・課題領域・顧客業務領域)言語を育てる - ドメインに登場する用語を統一・整理する - 名が体を表すようにする 3. 独自のミニ言語を用意することが多い - 構造表現そのものはcsvやjsonでもよい - 可読性がある程度良いものにしておくと良い 4. 大雑把でいいのか正確に必要なのか理解した上で、なんでも見積る - サイズ、速度、増加量、アクセス量、ピーク、時間、スケジュール - 見積計算が正しいのにおかしな答えが出たら、問題理解や想定がおかしい - 慌てて回答しない 5. 顧客の隠されたニーズを引き出す 6. 大抵いらない機能がある。いらない機能を削って仕様変更の時間を確保する ``` 良いプログラマーが使いこなすツールは ```text 1. プレーンテキスト 2. シェル 3. 愛用のテキストエディタ 4. ショートカットキー、正規表現、grep 5. ソースコード管理 6. シェルスクリプト、python3、perl、rubyいずれか 7. 自前コードジェネレータ ``` 良いデバッグアプローチは ```text 1. デバッグ時にパニックに陥らないように努める 2. あらゆるエラーや警告を無視しない 3. バグの再現から始める 4. ロギングやステップ実行を活用する 5. スタックトレースを活用する 6. ゴムのアヒルに問題を説明する 7. 大体の場合、我々のコードがまずい ``` 良い設計とは 開発現場で役立たせるための設計原則とパターン https://www.youtube.com/watch?v=qUKpgGFGHVo ```text 設計とは『「課題や要件」を「最適」な「選択肢」で構造化や分割する』こと 1. 最適か(設計原則による評価) - シンプルにして過剰設計しない。「課題や要件」を基準に変更する気がない場所を決める(YAGNI, KISS) - 単一責任原則(SOLIDのS) - 責務? - あるクラスを変更する理由が2つあってはならない - S1という仕様変更でクラスXが変わる、S2という仕様変更でもクラスXが変わる、ではだめ - 開放閉鎖原則(SOLIDのO) - 機能追加するときに、既存クラスに影響がないようにできているか? - バグの訂正や機能訂正をした際、それを利用しているクラスを書き換える必要がないか? - Liskov置換原則(SOLIDのL):略。常に守られるべき - インターフェース分離の原則(SOLIDのI):interfaceはダブルミーニングになってしまっていないか? - 依存性逆転の原則(SOLIDのD):具象クラスに依存するより、interfaceに依存したい - 高凝集度かつ低結合度(GRASP、ソフトウェア直交性など複数の原則で言及されている) - 例:Customerを操作する(例:フィールドを書き換え作り直す)手続きはCustomerに持たせましょう 2. 選択肢(手法) - デザインパターン - その他様々な設計パターン 3. 課題や要件 - 課題や要件と可能性(仕様変更・仕様追加が起きうるところはどこか。変更する気がないコードはどこか) 上記が意味すること - 「課題や要件」が何かを見極めないと、設計原則の評価ができない - 「課題や要件」が根本から変わると - 良い設計か悪い設計の評価は変わる - 設計構造を変える必要が出てくる ``` ________________________________________ ## 2. コーディング規則 ________________________________________ ### 2.1. 大原則と命名規則 プログラムの大原則 ```text 1. 1ファイル1主クラス 2. 自動生成されるコードの命名は、自動生成結果を優先する 3. インデントスタイルは、プロジェクトの共通設定に従う 4. UPPER_SNAKE:定数 5. UpperCamel:クラス等。C#は定数・private・引数・ローカル以外全て 6. lowerCamel:その他 - alllowercase : Javaにおけるpackage名(=フォルダ名) 7. Boolean変数や関数は、Is○○、動詞三単現s、Can○○。flgは避ける 8. Boolean以外の変数やプロパティは名詞 9. Boolean以外の関数は動詞 10. イベント変数は、現在分詞または過去分詞 11. コレクションは、複数形かList・Array・Dic・Assoc・Queue・Stack接尾辞 12. Get関数はそのインスタンスの状態を変更しない 13. Set関数はそのインスタンスの状態を変更する 14. New関数、Create関数はインスタンスを生成する 15. インターフェースはI接頭辞 16. 抽象クラスはBase接尾辞 17. 型パラメータはT接頭辞 18. async関数はAsync接尾辞 19. ループ変数はiまたはi接頭辞。j、kとしてはいけない 20. (オプション)_lowerCamel:privateフィールド 21. (オプション)C#、VB.NET、Javaにおいて、this.やMe.を常につける 22. 名前空間は、企業名やサークル名.製品名 23. そのオブジェクトの意味的な状態や性質を変更するメソッド名は、objを主語として能動態が好ましい NG : obj.SetActive() OK : obj.Activate() 24. そのオブジェクトの意味的な状態や性質を返すプロパティ名は、objを主語としてSVCが好ましい NG : task.Completed OK : task.IsCompleted 25. objを主語にしづらいメソッドや処理は、そもそも所属するべきクラスを間違えている可能性が高い(GetX, SetXを除く) ``` Googleが公開しているガイドの場合はこちら https://github.com/google/styleguide プログラムの禁則 ```text - foreach内で、ループ中に要素数が変動する処理をしてはならない - コレクションを返すメソッドで、nullを返してはならない(要素0オブジェクトを返す) ``` SQLの大原則 ```text 1. キーワードは大文字 2. テーブルや列は小文字(ただし大文字小文字を区別している場合、物理名に合わせる) 3. カンマ区切りでのテーブル結合は使わない(記述ミスでクロスジョインになる) 4. Order byしていないSQLは順序を想定してはならない 5. サブクエリ内のOrder byは避ける(ベンダーによって挙動が違う) 6. インデントスタイルは、プロジェクトの共通設定に従う。例としては下記 - (オプション)結合される側が左辺、結合する側が右辺 -- A5:SQL Mk-2 自動整形 SELECT t1.col_a , t1.col_b , t1.col_c , t1.col_d FROM table_a t1 LEFT JOIN table_b t2 ON t1.col_a = t2.col_a AND t1.col_b = t2.col_b WHERE t1.col_a = 'x' AND t1.col_b = 'y' ``` 命名規則の付則:UIハンガリアン ```text 1. Button : btn○○ 2. CheckBox : chk○○ 3. ComboBox : cmb○○ 4. Dialog : dlg○○ 5. Form : frm○○ 6. Label : lbl○○ 7. RadioButton : rdo○○ 8. SelectBox : slct○○ 9. TextBox : txt○○ ``` 命名規則の付則:紛らわしい変数には、単位接尾辞をつける ```text 16進数   : ○○_hex 複数貨幣   : ○○_yen、○○_usd 税抜き税込み : ○○_extax、○○_intax 年月     : ○○_ym 年月日    : ○○_ymd 単位(時間) : ○○_ms、○○_sec、○○_day 単位(速度) : ○○_bps、○○_kbps 単位(サイズ): ○○_kb、○○_mb 要検証値   : ○○_untrusted 文字コード変換: ○○_sjis、○○_utf8 ``` 命名規則の付則:誤解されない名前をつける ```text - 例1 : filterよりselect、excludeを好む - 例2 : limitよりmin、maxを好む - 例3 : checkFooより、isBar、canBar、barExistsを好む ``` ________________________________________ ### 2.2. インデント、コメント、制御 インデントと整形 ```text 1. インデントを統一する。自動整形を活用する 2. (可能ならば)改行を揃えてシルエットを合わせる 3. (可能ならば)縦を揃える 4. 関数内の手続きの塊ごとに空行と要約コメントを入れる ``` コメントの書き方 ```text 1. コメントすることは、次のどちらか - 手続きの塊に対する要約 - Hack理由 2. 思いついたままに書く 3. 指示語を具体的な単語に置き換える 4. 上がるのか下がるのか、増えるのか減るのか、などをはっきり書く * コードにWhatとHowが書かれている。OverviewとWhyはコメントにしか書けない ``` 関数内の手続き ```text 1. 比較式は、調査対象を左に置く 2. if-then-throw/returnパターンを積極的に使用する 3. ネストを浅くできないか考える(try-catchを除いて3段程度が目安) - 付則:単クラス内private関数のスタック数もルートメソッド含めて3段程度が目安 4. 条件を反転して考え、単純にならないか考える 5. 記述を減らすために、ローカル変数を宣言して入れることも視野に入れる 6. 制御フロー変数を削除できないか考える 7. 大半の変数は、1度だけ書き込まれるようにする ``` 関数の分割 ```text 1. 3行以上にわたる数学計算は関数にする 2. 純粋なUtility操作(ライブラリに○○操作があればなぁ)を関数にする 3. やりすぎない ``` 関数自体の書き方 ```text 1. 関数が行う手続きの塊をすべて書き出す 2. 手続きの一部が前述の「関数の分割」の条件を満たしそうなら、関数を分ける 3. 残った手続きの塊を、インデントと整形に従って記述する ``` 書いている関数が分かりづらい時 ```text 1. 手続きの説明をコメントに書き出す 2. 手続きの説明を整理する 3. プログラムに反映する ``` ________________________________________ ### 2.3. 関数間やOOPの原則 並列処理可能化の推進 ```text ※ クラスレベル、スレッド(プロセス)レベルに話は分かれる ※ クラスレベルの並列処理の前提制約は、以下のような当たり前のことになる [クラスレベル] 1. 複数のインスタンス間で、「暗黙」に状態を共有しない 2. 複数のインスタンス間で、状態を共有する必要がある場合「明示的」にする - シングルトン - 初期化保証の確約 - 並列処理に対する、大域変数の保護 - 並列処理に対する、静的変数の保護 3. コンストラクタと初期化が別タイミングになるのは可能なら避ける [DBトランザクションレベル or スレッドレベル] 1. 楽観ロックか悲観ロックを実装する - 通常のDBトランザクションであれば、楽観ロックが定番 - スレッドレベルのC#のlockステートメントなどは、悲観ロックに相当する 2. 1DBトランザクション/lockになる必要のある代表的なパターン - read-modify-write - check-then-act ``` OOPの原則 ```text - 多態性以外の目的で継承を使ってはならない - 継承(is A)は集約(has A)で置き換えられないか検討する ``` ________________________________________ ## 3. イディオム ________________________________________ ### 3.1. 契約による設計 と Assertマクロ 要約:現実解としては、if-then-throw/return、Assertマクロだけ頑張る ```text [本来あるべき姿] 事前条件       : requres。メソッド開始時に保証されるべき関係式 事後条件       : ensures。メソッド完了時に保証されるべき関係式 オブジェクト不変条件 : invariants。オブジェクトが常に保証する関係式 [現実解としてのイディオム] 1. if-then-throw/returnパターン 2. 速度に不安が残る部分は、Assertマクロで実装する - System.Diagnostics.Debug.Assert 3. 事前条件、事後条件、オブジェクト不変条件はテストコードで伝える ``` ________________________________________ ### 3.2. 例外設計とロギング設計 例外処理とロギングのベストプラクティス https://codezine.jp/article/detail/1581 Why is “log and throw” considered an anti-pattern? https://stackoverflow.com/questions/6639963/why-is-log-and-throw-considered-an-anti-pattern エラー コードを返す代わりに、例外をスローする https://learn.microsoft.com/ja-jp/dotnet/standard/exceptions/best-practices-for-exceptions#throw-exceptions-instead-of-returning-an-error-code 前提背景 ```text [本来あるべき姿] 例外的なことのみに例外を使用する。例外は握りつぶされない 方針1. ビジネスロジックの戻り値を、リターンコードと本来の戻り値を返す複合型にする 方針2. ビジネスロジックを持つインスタンスがエラーイベント変数メンバを持ち、発生させる [現実解としてのイディオム] 1. 全ての入力はバリデーションされるべきだ 2. try=正常系、ビジネスロジック例外catch=準正常系、その他例外catch=異常系とする 3. DBトランザクション制御はドメインlayer(model)ではなくアプリケーションlayer(controller)の責任だ [補足] 正常系=ハッピーパス、準正常系=拡張シナリオ、異常系=システム異常と定義 [補足2] 古い文献をあさると業務エラーは例外にするべきではない、という記事が散見される これは「リソース面」の問題、「例外的なことのみに例外」を守ること、「デストラクタ・メモリリーク」がプラクティスの前提に横たわることによる 設計の主戦場がデストラクタ・リソース解放保証ではなく、GCありドメインモデルになった現在では、便利なgotoとしての業務エラー例外が生きてくる (この方式の原点を辿るとJavaの言語仕様と例外の推奨に行き着く。そもそも検査例外という文法の存在。やはりGCと業務エラー例外は表裏一体である) 組み込みやC/C++言語をやってきた人にとっては、業務エラーを例外でthrowする設計はかなり異質に映るかもしれない ``` 例外設計 ```text 1. try catchは以下の4か所でのみ全体を囲むように使う 1. ハンドラ/コントローラアクションルート 2. ビジネスロジックルート 3. DBトランザクションの開始から終了 4. ファイルやネットワークなど手動解放が必要な資源(C#の場合は代わりにusing) 2. try句とcatch句を正常系、準正常系、異常系に対応させ、finally句も書く 1. DomainLogicExceptionを定義し準正常系とし、準正常系catch句で受け取る 2. try句先頭、準正常系catch句先頭、finally句末尾でINFOロギングする 3. 異常系catch句の先頭でERRORロギングする 3. throw exによる再throwは禁止(スタックトレースがリセットされてしまう) 4. finally句を途中脱出する記述は禁止(Javaは警告、C#はコンパイルエラー) 5. ファイルやネットワークなど確実なリソースの開放が必要な場合は、必ず適切な考慮をする 1. usingがある言語では、必ず使用する 2. usingがない言語では、リソースの開放を確実に行う 6. 自作Exceptionクラスでは、継承元の全てのコンストラクタに対応するコンストラクタをそれぞれ作成する 7. 自作Exceptionクラスでは、内部にロギング機構は組み込んではならない 8. Javaにおいてthrows句を指定する場合は、具体的な例外を指定する ``` ロギング設計 ```text 1. ロギングは以下を出す 1. ログレベル 2. 時刻 3. スレッド番号 4. アプリケーション内のUserId 5. クラス完全名または"SQL"(logger名) 6. メソッド名 7. 行 8. 固有メッセージ - Begin、End、Invalid、Exceptionいずれか - そのあとに続く詳細 - Begin → 引数 - End → 戻り値 - Invalid → MSG_111 XXXが入力されていません。 - Exception → MSG_123 システムエラーが発生しました。 例外メッセージとスタックトレース 2. ログレベルはFATAL、ERROR、WARNING、INFO、DEBUGを基本とする 1. FATALは、プログラムを停止させるべき不具合や確実に成功する必要がある処理の失敗により、SE作業が必須になる場合に出力する 2. ERRORは、システムエラーが発生した場合に出力する。異常系エラー、SQLエラー 3. WARNINGは、自由定義。例:楽観ロック失敗発生、デッドロック発生 4. INFOは、前述の設計の条件で出す 5. DEBUGは、自由定義 - 強く推奨:外部システム間やサブシステム間のリクエスト内容/レスポンス内容のBodyであるJSON等 - 全SQL - 全httpリクエスト 3. logger名にクラス完全名を指定すると良い - 設定ファイルの書き換えで「特定の名前空間だけDebugレベルにする」などが可能になる 4. Aopライブラリを導入することで、ERROR、INFOは自動的に出力できると良い 5. ログ出力フォルダはリリースフォルダに含めない - デプロイやリリース時、リリースディレクトリ丸ごと置き換えで済むように、logやアップロードファイルなどトランザクションデータ的な情報はリリースディレクトリ外に置く ``` log4net例 ```text [定義] "%-5.5level %date{yyyy-MM-ddTHH:mm:ss,fffzzz} [%5.5thread] [%8.8property{UserId}] %logger %M %L - %message%newline" [サンプル] DEBUG 2019-07-18T09:18:24,009+09:00 [ 13] [10021519] SQL ReaderFinish 44 - [ 8ms] select * from accounts INFO 2019-07-18T09:18:24,459+09:00 [12345] [10021519] TeamXX.ProjectXX.Controllers.UserController AddUser 102 - Begin { 1, "Alice" } INFO 2019-07-18T09:18:24,459+09:00 [12345] [10021519] TeamXX.ProjectXX.Services.UserService AddUser 62 - Begin { 1,"Alice" } FATAL 2019-07-18T09:18:24,459+09:00 [12345] [10021519] TeamXX.ProjectXX.Services.UserService AddUser 68 - Exception MSG_123 システムエラーが発生しました。 System.IO.FileNotFoundException: ファイル 'C:\this_is_unknown_path' が見つかりませんでした。 ファイル名 'C:\this_is_unknown_path' です。'C:\this_is_unknown_path' 場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) 場所 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) 場所 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) 場所 System.IO.File.Open(String path, FileMode mode) 場所 TeamXX.ProjectXX.Services.UserService.AddUser 場所 c:\users\john.doe\repos\TeamXX\ProjectXX\Services\UserService.cs:行 14 場所 TeamXX.ProjectXX.Controllers.UserController.AddUser 場所 c:\users\john.doe\repos\TeamXX\ProjectXX\Controllers\UserController.cs:行 14 ``` ________________________________________ ### 3.3. リソースの確保と解放 ```text [すぐに解放できる場合] 1. 確保したルーチン内で解放する 2. 確保した順番と逆にの順番で解放する [すぐに解放できない場合] 誰が開放するのか、全体として一貫性のあるルールを設ける 方針1. トップレベル構造が解放された時に連鎖的に解放する 方針2. トップレベル構造が下位構造にnull以外が入っている限り、解放できない 方針3. 自殺できるように、ガベージコレクションを自前実装する ``` ________________________________________ ### 3.4. API(JSON)設計 シリアライズ・デシリアライズしやすい形式にする ```text OK 1. シンプルな配列 2. シンプルな連想配列 3. 連想配列メンバや不確定型メンバを含まないオブジェクト NG 1. 連想配列メンバや不確定型メンバを含むオブジェクト 理由 Serialize、Deserializeがめんどい ``` ________________________________________ ### 3.5. ソフトウェア原則 butunclebob.com http://butunclebob.com/FrontPage The Clean Code Blog http://blog.cleancoder.com/ ソフトウェア原則は何のために ```text 1. 高い直交性(依存関係が少ない) 2. 低い結合度(依存関係が少ない) 3. DRY(ビジネスロジックや仕様はマスタとなるものがただ1つ) ``` SOLID - Single, Open/closed, Liskov, Interface, Dependency ```text 1. クラスに仕様変更が起こる理由は、1軸であるべき Single responsibility principle - クラスのダブルミーニングはやめよう 2. インターフェース相互作用が仕様であり、インターフェースの実装が拡張なのだ Open/closed Principle - 5番と表裏一体である 3. 派生型はその基本型と置換可能でなければならない Liskov substitution principle - 普通 4. 一枚岩のインターフェースより、複数のインターフェース Interface segregation principle - インターフェースもダブルミーニングはやめる 5. 上位モジュールは下位モジュールの具象クラスを直接参照せず、インターフェースで仲介するべき Dependency inversion principle - 再利用性が高い=抽象度が高い=具象実装を直接利用しない - DDD + DIP = ヘキサゴナル ``` 有名な格言 ```text デメテルの法則 Law of Demeter - 原則、孫メンバを使うな。使っていいのは以下のみ - 自分のメンバ - 自分のメンバのメンバ - 引数のメンバ - newしたローカル変数の内容のメンバ - ただし、閉じた操作が実現されているメソッドチェーンはOK 3Virtues、YAGNI、DRY、KISS、CQS、PIE 1. 「手作業」を嫌悪し「手戻り」を嫌悪し「我こそ最強のプログラマ」と思え 3Virtues 2. 「無駄と余計」は嫌え YAGNI - You Aren't Gonna Need It! 3. 「ビジネスロジック・仕様・情報源」は重複・競合・分散するな DRY - Don't Repeat Yourself. 4. 「シンプル」を好め KISS - Keep It Simple, Stupid! 5. 「変更」と「問い合わせ」は混ぜるな CQS - Command Query Separation. 6. 「意図」を好め PIE - Program Intently and Expressively. 補足 - YAGNIは難しい。ハードコードはダメだが、余計な抽象化はダメ - YAGNIは難しい。依存逆転インターフェースするか、単純な関連でよいのか判断基準がない - DRYは明確。共通化という単語は不適切 - 手続きを共通化するな、仕様をDRYせよ - DRYは明確。無理矢理にシグネチャやインターフェースを合わせて共通化するのは間違い - 他人の空似を抽象化するな。仕様を抽象化せよ ``` ________________________________________ ### 3.6. パラメータ化を推進 ```text [環境のパラメータ化] - CSSの活用 - 設定ファイルor設定マスタ化 - 初期値 - 規定値 - デフォルト値 - DBエンジンの種類の切り替え容易化 - ベンダー依存のSQLが多発するので、開発中盤移行になって切り替える前提はない - ただし、接続パラメータやDBトランザクション処理を抽象化することには意味がある [ビジネスロジックのパラメータ化] - 詳細は外に出す - コードのWhatとHowの内、Howをパラメータ化する - 独自の簡易言語やスクリプト言語の組み込み ``` ________________________________________ ### 3.7. イベント駆動 イベント駆動の別名たち パターン名 |srcまたはbrokerへのバインド |raise |handler ----------------------|---------------------------------------------------------------------|------------------------------------------------------|------------------ Event-EventHandler |fooEvent += bar; |fooEvent(args); |bar(sender, args) {} Action-ActionListener |btn.addActionListener(lsnr); |btn.doClick();
(内部的にはlsnr.actionPerformed(e);)|actionPerformed(e) {} Subject-Observer(Gof) |subject.atattch(obs); |subject内部でobs.update(args); |update(args) {} Publish-Subscriber |Sub側
client.on_message=on_message
client.subscribe(topic, qos)|Pub側
client.publish(topic, msg, qos, retain) |def on_message(client, userdata, message): ※ それぞれ上から順にC#のeventデリゲート全般(いわゆる関数ポインタの親戚)、JavaのSwingのButton、Gof、PythonのEclipse Pahoによる例 ※ Gofは原著に基づきupdate(args)としているが、大抵のハンドラ名はnotify(args) Reactive Extension ```text Observerの発展形で、以下の3つのメソッドを持つObserver - OnCompleted(類似内容:always, finally) - OnError(類似内容:fail, catch) - OnNext(類似内容:done, try中の後続処理) ※ Observable側の追加メソッドの名前は慣習上subscribe()である。主語述語がおかしいが気にしない ``` 用語の意味補足 ```text Subject : 直訳すると主体。イベント発生元。特にRx文脈においてはObserverかつObservableである便利クラスのこと Observer : 直訳すると観察者。リスナ。イベントハンドラ Observable : 直訳すると観察されるもの。イベント発生元。Rx的に言えば入力あるいはコレクション Subscribe() : 直訳すると購読。pub/subやRxでイベントハンドラをバインドする事 ``` ________________________________________ ### 3.8. 構造化 3layer分離の要点 ```text 1. ビジネスロジックは明確に他のlayerと分ける - プレゼンテーション&アプリケーションロジックlayer:描画制御、描画制御のためのデータ構造など - ドメインlayer :業務ルール、業務ドメインと言われる部分 - データインターフェースlayer :DBなどからデータを取りだしプログラム上でレコードを表現するまで 2. dbからのデータ取得はorm的である。ビジネスロジックごとのSQLファサード(repositoryっぽいもの)を定義する 3. DataGridViewにバインドしたいなどの特別な理由がない限り、.NETのDataTable(table data gateway)を使用しない ※ 3ないし4layer分離は、方針によってどのlayerがどの範囲を行うかが異なる ``` プレゼンテーション+アプリケーションロジックlayer ```text このlayerの関連用語や別名 - View、ViewModel、アプレット/Spa、UI、UIロジック - Controller、UIイベントハンドラ - 狭義のアプリケーションlayer、pofeaa/aagサービスlayer 概要 1. このlayerは、UIパーツにデータをバインドする部分(ViewModel)やUIロジックを含む 2. UIに関する処理は、大きめの論理パーツ単位ごとにまとまった位置に宣言・定義する 3. 典型的な登録画面は、CRUDごとに画面を分けるか、Search/Create/Editのステートで明確に分ける 4. 上記ステートを採用する際に状態数が足らない場合、4ステート目を作らず、ステートをネストする ※ 補足:例えば「前月比売上増加10%以上を赤字で表示する」の「前月比売上増加10%」をUIロジックに任せるかビジネスロジックかは判断が分かれる ``` ドメインlayer ```text このlayerの関連用語や別名 - Model、ビジネスロジックlayer、Service、Entity、ドメインロジック、ビジネスロジック、Repository 概要 1. このlayerは、Controller、UIイベントハンドラは含まない 2. ビジネスロジックと狭義のアプリケーションlayerやpofeaa/aagサービスlayerの関係は以下の通り - MVCのCとしてのアプリケーションlayerはビジネスロジックとして含まない認識が多い - DDDのアプリケーションlayerは、ビジネスロジックに含まれる - いずれにしても、ドメインlayerとは別のlayerである。前述の通り、今回はプレゼンテーションlayer+狭義のアプリケーションlayerと分解した 2. ある出力処理は、データ用意、データ整形、出力内容生成、出力処理の4フェーズに分けて実装する事が多い 3. God、Super、Processor、Managerという名前のクラスは、責任が多すぎる。クラスを分割する 4. Validationは、ValidateInput(入力チェック)、ValidateIntegrity(整合性チェック)を意識する ``` データインターフェースlayer ```text このlayerの関連用語や別名 - データlayer、データアクセスlayer、DAO、データベースlayer、データソースロジック ``` モジュールという言葉 ```text 1. 採用するフレームワークの作法を優先する 2. 上記制限がない場合、「モジュール = dll = 名前空間 = フォルダ = package」がおススメ ``` Dependency Injection(DI)を採用する ```text 一言で説明すると 「あるインスタンスが利用する別インスタンスは、自身のインスタンスメンバの中でnewしてはならない」 ・Tddやxunit testingを視野に入れる場合、必須なデザインパターン ・"全部static関数ならxunit testingが楽"の考え方をoopに拡張した考え方である ・受け取るタイミングの方針でconstructor injectionとsetter injectionと呼び分ける ・Constructor injection + static create関数が強く推奨される ・DIコンテナやDI文脈で語られるIoCコンテナは、これとは全く別の概念である ・別件:DIコンテナの誤った使い方であるDIロケーターはアンチパターンである ``` ソースコードは機能ごとに分類した方がいいのか? ```text 業務仕様の投影であるドメインのソースコードに関してはそのような構成にした方が良い ルートディレクトリは全業務で使用されるドメイン、特定のドメインでしか利用されないドメインは末端ディレクトリ ``` ________________________________________ ### 3.9. グローバル変数の古今 グローバル変数の問題点 ```text - 不注意な変更 - エイリアス問題(グローバル引数問題) - 再入問題 - 再利用性の妨げ - C++における、初期化手順が一定でない問題 - モジュール性の低下 - 直交性の低下 - バグの原因になった場合、発生原因の特定が困難 - 全ての機能が関わっている可能性が出てくる - Xunit testingの複雑化、事実上の不能化 ``` グローバル変数の問題点を部分的に解消したパターン ```text ※ いずれも使い方を誤ると、グローバル変数と同様の問題点が再発する - DB - シングルトン - staticフィールド - ホワイトボード(黒板モデル、オンメモリDB) - AOP - DIコンテナ ``` ________________________________________ ## 4. 普遍的な実装指針 ________________________________________ ### 4.1. チーム開発留意点と複数拠点 チーム開発の前提 ```text 1. テーブル単位のDAO(data mapper/repository)は、全体を管理・レビューする人がいないと負債となる 2. 複数拠点開発において、個々のサブシステムを別個に作れるようにする(exeを分ける、など) 3. 複数拠点開発において、DAOやRepositoryの共通化を諦め、拠点間でのクローンコードや重複実装を許容する ``` ________________________________________ ### 4.2. 方式設計 ```text 1. 要件を満たすハードウェア、ソフトウェア、インフラの構成 - 信頼性・可用性①:インフラ冗長化構成 - 信頼性・可用性②:セキュリティ検知、インフラ検知、障害検知 - 信頼性・可用性③:LB拡張、ディスク拡張 2. 処理形態 - オンタイム処理のAppサーバアーキテクチャ - MVCの大まかなスタイル - DB接続の管理方式 - ログイン情報の管理方式 - 例外仕様 - ロギング仕様 - バッチ処理のAppサーバアーキテクチャ - Re-runの制御や解決 - DB接続の管理方式 - ログイン情報の管理方式 - 例外機構 - ロギング仕様 3. サーバログ、Appログ(操作/エラー/アプリケーション/システム)定義 4. 開発、検証、本番環境の有無、定義 5. 共通UI - ダイアログ表示 - 削除されたトランザクションデータへのURL直接アクセス時の挙動 6. 実装や設計の統一方針(DB周り) - deleted列の扱い - 設計資料はinner joinとleft joinのどちらをデフォルトとするか - マスタとトランザクションデータの関係 - どういう時にマスタを直接参照し、どういう場合はマスタのコピー列を保持するのか - 参照されているマスタを変更した場合、トランザクションデータはどうなるべきか - ヘッダ&明細のマスタを更新した際、トランザクションデータとの関係はどうなるべきか - Delete&insertなのか、deleted update&mergeなのか - 期間を持つマスタの設計 - テーブル名はビジネスロジックに引きずられるべきか、純粋なテーブルの役割を表すべきか - バッチから呼び出されるストプロの(主にデバッグ・ロギング・ジョブネット対応のための)共通シグネチャ - DBのパッケージの単位 - ストプロ用ログテーブルの用意 - コネクションプールとストプログローバル変数の再初期化の徹底 - Cursorの明示的なopenを禁止するかどうか 7. テスト周り - XUnit testingを設定するか - C0カバレッジ基準を50%~80%のどこに設定するか - UT、ITa、ITb、UTの観点の明確化 - ITaにおいて、基本設計レベルの正常系表示・異常系表示の内、両方最低1つずつは確認したか - 負荷試験と性能要件は定義されているか - セキュリティ試験とセキュリティ要件は定義されているか 8. 非GUI処理 - ジョブネットからキックと応答できるような方式の確立 - 例:.bat -> コンソールアプリ or SQLPlusキック ``` ________________________________________ ## 5. リファクタリング ________________________________________ レガシーコード改善ガイド 8刷 https://www.shoeisha.co.jp/book/detail/9784798116839 リファクタリングの原則 ```text 1. 機能追加や修正とリファクタリングを同時にしない 2. テストコードを用意せずにリファクタリングを始めない ``` ________________________________________ ## 6. パッケージ ________________________________________ パッケージングのベストプラクティス - 実行ファイル群と、リリース後に蓄積されるファイル群(キャッシュは除く)は明確に分ける - アプリケーションのデプロイをdelete©で済むように構成しておく - リリース後に蓄積されるファイルの例 - logフォルダ - アップロードファイル - ファイルベースのデータストア(Pukiwikiなどで見られる)