web-dev-qa-db-ja.com

マジックストリングの何が問題になっていますか?

経験豊富なソフトウェア開発者として、私はマジックストリングを回避することを学びました。

私の問題は、私がそれらを使用してから非常に長い時間であり、その理由のほとんどを忘れてしまったことです。その結果、経験の浅い同僚になぜそれらが問題であるのか説明するのに苦労しています。

それらを回避するための客観的な理由は何ですか?彼らはどのような問題を引き起こしますか?

176
Kramii
  1. コンパイルする言語では、マジックストリングの値はコンパイル時にチェックされないです。文字列が特定のパターンに一致する必要がある場合は、プログラムを実行して、そのパターンに適合することを保証する必要があります。列挙型などを使用した場合、たとえ間違った値であっても、その値はコンパイル時に少なくとも有効です。

  2. マジックストリングが複数の場所に書き込まれている場合それらすべてを変更する必要があります安全性なしで(コンパイル時エラーなど)。ただし、これを1か所で宣言して変数を再利用するだけで対処できます。

  3. タイプミスは深刻なバグになる可能性があります。関数がある場合:

    func(string foo) {
        if (foo == "bar") {
            // do something
        }
    }
    

    そして誰かが誤って入力します:

    func("barr");
    

    これは、特にプロジェクトの母国語に不慣れなプログラマーがいる場合、文字列がまれであるか、またはより複雑になります。

  4. マジックストリングが自己文書化することはほとんどありません。 1つの文字列が表示された場合、それはその文字列が他に何であるか/あるべきかを何も知らないおそらく、実装を調べて、正しい文字列を選択したことを確認する必要があります。

    この種の実装はleakyであり、特にポイント3のように完全な文字である必要があるため、何を記述すべきかを理解するために外部ドキュメントまたはコードへのアクセスが必要です。

  5. IDEの「文字列検索」関数のほかに、パターンをサポートする少数のツールがあります。

  6. 同じマジックストリングを2つの場所で偶然に使用することがありますが、実際には2つの場所が異なるため、検索と置換を行って両方を変更した場合、一方が機能しているときに一方が破損する可能性があります。

215
Erdrik Ironrose

他の回答が把握していることの頂点は、「魔法の値」が悪いということではなく、次のようなものであるべきだということです。

  1. 認識できるほど定数として定義されている。
  2. 使用ドメイン全体で一度だけ定義されます(アーキテクチャ上可能な場合)。
  3. それらが何らかの形で関連する定数のセットを形成する場合、一緒に定義されます。
  4. それらが使用されるアプリケーションの一般性の適切なレベルで定義されている;そして
  5. 不適切なコンテキストでの使用を制限するような方法で定義されている(たとえば、型チェックが可能)。

通常、許容可能な「定数」と「マジック値」を区別するのは、これらのルールの1つ以上の違反です。

定数を使用すると、コードの特定の公理を簡単に表現できます。

最後のポイントになりますが、それでも上記の基準に準拠している場合でも(特にそれらから逸脱している場合)、定数の過度の使用(したがって、値で表した仮定または制約の数が多すぎる)、考案されているソリューションが十分に一般的または適切に構造化されていないことを意味している可能性があります(したがって、私たちは定数の賛否両論についてではなく、十分に構造化されたコードの長所と短所について話しているわけではありません)。

高水準言語には、定数を使用する必要がある低水準言語のパターンの構成があります。同じパターンを高級言語でも使用できますが、使用すべきではありません。

しかし、それはすべての状況の印象に基づいた専門家の判断であり、解決策はどのように見えるべきであり、その判断がどのように正当化されるかは、コンテキストに大きく依存します。確かに、「私は、この種の仕事をすでに見たことがあるほど年を取っているので、慣れ親しんでいる、よりよく成し遂げられた」と主張することを除いて、一般原則として正当化できないかもしれません。

編集: 1つの編集を受け入れ、別の編集を拒否し、自分自身の編集を実行した後、ルールのリストのフォーマットと句読点のスタイルを一度に解決することを検討できますか!

89
Steve
  • 彼らは追跡するのが難しいです。
  • すべてを変更するには、複数のプロジェクトで複数のファイルを変更する必要がある場合があります(維持が困難)。
  • 彼らの価値を見ただけでは、彼らの目的が何であるかを理解するのが難しい場合があります。
  • 再利用しないでください。
34
jason

