상수 값이 시간이 지남에 따라 변경 될 수 있습니까?


28

개발 단계 동안 동일한 실행에서 고정되어야하는 특정 변수가 있지만 시간이 지남에 따라 수정해야 할 수도 있습니다. 예를 들어 boolean디버그 모드를 알리는 신호이므로 일반적으로하지 않는 프로그램에서 작업합니다.

이러한 값을 상수, 즉 final static int CONSTANT = 0Java 에 포함시키는 것이 나쁜 스타일 입니까? 런타임 동안 상수가 동일하게 유지되지만 계획되지 않은 변경을 제외하고 전체 개발 중에도 동일해야한다는 것을 알고 있습니까?

비슷한 질문을 검색했지만 정확히 일치하는 것을 찾지 못했습니다.


11
궁금합니다. 왜 이런 스타일을 바꾸는 것이 나쁜 스타일이라고 생각하십니까?
Vincent Savard

36
알려진 수학적 값을 가진 상수를 사용하여 물리적 특성을 모델링하지 않으면 모든 것이 한 번에 변경 될 수 있습니다.
Berin Loritsch

19
소프트웨어는 부드럽습니다 .
Erik Eidt

10
@GregT 나는 동의하지 않을 것이다. final프로그램이 값을 수정하지 않는다는 컴파일러 적용 보증을 제공합니다. 프로그래머가 소스 코드에 지정된 값을 수정하고 싶을 수도 있기 때문에 그 점을 피하지 않을 것입니다.
Alexander-복직 자 Monica Monica

10
전체 답변을 표현할 시간은 많지 않지만 동료의 우려는 상수가 아니라 상수로 표시되는 구성 값의 코드 인라이닝과 관련이 있다고 생각합니다. ... 상수는 gravity게임 중 / 런을 실수로 할당하는 것과 같은 어리석은 실수로부터 사용자를 보호합니다 . 그것들은 반드시 gravity모든 행성에서 동일 하다는 것을 의미하지는 않습니다 ... 건강한 해결책은 gravity상수 를 만드는 것이지만 planet관련 범위의 시작 부분에서 파일이나 데이터베이스에서 가져 오는 것입니다 .
svidgen

답변:


6

Java에서는 정적 최종 상수를 컴파일러에서 값 으로 사용하는 코드에 복사 할 수 있습니다 . 결과적으로 새 버전의 코드를 릴리스하고 상수를 사용한 일부 다운 스트림 종속성이있는 경우 다운 스트림 코드를 다시 컴파일하지 않으면 해당 코드의 상수가 업데이트되지 않습니다. 소스 코드가 맞더라도 이진 코드가 아닌 것처럼 새로운 값을 기대하는 코드에서 상수를 사용하면 문제가 될 수 있습니다.

소스 호환성과 이진 호환성이 동일하지 않은 경우 (아마도 유일한 경우)이기 때문에 이것은 자바 디자인에있어 경고입니다. 이 경우를 제외하고는 종속성을 다시 컴파일하지 않고도 새로운 API 호환 버전으로 종속성을 교체 할 수 있습니다. Java 의존성이 일반적으로 관리되는 방식을 고려할 때 이것은 매우 중요합니다.

설상가상으로 코드는 유용한 오류를 생성하지 않고 잘못된 일을 자동으로 수행합니다. 호환되지 않는 클래스 또는 메소드 정의가있는 버전으로 종속성을 바꾸려면 클래스 로더 또는 호출 오류가 발생하여 문제가 무엇인지에 대한 최소한의 힌트를 제공합니다. 값의 유형을 변경하지 않으면이 문제는 런타임에 이상하게 보일 수 있습니다.

더 성가신 것은 불행히도 JIT 전날부터 언어 날짜의 의미를 불분명하게하여 오늘날의 JVM이 성능 저하없이 런타임에 모든 상수를 쉽게 인라인 할 수 있다는 것입니다 (어쨌든로드되는 상수를 정의하는 클래스를로드 할 필요가 없음) . 그리고 이전 컴파일러로 컴파일 된 코드가 올바르지 않기 때문에 언어를 변경할 수 없습니다. 버그 호환성이 다시 발생합니다.

이 모든 것 때문에 일부 사람들은 정적 최종 값을 전혀 변경하지 않는 것이 좋습니다. 알려지지 않은 시간에 알 수없는 방식으로 널리 배포되고 업데이트 될 수있는 라이브러리의 경우이 방법이 좋습니다.

