변수의 범위를 줄이기 위해 블록을 만드는 것이 합리적입니까?

한 시점에서 키 저장소의 비밀번호를로드해야하는 Java로 프로그램을 작성 중입니다. 재미를 위해 다음을 수행하여 비밀번호를 Java로 가능한 한 짧게 유지하려고했습니다.

//Some code
....

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

...
//Some more code

자, 저는이 사례에서 좀 바보입니다. 내가 할 수있는 다른 것들이 많이 있는데, 대부분 실제로 실제로 더 좋습니다 (나는 변수를 전혀 사용할 수 없었습니다).

그러나 이것이 그렇게 멍청하지 않은 경우가 있는지 궁금했습니다. 내가 생각할 수있는 유일한 다른 것은 count, 또는 같은 일반적인 변수 이름을 재사용 temp하고 싶지만 좋은 명명 규칙과 짧은 방법으로 유용하지 않을 것입니다.

블록을 사용하는 경우가 있습니다 변수 범위 말이 줄일 수는?



답변

먼저 기본 역학에 말하기 :

C ++ 범위에서 == 수명 b / c 소멸자는 범위 종료시 호출됩니다. 또한 C / C ++에서 중요한 차이점은 로컬 객체를 선언 할 수 있다는 것입니다. C / C ++ 런타임에서 컴파일러는 일반적으로 변수를 선언하는 각 범위에 진입시 더 많은 스택 공간을 할당하는 대신 필요한만큼 큰 메소드의 스택 프레임을 사전에 할당합니다. 따라서 컴파일러는 범위를 축소하거나 평탄화합니다.

C / C ++ 컴파일러는 사용 수명이 충돌하지 않는 로컬에 스택 저장 공간을 재사용 할 수 있습니다 (일반적으로 실제 코드의 분석을 사용하여 범위가 아닌 범위를 결정하고 범위보다 정확한 b / c를 결정합니다).

C / C ++ b / c Java의 구문, 즉 중괄호 및 범위 지정은 적어도 부분적으로 해당 패밀리에서 파생 된 것입니다. 또한 C ++이 질문 의견에 등장했기 때문입니다.

반대로 Java에 로컬 객체를 가질 수는 없습니다. 모든 객체는 힙 객체이며 모든 로컬 / 포멀은 참조 변수 또는 기본 유형입니다 (btw, 정적의 경우도 마찬가지).

또한 Java 범위와 수명은 정확히 동일하지 않습니다. 범위 내 / 외의 것은 대부분 접근성 및 이름 충돌에 대한 컴파일 시간 개념입니다. 변수 정리와 관련하여 범위 종료시 Java에서 실제로 발생하는 것은 없습니다. Java의 가비지 콜렉션은 객체의 수명 (종료점)을 결정합니다.

또한 Java의 바이트 코드 메커니즘 (Java 컴파일러의 출력)은 제한된 범위 내에 선언 된 사용자 변수를 바이트 코드 레벨에 범위 기능이 없기 때문에 메소드의 최상위 레벨로 승격시키는 경향이 있습니다 (이는 C / C ++의 C / C ++ 처리와 유사 함). 스택). 기껏해야 컴파일러는 로컬 변수 슬롯을 재사용 할 수 있지만 C / C ++와 달리 유형이 동일한 경우에만 가능합니다.

(런타임의 기본 JIT 컴파일러가 바이트 코드를 충분히 분석하여 서로 다른 유형의 (및 다른 슬롯 된) 로컬에 동일한 스택 스토리지 공간을 재사용 할 수 있도록하기 위해)

프로그래머의 이점에 대해 말하면, (개인) 방법이 한 번만 사용하더라도 더 나은 구성이라는 데 다른 사람들과 동의하는 경향이 있습니다.

그럼에도 불구하고 이름을 충돌시키기 위해 그것을 사용하는 것은 실제로 아무 문제가 없습니다.

개별 방법을 만드는 것이 부담이되는 드문 상황에서이 작업을 수행했습니다. 예를 들어, switch루프 내 에서 큰 명령문을 사용하여 인터프리터를 작성할 때 (인수에 따라) case각 사례를 다른 방법으로 만드는 대신 개별 사례를 서로 더 분리하기 위해 각각에 대해 별도의 블록을 도입 할 수 있습니다. 한 번만 호출됩니다).

