ハードコードされた STRING は受け入れられるのでしょうか?
-
20-08-2019 - |
質問
に似ている ハードコーディングされたリテラルは受け入れられるのでしょうか?, しかし、ここでは特に「魔法の文字列」について考えています。
大規模なプロジェクトでは、次のような構成オプションの表があります。
Name Value
---- -----
FOO_ENABLED Y
BAR_ENABLED N
...
(数百件)。
一般的な方法は、次のように汎用関数を呼び出してオプションをテストすることです。
if (config_options.value('FOO_ENABLED') == 'Y') ...
(もちろん、これと同じオプションをシステム コードの多くの場所でチェックする必要がある場合があります。)
新しいオプションを追加するときに、次のように「マジック文字列」を非表示にする機能を追加することを検討していました。
if (config_options.foo_enabled()) ...
しかし、同僚は私が行き過ぎだと考え、これを行うことに反対し、次の理由からハードコーディングを好みました。
- それが私たちが普段やっていることです
- コードのデバッグ時に何が起こっているかを確認しやすくなります。
問題は、彼らの言いたいことは私にも理解できるということです!現実的には、いかなる理由があってもオプションの名前を変更することはありません。そのため、この関数に関して思いつく唯一の利点は、コンパイラーが fo_enabled() のようなタイプミスをキャッチするが、'FO_ENABLED' はキャッチしないことです。
どう思いますか?他に利点/欠点を見逃していませんか?
解決
if (config_options.isTrue('FOO_ENABLED')) {...
}
それはあなたの地図のためのラッパークラスを書くことを意味していても、一箇所にハードコーディングされたYチェックを制限します。
if (config_options.isFooEnabled()) {...
}
あなたが100個の構成オプションと100個のメソッドを持つまで大丈夫に見えるかもしれません(ので、ここであなたの実装を決定する前に、将来のアプリケーションの成長とニーズについて判断を下すことができます)。それ以外の場合は、パラメータ名のための静的文字列のクラスを持っている方が良いです。
if (config_options.isTrue(ConfigKeys.FOO_ENABLED)) {...
}
他のヒント
私は、コードに一度文字列を使用している場合は、私は一般的にどこかでそれを一定にすることを心配しないでください。
私はコード内の二回の文字列を使用する場合は、、私はののそれに一定にすることを検討します。
私はコード内の文字列を3回使用している場合は、私はほぼ確実にそれ定数作ってあげるます。
質問が古いことは承知していますが、思いついたものです。
私の知る限り、質問でも回答でも、ここでの問題は正確に特定されていません。「文字列のハーコーディング」のことはしばらく忘れてください。
データベースには参照テーブルがあり、次の内容が含まれます。
config_options
. 。PK は文字列です。PK には 2 つのタイプがあります。
ユーザー (および開発者) が見て使用する意味のある識別子。これらの PK は安定していると考えられており、信頼できます。
無意味
Id
ユーザーが決して表示すべきではない列、開発者が認識してコードを記述する必要がある列。これらは信頼できません。
意味のある PK の絶対値を使用してコードを書くのは普通のことです。
IF CustomerCode = "IBM" ...
またはIF CountryCode = "AUS"
等- 意味のない PK の絶対値の参照は受け入れられません (自動インクリメントのため)。ギャップが変更される。値は完全に置き換えられます)。
.
- 意味のない PK の絶対値の参照は受け入れられません (自動インクリメントのため)。ギャップが変更される。値は完全に置き換えられます)。
参照テーブルでは意味のある PK が使用されています。コード内でこれらのリテラル文字列を参照することは避けられません。値を非表示にすると、メンテナンスがより困難になります。コードはもはやリテラルではありません。あなたの同僚は正しいです。さらに、サイクルを調整する追加の冗長機能もあります。リテラルにタイプミスがある場合は、UAT よりずっと前の開発テスト中にすぐにわかります。
数百のリテラルに対して数百の関数を使用するのは不合理です。関数を実装する場合は、コードを正規化し、数百のリテラルのいずれかに使用できる単一の関数を提供します。その場合、裸のリテラルに戻り、関数を省略できます。
重要なのは、リテラルを隠蔽しようとする試みには何の価値もないということです。
.
これは「ハードコーディング」とはまったく異なるものであると解釈できません。これらの構成要素を「ハードコード」として識別するのは、そこに問題があると思います。これは文字通り意味のある PK を参照しているだけです。
コード セグメントのみの観点から見ると、同じ値を数回使用する場合、リテラル文字列を変数にキャプチャし、その変数をコード ブロックの残りの部分で使用することでコードを改善できます。確かに関数ではありません。しかし、それは効率性と優れた実践の問題です。それでも効果は変わらない
IF CountryCode = @cc_aus
私の経験では、この種の問題はより深い問題を隠しています。実際の OOP を実行せず、DRY 原則に従わないこと。
一言で言えば、各アクションの適切な定義によって起動時の決定を取得します。 内部 の if
ステートメントを作成し、両方のステートメントを破棄します。 config_options
そして実行時テスト。
詳細は以下をご覧ください。
サンプルの使用法は次のとおりです。
if (config_options.value('FOO_ENABLED') == 'Y') ...
特に次の文を考えると、「省略記号の中で何が起こっているのか?」という明らかな疑問が生じます。
(もちろん、これと同じオプションをシステム コードの多くの場所でチェックする必要がある場合があります。)
これらがそれぞれであると仮定しましょう config_option
値は実際には、単一の問題領域 (または実装戦略) の概念に対応しています。
これを行う代わりに (コード全体のさまざまな場所で繰り返し):
- 文字列(タグ)を取得し、
- 対応する他の文字列 (値) を見つけます。
- その値をブール値と同等のものとしてテストします。
- そのテストに基づいて、何らかのアクションを実行するかどうかを決定します。
「構成可能なアクション」の概念をカプセル化することをお勧めします。
例として考えてみましょう(もちろんこれは単なる仮説ですが) FOO_ENABLED
...;-) コードは英国単位またはメートル単位で機能する必要があります。もし METRIC_ENABLED
が「true」の場合、内部計算のためにユーザーが入力したデータをメートル法から英語に変換し、結果を表示する前に逆変換します。
インターフェースを定義します。
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 を実行するときのオーバーヘッドに比べれば些細なものです。
私は、実行時まで検出できないあなたが言及した2つの理由から、文字列の可能性のあるスペルミス、と信じていると、名前の変更の可能性が(スリムが)自分の考えを正当化するだろう。
その上であなたは関数を入力した得ることができ、今あなたが唯一の私はむしろ(「FOOをget_stringよりも、タイプ付き)(get_fooを使用することになりますが、int型を格納する必要がある場合にどのような真偽値、文字列などを保存するようです")またはget_int(" FOO ")。
私は本当に定数となし、ハードコーディングされたリテラルを使用する必要があります。
あなたは、彼らは変更されませんが、あなたが知っていることはないかもしれ言うことができます。そして、それはそれの習慣にするのがベストです。シンボリック定数を使用します。
ここには 2 つの異なる問題があると思います。
- 現在のプロジェクトでは、ハードコーディングされた文字列を使用する規則がすでに確立されているため、プロジェクトに取り組んでいるすべての開発者はこれに精通しています。これは、列挙したすべての理由から最適とは言えない規則かもしれませんが、コードに精通している人なら誰でもそれを見ることができ、そのコードが何をすべきかを直感的に理解できます。特定の部分で「新しい」機能を使用するようにコードを変更すると、コードが若干読みにくくなり (新しい規約が何をするのか考えたり覚えたりする必要があるため)、保守が少し難しくなります。しかし、プロジェクト全体を新しい規約に変更するには、変換のスクリプトを迅速に作成できない限り、法外なコストがかかる可能性があると思います。
- で 新しい プロジェクトでは、列挙されたすべての理由から、シンボリック定数が IMO の方法です。 特に なぜなら、人間が実行時に検出するエラーをコンパイラがコンパイル時に検出できるようにするものは、確立するのに非常に役立つ規則だからです。
私はあまりにも強く型付けされたコンフィギュレーション・クラスを好みます。適切に名前のメソッドでは、あなたは任意の可読性を失いません。あなたが別のデータ型(小数点/フロート/ INT)に、文字列からの変換を行う必要がある場合は、複数の場所で変換を行い、変換が一度だけ行われますので、結果をキャッシュすることができ、コードを繰り返す必要はありません。あなたはすでに、すでにので、私はそれが物事のの新しいのやり方に慣れるために多くを取るとは思わない場所でこのの基礎を持っています。
考慮すべきもう一つは、意図です。あなたがローカライズを必要とするプロジェクトである場合は、ハードコードされた文字列は、あいまいなことができます。次の点を考慮します:
const string HELLO_WORLD = "Hello world!";
print(HELLO_WORLD);
プログラマの意図は明らかです。定数を使用すると、この文字列をローカライズする必要がないことを意味します。今、この例を見てみます:
print("Hello world!");
ここでは、それほど確かではありません。プログラマは、実際にこの文字列をローカライズするか、彼はこのコードを書いていた一方で、プログラマがローカライズを忘れなかったしたくなかった?