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


164

経験豊富なソフトウェア開発者として、私は魔法の文字列を避けることを学びました。

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

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


38
魔法の文字列とは何ですか?マジックナンバーと同じもの?
ライヴ

14
@Laiv:それらはマジックナンバーに似ています、はい。deviq.com/ magic-stringsの定義が好きです:「マジックストリングは、アプリケーションの動作に影響を与えるアプリケーションコード内で直接指定されるストリング値です。」(en.wikipedia.org/wiki/Magic_stringの定義は、私がまったく念頭に置いているものではありません)
Kramii

17
これは私が嫌悪することを学んだ面白いです... 後輩を説得するためどのような引数を使用できます ...終わりのない物語:-)。私は彼ら自身で学ぼうとするよりも「納得させよう」とは思わないでしょう。自分の経験から得た教訓/アイデア以上のものはありません。あなたがやろうとしているのは教化です。レミングのチームが必要な場合を除いて、そうしないでください。
ライヴ

15
@Laiv:人々に自分の経験から学ばせたいのですが、残念ながらそれは私にとって選択肢ではありません。私は公的に資金提供されている病院で働いており、そこでは微妙なバグが患者のケアを損なう可能性があり、回避可能なメンテナンス費用を支払う余裕はありません。
クラミイ

6
@DavidArno、それがまさに彼がこの質問をすることでやっていることです。
user56834

回答:


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

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

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

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

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

    func("barr");
    

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

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

    この種の実装は漏れやすく、特にポイント3のように完全な文字でなければならないため、外部ドキュメントまたはコードへのアクセスが必要です。

  5. IDEの「文字列の検索」機能以外では、パターンをサポートする少数のツールがあります。

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


34
最初の引数について:TypeScriptは、文字列リテラルを型チェックできるコンパイルされた言語です。これにより、引数2〜4も無効になります。したがって、文字列自体ではなく、値が多すぎる型を使用することが問題です。同じ推論は、列挙にマジック整数を使用する場合にも適用できます。
ヨグ

11
私はTypeScriptの経験がないので、そこでの判断に任せます。私が言うことは、チェックされていない文字列(使用したすべての言語の場合)が問題だということです。
Erdrik Ironrose

23
@Yogu Typescriptは、期待する静的な文字列リテラルタイプを変更した場合、すべての文字列の名前を変更しません。あなたはそれらのすべてを見つけるのを助けるためにコンパイル時エラーを取得しますが、それは2の部分的な改善に過ぎません。列挙型の利点を完全に排除します。私たちのプロジェクトでは、列挙型を使用する場合と使用しない場合は、不明な種類のオープンスタイルの質問のままです。どちらのアプローチにも迷惑と利点があります。
KRyan

30
数字ほどの文字列ではなく、文字列で発生する可能性のある大きな値は、同じ値を持つ2つの魔法の値がある場合です。次に、そのうちの1つが変更されます。これで、古い値を新しい値に変更するコードを実行しますが、これはそれ自体で動作しますが、間違った値を変更していないことを確認するために余分な作業も行っています。定数変数を使用すると、それを手動で実行する必要がないだけでなく、間違ったものを変更したことを心配する必要もありません。
corsiKa

35
@Yoguさらに、コンパイル時に文字列リテラルの値がチェックされている場合、それは魔法の文字列ではなくなると主張します。その時点では、たまたまおかしな方法で記述されているのは、通常のconst / enum値です。その観点から、あなたのコメントは、反論するのではなく、実際にErdrikのポイントを支持していると私は主張します。
-GrandOpener

89

他の答えが把握しているサミットは、「魔法の価値」が悪いということではなく、そうあるべきだということです。

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

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

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

それは最終的なポイントに私を連れて行きます、定数の過度の使用(そして値の観点から表現された過度の数の仮定または制約)、たとえそれ以外の場合上記の基準に準拠していても(特にそれらが逸脱している場合)、考案されているソリューションが十分に一般的または適切に構造化されていないことを意味する可能性があります(したがって、定数の長所と短所についてはもう話していないが、適切に構造化されたコードの長所と短所については話していません)。

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

しかし、それはすべての状況とソリューションがどのように見えるべきかという印象に基づいた専門家の判断であり、その判断が正確にどのように正当化されるかはコンテキストに大きく依存します。確かに、「私はよく知っているこの種の作品をすでに見てきたので、私は十分に年を取っている」と断言する以外は、一般原則の観点からは正当化できないかもしれません!

編集: 1つの編集を受け入れ、別の編集を拒否し、独自の編集を実行したので、ルールのリストの書式設定と句読点のスタイルを1回すべて解決することを検討できますか!


2
私はこの答えが好きです。結局、「構造体」(および他のすべての予約語)はCコンパイラにとって魔法の文字列です。それらのコーディングには良い方法と悪い方法があります。
アルフレッドアームストロング

6
たとえば、コードに「X:= 898755167 * Z」と表示された場合、その意味がわからず、間違っていることを知る可能性は低いでしょう。しかし、「Speed_of_Light:constant Integer:= 299792456」と表示された場合、誰かがそれを検索し、正しい値(さらにはより良いデータ型)を提案します。
WGroleau

