ベストプラクティスに対する冗長な状態チェックはありますか


16

私は過去3年間ソフトウェアを開発してきましたが、最近、自分が良い慣習にどれほど無知であるかに目覚めました。これにより、Clean Codeという本を読み始めることになりました。これは私の人生をより良い方向に変えていますが、プログラムを書くための最良のアプローチのいくつかについて洞察を得るのに苦労しています。

Pythonプログラムがあります。

  1. argparse required=Trueを使用して、両方ともファイル名である2つの引数を適用します。最初は入力ファイル名、2番目は出力ファイル名
  2. readFromInputFile入力ファイル名が入力されたことを最初に確認する機能があります
  3. writeToOutputFile出力ファイル名が入力されたことを最初に確認する機能があります

私のプログラムは十分に小さいので、#2と#3のチェックは冗長であり、削除する必要があると信じるようになるため、両方の機能を不要なif状態から解放します。しかし、「二重チェックは大丈夫」であると信じるようになり、引数の解析が行われない別の場所から関数を呼び出すことができるプログラムでは正しいソリューションになるかもしれません。

(また、読み取りまたは書き込みが失敗した場合、try except適切なエラーメッセージを生成する各関数があります。)

私の質問は、すべての冗長な状態チェックを避けるのが最善ですか?プログラムのロジックは非常に堅固であるため、チェックは一度だけで済みますか?これまたはその逆を説明する良い例はありますか?

編集:答えてくれてありがとう!それぞれから何かを学びました。非常に多くの視点を見ると、この問題にどのようにアプローチし、自分の要件に基づいて解決策を決定する方法についての理解が深まります。ありがとうございました!


あなたの質問の非常に一般化されたバージョンはここにあります:softwareengineering.stackexchange.com/questions/19549/…。焦点がかなり大きいので重複しているとは言いませんが、役立つかもしれません。
ドックブラウン

回答:


15

あなたが求めているものは「ロバストネス」と呼ばれ、正解も不正解もありません。これは、プログラムのサイズと複雑さ、プログラムで作業する人の数、および障害を検出する重要性に依存します。

単独で自分用に作成する小さなプログラムでは、通常、堅牢性は、複数のコンポーネントで構成される複雑なプログラム(おそらくはチームによって作成される)を作成する場合よりもはるかに小さな懸念です。このようなシステムでは、パブリックAPIの形式でコンポーネント間に境界があり、各境界では、「プログラムのロジックが非常に堅固で、これらのチェックが冗長であっても」入力パラメーターを検証することをお勧めします。 「。これにより、バグの検出が非常に簡単になり、デバッグ時間を短縮できます。

あなたの場合、プログラムにどのようなライフサイクルを期待するかを自分で決める必要があります。それはあなたが長年にわたって使用され維持されることを期待するプログラムですか?コードが将来リファクタリングされる可能性は低くreadwrite関数と関数は異なるコンテキストで使用される可能性があるため、冗長チェックを追加することはおそらくより良いでしょう。

それとも、単に学習や楽しい目的のための小さなプログラムですか?その場合、これらの二重チェックは必要ありません。

「クリーンコード」のコンテキストでは、二重チェックがDRY原則に違反しているかどうかを尋ねることができます。実際には、少なくともある程度、入力検証はプログラムのビジネスロジックの一部として解釈される場合があり、これを2か所に置くと、DRYの違反によって引き起こされる通常のメンテナンスの問題につながる可能性があります。堅牢性とDRYはしばしばトレードオフです。DRYは冗長性を最小限に抑えようとする一方で、堅牢性にはコードの冗長性が必要です。また、プログラムの複雑さが増すにつれて、検証においてDRYであるよりも堅牢性がますます重要になります。

最後に、それがあなたの場合に何を意味するか例を挙げましょう。要件が次のように変わると仮定しましょう

  • プログラムは、入力ファイル名という1つの引数でも機能します。出力ファイル名が指定されていない場合、接尾辞を置き換えることにより、入力ファイル名から自動的に構築されます。

そのため、2つの場所で二重検証を変更する必要がありますか?おそらくそうではありませんが、そのような要件はを呼び出すときに1つの変更につながりますargparseが、変更はありませんwriteToOutputFile:その関数はファイル名を必要とします。したがって、あなたの場合、入力検証を2回行うことに投票します。変更する場所が2つあるためにメンテナンスの問題が発生するリスクは、チェックが少なすぎるためにマスクされたエラーが原因でメンテナンスの問題が発生するリスクよりもはるかに低いです。


