3

ハードコーディングされたリテラルはこれまでに受け入れられますか?に似ている 、しかし、私はここで「魔法の糸」を具体的に考えています。

大規模なプロジェクトでは、次のような構成オプションの表があります。

Name         Value
----         -----
FOO_ENABLED  Y
BAR_ENABLED  N
...

(何百もの)。

一般的な方法は、ジェネリック関数を呼び出して、次のようにオプションをテストすることです。

if (config_options.value('FOO_ENABLED') == 'Y') ...

(もちろん、この同じオプションは、システム コードの多くの場所でチェックする必要があるかもしれません。)

新しいオプションを追加するとき、次のような「魔法の文字列」を非表示にする機能を追加することを検討していました。

if (config_options.foo_enabled()) ...

しかし、同僚は私がやり過ぎだと思って、これを行うことに反対しました。

  • それが私たちが通常していることです
  • コードのデバッグ時に何が起こっているかを簡単に確認できます

問題は、私には彼らの要点がわかるということです! 現実的には、何らかの理由でオプションの名前を変更することは決してありません。そのため、私の関数について考えられる唯一の利点は、コンパイラが fo_enabled() のようなタイプミスを検出するが、'FO_ENABLED' は検出しないことです。

どう思いますか?他の利点/欠点を見逃していませんか?

4

9 に答える 9

37

コード内で文字列を 1 回使用すると、通常、どこかで文字列を定数にすることについて心配する必要はありません。

コードで文字列を 2 回使用する場合は、定数にすることを検討します。

コードで文字列を 3 回使用すると、ほぼ確実に定数になります。

于 2009-01-28T12:54:36.513 に答える
9
if (config_options.isTrue('FOO_ENABLED')) {...
}

Map のラッパー クラスを記述することを意味する場合でも、ハード コーディングされた Y チェックを 1 か所に制限します。

if (config_options.isFooEnabled()) {...
}

100 個の構成オプションと 100 個のメソッドが用意されるまでは問題ないように思えるかもしれません (したがって、実装を決定する前に、将来のアプリケーションの成長とニーズについて判断を下すことができます)。それ以外の場合は、パラメーター名に静的文字列のクラスを使用することをお勧めします。

if (config_options.isTrue(ConfigKeys.FOO_ENABLED)) {...
}
于 2009-01-28T13:19:16.420 に答える
5

質問が古いことは承知していますが、余白に出てきました。

AFAIC、ここでの問題は、質問でも回答でも正確に特定されていません。少しの間、「文字列のハーコーディング」について忘れてください。

  1. データベースには、を含む参照テーブルがありますconfig_options。PK は文字列です。

  2. PK には次の 2 種類があります。

    • ユーザー (および開発者) が見て使用する意味のある識別子。これらの PK は安定している必要があり、信頼できます。

    • Idユーザーが決して見るべきではない無意味な列、開発者が認識しなければならない列、およびコードの回避。これらは当てにできません。

  3. IF CustomerCode = "IBM" ...意味のあるPKなどの絶対値を使ってコードを書くのは普通、普通ですIF CountryCode = "AUS"

    • 無意味な PK の絶対値を参照することは受け入れられません (自動インクリメントのため、ギャップが変更され、値が大規模に置き換えられます)。
      .
  4. 参照テーブルは意味のある PK を使用します。コードでこれらのリテラル文字列を参照することは避けられません。値を非表示にすると、メンテナンスがより困難になります。コードはもはやリテラルではありません。あなたの同僚は正しいです。さらに、サイクルを噛む追加の冗長機能があります。リテラルにタイプミスがある場合は、UAT のずっと前に、開発テスト中にすぐに見つかります。

    • 何百ものリテラルに対して何百もの関数を使用するのはばかげています。関数を実装する場合は、コードを正規化し、何百ものリテラルのいずれにも使用できる単一の関数を提供します。その場合、裸のリテラルに戻り、関数を省くことができます。

    • 要点は、リテラルを隠そうとしても何の価値もないということです。
      .

  5. 「ハードコーディング」と解釈することはできません。これはまったく別のものです。これらの構造を「ハードコード」として識別して、それがあなたの問題があるところだと思います。意味のある PK を文字通り参照しているだけです。

  6. コード セグメントのみの観点から見ると、同じ値を数回使用する場合は、リテラル文字列を変数に取り込んでから、コード ブロックの残りの部分でその変数を使用することにより、コードを改善できます。確かに関数ではありません。しかし、それは効率と優れた実践の問題です。それでも効果は変わりませんIF CountryCode = @cc_aus

于 2011-01-31T03:02:33.243 に答える
5

私は本当に定数を使用し、ハードコードされたリテラルを使用しないでください。

それらは変更されないと言うことはできますが、決してわからないかもしれません。そして、それを習慣にするのが最善です。記号定数を使用するには。

于 2009-01-28T12:52:50.980 に答える
5

私の経験では、この種の問題は、より深い問題を隠しています: 実際の OOP を実行できず、DRY 原則に従わないことです。

簡単に言えば、ステートメントの各アクションの適切な定義によって起動時の決定を取得し、実行時テストと実行時テストの両方を破棄します。ifconfig_options

詳細は以下。

サンプルの使用法は次のとおりです。

if (config_options.value('FOO_ENABLED') == 'Y') ...

特に次のステートメントを考えると、「省略記号で何が起こっているのか?」という明白な疑問が生じます。

(もちろん、この同じオプションは、システム コードの多くの場所でチェックする必要があるかもしれません。)

config_optionこれらの値のそれぞれが、実際には 1 つの問題領域 (または実装戦略) の概念に対応していると仮定しましょう。