実際の例:私は、「エンティティ」が「フィールド」とともに格納されるサードパーティのシステムを使用しています。基本的に [〜#〜] eav [〜#〜] システム。別のフィールドを追加するのはかなり簡単なので、フィールドの名前を文字列として使用すると、フィールドにアクセスできます。

Field nameField = myEntity.GetField("ProductName");

(魔法の文字列「ProductName」に注意してください)

これはいくつかの問題を引き起こす可能性があります:

  • 「ProductName」が存在することとその正確なスペルを知るために、外部のドキュメントを参照する必要があります
  • さらに、そのドキュメントを参照して、そのフィールドのデータ型を確認する必要があります。
  • このマジック文字列のタイプミスは、このコード行が実行されるまで捕捉されません。
  • 誰かがサーバーでこのフィールドの名前を変更することを決定した場合(データ損失を防ぐのは難しいですが、不可能ではありません)、コードを簡単に検索して、この名前を調整する場所を確認することはできません。

したがって、これに対する私の解決策は、エンティティタイプ別に編成されたこれらの名前の定数を生成することでした。だから今私は使うことができます:

Field nameField = myEntity.GetField(Model.Product.ProductName);

それはまだ文字列定数であり、まったく同じバイナリにコンパイルされますが、いくつかの利点があります。

  • 「Model。」と入力した後、IDEは使用可能なエンティティタイプのみを表示するので、「Product」を簡単に選択できます。
  • 次に、私のIDEは、このタイプのエンティティで使用可能なフィールド名のみを提供し、これも選択可能です。
  • 自動生成されたドキュメントは、このフィールドの意味と、その値を格納するために使用されるデータ型を示しています。
  • 定数から始めて、my IDEは、その値とは対照的に、その正確な定数が使用されているすべての場所を見つけることができます)
  • タイプミスはコンパイラーによって検出されます。これは、新しいフィールド(フィールドの名前を変更または削除した後)を使用して定数を再生成する場合にも適用されます。

次に、これらの定数を生成された厳密に型指定されたクラスの背後に隠します。データ型も保護されます。

26
Hans Kesting

マジックストリング常に悪いわけではありませんなので、これを回避するための包括的な理由が思いつかないのはこのためかもしれません。 (「マジックストリング」とは、式の一部としての文字列リテラルを意味し、定数として定義されていないことを想定しています。)

特定のケースでは、マジックストリングは回避する必要があります。

  • 同じ文字列がコードに複数回出現します。つまり、いずれかの場所でスペルミスが発生する可能性があります。そして、それは文字列の変更の面倒になります。文字列を定数に変換すると、この問題を回避できます。
  • 文字列は、出現するコードとは関係なく変更される場合があります。例えば。文字列がエンドユーザーに表示されるテキストの場合、ロジックの変更とは関係なく変更される可能性があります。そのような文字列を別のモジュール(または外部構成またはデータベース)に分離すると、個別に変更することが容易になります
  • 文字列の意味は、コンテキストからは明らかではありません。その場合、定数を導入するとコードが理解しやすくなります。

ただし、場合によっては、「マジックストリング」で十分な場合もあります。単純なパーサーがあるとします。

switch (token.Text) {
  case "+":
    return a + b;
  case "-":
    return a - b;
  //etc.
}

ここには本当に魔法はなく、上記の問題のどれも当てはまりません。 IMHOを定義してもメリットはありませんstring Plus="+"など。シンプルにしてください。

11
JacquesB

既存の回答に追加するには:

国際化(i18n)

画面に表示するテキストがハードコーディングされ、関数のレイヤーに埋め込まれている場合、そのテキストを他の言語に翻訳するのは非常に困難です。

一部の開発環境(Qtなど)では、ベース言語のテキスト文字列から翻訳された言語へのルックアップによって翻訳を処理します。マジックストリングは通常、これを乗り切ることができます。同じテキストを他の場所で使用してタイプミスを取得するまでは。それでも、別の言語のサポートを追加したい場合、どの魔法の文字列を翻訳する必要があるかを見つけるのは非常に困難です。

一部の開発環境(MS Visual Studioなど)では、別のアプローチを採用しており、すべての翻訳された文字列をリソースデータベース内に保持し、その文字列の一意のIDによって現在のロケールに読み戻す必要があります。この場合、マジックストリングを使用したアプリケーションは、大幅な手直しなしでは他の言語に翻訳することはできません。効率的な開発では、すべてのテキスト文字列をリソースデータベースに入力し、コードを最初に作成するときに一意のIDを指定する必要があります。その後のi18nは比較的簡単です。事実の後でこれを埋め戻しようとすると、通常は非常に大きな労力が必要になります(そして、はい、私はそこに行ったことがあります)。そのため、最初から正しいことを行う方がはるかに優れています。

6
Graham

これは誰にとっても優先事項ではありませんが、自動化された方法でコードのカップリング/凝集度メトリックを計算できるようにしたい場合、マジックストリングはこれをほぼ不可能にします。ある場所の文字列は別の場所のクラス、メソッド、または関数を参照するため、コードを解析するだけで文字列がクラス/メソッド/関数に結合されていることを簡単に自動的に判断する方法はありません。基盤となるフレームワーク(Angularなど)のみがリンケージがあると判断できます-実行時にのみ実行できます。自分でカップリング情報を取得するには、パーサーは、コーディングしている基本言語を超えて、使用しているフレームワークに関するすべてを知っている必要があります。

しかし、これも多くの開発者が気にすることではありません。

3
user3511585