「...パブリックAPIの形式でのコンポーネント間の境界...」「クラスは、いわば境界を跳躍する」ということがわかります。必要なのはクラスです。首尾一貫したビジネスドメインクラス。このOPから、「クラスは必要ないので簡単だ」という普遍的な原則がここで機能していると推測しています。「プライマリオブジェクト」をラップする単純なクラスがあり、「ファイルには名前が必要」などのビジネスルールを適用し、既存のコードをDRYするだけでなく、将来DRYに保ちます。
レーダーボブ16

@radarbob:私が書いたことは、OOPやクラス形式のコンポーネントに限定されません。これは、オブジェクト指向かどうかに関係なく、パブリックAPIを備えた任意のライブラリにも適用されます。
Doc Brown

5

冗長性は罪ではありません。不必要な冗長性があります。

  1. もしreadFromInputFile()およびwriteToOutputFile()パブリック関数です(そしてそれらの名前は2つのアンダースコアで始まっていなかったので、彼らは、Pythonの命名規則による)、その後の機能は、いつの日か完全にargparse回避誰かによって使用される可能性があります。つまり、引数を省略すると、カスタムargparseエラーメッセージが表示されなくなります。

  2. 場合readFromInputFile()writeToOutputFile()パラメータのために自分自身をチェックし、再度ファイル名の必要性を説明してカスタムエラーメッセージを表示するために得ます。

  3. パラメータ自体をチェックしない場合readFromInputFile()writeToOutputFile()カスタムエラーメッセージは表示されません。ユーザーは、結果の例外を自分で把握する必要があります。

すべては3になります。実際にこれらの関数を使用してargparseを回避するコードを記述し、エラーメッセージを生成します。これらの関数の内部をまったく見ておらず、使用するのに十分な理解を提供するために名前を信頼しているだけだと想像してください。それがあなたが知っているすべてであるとき、例外によって混同される方法がありますか?カスタマイズされたエラーメッセージが必要ですか?

これらの機能の内部を記憶する脳の部分をオフにすることは難しいです。そのため、使用するコードの前にusing-codeを記述することをお勧めします。そうすれば、外部から物事がどのように見えるかをすでに知っている問題に出くわします。TDDを行う必要はありませんが、TDDを行う場合は、最初に外部から入ってくることになります。


4

メソッドをスタンドアロン再利用可能にする程度は良いことです。つまり、メソッドは受け入れるものを許容し、明確に定義された出力(返されるものを正確に)する必要があります。それはまた、彼らが渡されたすべてを優雅に処理でき、入力の性質、品質、タイミングなどについて何も仮定しないことができることを意味します

プログラマーが、「これが壊れた場合、もっと大きな心配事がある」または「パラメーターXが値Yを持つことができない」などの考えに基づいて、渡されたものについて仮定するメソッドを書く習慣がある場合コードはそれを防ぎます」、そして突然、独立した、分離されたコンポーネントが本当になくなります。コンポーネントは基本的に、より広いシステムに依存しています。これは一種の微妙な密結合であり、システムの複雑さが増すにつれて総所有コストが指数関数的に増加します。

これは、同じ情報を複数回検証していることを意味する場合があることに注意してください。しかし、これは大丈夫です。各コンポーネントは、独自の方法で独自の検証を行います。これはDRYの違反ではありません。検証は分離された独立したコンポーネントによるものであり、一方の検証への変更は必ずしも他方で正確に複製する必要がないためです。ここには冗長性はありません。Xは、自分のニーズに対して入力をチェックし、Yに一部を渡す責任があります。Yは、自分のニーズに対して入力をチェックする責任あります


1

関数があると仮定します(Cで)

void readInputFile (const char* path);

また、パスに関するドキュメントが見つかりません。そして、あなたは実装を見て、それは言います

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

これは、関数への入力をテストするだけでなく、関数のユーザーに、パスをNULLまたは空の文字列にすることは許可されていないことも通知します。


0

一般に、二重チェックは常に良いとか悪いとは限りません。問題が依存する特定のケースには、常に質問の多くの側面があります。あなたの場合:

  • プログラムの大きさは?小さければ小さいほど、呼び出し元が正しいことをしていることは明白です。プログラムが大きくなると、各ルーチンの前提条件と事後条件を正確に指定することが重要になります。
  • 引数はargparseモジュールによってすでにチェックされています。多くの場合、ライブラリを使用してから自分でその作業を行うことは悪い考えです。なぜライブラリを使用するのですか?
  • 呼び出し元が引数をチェックしないコンテキストでメソッドが再利用される可能性はどのくらいありますか?可能性が高いほど、引数を検証することが重要になります。
  • 引数欠落するとどうなりますか?入力ファイルが見つからないと、おそらく処理が完全に停止します。これはおそらく、修正が容易な明らかな障害モードです。陰湿な種類のエラーは、プログラムが気付かないうちに問題なく動作し、間違った結果生成するエラーです

