経験豊富なソフトウェア開発者として、私は魔法の文字列を避けることを学びました。
私の問題は、それらを使用してから非常に長い時間であり、その理由のほとんどを忘れてしまったことです。その結果、私が経験の浅い同僚になぜ彼らが問題なのかを説明するのに苦労しています。
それらを避けるための客観的な理由は何ですか?それらはどのような問題を引き起こしますか?
経験豊富なソフトウェア開発者として、私は魔法の文字列を避けることを学びました。
私の問題は、それらを使用してから非常に長い時間であり、その理由のほとんどを忘れてしまったことです。その結果、私が経験の浅い同僚になぜ彼らが問題なのかを説明するのに苦労しています。
それらを避けるための客観的な理由は何ですか?それらはどのような問題を引き起こしますか?
回答:
コンパイルする言語では、コンパイル時にマジックストリングの値はチェックされません。文字列が特定のパターンに一致する必要がある場合は、そのパターンに適合することを保証するためにプログラムを実行する必要があります。列挙などの何かを使用した場合、値は間違った値であっても、コンパイル時に少なくとも有効です。
マジックストリングが複数の場所に書き込まれている場合は、安全性なしですべてを変更する必要があります(コンパイル時エラーなど)。ただし、これは1か所で宣言して変数を再利用するだけで対処できます。
タイプミスは深刻なバグになる可能性があります。機能がある場合:
func(string foo) {
if (foo == "bar") {
// do something
}
}
そして誰かが誤って入力します:
func("barr");
これは、プロジェクトの母国語に不慣れなプログラマーがいる場合は特に、文字列がよりまれまたはより複雑であるほど悪化します。
マジックストリングが自己文書化されることはめったにありません。1つの文字列が表示された場合、それは文字列が他に何をすべきか/何をすべきかを教えてくれません。適切な文字列を選択したことを確認するには、おそらく実装を調べる必要があります。
この種の実装は漏れやすく、特にポイント3のように完全な文字でなければならないため、外部ドキュメントまたはコードへのアクセスが必要です。
IDEの「文字列の検索」機能以外では、パターンをサポートする少数のツールがあります。
2つの場所で偶然同じマジックストリングを使用する場合がありますが、実際には異なるものであるため、検索と置換を行い、両方を変更した場合、一方が機能している間に一方が壊れる可能性があります。
他の答えが把握しているサミットは、「魔法の価値」が悪いということではなく、そうあるべきだということです。
通常、許容可能な「定数」と「マジック値」を区別するのは、これらのルールの1つ以上の違反です。
定数を使用すると、コードの特定の公理を簡単に表現できます。
それは最終的なポイントに私を連れて行きます、定数の過度の使用(そして値の観点から表現された過度の数の仮定または制約)、たとえそれ以外の場合上記の基準に準拠していても(特にそれらが逸脱している場合)、考案されているソリューションが十分に一般的または適切に構造化されていないことを意味する可能性があります(したがって、定数の長所と短所についてはもう話していないが、適切に構造化されたコードの長所と短所については話していません)。
高レベル言語には、定数を使用する必要がある低レベル言語のパターンの構成体があります。同じパターンを高水準言語でも使用できますが、使用すべきではありません。
しかし、それはすべての状況とソリューションがどのように見えるべきかという印象に基づいた専門家の判断であり、その判断が正確にどのように正当化されるかはコンテキストに大きく依存します。確かに、「私はよく知っているこの種の作品をすでに見てきたので、私は十分に年を取っている」と断言する以外は、一般原則の観点からは正当化できないかもしれません!
編集: 1つの編集を受け入れ、別の編集を拒否し、独自の編集を実行したので、ルールのリストの書式設定と句読点のスタイルを1回すべて解決することを検討できますか!
value / 2
よりも、単純に記述する方がより明確で簡単な場合があります。CSVを処理するメソッドを一般化する場合は、おそらくセパレーターをパラメーターとして渡し、定数としてはまったく定義しないでください。しかし、それはすべてコンテキストの判断の問題です-@WGroleauの例は明示的に名前を付けたいものですが、すべてのリテラルがこれを必要とするわけではありません。value / VALUE_DIVISOR
2
SPEED_OF_LIGHT
実際の例:私は、「エンティティ」が「フィールド」とともに保存されるサードパーティシステムで作業しています。基本的にEAVシステム。別のフィールドを追加するのは非常に簡単なので、フィールドの名前を文字列として使用してフィールドにアクセスできます。
Field nameField = myEntity.GetField("ProductName");
(魔法の文字列「ProductName」に注意してください)
これにより、いくつかの問題が発生する可能性があります。
そのため、これに対する私の解決策は、エンティティタイプごとに整理されたこれらの名前の定数を生成することでした。だから今私は使用することができます:
Field nameField = myEntity.GetField(Model.Product.ProductName);
まだ文字列定数であり、まったく同じバイナリにコンパイルされますが、いくつかの利点があります。
次のリストでは、生成された強く型付けされたクラスの背後にこれらの定数を隠します-データ型も保護されます。
nameField = myEntity.ProductName;
。
魔法の糸は必ずしも悪いわけではないので、これがあなたがそれらを避ける包括的な理由を思い付かない理由かもしれません。(「マジックストリング」とは、定数として定義されていない式の一部としての文字列リテラルを意味すると仮定します。)
場合によっては、マジックストリングを避ける必要があります。
しかし、場合によっては、「マジックストリング」で十分です。簡単なパーサーがあるとします:
switch (token.Text) {
case "+":
return a + b;
case "-":
return a - b;
//etc.
}
ここには魔法はありませんし、上記の問題は当てはまりません。私見string Plus="+"
などを定義してもメリットはありません。シンプルにしてください。
if (dx != 0) { grad = dy/dx; }
です。
"+"
and "-"
をTOKEN_PLUS
andに置き換えたswitchステートメントを見てきましたTOKEN_MINUS
。それを読むたびに、そのせいで読みにくく、デバッグしにくいと感じました!単純な文字列を使用する方が良いと私が同意する場所です。
既存の回答に追加するには:
画面に表示するテキストがハードコーディングされ、機能のレイヤーに埋もれている場合、そのテキストを他の言語に翻訳するのは非常に困難になります。
一部の開発環境(Qtなど)は、ベース言語のテキスト文字列から翻訳された言語へのルックアップによって翻訳を処理します。マジックストリングは、一般的にこれを生き残ることができます-同じテキストを他の場所で使用してタイプミスを取得することを決定するまで。それでも、別の言語のサポートを追加する場合、どのマジックストリングを翻訳する必要があるかを見つけるのは非常に困難です。
一部の開発環境(MS Visual Studioなど)では、別のアプローチを採用し、すべての翻訳された文字列をリソースデータベース内に保持し、その文字列の一意のIDによって現在のロケールを読み取る必要があります。この場合、マジックストリングを使用したアプリケーションは、大幅な手直しを行わなければ、別の言語に翻訳することはできません。効率的な開発では、すべてのテキスト文字列をリソースデータベースに入力し、コードを最初に記述するときに一意のIDを与える必要があり、その後のi18nは比較的簡単です。事実の後にこれを埋め戻そうとすると、通常、非常に大きな努力が必要になります(そして、私はそこにいました!)、最初に正しいことをする方がはるかに良いです。
これは誰にとっても優先事項ではありませんが、自動化された方法でコードのカップリング/結合メトリックを計算できるようにしたい場合、マジックストリングはこれをほぼ不可能にします。ある場所にある文字列は、別の場所にあるクラス、メソッド、または関数を参照し、コードを解析するだけで文字列がクラス/メソッド/関数に結合されていることを簡単かつ自動的に判断する方法はありません。基礎となるフレームワーク(Angularなど)のみがリンケージがあることを判断できます。そして、それは実行時にのみ可能です。カップリング情報を自分で取得するには、パーサーは、使用しているフレームワークについて、コーディングしているベース言語以上のすべてを知っている必要があります。
しかし、繰り返しますが、これは多くの開発者が気にすることではありません。