답변:
매직 넘버는 코드에서 숫자를 직접 사용하는 것입니다.
예를 들어, Java로 된 경우 :
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
이것은 다음과 같이 리팩토링되어야합니다.
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
코드의 가독성을 향상시키고 유지 관리가 더 쉽습니다. GUI에서 암호 필드의 크기를 설정 한 경우를 상상해보십시오. 마법의 숫자를 사용하면 최대 크기가 변경 될 때마다 두 개의 코드 위치에서 변경해야합니다. 하나를 잊어 버리면 불일치가 발생합니다.
JDK에는이처럼 예 가득 Integer
, Character
및 Math
클래스.
추신 : FindBugs 및 PMD와 같은 정적 분석 도구는 코드에서 마술 숫자의 사용을 감지하고 리팩토링을 제안합니다.
TRUE
/ FALSE
)
매직 넘버는 하드 코딩 된 값으로 나중에 변경 될 수 있지만 업데이트하기가 어려울 수 있습니다.
예를 들어 "주문"개요 페이지에 마지막 50 개의 주문을 표시하는 페이지가 있다고 가정합니다. 50은 매직 넘버입니다. 표준 또는 컨벤션을 통해 설정되지 않았기 때문에 스펙에 설명 된 이유로 만든 숫자입니다.
이제 SQL 스크립트 ( SELECT TOP 50 * FROM orders
), 웹 사이트 (최근 50 주문), 주문 로그인 ( for (i = 0; i < 50; i++)
) 및 다른 많은 장소와 같은 다른 장소에 50 개가 있습니다.
이제 누군가가 50에서 25로 변경하기로 결정하면 어떻게됩니까? 또는 75? 또는 153? 이제 모든 장소에서 50을 교체해야하며 놓칠 가능성이 큽니다. 50은 다른 용도로 사용될 수 있기 때문에 찾기 / 바꾸기가 작동하지 않을 수 있으며, 맹목적으로 50을 25로 바꾸면 부작용이 발생할 수 있습니다 (예 : Session.Timeout = 50
통화도 25로 설정되고 사용자가 너무 자주 시간 초과보고를 시작 함).
또한 코드가 이해하기 어려울 수 있습니다. 즉, " if a < 50 then bla
"-복잡한 함수 도중에이 코드를 발견하면 코드에 익숙하지 않은 다른 개발자가 "WTF는 50 ???"
그렇기 const int NumOrdersToDisplay = 50
때문에 코드를 더 읽기 쉽도록 ( " if a < NumOrdersToDisplay
") 명확하게 정의 된 1 개 위치에서만 변경 하면되기 때문에 "1"- " " 와 같이 모호하고 임의의 숫자를 갖는 것이 가장 좋습니다 .
마법의 숫자는 적절한 장소가 표준을 통해 정의 된 모든 것, 즉 SmtpClient.DefaultPort = 25
나 TCPPacketSize = whatever
(즉,이 표준화하지 않도록 경우). 또한 1 함수 내에서만 정의 된 모든 것이 허용 될 수는 있지만 상황에 따라 다릅니다.
SmtpClient.DefaultPort = 25
아마도 분명하다 어 보다 SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
습니다. 응용 프로그램 전체 를 검색 25
하고 SMTP 포트에 대한 항목 만 변경해야합니다. 예를 들어 테이블 열의 너비 나 숫자와 같은 25는 아닙니다. 페이지에 표시 할 레코드 수
IANA
입니다.
마법 번호 에 대한 Wikipedia 항목을 살펴 보셨습니까 ?
매직 넘버 레퍼런스가 만들어지는 모든 방법에 대해 조금 자세하게 설명합니다. 나쁜 프로그래밍 연습으로 마술 번호에 대한 인용문은 다음과 같습니다.
매직 넘버라는 용어는 또한 설명없이 소스 코드에서 직접 숫자를 사용하는 나쁜 프로그래밍 방식을 말합니다. 대부분의 경우 이로 인해 프로그램을 읽고 이해하고 유지하기가 더 어려워집니다. 대부분의 안내서는 숫자 0과 1을 제외하지만 코드의 다른 모든 숫자를 명명 된 상수로 정의하는 것이 좋습니다.
마법 : 알 수없는 의미
Symbolic Constant-> 올바른 의미 및 올바른 컨텍스트를 제공합니다
시맨틱 : 사물의 의미 또는 목적.
"상수를 작성하고 의미 뒤에 이름을 지정한 다음 숫자로 바꾸십시오." -마틴 파울러
첫째, 매직 넘버는 단순한 숫자가 아닙니다. 모든 기본 값은 "마법"이 될 수 있습니다. 기본 값은 정수, 실수, double, float, 날짜, 문자열, 부울, 문자 등과 같은 매니페스트 엔터티입니다. 문제는 데이터 유형이 아니라 코드 텍스트에 나타나는 값의 "마법"측면입니다.
"마술"은 무엇을 의미합니까? 정확하게하기 위해 : "마법"에 의해, 우리는 코드의 맥락에서 가치의 의미 (의미 또는 목적)를 가리 키려고합니다. 알 수 없거나, 알 수 없거나, 불분명하거나 혼동된다. 이것이 "마술"의 개념입니다. 특별한 의미의 단어 (예 : 기호 상수)없이 서라운드 컨텍스트에서 의미 론적 의미 또는 목적이 빠르고 쉽게 알려지고 명확하고 이해 (혼동되지 않음) 할 때 기본 값은 마술이 아닙니다.
따라서, 우리는 주변 환경으로부터 기본 가치의 의미와 목적을 알고, 명확하게 이해하는 코드 리더의 능력을 측정하여 마법의 숫자를 식별합니다. 독자가 덜 알려지고 덜 명확하고 더 혼란 스러울수록 기본 값이 "매직"입니다.
마법의 기본 가치에 대한 두 가지 시나리오가 있습니다. 프로그래머와 코드에서 두 번째 만이 가장 중요합니다.
"매직"에 대한 가장 중요한 의존성은 고독한 기본 값 (예 : 숫자)이 일반적으로 알려진 의미론 (예 : Pi)을 갖지 않지만, 로컬에서 알려진 의미론 (예 : 프로그램)을 가지므로 문맥에서 완전히 명확하지 않거나 악용 될 수 있습니다. 좋은 또는 나쁜 맥락에서.
대부분의 프로그래밍 언어의 의미는 (아마도) 데이터 (예 : 데이터 테이블)를 제외하고 고독한 기본 값을 사용할 수 없습니다. 우리가 "마법의 숫자"를 만날 때 우리는 일반적으로 맥락에서 그렇게합니다. 따라서
"이 마법 번호를 기호 상수로 대체합니까?"
입니다 :
"컨텍스트에서 숫자의 의미 적 의미 (거기에 대한 목적)를 얼마나 빨리 평가하고 이해할 수 있습니까?"
이러한 생각을 염두에두고 적절한 맥락에 배치 할 때 Pi (3.14159)와 같은 숫자가 어떻게 "마법의 숫자"가 아닌지 빠르게 알 수 있습니다 (예 : 2 x 3.14159 x 반경 또는 2 * Pi * r). 여기서 숫자 3.14159는 기호 상수 식별자없이 정신적으로 Pi로 인식됩니다.
여전히, 우리는 일반적으로 3.14159를 숫자의 길이와 복잡성 때문에 Pi와 같은 기호 상수 식별자로 대체합니다. Pi의 길이와 복잡성의 측면 (정확도의 필요성과 결합)은 일반적으로 기호 식별자 또는 상수가 오류가 덜 발생한다는 것을 의미합니다. "Pi"를 이름으로 인식하는 것은 단순히 편리한 보너스이지만 상수를 갖는 주된 이유는 아닙니다.
Pi와 같은 일반적인 상수를 제외하고는 주로 특별한 의미를 가진 숫자에 중점을 두지 만 그 의미는 소프트웨어 시스템의 세계에 제약을받습니다. 이러한 숫자는 "2"(기본 정수 값) 일 수 있습니다.
숫자 2를 단독으로 사용하는 경우 첫 번째 질문은 "2"는 무엇을 의미합니까? "2"자체의 의미는 문맥 없이는 알 수없고 알 수 없으므로 사용이 불분명하고 혼동됩니다. 언어 의미 때문에 소프트웨어에 "2"만있는 것은 아니지만 "2"자체에는 특별한 의미가 없거나 명백한 목적 만 가지고 있지 않습니다.
우리의 고독한 "2"를 다음과 같은 맥락에 두자. padding := 2
문맥이 "GUI 컨테이너"인 . 이와 관련하여 2의 의미 (픽셀 또는 기타 그래픽 단위)는 의미 (의미 및 목적)를 빠르게 추측 할 수있게합니다. 우리는 여기서 멈추고이 맥락에서 2가 괜찮다고 말할 수 있으며 우리가 알아야 할 것이 없습니다. 그러나 아마도 우리의 소프트웨어 세계에서 이것은 전체 이야기가 아닙니다. 그것에 더 많은 것이 있지만, 문맥으로 "padding = 2"는 그것을 드러 낼 수 없습니다.
우리 프로그램에서 2 개의 픽셀 패딩이 시스템 전체에서 "default_padding"다양성을 가지고 있다고 가정 해 봅시다. 따라서 지시를 작성하는 padding = 2
것만으로는 충분하지 않습니다. "기본"의 개념은 공개되지 않습니다. 내가 쓸 때 : padding = default_padding
문맥으로 그리고 다른 곳에서 : default_padding = 2
나는 우리 시스템에서 2의 더 나은 의미 (의미 적 및 목적)를 완전히 실현합니까?
위의 예는 "2"자체만으로도 가능하기 때문에 꽤 좋습니다. "내 프로그램" default_padding
의 GUI UX 부분에 2가있는 "내 프로그램"으로 이해의 범위와 영역을 제한 할 때만 , 적절한 문맥에서 "2"를 이해해야합니다. 여기서 "2"는 "마법의"숫자이며, default_padding
"내 프로그램"의 GUI UX 컨텍스트 내에서 기호 상수를 고려 default_padding
하여 묶는 코드의 더 큰 컨텍스트에서 빠르게 이해할 수 있도록 사용 합니다.
따라서 의미 (의미 및 목적)를 충분하고 신속하게 이해할 수없는 모든 기본 값은 기본 값 (예 : 마법 수) 대신 기호 상수에 적합한 후보입니다.
스케일의 숫자에도 의미가있을 수 있습니다. 예를 들어, 우리가 괴물이라는 개념을 가지고있는 D & D 게임을하는 것처럼 가장하십시오. 우리의 괴물 객체는 life_force
정수 라는라는 기능을 가지고 있습니다. 숫자에는 의미를 제공 할 단어가 없으면 알 수 없거나 명확한 의미가 있습니다. 따라서 우리는 임의로 다음과 같이 말합니다.
위의 상징적 인 상수로부터, 우리는 D & D 게임에서 우리의 몬스터에 대한 생존, 사망 및 "언데드"(그리고 가능한 결과 또는 결과)에 대한 정신적 인 그림을 얻기 시작합니다. 이 단어들 (기호 상수)이 없으면, 우리는 ~에 이르는 숫자들만 남게됩니다 -10 .. 10
. 게임의 다른 부분이 무엇인지와 같은 다양한 작업의 숫자 수단의 범위에 대한 종속성이있는 경우 가능성이 큰 혼란의 장소에있는 단어 잎 우리없이 잠재적으로 우리의 게임에 오류가 그냥 범위 attack_elves
나 seek_magic_healing_potion
.
따라서 "매직 숫자"의 대체를 검색하고 고려할 때 우리는 소프트웨어의 맥락에서 숫자와 숫자가 의미 적으로 상호 작용하는 방식에 대해 매우 의도적으로 채워진 질문을하고 싶습니다.
어떤 질문을해야하는지 검토해 보겠습니다.
다음과 같은 경우 마법 번호가있을 수 있습니다 ...
코드 텍스트에서 독립형 매니페스트 상수 기본 값을 검사하십시오. 그러한 가치의 각 사례에 대해 천천히 그리고 신중하게 질문하십시오. 답의 힘을 고려하십시오. 여러 번 대답은 흑백이 아니지만 의미와 목적, 학습 속도 및 이해 속도를 잘못 이해하는 음영이 있습니다. 또한 소프트웨어 소프트웨어가 주변의 소프트웨어 시스템에 어떻게 연결되어 있는지 확인해야합니다.
결국, 대체에 대한 대답은 독자의 강점 또는 약점의 측정치 (당신의 마음에)에 답하여 연결을 만드는 것입니다 (예 : "얻습니다"). 의미와 목적을 더 빨리 이해할수록 "마법"이 적습니다.
결론 : 마법이 혼동으로 인해 발생하는 버그를 감지하기 어려울 정도로 큰 경우에만 기본 값을 기호 상수로 대체하십시오.
매직 넘버는 파일 형식 또는 프로토콜 교환을 시작할 때 일련의 문자입니다. 이 번호는 상태 점검 기능을합니다.
예 : GIF 파일을 열면 시작 부분에 GIF89가 표시됩니다. "GIF89"는 매직 넘버입니다.
다른 프로그램은 파일의 처음 몇 문자를 읽고 GIF를 올바르게 식별 할 수 있습니다.
임의의 이진 데이터는 동일한 문자를 포함 할 수 있습니다. 그러나 매우 가능성이 낮습니다.
프로토콜 교환과 관련하여 전달 된 현재 '메시지'가 손상되었거나 유효하지 않음을 신속하게 식별 할 수 있습니다.
매직 넘버는 여전히 유용합니다.
프로그래밍에서 "마법의 숫자"는 상징적 인 이름을 부여해야하는 값이지만, 대신 일반적으로 여러 곳에서 리터럴로 코드에 삽입되었습니다.
SPOT (Single Point of Truth)이 좋은 것과 같은 이유로 나쁩니다. 나중에이 상수를 변경하려면 모든 인스턴스를 찾기 위해 코드를 찾아야합니다. 이 숫자가 무엇을 나타내는 지 다른 프로그래머에게 명확하지 않을 수 있기 때문에 "나쁜"마법이기도합니다.
사람들은 때때로 이러한 상수를 별도의 파일로 이동하여 구성으로 작동함으로써 마법 번호를 더 이상 제거하지 않습니다. 이것은 때때로 도움이되지만 가치보다 더 복잡해질 수도 있습니다.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
루프보다 훨씬 빠른 식이 평가 될 수 있습니다. 3
코드를 루프로 다시 작성하지 않고 교체하는 경우 ITEMS_TO_AVERAGE
정의 된 것을 본 사람은 코드를 3
변경하여 5
평균 더 많은 항목을 가질 수 있다고 생각할 수 있습니다 . 대조적으로, 문자와 함께 발현에보고있는 사람은 3
(가) 실현 것이다 3
합산되는 항목의 수를 나타낸다.
매직 넘버는 또한 특수한 하드 코딩 된 시맨틱을 가진 숫자 일 수도 있습니다. 예를 들어, 레코드 ID> 0이 정상적으로 처리되고 0 자체가 "새 레코드"이고 -1이 "이것이 루트"이고 -99가 "이것이 루트에 작성 됨"인 시스템을 본 적이 있습니다. 0과 -99는 WebService가 새 ID를 제공하게합니다.
나쁜 점은 특별한 능력을 위해 공백 (레코드 ID의 부호있는 정수)을 재사용한다는 것입니다. ID 0 또는 음수 ID를 사용하여 레코드를 만들지 않을 수도 있지만, 그렇지 않더라도 코드 나 데이터베이스를 보는 모든 사람이이 문제를 발견하고 처음에는 혼란 스러울 수 있습니다. 그 특별한 가치가 잘 문서화되지 않았다는 것은 말할 나위도 없습니다.
틀림없이, 22, 7, -12 ~ 620 도 매직 넘버로 계산. ;-)
마법의 숫자를 사용하여 언급되지 않은 문제 ...
당신이 그들 중 많은 것을 가지고 있다면, 당신이 값 이 같은 곳에서 마술 숫자를 사용하는 두 가지 다른 목적 을 가지고 있다는 것이 합리적 입니다.
그런 다음, 한 가지 목적으로 만 값을 변경해야합니다.
저는 항상 "매직 넘버"라는 용어를 다르게 사용했습니다. 빠른 유효성 검사로 확인할 수있는 데이터 구조 내에 저장된 모호한 값입니다. 예를 들어 gzip 파일은 처음 3 바이트로 0x1f8b08을 포함하고 Java 클래스 파일은 0xcafebabe로 시작합니다.
파일이 다소 난잡하게 전송되고 생성 방식에 대한 메타 데이터가 손실 될 수 있기 때문에 파일 형식에 포함 된 매직 넘버가 종종 표시됩니다. 그러나 매직 넘버는 ioctl () 호출과 같은 메모리 내 데이터 구조에도 사용됩니다.
파일 또는 데이터 구조를 처리하기 전에 매직 번호를 빠르게 확인하면 입력이 완료되었다는 것을 알리기 위해 잠재적으로 긴 처리 과정을 거치지 않고 오류를 조기에 알릴 수 있습니다.
때로는 코드에 구성 할 수없는 "하드 코딩 된"숫자를 원할 수도 있습니다. 최적화 된 역 제곱근 알고리즘에 사용되는 0x5F3759DF를 포함 하여 많은 유명한 것들이 있습니다 .
드문 경우에 그러한 매직 넘버를 사용해야 할 필요가있는 경우, 코드에서 그것들을 const로 설정하고 그것들이 사용되는 이유, 작동 방식 및 출처를 문서화합니다.
클래스 상단의 변수를 기본값으로 초기화하는 것은 어떻습니까? 예를 들면 다음과 같습니다.
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
이 경우 15000은 마법 번호입니다 (CheckStyles에 따름). 나에게 기본값을 설정하는 것은 괜찮습니다. 나는하고 싶지 않다 :
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
읽기가 더 어려워 집니까? CheckStyles를 설치하기 전까지는 이것을 고려하지 않았습니다.
static final
한 가지 방법으로 상수를 사용하면 상수가 과도 하다고 생각 합니다. final
메소드 상단에 선언 된 변수는 더 읽기 쉬운 IMHO입니다.
@ eed3si9n : 심지어 '1'은 마법의 숫자라고 제안합니다. :-)
매직 넘버와 관련된 원칙은 코드가 다루는 모든 사실이 정확히 한 번 선언되어야한다는 것입니다. 코드에 마술 숫자 (예 : @marcio가 제공 한 암호 길이 예제)를 사용하면 해당 사실을 쉽게 복제 할 수 있으며 해당 사실을 이해하면 유지 관리 문제가 발생합니다.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
반환 변수는 어떻습니까?
저장 프로 시저를 구현할 때 특히 어려운 점이 있습니다. .
다음 저장 프로 시저를 상상해보십시오 (잘못된 구문, 예를 보여주기 위해 알고 있습니다).
int procGetIdCompanyByName(string companyName);
특정 테이블에 존재하면 회사의 ID를 반환합니다. 그렇지 않으면 -1을 반환합니다. 어떻게 든 그것은 마법의 숫자입니다. 지금까지 읽은 권장 사항 중 일부는 실제로 이와 같은 디자인을 수행해야한다고 말합니다.
int procGetIdCompanyByName(string companyName, bool existsCompany);
그런데 회사가 존재하지 않으면 무엇을 반환해야합니까? Ok : existesCompany 를 false 로 설정하고 -1도 반환합니다.
Antoher 옵션은 두 가지 기능을 수행하는 것입니다.
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
따라서 두 번째 저장 프로 시저의 전제 조건은 회사가 존재한다는 것입니다.
그러나이 시스템에서는 다른 사용자가 회사를 만들 수 있기 때문에 동시성이 두렵습니다.
결론은 다음과 같습니다. 상대적으로 알려져 있고 안전한 어떤 종류의 "매직 숫자"를 사용하여 무언가가 성공하지 못했거나 존재하지 않는다는 것을 어떻게 생각하십니까?
상수로 매직 넘버를 추출하는 또 다른 장점은 비즈니스 정보를 명확하게 문서화 할 수있는 가능성을 제공합니다.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
것입니다. 주민과 같은.