0

ダブルチェックは、めったに使用されない場所にあるようです。したがって、これらのチェックは単にプログラムをより堅牢にしているだけです。

チェックが多すぎても傷つかず、少なすぎる場合もあります。

ただし、頻繁に繰り返されるループ内でチェックする場合は、チェック自体がほとんどの場合、チェック後に続くものと比較して費用がかからない場合でも、冗長性を削除することを検討する必要があります。


そして、既にそれを持っているので、それがループまたは何かにない限り、それは削除する努力の価値がありません。
StarWeaver

0

おそらくあなたの視点を変えることができます:

何かがうまくいかない場合、結果は何ですか?アプリケーション/ユーザーに害を及ぼしますか?

もちろん、多かれ少なかれチェックが良いか悪いかにかかわらず、常に議論することができますが、それはむしろ学問的な質問です。そして、あなたは現実世界のソフトウェアを扱っているので、現実世界の結果があります。

あなたが与えているコンテキストから:

  • 1つの入力ファイルA
  • 1つの出力ファイルB

AからBへの変換を行っていると思います。場合はABが小さく、変換が小さい場合、結果は何ですか?

1)どこから読み込むかを指定するのを忘れた場合:結果は何もありません。そして、実行時間は予想よりも短くなります。結果を見る-またはそれ以上:欠落している結果を探し、間違った方法でコマンドを呼び出したことを確認し、最初からやり直して、すべて順調です

2)出力ファイルを指定するのを忘れました。これにより、さまざまなシナリオが発生します。

a)入力はすぐに読み取られます。変換が開始され、結果が書き込まれる必要がありますが、代わりにエラーが表示されます。時間に応じて、ユーザーは待機する必要があります(処理されるデータの量に依存します)これは迷惑です。

b)入力は段階的に読み取られます。その後、書き込みプロセスは(1)のようにすぐに終了し、ユーザーは最初からやり直します。

ある状況下では、ずさんなチェックOKと見なされることがあります。それは完全にあなたのユースケースとあなたの意図に依存します。

さらに:パラノイアを避け、二重チェックをしすぎないようにする必要があります。


0

テストは冗長ではないと主張します。

  • 入力パラメーターとしてファイル名を必要とする2つのパブリック関数があります。パラメータを検証するのが適切です。関数は、機能を必要とするプログラムで使用される可能性があります。
  • ファイル名でなければならない2つの引数を必要とするプログラムがあります。たまたま関数を使用しています。プログラムがパラメータをチェックするのが適切です。

ファイル名は2回チェックされていますが、異なる目的でチェックされています。関数のパラメーターが検証されていることが信頼できる小さなプログラムでは、関数のチェックは冗長であると考えることができます。

より堅牢なソリューションには、1つまたは2つのファイル名検証ツールがあります。

  • 入力ファイルの場合、パラメーターで読み取り可能なファイルが指定されていることを確認できます。
  • 出力ファイルの場合、パラメーターが書き込み可能ファイルまたは作成および書き込み可能な有効なファイル名であることを確認することができます。

アクションを実行するタイミングに2つのルールを使用します。

  • できるだけ早くそれらを行います。これは、常に必要なものに適しています。このプログラムの観点から見ると、これはargv値のチェックであり、プログラムロジックでの後続の検証は冗長です。関数がライブラリに移動された場合、ライブラリはすべての呼び出し元がパラメータを検証したことを信頼できないため、もはや冗長ではありません。
  • できるだけ遅くしてください。これは、ほとんど必要とされないものに対して非常にうまく機能します。このプログラムの観点から見ると、これは関数パラメーターのチェックです。

0

チェックは冗長です。ただし、これを修正するには、readFromInputFileとwriteToOutputFileを削除し、readFromStreamとwriteToStreamに置き換える必要があります。

コードがファイルストリームを受信した時点で、有効なファイルに接続された有効なストリーム、またはストリームに接続できる他のすべてがあることがわかります。これにより、冗長なチェックが回避されます。

そうすると、ストリームをどこかで開く必要があるかもしれません。はい、ただし、それは引数解析メソッドの内部で発生します。2つのチェックがあります。1つはファイル名が必要であることをチェックし、もう1つはファイル名が指すファイルが指定されたコンテキストで有効なファイルであることをチェックします。これらは異なるタイプのチェックであるため、冗長ではなく、コアアプリケーション内ではなく、引数解析メソッド(アプリケーション境界)内で発生します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.