매직 스트링에 어떤 문제가 있습니까?


164

숙련 된 소프트웨어 개발자로서 나는 마법의 끈을 피하는 법을 배웠습니다.

내 문제는 내가 사용한 이후로 오랜 시간이 걸린다는 것인데, 그 이유를 대부분 잊어 버렸습니다. 결과적으로 저 경험이 적은 동료들에게 왜 문제인지 설명하는 데 어려움을 겪고 있습니다.

피해야 할 객관적인 이유는 무엇입니까? 어떤 문제가 발생합니까?


38
마법의 끈은 무엇입니까? 마법의 숫자 와 같은 것 ?
Laiv

14
@Laiv : 매직 넘버와 비슷합니다. 예. deviq.com/magic-strings의 정의가 마음듭니다 . "매직 문자열은 어플리케이션 코드 내에서 직접 지정되어 어플리케이션의 동작에 영향을주는 문자열 값입니다." ( en.wikipedia.org/wiki/Magic_string에 대한 정의 는 전혀 염두에 두지 않습니다)
Kramii

17
이것은 내가 혐오하는 법을 배웠습니다 ... 나중에 후배 를 설득 하기 위해 어떤 주장을 사용할 수 있습니까 ... 끝없는 이야기 :-). 나는 "자신의 학습"을하려고하지 않을 것이다. 자신의 경험으로 얻은 교훈 / 아이디어 이상의 것은 없습니다. 당신이하려는 것은 교리 입니다. Lemmings 팀을 원하지 않는 한 그렇게하지 마십시오.
Laiv

15
@Laiv : 사람들이 자신의 경험을 통해 배우게하고 싶지만 불행히도 그것은 나에게 선택 사항이 아닙니다. 미묘한 버그로 인해 환자 치료가 어려워지고 예방 가능한 유지비를 감당할 수없는 공공 자금 지원 병원에서 근무하고 있습니다.
Kramii

6
@DavidArno, 즉 정확히 그는이 질문을 물어 무엇을하고 있는지.
user56834

답변:


212
  1. 컴파일하는 언어에서는 컴파일 타임에 매직 문자열 값이 확인되지 않습니다 . 문자열이 특정 패턴과 일치해야하는 경우 해당 패턴에 맞는지 확인하기 위해 프로그램을 실행해야합니다. 열거 형과 같은 것을 사용하면 값이 잘못되어도 적어도 컴파일 타임에 값이 유효합니다.

  2. 매직 스트링이 여러 곳에서 쓰여지는 경우, 컴파일 타임 오류와 같은 안전없이 모든 스트링 을 변경해야합니다 . 그러나 한 곳에서만 선언하고 변수를 재사용하여 대응할 수 있습니다.

  3. 오타는 심각한 버그가 될 수 있습니다. 기능이있는 경우 :

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

    누군가 실수로 다음을 입력합니다.

    func("barr");
    

    특히 프로젝트의 모국어에 익숙하지 않은 프로그래머가있는 경우 문자열이 더 드물거나 복잡해집니다.

  4. 마술 줄은 거의 자기 문서화가 아닙니다. 하나의 문자열이 보이면 문자열이 무엇을 할 수 있는지 / 아무것도 말하지 않아도됩니다. 올바른 문자열을 선택했는지 확인하려면 구현을 조사해야 할 것입니다.

    이러한 종류의 구현은 누출 되어서, 특히 문서화해야 할 점을 이해하기 위해 외부 문서 나 코드에 액세스해야합니다.

  5. IDE에서 "find string"함수가 부족하면 패턴을 지원하는 도구가 거의 없습니다.

  6. 우연히 두 곳에서 동일한 마술 줄을 사용할 수 있습니다. 실제로 다른 것들 일 때 찾기 및 바꾸기를 수행하고 둘을 변경하면 다른 곳이 작동하는 동안 둘 중 하나가 깨질 수 있습니다.


34
첫 번째 인수와 관련하여 : TypeScript는 문자열 리터럴을 유형 검사 할 수있는 컴파일 된 언어입니다. 이것은 또한 인수 2-4를 무효화합니다. 따라서 문자열 자체가 문제가 아니라 너무 많은 값을 허용하는 형식을 사용하는 것입니다. 열거에 마법 정수를 사용하는 경우에도 동일한 추론을 적용 할 수 있습니다.
Yogu

