ハードコードされた値と防御的な設計対YAGNIの削除


10

最初に少し背景を説明します。Age-> Rateからルックアップをコーディングしています。年齢層は7つあるため、ルックアップテーブルは3列(From | To | Rate)で7行になります。値はめったに変化しません-それらは3年間同じままであった法定レート(最初と3列目)です。このテーブルをハードコーディングせずに保存する最も簡単な方法は、CSVを含む単一のテキスト値として、グローバル構成テーブルのデータベースにあると考えました(「65,69,0.05,70,74,0.06」は、 65-69および70-74の階層が格納されます)。比較的簡単に解析して使用できます。

次に、これを実装するには、新しいテーブル、それをラップするリポジトリ、リポジトリのデータレイヤーテスト、CSVをテーブルに展開するコードの単体テスト、ルックアップ自体のテストを作成する必要があることに気付きました。このすべての作業の唯一の利点は、ルックアップテーブルのハードコーディングを回避することです。

ユーザー(現在、ルックアップテーブルを直接使用している-ハードコピーを見る)と話すとき、「レートは決して変わらない」という意見がほとんどです。明らかにそれは実際には正しくありません-レートは3年前に作成されただけであり、過去に「決して変化しない」という変化の傾向があったため、これを防御的にプログラミングするには、ルックアップテーブルを絶対に保存しないでくださいアプリケーション。

YAGNIだと思う時を除いて。私が実装している機能は、レートが変更されることを指定していません。レートが変更されても、レートが変更されることはめったにないため、メンテナンスが考慮されることすらありません。また、レートの変更と更新されたアプリケーションとの間に遅延があった場合に影響が及ぶほどの重要な機能ではありません。

ルックアップをハードコーディングしても値が失われることはほとんどないと判断し、この特定の機能への私のアプローチについてあまり心配していません。私の質問は、専門家としてその決定を適切に正当化したかどうかです。値をハードコーディングすることは悪い設計ですが、アプリケーションから値を削除するという面倒を見ると、YAGNIの原則に違反するようです。

編集質問を明確にするために、私は実際の実装について心配していません。私は、素早く悪いことをして、YAGNIと言って正当化できるか、または最も防御力のある、努力の行き届いたアプローチをとることができるか心配しています。プロのプログラマーとして、欠陥があるとわかっている設計を実装するという私の決定は、単にコスト/利益分析に帰着しますか?

編集個人のデザインの選択に帰着すると思うので、すべての回答は非常に興味深いものでしたが、@ Corbin@EZ Hartが私が質問で考慮していなかったことを提示するので、最良の回答があったと思います。

  • データベースに移動することによる「ハードコードされた値の正しい削除」と、ハードコーディングを使用した「YAGNIの効率的な適用」の誤った二分法。ルックアップテーブルをアプリの構成に追加する3番目のオプションがありましたが、これは正しい方法のオーバーヘッドを発生させず、YAGNIの効率がありません。私たちは通常、どちらか一方または両方の決定に限定されず、コスト/利益の決定に帰着します。
  • コード生成により、ハードコードされた値をデータベースに移動するオーバーヘッドを削減できます。また、CSVをテーブルに処理するという過度に設計された決定も削除されます。これにより、ルックアップメソッドの基本的な要件が変更された場合、生成されたコードに長期的なメンテナンスの問題が追加される可能性があります。これはすべて費用対効果分析に影響を与えるだけであり、その自動化が利用可能だったとしたら、このようなものをハードコーディングすることさえ考えていなかっただろう。

@Corbinの答えを正しいとマークしています。これは、開発コストの想定を変更するためです。近い将来、いくつかのコード生成ツールを武器に追加する予定です。


レートが変化し、すべてがハードコードされた値である場合、レートが変化すると履歴レコードの計算が台無しになる可能性があることを忘れないでください(クライアントが何を言っているかに関係なく、そうなります)。
アンディ

回答:


6