これを行う代わりに (コード全体のさまざまな場所で繰り返し):

  1. 文字列(タグ)を取得し、
  2. 対応する他の文字列 (値) を見つけ、
  3. その値をブール値に相当するものとしてテストし、
  4. そのテストに基づいて、何らかのアクションを実行するかどうかを決定します。

「構成可能なアクション」の概念をカプセル化することをお勧めします。

FOO_ENABLEDコードが英単位またはメートル単位のいずれかで機能する必要があることを例に取りましょう (明らかに ... と同じくらい仮説的です ;-)。が「true」の場合METRIC_ENABLED、内部計算のためにユーザーが入力したデータをメートル法から英語に変換し、結果を表示する前に元に戻します。

インターフェイスを定義します。

public interface MetricConverter {
    double toInches(double length);
    double toCentimeters(double length);
    double toPounds(double weight);
    double toKilograms(double weight);
}

の概念に関連するすべての動作を 1 か所で識別しMETRIC_ENABLEDます。

次に、これらの動作が実行されるすべての方法の具体的な実装を記述します。

public class NullConv implements MetricConverter {
    double toInches(double length) {return length;}
    double toCentimeters(double length) {return length;}
    double toPounds(double weight)  {return weight;}
    double toKilograms(double weight)  {return weight;}
}

// lame implementation, just for illustration!!!!
public class MetricConv implements MetricConverter {
    public static final double LBS_PER_KG = 2.2D;
    public static final double CM_PER_IN = 2.54D
    double toInches(double length) {return length * CM_PER_IN;}
    double toCentimeters(double length) {return length / CM_PER_IN;}
    double toPounds(double weight)  {return weight * LBS_PER_KG;}
    double toKilograms(double weight)  {return weight / LBS_PER_KG;}
}

config_options起動時に、一連の値をロードする代わりに、構成可能な一連のアクションを次のように初期化します。

MetricConverter converter = (metricOption()) ? new MetricConv() : new NullConv();

(metricOption()上記の式は、METRIC_ENABLED の値を調べるなど、必要な 1 回限りのチェックの代用です ;-)

次に、コードが言うところはどこでも:

double length = getLengthFromGui();
if (config_options.value('METRIC_ENABLED') == 'Y') {
    length = length / 2.54D;
}
// do some computation to produce result
// ...
if (config_options.value('METRIC_ENABLED') == 'Y') {
    result = result * 2.54D;
}
displayResultingLengthOnGui(result);

次のように書き換えます。

double length = converter.toInches(getLengthFromGui());
// do some computation to produce result
// ...
displayResultingLengthOnGui(converter.toCentimeters(result));

その 1 つの概念に関連するすべての実装の詳細がきれいにパッケージ化されているため、関連する将来のすべてのメンテナンスMETRIC_ENABLEDを 1 か所で行うことができます。さらに、実行時のトレードオフは有利です。メソッドを呼び出す「オーバーヘッド」は、Map から String 値を取得して String#equals を実行するオーバーヘッドと比較すると些細なものです。

于 2009-01-28T13:44:21.047 に答える
4

あなたが言及した 2 つの理由、実行時まで検出できない文字列のスペルミスの可能性、および名前変更の可能性 (わずかですが) は、あなたの考えを正当化するものだと思います。

それに加えて、型指定された関数を取得できます。現在、ブール値のみを格納しているようです。int、文字列などを格納する必要がある場合はどうでしょう。 get_string("FOO") または get_string("FOO") またはget_int("FOO")。

于 2009-01-28T12:51:05.353 に答える
3

ここには 2 つの異なる問題があると思います。

  • 現在のプロジェクトでは、ハードコーディングされた文字列を使用する慣例がすでに確立されているため、プロジェクトに取り組んでいるすべての開発者はそれに精通しています。リストされているすべての理由から、これは次善の慣例かもしれませんが、コードに精通している人なら誰でもそれを見て、コードが何をすべきかを本能的に知ることができます。特定の部分で「新しい」機能を使用するようにコードを変更すると、コードが少し読みにくくなり (人々は新しい慣習が何をするかを考えて覚える必要があるため)、保守が少し難しくなります。しかし、変換をすばやくスクリプト化できない限り、プロジェクト全体を新しい規則に変更することは、法外なコストがかかる可能性があると思います。
  • 新しいプロジェクトでは、リストされているすべての理由から、シンボリック定数がIMOの方法です。特に、実行時に人間がキャッチするエラーをコンパイル時にコンパイラーにキャッチさせるものは、確立するのに非常に便利な規則であるためです。
于 2009-01-28T13:01:28.483 に答える
2

考慮すべきもう 1 つのことは、意図です。ローカリゼーションが必要なプロジェクトに参加している場合、ハードコードされた文字列があいまいになる可能性があります。次の点を考慮してください。

const string HELLO_WORLD = "Hello world!";
print(HELLO_WORLD);

プログラマーの意図は明らかです。定数を使用することは、この文字列をローカライズする必要がないことを意味します。次の例を見てください。

print("Hello world!");

ここでは、よくわかりません。プログラマーは本当にこの文字列をローカライズすることを望まなかったのでしょうか?それともプログラマーはこのコードを書いているときにローカライズを忘れたのでしょうか?

于 2013-09-11T04:18:39.477 に答える
1

コード全体で使用する場合は、厳密に型指定された構成クラスを好みます。適切な名前のメソッドを使用すると、可読性が失われません。文字列から別のデータ型 (decimal/float/int) への変換を行う必要がある場合、変換を行うコードを複数の場所で繰り返す必要はなく、変換が 1 回だけ行われるように結果をキャッシュできます。すでに基礎はできているので、新しいやり方に慣れるのにそれほど時間はかからないと思います。

于 2009-01-28T13:09:19.840 に答える