11
TypeScript에 대한 경험이 없기 때문에 나는 당신의 판단을 연기 할 것입니다. 내가 말하고 싶은 것은 검사되지 않은 문자열 (사용 한 모든 언어의 경우와 마찬가지로)이 문제라는 것입니다.
Erdrik Ironrose

23
@Yogu Typescript는 예상되는 정적 문자열 리터럴 유형을 변경하면 모든 문자열의 이름을 바꾸지 않습니다. 컴파일 시간 오류가 발생하여 모든 것을 찾는 데 도움이되지만 2의 부분적인 개선 사항입니다. 열거 형의 이점을 완전히 제거하십시오. 우리 프로젝트에서 열거 형을 사용할 때와 확실하지 않은 개방형 스타일의 질문으로 남아서는 안 될 때; 두 가지 접근법 모두 성가심과 장점이 있습니다.
KRyan

30
문자열만큼 숫자는 아니지만 문자열에서 발생할 수있는 하나의 큰 값은 동일한 값을 가진 두 개의 마법 값이있을 때입니다. 그런 다음 그들 중 하나가 변경됩니다. 이제 이전 값을 새 값으로 변경하는 코드를 통해 진행하지만 자체적으로 작동하지만 잘못된 값을 변경하지 않도록 EXTRA 작업도 수행하고 있습니다. 상수 변수를 사용하면 수동으로 수행 할 필요가있을뿐 아니라 잘못된 내용을 변경했다고 걱정하지 않아도됩니다.
corsiKa

35
@ Yogu 필자는 컴파일시 문자열 리터럴 값을 확인 하면 마술 문자열이 아니라고 주장 합니다 . 이 시점에서 그것은 재미있는 방식으로 쓰여지는 일반적인 const / enum 값입니다. 이러한 관점을 감안할 때, 귀하의 의견은 실제로 반박하기보다는 Erdrik의 견해를 지지 한다고 주장합니다.
GrandOpener

89

다른 답변이 파악한 것에 대한 정상 회담은 "마법의 가치"가 나쁘다는 것이 아니라 다음과 같아야한다는 것입니다.

  1. 인식 가능하게 상수로 정의;
  2. 전체 사용 영역 내에서 한 번만 정의 (건축 적으로 가능한 경우)
  3. 그것들이 어떻게 든 관련된 상수 세트를 형성하면 함께 정의됩니다.
  4. 그것들이 사용되는 응용 프로그램에서 적절한 수준의 일반성으로 정의; 과
  5. 부적절한 상황에서 사용을 제한하는 방식으로 정의됩니다 (예 : 유형 검사가 가능함).

일반적으로 허용되는 "상수"와 "마법의 가치"를 구별하는 것은 이러한 규칙 중 하나 이상을 위반하는 것입니다.

잘 사용하면 상수를 사용하면 코드의 특정 공리를 표현할 수 있습니다.

위의 기준을 준수하더라도 (특히 그 값에서 벗어난 경우) 과도한 상수 사용 (따라서 값으로 표현 된 과도한 수의 가정 또는 제약 조건)을 마지막으로 알려줍니다. 고안 된 솔루션이 충분히 일반적이거나 잘 구조화되어 있지 않다는 것을 암시 할 수 있습니다 (따라서 우리는 더 이상 상수의 장단점에 대해 이야기하는 것이 아니라 잘 구성된 코드의 장단점에 대해 이야기하고 있습니다).

고급 언어에는 상수를 사용해야하는 하위 언어의 패턴에 대한 구성이 있습니다. 같은 언어를 더 높은 수준의 언어로 사용할 수도 있지만 그렇게해서는 안됩니다.

그러나 그것은 모든 상황에 대한 인상과 해결책이 어떤 모습이어야하는지에 기초한 전문가 판단 일 수 있으며, 그 판단이 어떻게 정당화 될 것인지는 상황에 따라 크게 좌우 될 것입니다. 실제로 그것은 "나에게 익숙하고 더 잘한 이런 종류의 일을 이미 보았을만큼 나이가 든다"고 주장하는 것을 제외하고는 일반적인 원칙의 관점에서 정당화 될 수 없다!

편집 : 하나의 편집을 수락하고 다른 편집을 거부했으며 이제 내 자신의 편집을 수행 한 후 이제 규칙 목록의 형식 및 문장 스타일을 한 번에 설정하는 것이 좋습니다.