자신의 코드에서, 특히 종속성 계층의 최상위에서, 아마도 그것을 벗어날 것입니다. 그러나 이러한 경우 상수를 공개 (또는 보호)하기 위해 정말로 필요한지 고려하십시오. 상수가 패키지 가시성 만 인 경우 상황과 코드 표준에 따라 전체 패키지가 항상 한 번에 다시 컴파일되고 문제가 해결되는 것이 합리적입니다. 상수가 개인용 인 경우 문제가 없으며 언제든지 변경할 수 있습니다.


85

const선언 된 전역 상수를 포함하여 소스 코드의 내용 은 새로운 소프트웨어 릴리스에 따라 변경 될 수 있습니다.

키워드 const(또는 finalJava)는 프로그램의이 인스턴스가 실행되는 동안이 변수가 변경되지 않음을 컴파일러에 알리기 위해 존재합니다 . 더 이상 없습니다. 다음 관리자에게 메시지를 보내려면 소스에서 주석을 사용하십시오.

// DO NOT CHANGE without consulting with the legal department!
// Get written consent form from them before release!
public const int LegalLimitInSeconds = ...

미래의 자기 자신과 소통하는 더 좋은 방법입니다.


11
나는 미래에 대한이 의견을 정말로 좋아했다.
GregT

4
TaxRate존재는 public나를 긴장하게. 이 변경으로 인해 영향을받는 영업 부서 만, 세금을 부과하는 공급 업체도 아닌지 확인하고 싶습니다. 주석이 작성된 이후 코드베이스에서 발생한 일을 누가 알 수 있습니까?
candied_orange

3
@IllusiveBrian은 상수 사용을 비판하지 않았습니다. 최신 의견을 신뢰하지 말라고 경고했습니다. 변경하기 전에 항상 어떤 것이 사용되는지 확인하십시오.
candied_orange

8
이것은 Java에 대한 좋은 조언 입니다 . 다른 언어에서는 다를 수 있습니다. 예를 들어 const 값이 C #의 호출 사이트에 바인딩되는 방식으로 인해 public const필드는 Math.pi와 같이 절대 변경되지 않는 항목 에만 사용해야 합니다. 라이브러리를 작성하는 경우 라이브러리 public static readonly사용자에게 문제를 일으키지 않도록 개발 중 또는 새 버전으로 변경 될 수있는 사항이 있어야합니다 .
GrandOpener

6
다른 예를 선택해야합니다. 돈 가치는 절대 부동 소수점이 아니어야합니다!
corsiKa

13

상수의 두 가지 측면을 구별해야합니다.

  • 더 나은 유지 보수성을 위해 도입 할 때 알려진 값의 이름
  • 컴파일러가 사용할 수있는 값.

그리고 관련된 세 번째 종류가 있습니다 : 값이 변하지 않는 변수, 즉 이름의 이름. 이러한 불변 변수와 상수의 차이점은 값을 결정 / 할당 / 초기화 할 때입니다. 변수는 런타임에 초기화되지만 상수 값은 개발 중에 알려져 있습니다. 개발 중에는 값을 알 수 있지만 실제로는 초기화 중에 만 생성되므로이 차이는 약간 흐릿합니다.

그러나 상수 값이 컴파일 타임에 알려진 경우 컴파일러는 해당 값으로 계산을 수행 할 수 있습니다. 예를 들어, Java 언어에는 상수 표현식 이라는 개념이 있습니다. 상수 표현식은 프리미티브 또는 문자열의 리터럴, 상수 표현식 (예 : 캐스트, 추가, 문자열 연결) 및 상수 변수의 조작으로 만 구성된 표현식입니다. [ JLS §15.28 ] 상수 변수는 final상수 식으로 초기화 되는 변수입니다. [JLS §4.12.4] 따라서 Java의 경우 컴파일 타임 상수입니다.

public static final int X = 7;

상수 변수가 여러 컴파일 단위로 사용 된 다음 선언이 변경 될 때 이것은 흥미로워집니다. 치다:

  • A.java:

    public class A { public static final int X = 7; }
  • B.java:

    public class B { public static final int Y = A.X + 2; }

이제이 파일들을 컴파일 할 때 B.class바이트 코드는 상수 변수 Y = 9이기 때문에 필드를 선언 할 것 B.Y입니다.

그러나 A.X변수를 다른 값 (예 X = 0:)으로 변경하고 A.java파일 만 다시 컴파일하면 B.Y여전히 이전 값을 참조합니다. 이 상태 A.X = 0, B.Y = 9는 소스 코드의 선언과 일치하지 않습니다. 행복한 디버깅!