開発プロセスに欠陥を見つけました。正しいことをするのが面倒なとき(テーブルを作成する、リポジトリ、テストをフラット化する、テストをフラットにする...)、開発者はそれを回避する方法を見つけるでしょう。これには通常、間違った行為が含まれます。この場合、アプリケーションデータをアプリケーションロジックとして扱いたくなります。しないでください。代わりに、開発プロセスに便利な自動化を追加してください。CodeSmithを使用して、誰も書きたくない退屈な定型コードを生成します。テーブルを作成したら、CodeSmithを実行し、DAO、DTO、およびそれぞれの単体テストのスタブを生成します。

使用しているテクノロジーによっては、同様のオプションが必要です。多くのORMツールは、既存のスキーマからモデルを生成します。Railsの移行は逆方向に機能します-モデルのテーブルです。動的言語でのメタプログラミングは、ボイラープレートコードの排除に特に優れています。複雑な多層アプリケーションがある場合は、必要なすべてを生成するためにもう少し努力する必要がありますが、それだけの価値があります。「うわー、これは首の痛みだ」という気持ちにとらわれて、正しいことをやめてはいけません。

ああ、追加の処理(CSV)を必要とする形式でデータを保存しないでください。これは、注意とテストを必要とする追加の手順を追加するだけです。


Railsのマイグレーションがモデルからテーブルを作成するとは言いません... Railsのマイグレーションはテーブルへの変更を記述します。移行を実行すると、データベースが変更されます。テーブル構造に一致するモデルプロパティが実行時に作成されます。
ケビンクライン

これは私にとって興味があり、私は最初の作業の一部をそのレベルまで自動化することを考えていませんでした。そして、その自動化があれば、CSVを使用して時間を節約するのではなく、テーブル全体をセットアップできることになります。私が心配しているのは、生成されたコードの長期的なメンテナンスです。事前に生成することで、ルックアップを実行する方法は決して変わらないと、私は早い段階で想定しました。それが可能性である場合、私はそれをコスト/メリットの一部として考慮に入れ、ボイラープレートのコストを削減する必要があると思います。+1
レベッカスコット

問題は、「クライアントが変更しないと言ったときに値をハードコーディングする必要があるか」ということでした。そしてあなたの答えは「いいえ、そして実際には問題にORMを投げるべきです」です。同意しません。OPがハードコーディングを回避できるようにする簡単なオプションがあります。明示的に不要と指定されたものをサポートするためにアーキテクチャに大きな追加を行うことは非倫理的です。多くの開発者がそのようなことをする傾向があることは残念です。そして、私はあなたの提案に100%反対します。「「うわー、これは首の痛みだ」という気持ちで正しいことをやめさせてはならない」という提案には同意しません。それらの感情は重要です!
user1172763 2015

10

@ThorbjørnRavn Andersenの答えをキーオフして拡張するには:計算/ルックアップを1か所に保つことは良いスタートです。

防御対YAGNIの思考プロセスは共通の問題です。この場合、私はそれがさらに二つのことによって知らされることを提案するでしょう。

まず、ユーザー要件はどのように提示されましたか?それらはレートの編集可能性を指定しましたか?そうでない場合、請求できるものに追加された複雑さの部分はありますか?(または、スタッフがいる場合は、代わりに他の作業に時間を費やすことができますか?)そうであれば、間違いなく先に進んで、合理的に求められたものを提供してください。

第二に、そしておそらくもっと重要なこととして、単なる編集可能性が、法改正に直面して実際の要件を満たす可能性は低いです。レートが変更された場合、カットオーバー日と前後に処理が発生する可能性があります。さらに、その同じインターフェイスが何らかの日付の古いクレーム処理を行う必要がある場合は、実際の日付ではなく、エントリの発効日に基づいて正しいレートを検索する必要があります。

要するに、実際の編集可能な要件はかなり複雑になる可能性があるので、具体化しない限り、または具体化するまでは、シンプルな方が良いと思います。

幸運を


1
2番目のポイントに+1。将来、立法府が何をするかを推測しようとはしません。
David Thornley、2011年

ライブラリ関数でそれを行います。ユーザーは1人だけでもかまいません。要件が変更された場合、変更する場所は1つだけです。(特定の日付の値を検索する関数を追加する必要がある場合があります。)
BillThor