(블록 단위의 코드로서 개별 사례는 엔 클로징 루프에 대한 “break;”및 “continue;”문에 액세스 할 수 있지만 메소드는 부울 리턴 및 호출자가 조건부를 사용하여 이러한 제어 플로우에 액세스해야합니다. 진술.)


답변

제 생각에는 블록을 자체 방법으로 꺼내는 것이 더 분명 할 것입니다. 이 블록을 그대로 두었다면, 왜 블록을 먼저 넣었는지 설명하는 주석이 표시되기를 바랍니다. 그 시점 initializeKeyManager()에서 암호 변수를 메소드의 범위로 제한하고 코드 블록에 대한 의도를 보여주는 메소드 호출이 더 깨끗합니다 .


답변

그들은 엄격한 차용 규칙으로 Rust에서 유용 할 수 있습니다. 그리고 일반적으로 기능적 언어에서는 해당 블록이 값을 반환합니다. 그러나 Java와 같은 언어로? 아이디어는 우아해 보이지만 실제로는 거의 사용되지 않으므로 변수의 범위를 제한하는 잠재적 인 명확성을 떨어 뜨릴 수 있습니다.

나는 이것이 유용하다고 생각하는 경우가 있습니다- switch성명서에서! 때로는 다음과 같이 변수를 선언하려고합니다 case.

switch (op) {
    case ADD:
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
        break;
    case SUB:
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
        break;
}

그러나 이것은 실패합니다.

error: variable result is already defined in method main(String[])
            int result = a - b;

switch 문은 이상합니다-제어 문이지만, 그 실패로 인해 범위를 소개하지 않습니다.

따라서 블록을 사용하여 범위를 직접 만들 수 있습니다.

switch (op) {
    case ADD: {
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
    } break;
    case SUB: {
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
    } break;
}

답변

  • 일반적인 경우 :

변수 범위를 줄이기 위해 블록을 사용하는 것이 의미가있는 경우가 있습니까?

일반적으로 방법을 짧게 유지하는 것이 좋습니다. 일부 변수의 범위를 줄이면 분석법이 상당히 길어서 변수의 범위를 줄여야한다고 생각할 수 있습니다. 이 경우 코드의 해당 부분에 대해 별도의 메소드를 작성하는 것이 좋습니다.

문제가되는 경우는 코드의 일부가 소수 이상의 인수를 사용하는 경우입니다. 각각 많은 매개 변수를 사용하는 작은 메서드로 끝나거나 여러 값 반환을 시뮬레이트하기위한 해결 방법이 필요한 상황이 있습니다. 이 특정 문제에 대한 해결책은 사용하는 다양한 변수를 유지하는 다른 클래스를 작성하고 비교적 짧은 메소드에서 염두에 두었던 모든 단계를 구현하는 것 입니다. 이는 빌더 패턴 을 사용하는 일반적인 사용 사례입니다 .

즉, 이러한 모든 코딩 지침은 때때로 느슨하게 적용될 수 있습니다. 코드를 작은 하위 메소드 (또는 빌더 패턴)로 나누지 않고 약간 더 긴 방법으로 코드를 유지하면 코드를 더 쉽게 읽을 수있는 경우가 가끔 있습니다. 일반적으로 이것은 중간 크기이고 매우 선형 적이며 너무 많은 분할이 실제로 가독성을 방해하는 문제에 효과적입니다 (특히 코드가 수행하는 작업을 이해하기 위해 너무 많은 메소드간에 이동해야하는 경우).

말이 될 수 있지만 매우 드 rare니다.

  • 사용 사례 :

코드는 다음과 같습니다.

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

을 (를) 만들고 FileInputStream있지만 절대 닫지 않습니다.

이를 해결하는 한 가지 방법은 try-with-resources 문 을 사용하는 것 입니다. 범위를 지정하고 원하는 InputStream경우이 블록이 관련되어 있기 때문에이 블록을 사용하여 다른 변수의 범위를 줄일 수도 있습니다.

KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Keystore keyStore = KeyStore.getInstance("JKS");

try (InputStream fis = new FileInputStream(keyStoreLocation)) {
    char[] password = getPassword();
    keyStore.load(fis, password);
    keyManager.init(keyStore, password);
}