상수가 절대 변하지 않아야한다는 의미는 아닙니다. 상수는 소스 코드에서 설명없이 나타나는 매직 숫자보다 확실히 좋습니다. 그러나 대중 상수는 공개 API의 일부입니다 . 이것은 Java에만 국한된 것이 아니라 별도의 컴파일 단위가있는 C ++ 및 기타 언어에서도 발생합니다. 이 값을 변경하면 모든 종속 코드를 다시 컴파일해야합니다 (예 : 클린 컴파일).

상수의 특성에 따라 개발자가 잘못 가정했을 수 있습니다. 이러한 값이 변경되면 버그가 발생할 수 있습니다. 예를 들어, 상수 세트는 예를 들어 특정 비트 패턴을 형성하도록 선택 될 수 있습니다 public static final int R = 4, W = 2, X = 1. 이러한 구조가 다른 구조를 형성하도록 변경되면 R = 0, W = 1, X = 2기존 코드와 같은 코드 boolean canRead = perms & R가 잘못됩니다. 그리고 그에 따른 재미가 Integer.MAX_VALUE바뀌는 것이라고 생각 하십시오! 여기에는 수정이 없으며 일부 상수 의 값이 실제로 중요하며 단순히 변경할 수 없다는 것을 기억하는 것이 중요합니다.

그러나 대부분의 상수의 경우 위의 제한 사항을 고려하면 변경하는 것이 좋습니다. 상수는 특정 값이 중요하지 않은 의미로 변경하기에 안전합니다. 이 같은 튜너 블의 경우를 예입니다 BORDER_WIDTH = 2거나 TIMEOUT = 60; // seconds또는 템플릿과 같은 API_ENDPOINT = "https://api.example.com/v2/"-하지만 틀림없이 그 일부 또는 모든 구성 파일이 아닌 코드에 지정되어야한다.


5
나는이 분석을 좋아한다. 나는 그것을 다음과 같이 읽습니다 : 당신은 그것이 어떻게 사용되는지 이해하는 한 상수를 자유롭게 바꿀 수 있습니다.
candied_orange

+1 C #은 공용 상수와 동일한 문제에서 "완화"합니다.
레지날드 블루

6

상수는 응용 프로그램 런타임 수명 동안 일정하게 유지 됩니다 . 그것이 사실이라면 언어 기능을 이용하지 않을 이유가 없습니다. 동일한 목적으로 상수 대 컴파일러 플래그를 사용하면 결과가 무엇인지 알아야합니다.

  • 상수 는 응용 프로그램 공간을 차지합니다
  • 컴파일러 플래그
  • 상수에 의해 꺼진 코드는 최신 리팩토링 도구로 업데이트하고 변경할 수 있습니다
  • 컴파일러 플래그로 코드를 끌 수 없음

도형에 대한 경계 상자 그리기를 켜는 응용 프로그램을 유지 관리하여 도형을 그리는 방법을 디버깅 할 수 있었으므로 문제가 발생했습니다. 리팩토링 후에는 컴파일러 플래그에 의해 꺼진 모든 코드가 컴파일되지 않습니다. 그 후, 의도적으로 컴파일러 플래그를 응용 프로그램 상수로 변경했습니다.

나는 트레이드 오프가 있음을 증명하기 위해 말하고 있습니다. 몇 부울의 무게로 인해 응용 프로그램에 메모리가 부족하거나 너무 많은 공간을 차지하지 않았습니다. 상수가 실제로 코드의 모든 것을 처리하는 큰 객체이면 사실이 아닙니다. 더 이상 필요하지 않은 개체에 대한 모든 참조를 제거하지 않으면 개체가 메모리 누수의 원인 일 수 있습니다.

유스 케이스를 평가하고 상수를 변경하려는 이유를 평가해야합니다.

나는 단순한 담요 진술의 팬이 아니지만 일반적으로 선임 동료가 정확합니다. 무언가가 자주 바뀔 경우 구성 가능한 항목이어야합니다. 예를 들어 IsInDebugMode = true일부 코드가 손상되지 않도록 보호하려는 경우 동료에게 상수를 확신시킬 수 있습니다 . 그러나 응용 프로그램을 릴리스하는 것보다 더 자주 변경해야 할 사항이있을 수 있습니다. 이 경우 적절한 시간에 해당 값을 변경하는 방법이 필요합니다. 의 예를들 수 있습니다 TaxRate = .065. 코드를 컴파일 할 때 사실 일 수 있지만 새로운 법으로 인해 다음 버전의 응용 프로그램을 릴리스하기 전에 코드가 변경 될 수 있습니다. 그것은 파일이나 데이터베이스와 같은 일부 저장 메커니즘에서 업데이트 해야하는 것입니다.