2
나는이 답변을 좋아한다. 모든 "struct"(및 다른 모든 예약어)는 C 컴파일러에 대한 마술 문자열입니다. 그것들을 코딩하는 좋은 방법과 나쁜 방법이 있습니다.
Alfred Armstrong

6
예를 들어, 누군가 코드에서 "X : = 898755167 * Z"를 보게되면 그 의미가 무엇인지 알지 못하고 그것이 잘못되었음을 알 가능성도 낮아집니다. 그러나 "Speed_of_Light : constant Integer : = 299792456"이 표시되면 누군가이를 찾아 올바른 값 (그리고 더 나은 데이터 유형)을 제안합니다.
WGroleau

26
어떤 사람들은 요점을 완전히 놓치고 SEPARATOR = ","대신 COMMA = ","라고 씁니다. 전자는 더 명확하지 않지만 후자는 의도 된 사용법을 나타내며 나중에 한 곳에서 분리자를 변경할 수 있습니다.
marcus

1
실제로 @marcus! 물론 간단한 리터럴 값을 대신 사용하는 경우도 있습니다. 예를 들어, 방법이 값을 2로 나누는 경우 다른 곳에서 정의 된 value / 2값을 value / VALUE_DIVISOR사용하는 것보다 간단히 작성하는 것이 더 명확하고 간단 할 수 있습니다 2. CSV를 처리하는 메서드를 일반화하려는 경우 구분 기호를 매개 변수로 전달하고 상수로 정의하지 않는 것이 좋습니다. 그러나 그것은 맥락에서 판단의 문제입니다. @WGroleau의 예제는 SPEED_OF_LIGHT명시 적으로 이름을 지정하고자하는 것이지만 모든 리터럴이 이것이 필요한 것은 아닙니다.
Steve

4
마법의 끈이 "나쁜 것"임을 설득해야 할 필요가 있다면이 대답보다 낫습니다. 이 답변은 그들이 "나쁜 것"이고 그들이 유지할 수있는 방식으로 서비스를 제공 할 수있는 최선의 방법을 찾아야한다는 것을 알고 받아들이면 더 좋습니다.
corsiKa

34
  • 추적하기가 어렵습니다.
  • 모두를 변경하려면 여러 프로젝트에서 여러 파일을 변경해야 할 수 있습니다 (유지 관리하기 어려움).
  • 때로는 그들의 가치를 보면서 그들의 목적이 무엇인지 말하기가 어렵습니다.
  • 재사용하지 않습니다.

4
"재사용 금지"란 무엇입니까?
bye

7
하나의 변수 / 상수 등을 생성하고 모든 프로젝트 / 코드에서 재사용하는 대신 불필요한 중복을 일으키는 각 문자열을 새 문자열로 만듭니다.
제이슨

2 번과 4 번은 같나요?
토마스

4
@ThomasMoors 아니요 그는 이미 존재하는 매직 스트링 을 사용할 때마다 새로운 스트링을 만드는 방법에 대해 이야기하고 있습니다. 포인트 2는 스트링 자체를 변경하는 것입니다.
Pierre Arlaud

25

실제 예 : "엔터티"가 "필드"와 함께 저장되는 타사 시스템을 사용하고 있습니다. 기본적으로 EAV 시스템. 다른 필드를 추가하는 것은 상당히 쉬우므로 필드 이름을 문자열로 사용하여 필드에 액세스 할 수 있습니다.

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

(매직 문자열 "ProductName"참고)

이로 인해 몇 가지 문제가 발생할 수 있습니다.

  • "ProductName"이 존재하고 철자가 정확한지 확인하려면 외부 문서를 참조해야합니다.
  • 또한 해당 필드의 데이터 유형이 무엇인지 확인하려면 해당 문서를 참조해야합니다.
  • 이 마법 줄의 오타는이 코드 줄이 실행될 때까지 잡히지 않습니다.
  • 누군가가 서버 에서이 필드의 이름을 바꾸려고 할 때 (데이터 손실을 막는 것은 어렵지만 불가능하지는 않음) 코드를 쉽게 검색 하여이 이름을 조정 해야하는 위치를 볼 수 없습니다.

그래서 이것에 대한 해결책은 엔티티 유형별로 구성된 이러한 이름에 대한 상수를 생성하는 것이 었습니다. 이제 사용할 수 있습니다 :

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