最良かつ最も完全な回答、+ 1
アンディ

7

実際の検索をライブラリ関数にします。その後、ソースリポジトリでそのルックアップを使用してすべてのコード表示できるため、レートが変更されたときにアップグレードする必要があるプログラムがわかります。


ルックアップは単一の場所(PensionRateLookupクラスのようなもの)でのみ実装され、グローバルに使用されます。アプリの外部に保存されているか、ハードコードされているかに関係なく、PensionRateLookupクラスの実装のみを維持する必要がある方法でそれを行います。私の問題は、YAGNIを使用して、ルックアップテーブルをハードコーディングすることは許容できるという結論に至る方法です。
レベッカスコット

回答ありがとうございます。ルックアップ実装を1か所に維持することは、間違いなく優れた設計です。
レベッカスコット

YAGNIは、料金が変更されたときに新しいバイナリを出荷できる場合にのみ有効です。できないが、構成を変更できる場合は、現在のテキスト表現をデフォルトとして、起動時に読み取られる構成値にします。

私は通常毎週新しいバージョンを出荷していますので、実際にルックアップを更新することは問題ではありません。新しいバイナリでクライアント構成を変更できないため、クライアント構成に配置するのは実際にはさらに悪いことです。私が見る代替案は、データベースに置くことです。これは多くの労力を意味します。
レベッカスコット

それで問題は何ですか?料金が変わったら、コードを更新してすべての顧客に発送しますか?

2

質問が正しいかどうか確認させてください。機能を実装するには2つのオプションがあります。値をハードコード化して機能を実装するのが簡単(ハードコード部分が気に入らない)か、実行する多くのことを「やり直す」ために非常に大きな労力を費やします。したがって、機能をクリーンな方法で開発できます。あれは正しいですか?

私の頭に浮かぶ最初の事柄は、「良い実践を知ることについての最も重要なことは、彼らなしであなたがより良いときを知ることです」です。

この場合、労力は非常に高いので、クリーンな方法でそれを行うことができます。この変更の付随的な影響の可能性は大きく、得られるリターンは小さくなります(説明したように、そうはならないようです)変化する)。

私はハードコードアプローチを使用しますが(将来的に柔軟になるように準備します)、将来このレートが変更された場合は、この機会を使用して、コードのこの悪い設計セクションをすべてリファクタリングします。したがって、時間とコストを正しく見積もることができ、ハードコードされた値を変更するコストは最小限になります。

これは私のアプローチでしょう:)


@オスカーに感謝します。その技術的な部分はdefです。正しい。そして、必要なときだけリファクタリングすることで、問題へのアプローチ方法に同意します。それで、あなたは専門家として、コスト/利益のみに基づいて設計原則を選ぶことができ、選ぶべきだと言っていますか?理にかなっています。
レベッカスコット

@ベン・スコット:どういたしまして:) はい、私の見解では、デザイン原則は、美しく見えるためではなく、コードの「クリーンさ」、柔軟性、堅牢性などのメリットをもたらすために作成/カタログ化されました。しかし、常に2つの質問があります:1-必要ですか?2-私の制限(時間、技術など)で実装できますか?通常、これが私の設計原則を選択するきっかけになります。オーバーエンジニアリングも悪いです;)ps:面白い答えだと思ったら、投票してください。他の人もそれをより高い確率で読んでください。ありがとう!:)
JSBach

賛成投票;-)
レベッカスコット

2

変わるまで「変わらない」アイテムです。それが変わることは避けられませんが、その時は少し遠いかもしれません。

あなたの正当化は正しいです。現時点では、顧客はこれらのレートを簡単に変更する機能を求めていませんでした。そのように、YAGNI。

ただし、レートにアクセスし、コードベース全体に散在する結果を解釈するコードは必要ありません。優れたOO設計では、レートの内部表現をクラスにカプセル化し、データの使用に必要な1つまたは2つのメソッドのみを公開します。