(또한 종종 getDefaultAlgorithm()대신 대신 사용 하는 것이 좋습니다 SunX509.)

또한 코드 덩어리를 별도의 방법으로 분리하라는 조언은 일반적으로 적합합니다. 단 3 줄이면 아무것도 가치가 없습니다 (만약 별도의 메소드를 만드는 것이 가독성을 방해하는 경우).


답변

겸손한 견해로는 일반적으로 다른 작업을 수행하는 산발적 인 기능을 제외하고는 일반적으로 코드를 작성하려고하지 않기 때문에 일반적으로 프로덕션 코드에서 크게 피해야합니다. 테스트하는 데 사용되는 일부 스크랩 코드에서 수행하는 경향이 있지만 프로덕션 코드에서 각 함수가 수행해야 할 작업에 대해 미리 생각한 유혹을 찾지 않습니다. 로컬 상태와 관련하여 제한된 범위.

나는 익명 블록의 이러한 조건 (조건부, try트랜잭션 블록 등을 포함하지 않음)이 왜 그럴 수 없는지에 대한 질문을하지 않은 함수에서 의미있는 방식으로 범위를 줄이기 위해 사용 된 예를 본 적이 없습니다. 익명 블록의 진정한 SE 관점에서 실제로 실제로 이익을 얻는다면 범위가 줄어든 간단한 함수로 더 나눌 수 있습니다. 그것은 일반적으로 우리가 이것에 도달하려는 유혹을받는 느슨하게 관련되거나 매우 관련이없는 것들을 수행하는 절충주의 코드입니다.

예를 들어,이라는 변수를 재사용하기 위해이 작업을 수행하려는 경우 count두 가지 별개의 것을 세는 것이 좋습니다. 변수 이름이만큼 짧으면 count함수의 컨텍스트에 연결하는 것이 좋습니다. 잠재적으로 한 가지 유형의 항목 만 계산할 수 있습니다. 그런 다음 함수의 이름 및 / 또는 설명서를 count즉시 보고를 참조 하고 모든 코드를 분석하지 않고 함수가 수행하는 작업의 맥락에서 그 의미가 무엇인지 즉시 알 수 있습니다. 나는 익명의 스코프 / 블록을 대안에 비해 매력적으로 만드는 방식으로 동일한 변수 이름을 재사용하는 두 가지 다른 것을 세는 함수에 대한 좋은 주장을 찾지 못합니다. 모든 기능이 한 가지만 계산해야한다는 것은 아닙니다. 나는’엔지니어링은 동일한 변수 이름을 사용하여 둘 이상의 항목을 계산하고 익명 블록을 사용하여 각 개별 수의 범위를 제한하는 기능에 유용합니다. 함수가 단순하고 명확하다면, 처음에는 이상적으로 필요한 것보다 몇 줄 더 많은 스코프 가시성이있을 수있는 두 개의 다른 이름의 카운트 변수를 갖는 것이 세계의 끝이 아닙니다. 이러한 함수는 일반적으로 로컬 변수의 최소 범위를 더 줄이기 위해 익명 블록이없는 오류의 원인이 아닙니다.

불필요한 방법에 대한 제안이 아님

이것은 단순히 범위를 줄이기 위해 강제로 메소드를 작성하도록 제안하는 것이 아닙니다. 그것은 틀림없이 나쁘거나 나쁘고, 내가 제안하는 것은 익명의 범위에 대한 필요 이상으로 어색한 개인 “도우미”방법의 필요성을 요구해서는 안됩니다. 그것은 코드에 대해 너무 많이 생각하고 있으며 변수의 범위를 줄이는 방법보다 인터페이스의 수준에서 문제를 개념적으로 해결하는 방법보다 블록의 깊은 중첩없이 자연스럽게 로컬 함수 상태를 깨끗하고 짧은 가시성을 얻는 방법에 대한 생각보다 6 단계 이상의 들여 쓰기 나는 3 줄의 코드를 함수에 강제로 삽입하여 코드 가독성을 방해 할 수 있다고 Bruno에 동의하지만, 그것은 당신이 기존 구현에 따라 생성하는 함수를 진화시키고 있다는 가정에서 시작됩니다. 구현에 얽매이지 않고 함수를 디자인하는 대신. 후자의 방법으로 수행하면 무해한 코드 몇 줄로 변수의 범위를 열성적으로 줄이려고하지 않는 한 주어진 방법 내에서 변수 범위를 줄이는 것 외에는 목적이없는 익명의 블록이 거의 필요하지 않습니다. 이 익명 블록의 이국적인 도입은 그들이 제거하는 것만 큼 많은 지적 오버 헤드에 기여할 것입니다.