여전히 문자열 상수이며 정확히 동일한 이진으로 컴파일되지만 몇 가지 장점이 있습니다.

  • "모델"을 입력하면 IDE에 사용 가능한 엔티티 유형 만 표시되므로 "제품"을 쉽게 선택할 수 있습니다.
  • 그런 다음 IDE는이 유형의 엔티티에 사용 가능한 필드 이름 만 제공하며 선택 가능합니다.
  • 자동 생성 된 문서는이 필드의 의미에 값을 저장하는 데 사용되는 데이터 유형을 더한 것을 보여줍니다.
  • 상수에서 시작하여 IDE는 정확한 상수가 사용되는 모든 위치를 찾을 수 있습니다 (값과 반대)
  • 오타는 컴파일러에 의해 잡힐 것입니다. 이것은 새로운 모델 (필드의 이름을 바꾸거나 필드를 삭제 한 후)을 사용하여 상수를 재생하는 경우에도 적용됩니다.

다음으로 내 목록에서 : 생성 된 강력한 형식의 클래스 뒤에 이러한 상수를 숨기면 데이터 형식도 보호됩니다.


+1 코드 구조에만 국한되지 않는 좋은 점이 많이 있습니다. IDE 지원 및 툴링
kmdreko

엔티티 유형의 일부가 실제로 정적 이름을 정의 할만큼 충분히 정적 인 경우 적절한 데이터 모델을 정의하는 것이 더 적합하다고 생각합니다 nameField = myEntity.ProductName;.
Lie Ryan

@LieRyan-일반 상수를 생성하고 기존 프로젝트를 업그레이드하여 사용하는 것이 훨씬 쉬웠습니다. 그게 내가 말했다 하고 정적 유형을 생성하는 작업 그래서 정확하게 할 것을 수
한스 애 성이 보내고

9

마법의 끈 이 항상 나쁘지 는 않기 때문에 이것이 피할 수있는 담요 이유를 생각해 낼 수없는 이유 일 수 있습니다. ( "마법 문자열"은 표현식의 일부로서 상수로 정의되지 않은 문자열 리터럴을 의미한다고 가정합니다.)

특정한 경우에, 매직 스트링은 피해야합니다 :

  • 동일한 문자열이 코드에 여러 번 나타납니다. 즉, 장소 중 하나에 맞춤법 오류가있을 수 있습니다. 그리고 그것은 문자열 변경의 번거 로움이 될 것입니다. 문자열을 상수로 바꾸면이 문제를 피할 수 있습니다.
  • 문자열은 표시되는 코드와 관계없이 변경 될 수 있습니다. 예 : 문자열이 최종 사용자에게 표시되는 텍스트 인 경우 논리 변경과 무관하게 변경 될 수 있습니다. 이러한 문자열을 별도의 모듈 (또는 외부 구성 또는 데이터베이스)로 분리하면 독립적으로 쉽게 변경할 수 있습니다.
  • 문자열의 의미는 컨텍스트에서 명확하지 않습니다. 이 경우 상수를 도입하면 코드를 더 쉽게 이해할 수 있습니다.

그러나 어떤 경우에는 "매직 스트링"이 좋습니다. 간단한 파서가 있다고 가정 해보십시오.

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

여기에는 실제로 마술이 없으며 위에서 설명한 문제 중 어느 것도 적용되지 않습니다. IMHO string Plus="+"등 을 정의하면 이점이 없습니다 . 단순하게 유지하십시오.


7
"매직 스트링"에 대한 당신의 정의가 충분하지 않다고 생각합니다. 그것은 숨기고 / 숨기거나 / 미스터리 한 개념이 필요합니다. 해당 카운터 예제에서 "+"와 "-"를 "magic"이라고 말하지 않습니다 if (dx != 0) { grad = dy/dx; }. 에서 0을 magic이라고 합니다.
Rupe

2
@Rupe : 동의하지만 OP는 " 응용 프로그램의 동작에 영향을주는 응용 프로그램 코드 내에 직접 지정된 문자열 값 "이라는 정의를 사용합니다 . "문자열이 신비하지 않아도되므로 이것이 내가 사용하는 정의입니다. 대답.
JacquesB

7
귀하의 예를 참조하면, 나는 대체 스위치 문 본 "+""-"함께 TOKEN_PLUSTOKEN_MINUS. 내가 읽을 때마다 나는 그것 때문에 읽고 디버깅하기가 더 어렵다고 생각했습니다! 분명히 간단한 문자열을 사용하는 것이 더 낫다는 데 동의하는 곳입니다.
Cort Ammon

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.