コピーアンドペーストエラーを防ぐためにカプセル化が必要になるか、内部表現に変更を加える必要があるときにレートを使用するすべてのコードでリファクタリングを実行する必要があります。最初の予防策を講じる場合、より複雑なアプローチは、より機能的なバージョンへの単純な交換と交換です。

さらに、現在のデザインの制限をクライアントに伝えます。スケジュールを維持するために、値をハードコーディングしたことを伝えます。値を更新するには、コーディングの変更が必要です。このようにして、新しい法律に起因する保留中のレート変更に気づいたときに、単にルックアップテーブルを更新するか、その時点でより複雑な変更を実行するかを選択できます。しかし、その決定を彼らの膝の上に置いてください。


@berinに感謝します。質問ではSRPについて言及しませんでしたが、計画にはありました。問題の所有権をクライアントに返すことの利点。
レベッカスコット

将来的に問題が発生した場合に責任を負うことは、私には専門家ではないようです。
アンディ

@Andy、そのどの部分に責任があるのですか?設計の制限をクライアントに提示することで、複雑な作業に優先順位を付けて、他のものをテーブルから削除したり、期限を遅らせたり、限られた設計を受け入れたりすることができます。あなたはクライアントに彼らの製品よりも選択の力を与えています。顧客が関心を持ちたいと思う選択のリスク/報酬をクライアントに認識させることで、プロジェクトがスムーズに実行されます。
Berin Loritsch、2015

@BerinLoritschしかし、この特定の設計決定は、「配管工のパテを使用して、この漏れやすいパイプを差し込めます。これが最も安価なオプションです」と言って、標準以下の部品を使用するのと同じです。料金は変更されます。これは当然のことであり、専門家がそれを許可しないシステムを構築することは無責任です。そして、プロジェクトのコストの大規模な計画では、節約はおそらくごくわずかです。データをテーブルに入れます。ルックアップデータを取得するコードは少し異なり、どちらのレートを使用するかを決定するロジックはどちらの方法でも同じです。他の唯一の決定は...後の履歴データ処理する方法である
アンディ・

レートの変更。これは通常、レートを関連するレコードにコピーするだけで処理されます。この時点では管理画面は必要ありません。システムはレートが変更されたときに処理でき、レートを変更するのは簡単なスクリプトになります。これは数時間を超えてはなりませんが、パテが壊れたときに地下室が氾濫するのを防ぎます。
アンディ

2

差を分割し、レートデータを構成設定に入れます。すでに取得しているCSV形式を使用でき、不要なデータベースのオーバーヘッドを回避できます。変更が必要な場合は、再コンパイル/再インストールせずに混乱することなく、お客様が変更できるはずです。データベース内で-設定ファイルを編集するだけです。

通常、両極端(YAGNI違反と動的データのハードコーディング違反)のどちらかを選択している場合、最良の答えは真ん中のどこかにあります。誤った二分法に注意してください。

これは、すべての構成がファイルにあることを前提としています。レジストリのように難しい場合は、このアドバイスは無視してください。


私はそれを構成設定に入れることを検討しましたが、CSV文字列の編集に関するユーザーはあまり技術的ではないため、脇に置いておきます。そのため、Nユーザーの構成を更新します(私はすべてのITサポートを行うため)組織も)、労力の面では、私が1つの場所から管理できるデータベースにそれを取得するための事前の作業を行う方が簡単でしょう。それが考慮事項ではなかったとしたら(ユーザーが個々の構成を管理できれば)、この問題は発生しないでしょう。とても良い点、ありがとうございます。
レベッカスコット

これを一元化されたロジックの他の提案と組み合わせると、すばらしい答えになります。構成を介して変更できるようにするのではなく、ルックアップをハードコア化するのは純粋に悪です。他の開発者がそれに遭遇したときはいつでも、彼らはあなたを嫌います。たった一人の開発ショップであっても、バスに襲われた場合にどうなるかを検討し、次の開発者が完全なソフトウェアリリースなしで値を簡単に変更できるようにする必要があります。クライアントが嫌いで、他のすべての開発者が嫌いな場合にのみ、ハードコアすべきです。したがって、基本的にはありません。
simbo1905、2015

2