“컴파일러 플래그”는 무엇을 의미합니까? 매크로 / 정의 및 #ifdefs 를 지원하는 C 전 처리기 및 유사한 컴파일러 기능 일까요? 이들은 소스 코드의 텍스트 대체 를 기반으로하기 때문에 프로그래밍 언어 의미의 일부가 아닙니다. Java에는 전처리 기가 없습니다.
amon

@ amon, Java는 아니지만 여러 언어가 있습니다. 나는 #ifdef깃발을 의미한다 . 그것들은 C 의미론의 일부는 아니지만 C #의 일부입니다. 나는 언어 불가지론의 더 큰 맥락을 위해 글을 쓰고있었습니다.
Berin Loritsch

나는 "메모리 낭비"논란이 바보라고 생각한다. 인라인 및 트리 흔들림은 모든 릴리스 모드 최적화 프로그램에서 매우 보편적 인 단계입니다.
Alexander-복직 자 Monica Monica

@Alexander, 동의합니다. 그래도 알아야 할 것입니다.
Berin Loritsch

1
"상수는 응용 프로그램 공간을 차지합니다"– 킬로바이트 또는 2 개의 메모리 만있는 마이크로 컨트롤러를위한 내장형 응용 프로그램을 개발하지 않는 한 그러한 일에 대해서도 생각해서는 안됩니다.
vsz

2

const, #define또는 final컴파일러 힌트합니다 (이 점에 유의입니다 #define실제로 힌트 아닌, 자사의 매크로 및 훨씬 더 강력한). 프로그램 실행시 값이 변경되지 않으며 다양한 최적화가 수행 될 수 있음을 나타냅니다.

그러나 컴파일러 힌트로 컴파일러는 프로그래머가 항상 예상 할 수없는 작업을 수행합니다. 특히, javac 는 사용되는 static final int FOO = 42;곳이면 실제 컴파일 된 바이트 코드가 읽 도록 인라인합니다 .FOO42

누군가 다른 컴파일 단위 (.java 파일)를 다시 컴파일하지 않고 값을 변경하고 42바이트 코드에 남아 있을 때까지 놀라지 않습니다. 정적 최종 변수의 javac 인라인을 비활성화 할 수 있습니까?를 참조하십시오.

무언가를 static final만든다는 것은 그것이 의미가 있다는 것을 의미하며, 그 변화는 영원히 큰 일이 될 것입니다 private.

같은 것들에 대한 상수 final static int ZERO = 0는 문제가되지 않습니다. final static double TAX_RATE = 0.55(돈과 배가 아닌 것은 나쁘고 BigDecimal을 사용해야하지만, 기본이 아니므로 인라인되지 않아야 함)는 문제이며 사용되는 곳을 세 심하게 검사해야합니다.


작은 ZERO 값.

3
is a problem and should be examined with great care for where it is used.왜 문제입니까?
Alexander-복원 모니카

1

이름에서 알 수 있듯이 런타임 중에 상수는 변경되지 않아야 하며 제 의견으로는 상수는 장기간 변경되지 않도록 정의되어 있습니다 ( 자세한 내용 은 이 SO 질문 을 볼 수 있습니다) .

플래그가 필요한 경우 (예 : 개발 모드)이 모드를 활성화하려면 대신 구성 파일 또는 시작 매개 변수 (많은 IDE가 프로젝트 별 시작 매개 변수 구성 지원, 관련 문서 참조)를 사용해야합니다. 이런 식으로 이러한 모드를 사용할 수있는 유연성을 유지하며 코드가 생산적으로 전환 될 때마다 변경하는 것을 잊지 않습니다.


0

실행간에 변경 될 수 있다는 것은 소스 코드에서 상수를 정의하는 가장 중요한 포인트 중 하나입니다!

상수는 소스 코드의 수명 동안 필요할 때마다 값을 변경할 수 있도록 잘 정의되고 문서화 된 (어떤 의미에서) 위치를 제공합니다. 또한이 위치에서 상수를 변경하면 그것이 의미하는 바의 모든 발생이 실제로 변경 될 것이라는 약속입니다.

반대의 예로서 : 실제로 키워드를 가지고있는 언어에서 줄어드는 상수를 갖는 것은 의미 가 없습니다 . 잔인한 농담을 제외하고 는 결코 한 번도 선언하지 않을 것 입니다.TRUEtruetrueTRUE=false

물론 상수의 다른 용도, 예를 들어 코드 단축 ( CO_NAME = 'My Great World Unique ACME Company'), 중복 방지 ( PI=3.141), 규칙 설정 ( ) 등을 TRUE=1정의하지만 상수 변경 것이 확실히 가장 중요한 것 중 하나입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.