최소 범위를 더욱 줄이려고 노력

지역 변수 범위를 절대 최소값으로 줄이는 것이 가치가 있다면 다음과 같은 코드를 광범위하게 수용해야합니다.

ImageIO.write(new Robot("borg").createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())), "png", new File(Db.getUserId(User.handle()).toString()));

… 그러므로 처음에 변수를 참조 할 변수를 만들지 않아도 상태의 가시성이 최소화됩니다. 나는 독단적으로 벗어나고 싶지 않지만 실제로 실용적인 해결책은 위의 괴물 같은 코드 줄을 피하는 것과 마찬가지로 가능한 한 익명 블록을 피하는 것입니다. 함수 내에서 정확성과 불변성을 유지하는 관점에서 코드를 함수로 구성하고 인터페이스를 디자인하는 방법을 다시 검토 할 가치가 있다고 생각합니다. 당연히 분석법이 400 줄이고 변수의 범위가 필요 이상으로 300 줄의 코드로 보이는 경우 이는 진정한 엔지니어링 문제 일 수 있지만 익명 블록으로 해결해야하는 문제는 아닙니다.

다른 곳이 없다면, 사방에 익명의 블록을 사용하는 것이 관용적이 아닌 이국적이며, 이국적인 코드는 몇 년이 지난 후에도 다른 사람들에 의해 미워 될 위험이 있습니다.

범위 축소의 실용적 유용성

변수의 범위를 줄이는 궁극적 인 유용성은 상태 관리를 올바르게 유지하고 올바르게 유지하고 코드베이스의 특정 부분이 수행하는 작업에 대해 쉽게 추론 할 수 있도록하는 것입니다. 개념적 불 변형을 유지할 수 있습니다. 단일 함수의 로컬 상태 관리가 너무 복잡하여 코드에서 익명의 블록으로 범위를 강제로 줄여야하지만 최종적이고 좋은 것이 아니며 다시 말하면 함수 자체를 다시 검사해야한다는 신호입니다. . 로컬 함수 범위에서 변수의 상태 관리에 대한 추론이 어려운 경우 전체 클래스의 모든 메소드에 액세스 할 수있는 개인 변수에 대한 추론의 어려움을 상상하십시오. 익명 블록을 사용하여 가시성을 줄일 수 없습니다. 저에게 변수를 유지하는 데 어려움을 겪지 않는 지점에서 변수가 손에 닿지 않는 한 변수가 많은 언어에서 이상적으로 필요한 것보다 약간 넓은 범위를 갖는 경향이 있다는 점을 받아들이는 것이 도움이됩니다. 실용적인 관점에서 받아들이는 것처럼 볼 때 익명 블록으로 많은 것을 해결하는 것은 아닙니다.


답변

변수 범위를 줄이기 위해 블록을 사용하는 것이 의미가있는 경우가 있습니까?

동기 부여가 블록을 도입하기에 충분한 이유라고 생각합니다.

Casey가 지적했듯이, 블록은 “코드 냄새”로서 블록을 함수로 추출하는 것이 더 나을 수 있음을 나타냅니다. 그러나 그렇지 않을 수도 있습니다.

John Carmark는 2007 년 에 인라인 코드 에 대한 메모를 작성 했습니다. 그의 결론

  • 함수가 한 곳에서만 호출되는 경우 인라인을 고려하십시오.
  • 여러 장소에서 함수를 호출 한 경우 작업이 단일 장소 (플래그를 사용하여)에서 수행되도록 정렬 할 수 있는지 확인하고 인라인하십시오.
  • 함수의 버전이 여러 개인 경우 더 많은 기본 매개 변수를 사용하여 단일 함수를 만드는 것이 좋습니다.
  • 작업이 전 세계 상태에 대한 언급이 거의없는 순수한 기능에 근접한 경우에는 완벽하게 기능하도록하십시오.