このテーブルをハードコーディングせずに保存する最も簡単な方法は、CSVを含む単一のテキスト値として、グローバル構成テーブルのデータベースにあると考えました(「65,69,0.05,70,74,0.06」は、 65-69と70-74の層が格納されます。

DBテーブルを作成しました。(テーブル全体を1つのフィールドに格納することを考えている場合、あなたは怒っていますか?)

テーブルには3つのフィールドではなく2つのフィールドが必要です。年齢とレート。この次の行には上限値が含まれています!あなたはそれを知らなくても非正規化しています!

67歳の人のレートを取得するためのSQLは次のとおりです。

Select * from RateTable where Age in (Select max(age) from RateTable where age <=67) 

メンテナンス画面は対象外なので、気にしないでください。後で要求された場合は、変更要求を発行して実行します。

編集:他の人が言ったように、レート構造全体が変化する場合に備えて、コードを維持してレートを集中化します。


こんにちは@Morons、あなたの答えをありがとう。テーブルには実際には3つの列が必要です。最低年齢から最高年齢までの年齢の範囲(65歳から69歳は5%です)ごとに単一の料金です。また、限られた目的のために少量のデータのみを保存しているので、構造を想定して専用テーブル全体ではなくCSVを使用することの何が問題になっていますか?おそらく行区切り文字を追加し、行と列で分割して、必要なフィールドに列をプルします。これは常に書き込まれるよりもはるかに多く読み込まれるため、テーブル全体について心配する必要はありません。
レベッカスコット

次の行には、範囲の上限値が含まれています...これはデータの複製です。<69と> 7は同じことです。両方の値を持つ唯一の理由は、範囲に穴を開けるかどうかです。...テーブルの方が簡単(そしてデザインが優れている)なので、テーブルを使用すると言っています。csvをテーブルに保存すると時間や労力が節約できると思う理由がわかりません。その文字列を取得するためだけにDB呼び出しが必要で、それを解析する必要があります。上記で示したように、1回のDB呼び出しでレートを取得できます。
Morons

そうだね。テーブル自体を元の資料と同じように保っていました...そして、私があなたに悲しみを与えた悲しみがあったので、年齢があなたの答えの上限であることを逃しました。
レベッカスコット

1
「あなたはそれさえ知らずに士気を落としている!」-最初はこれはタイプミスであり、「非正規化」を意味するものだと思っていましたが、考えれば考えるほど、どちらの方法でも正しいように思えました:)
EZ Hart

@ez hart笑
レベッカスコット

1

与えられた答えのほとんどに同意します。また、アプリケーションの他の部分との一貫性が重要であることも付け加えておきます。これがコード内でハードコードされた値を持つ唯一の場所である場合、メンテナを驚かせるでしょう。これは、大規模で安定したコードベースの場合に特に当てはまります。それがあなたによって維持されている小さなプログラムであるなら、決定はそれほど重要ではありません。

有名なアジャイル/ OOPの人(デイブトーマスやケントベックなど)を読んで、コードの重複に関する経験則は2倍だけだったという遠い記憶があります。あなたの人生で二度だけそれを書くかもしれません。しかし、三回目...

あなたはYAGNIに質問しているので、それは質問に正確に対処していませんが、それはアジャイルルールの一般的な柔軟性について話していると思います。アジャイルのポイントは、状況に適応して前進することです。


@Dave、ありがとう。私は最初から唯一のメンテナーですが、それは大規模で比較的安定したコードベース(数千のファイル、100を超えるテーブルなど)であり、それでも私は常に驚かされています(そして驚かれています)。一貫性は間違いなく今後の目標の1つです。
レベッカスコット

0

関数のハードコード。

  • 値が変更された場合、顧客に再度請求できます
  • 値が変わると、テーブルの形式を変更する必要がある可能性が高く、CSVを実行すると時間が無駄になります
  • 実装が簡単で、現在の契約で予算を守る可能性が高い
  • 更新が必要になる時期を簡単に見つけることができます

この標準以下の値をインストールして、1年で最も確実に機能しなくなると、繰り返しビジネスが発生するようにします。それは怪しげだからです。
アンディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.