26
一部の人々はこの点を完全に見落とし、SEPARATOR = "、"の代わりにCOMMA = "、"と記述します。前者は何も明確にしませんが、後者は意図された使用法を示しており、後で単一の場所でセパレータを変更できます。
マーカス

1
@marcus、確かに!もちろん、インプレースで単純なリテラル値を使用する場合があります-たとえば、メソッドが値を2で除算する場合、後者を別の場所で定義するvalue / 2よりも、単純に記述する方がより明確で簡単な場合があります。CSVを処理するメソッドを一般化する場合は、おそらくセパレーターをパラメーターとして渡し、定数としてはまったく定義しないでください。しかし、それはすべてコンテキストの判断の問題です-@WGroleauの例は明示的に名前を付けたいものですが、すべてのリテラルがこれを必要とするわけではありません。value / VALUE_DIVISOR2SPEED_OF_LIGHT
スティーブ

4
マジックストリングが「悪いこと」であると確信する必要がある場合、一番上の答えはこの答えよりも優れています。この答えは、それらが「悪いこと」であり、保守可能な方法でサービスを提供するニーズを満たす最適な方法を見つける必要があることを知って受け入れている場合に優れています。
corsiKa

34
  • 追跡するのは難しいです。
  • すべてを変更するには、複数のプロジェクトで複数のファイルを変更する必要がある場合があります(保守が困難です)。
  • 価値を見ただけでは、目的が何なのかを判断するのが難しい場合があります。
  • 再利用できません。

4
「再利用なし」とはどういう意味ですか?
さようなら

7
1つの変数/定数などを作成してすべてのプロジェクト/コードで再利用する代わりに、それぞれに新しい文字列を作成して、不必要な重複を引き起こしています。
ジェイソン

ポイント2と4は同じですか?
トーマス

4
@ThomasMoorsいいえ、彼はあなたが使用したいたびに新しい文字列を構築する必要があります方法について話しています既存の魔法の文字列を、ポイント2は、文字列自体を変更することについてです
ピエールArlaud

25

実際の例:私は、「エンティティ」が「フィールド」とともに保存されるサードパーティシステムで作業しています。基本的にEAVシステム。別のフィールドを追加するのは非常に簡単なので、フィールドの名前を文字列として使用してフィールドにアクセスできます。

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

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

これにより、いくつかの問題が発生する可能性があります。

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

そのため、これに対する私の解決策は、エンティティタイプごとに整理されたこれらの名前の定数を生成することでした。だから今私は使用することができます:

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

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

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

次のリストでは、生成された強く型付けされたクラスの背後にこれらの定数を隠します-データ型も保護されます。


コード構造に限定されない多くの良い点を挙げてください:IDEサポートとツール、これは大規模プロジェクトで命の恩人になることがあります
kmdreko

エンティティ型の一部が十分に静的であり、実際に定数名を定義する価値がある場合、適切なデータモデルを定義する方が適切だと思いますnameField = myEntity.ProductName;
ライライアン

@LieRyan-単純な定数を生成し、既存のプロジェクトをアップグレードしてそれらを使用するのがずっと簡単でした。それは私が、言っています静的な型を生成するに取り組んで私は正確に行うことができ
ハンス柯STはINGの

9

魔法の糸は必ずしも悪いわけではないので、これがあなたがそれらを避ける包括的な理由を思い付かない理由かもしれません。(「マジックストリング」とは、定数として定義されていない式の一部としての文字列リテラルを意味すると仮定します。)

場合によっては、マジックストリングを避ける必要があります。

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

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

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

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


7
「魔法の文字列」の定義は不十分だと思います。隠蔽/隠蔽/謎を解くという概念が必要です。その反例の「+」と「-」を「マジック」とは呼びませんが、でゼロをマジックと呼ぶのと同じif (dx != 0) { grad = dy/dx; }です。
ルーペ

2
@Rupe:同意しますが、OPは「アプリケーションの動作に影響を与えるアプリケーションコード内で直接指定される文字列値」という定義を使用しますこれは、文字列が不可解である必要がないため、これが私が使用する定義です答え。
ジャックB

7
あなたの例を参照して、"+"and "-"TOKEN_PLUSandに置き換えたswitchステートメントを見てきましたTOKEN_MINUS。それを読むたびに、そのせいで読みにくく、デバッグしにくいと感じました!単純な文字列を使用する方が良いと私が同意する場所です。
コートアンモン

2
マジックストリングが適切な場合があることに同意します。それらを避けることは経験則であり、すべての経験則には例外があります。うまくいけば、なぜそれ悪いことになるのかが明確になれば、物事をするのではなく、賢明な選択ができるようになります。それは、(1)より良い方法があることを理解していない、または(2)上級開発者またはコーディング標準によって、物事を異なる方法で行うように言われました。
-Kramii

2
ここで「魔法」とは何なのかわかりません。それらは基本的な文字列リテラルのように見えます。
tchrist

6

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

国際化(i18n)

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

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

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


3

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

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

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