그래서 생각 , 목적과 선택, 그리고 당신이 만든 선택에 대한 동기 부여를 설명 (선택 사항) 휴가 증거를합니다.


답변

내 의견은 논란의 여지가 있지만, 나는 이런 종류의 스타일을 사용해야 할 상황이 있다고 생각합니다. 그러나 “변수의 범위를 줄이는 것”은 유효한 인수가 아닙니다. 이것이 이미 수행중인 작업이기 때문입니다. 그리고 그것을하기 위해 무언가를하는 것은 정당한 이유가 아닙니다. 또한 이러한 설명이 일반적인 구문인지 아닌지 팀이 이미 해결 한 경우이 설명은 의미가 없습니다.

나는 주로 C # 프로그래머이지만 Java에서 암호를 char[]메모리에 유지하는 시간을 줄일 수 있도록 암호를 반환한다고 들었습니다 . 이 예제에서는 가비지 수집기가 사용하지 않는 순간부터 배열을 수집 할 수 있으므로 암호가 범위를 벗어나더라도 중요하지 않기 때문에 도움이되지 않습니다. 배열을 사용한 후에 배열을 지울 수 있는지 여부는 논쟁의 여지가 없지만이 경우 변수를 범위 지정하는 것이 합리적입니다.

{
    char[] password = getPassword();
    try{
        keyStore.load(new FileInputStream(keyStoreLocation), password);
        keyManager.init(keyStore, password);
    }finally{
        Arrays.fill(password, '\0');
    }
}

이것은 리소스에 대한 범위를 지정하고 완료 한 후에 마무리를 수행하기 때문에 try-with-resources 문과 실제로 유사합니다 . 이 방법으로 암호를 처리한다고 주장하는 것이 아니라, 그렇게 결정하면이 스타일이 합리적이라는 점에 다시 한 번 유의하십시오.

변수가 더 이상 유효하지 않기 때문입니다. 의미있는 정보를 포함하지 않도록 작성하여 사용했으며 상태를 무효화했습니다. 이 블록 뒤에 변수를 사용하는 것은 의미가 없으므로 범위를 지정하는 것이 합리적입니다.

내가 생각할 수있는 또 다른 예는 이름과 의미가 비슷한 두 개의 변수가 있지만 하나를 사용하고 다른 것을 사용하고 별도로 유지하려는 경우입니다. 이 코드를 C #으로 작성했습니다.

{
    MethodBuilder m_ToString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofString, Type.EmptyTypes);
    var il = m_ToString.GetILGenerator();
    il.Emit(OpCodes.Ldstr, templateType.ToString()+":"+staticType.ToString());
    il.Emit(OpCodes.Ret);
    tb.DefineMethodOverride(m_ToString, m_Object_ToString);
}
{
    PropertyBuilder p_Class = tb.DefineProperty("Class", PropertyAttributes.None, typeofType, Type.EmptyTypes);
    MethodBuilder m_get_Class = tb.DefineMethod("get_Class", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofType, Type.EmptyTypes);
    var il = m_get_Class.GetILGenerator();
    il.Emit(OpCodes.Ldtoken, staticType);
    il.Emit(OpCodes.Call, m_Type_GetTypeFromHandle);
    il.Emit(OpCodes.Ret);
    p_Class.SetGetMethod(m_get_Class);
    tb.DefineMethodOverride(m_get_Class, m_IPattern_get_Class);
}

ILGenerator il;메소드 상단에 단순히 선언 할 수 있다고 주장 할 수는 있지만 변수를 다른 객체 (kinda 기능적 접근법)에 재사용하고 싶지 않습니다. 이 경우 블록을 사용하면 구문 및 시각적으로 수행되는 작업을보다 쉽게 ​​분리 할 수 ​​있습니다. 또한 블록 후, 나는 끝났 il으며 아무것도 접근해서는 안된다고 말합니다.

이 예제에 대한 논쟁은 메소드를 사용하는 것입니다. 아마도 그렇습니다.하지만이 경우 코드는 그렇게 길지 않으며 코드를 다른 메소드로 분리하면 코드에 사용 된 모든 변수를 전달해야합니다.