캐치 루프 내부 또는 외부로 이동해야합니까?


185

다음과 같은 루프가 있습니다.

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

이것은 부동 소수점 배열을 반환하는 유일한 목적을 가진 메소드의 주요 내용입니다. null오류가있는 경우이 메소드가 반환되기를 원 하므로 루프를 다음 try...catch과 같이 블록 안에 넣습니다 .

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

그러나 다음 try...catch과 같이 루프 안에 블록 을 넣는 것을 생각 했습니다.

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

다른 것을 선호하는 이유, 성능 또는 기타가 있습니까?


편집 : 합의는 try / catch 내부에 루프를 넣는 것이 더 깨끗한 것 같습니다. 그러나 여전히 어느 쪽이 더 빠른지에 대한 논쟁이 있습니다. 누군가 이것을 테스트하고 통일 된 답변으로 돌아올 수 있습니까?


2
성능에 대해서는 모르겠지만 for 루프 외부에서 try catch를 사용하면 코드가 더 깨끗해 보입니다.
Jack B Nimble

답변:


132

공연:

try / catch 구조의 위치에 성능 차이는 전혀 없습니다. 내부적으로는 메소드가 호출 될 때 작성되는 구조에서 코드 범위 테이블로 구현됩니다. 메소드가 실행되는 동안 throw / catch 구조는 throw가 발생하지 않는 한 완전히 그림에서 벗어난 것이므로 오류 위치는 테이블과 비교됩니다.

여기 참조가 있습니다 : http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

표는 반쯤 아래로 설명되어 있습니다.


자, 당신은 미학 이외의 차이가 없다고 말하고 있습니다. 당신은 소스에 연결할 수 있습니까?
Michael Myers

2
감사. 1997 년 이래로 상황이 변했다고 생각하지만, 효율성 을 떨어 뜨리지는 않을 것 입니다.
Michael Myers

1
절대 어려운 단어입니다. 어떤 경우에는 컴파일러 최적화를 방해 할 수 있습니다. 직접적인 비용은 아니지만 간접적 인 성과 불이익 일 수 있습니다.
Tom Hawtin-tackline

2
사실, 나는 열정에 약간의 통치를했을 것 같지만, 잘못된 정보는 내 신경에 닿아있었습니다! 최적화의 경우 성능에 어떤 영향을 줄지 알 수 없으므로 항상 시도해 보았습니다.
Jeffrey L Whitledge

1
코드가 예외없이 실행되는 경우에만 성능 차이가 없습니다.
Onur Bıyık

72

성능 : Jeffrey가 답장에서 말했듯이 Java에서는 큰 차이가 없습니다.

일반적으로 코드의 가독성을 위해 예외를 포착 할 위치를 선택하는 것은 루프 처리를 유지할지 여부에 따라 다릅니다.

귀하의 예에서 예외를 잡으면 돌아 왔습니다. 이 경우 루프 주위에 try / catch를 배치합니다. 단순히 나쁜 가치를 포착하고 가공을 계속하고 싶다면 그것을 안에 넣으십시오.

세 번째 방법 : 항상 자신의 정적 ParseFloat 메소드를 작성하고 루프가 아닌 해당 메소드에서 예외 처리를 처리 할 수 ​​있습니다. 예외 처리를 루프 자체에 격리시킵니다!

class Parsing
{
    public static Float MyParseFloat(string inputValue)
    {
        try
        {
            return Float.parseFloat(inputValue);
        }
        catch ( NumberFormatException e )
        {
            return null;
        }
    }

    // ....  your code
    for(int i = 0; i < max; i++) 
    {
        String myString = ...;
        Float myNum = Parsing.MyParseFloat(myString);
        if ( myNum == null ) return;
        myFloats[i] = (float) myNum;
    }
}

1
자세히 살펴. 그는 두 가지 예외 중 첫 번째 예외로 끝났습니다. 나는 처음에도 같은 것을 생각했다.
kervin September

2
나는 그가 문제에 부딪쳤을 때 돌아오고 나서 외부 캡처를 사용하기 때문에 그의 경우에 내가 말한 이유를 알고 있었다. 명확성을 위해, 이것이 내가 사용하는 규칙입니다.
Ray Hayes

좋은 코드이지만 불행히도 Java에는 고급 매개 변수가 없습니다.
Michael Myers

ByRef? Java를 만진 지 너무 오래되었습니다!
Ray Hayes

2
다른 사람은 C # / .net에서 TryParse를 제안하여 예외를 처리하지 않고 안전한 구문 분석을 수행 할 수 있습니다. 이제 복제되어 메소드를 재사용 할 수 있습니다. 원래 질문에 관해서는, 캐치 내부의 루프를 선호합니다. 성능 문제가 없어도 깨끗해 보입니다.
Ray Hayes

46

Jeffrey L Whitledge 가 성능 차이가 없다고 말한 후 (1997 년 기준) 테스트를 진행했습니다. 이 작은 벤치 마크를 실행했습니다.

public class Main {

    private static final int NUM_TESTS = 100;
    private static int ITERATIONS = 1000000;
    // time counters
    private static long inTime = 0L;
    private static long aroundTime = 0L;

    public static void main(String[] args) {
        for (int i = 0; i < NUM_TESTS; i++) {
            test();
            ITERATIONS += 1; // so the tests don't always return the same number
        }
        System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
        System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
    }
    public static void test() {
        aroundTime += testAround();
        inTime += testIn();
    }
    public static long testIn() {
        long start = System.nanoTime();
        Integer i = tryInLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static long testAround() {
        long start = System.nanoTime();
        Integer i = tryAroundLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static Integer tryInLoop() {
        int count = 0;
        for (int i = 0; i < ITERATIONS; i++) {
            try {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            } catch (NumberFormatException ex) {
                return null;
            }
        }
        return count;
    }
    public static Integer tryAroundLoop() {
        int count = 0;
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            }
            return count;
        } catch (NumberFormatException ex) {
            return null;
        }
    }
}

javap를 사용하여 결과 바이트 코드를 검사하여 아무것도 인라인되지 않았는지 확인했습니다.

결과는 중요하지 않은 JIT 최적화를 가정하면 Jeffrey가 정확 하다는 것을 보여주었습니다 . Java 6, Sun 클라이언트 VM에는 성능 차이 가 전혀 없습니다 (다른 버전에는 액세스 할 수 없었습니다). 총 시간 차이는 전체 테스트에서 몇 밀리 초 정도입니다.

따라서 유일한 고려 사항은 가장 깨끗하게 보이는 것입니다. 나는 두 번째 방법이 못 생겼다는 것을 알았으므로 첫 번째 방법이나 Ray Hayes의 방법 중 하나를 고수 할 입니다.


예외 핸들러는 루프 외부 흐름을 취하면, 나는 그것을 배치 할 명확한 느낀다 외부 루프 - 대신을 분명히 루프, 그럼에도 불구하고 반환 내 캐치 절을 배치하는 것이. 나는 이러한 "catch-all"메소드의 "catch-all"본문 주위에 공백을 사용하여 본문모든 포함 try 블록 과 더 명확하게 분리합니다 .
Thomas W

16

성능은 동일 할 수 있으며 "보이는"것이 더 주관적이지만 기능에는 여전히 큰 차이가 있습니다. 다음 예를 보자.

Integer j = 0;
    try {
        while (true) {
            ++j;

            if (j == 20) { throw new Exception(); }
            if (j%4 == 0) { System.out.println(j); }
            if (j == 40) { break; }
        }
    } catch (Exception e) {
        System.out.println("in catch block");
    }

while 루프는 try catch 블록 안에 있으며 변수 'j'는 40에 도달 할 때까지 증가하고 j mod 4가 0이면 인쇄되고 j가 20에 도달하면 예외가 발생합니다.

세부 사항 전에 다른 예는 다음과 같습니다.

Integer i = 0;
    while (true) {
        try {
            ++i;

            if (i == 20) { throw new Exception(); }
            if (i%4 == 0) { System.out.println(i); }
            if (i == 40) { break; }

        } catch (Exception e) { System.out.println("in catch block"); }
    }

위와 동일한 논리, 차이점은 try / catch 블록이 while 루프 안에 있다는 것입니다.

다음은 try / catch 동안 출력입니다.

4
8
12 
16
in catch block

그리고 다른 출력 (시도 / 잡기) :

4
8
12
16
in catch block
24
28
32
36
40

거기에는 상당한 차이가 있습니다.

try / catch가 루프를 벗어나는 동안

루프를 활성 상태로 유지하면서 try / catch in


따라서 try{} catch() {}루프가 단일 "유닛"으로 취급되고 해당 유닛에서 문제를 발견하면 전체 "유닛"(또는이 경우 루프)이 남쪽으로 이동하면 루프를 둘러 쌉니다. try{} catch() {}루프의 단일 반복 또는 배열의 단일 요소를 전체 "단위"로 취급하는 경우 루프 안에 넣으십시오 . "unit"에 예외가 발생하면 해당 요소를 건너 뛰고 다음 루프 반복으로 넘어갑니다.
Galaxy

14

모든 성능 및 가독성 게시물에 동의합니다. 그러나 실제로 중요한 경우가 있습니다. 다른 사람들이 이것을 언급했지만 예제를 통해 더 쉽게 볼 수 있습니다.

이 약간 수정 된 예를 고려하십시오.

public static void main(String[] args) {
    String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
    ArrayList asNumbers = parseAll(myNumberStrings);
}

public static ArrayList parseAll(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        myFloats.add(new Float(numberStrings[i]));
    }
    return myFloats;
}

오류가있는 경우 parseAll () 메서드가 null을 반환하도록하려면 (예제와 같이) try / catch를 다음과 같이 외부에 배치합니다.

public static ArrayList parseAll1(String[] numberStrings){
    ArrayList myFloats = new ArrayList();
    try{
        for(int i = 0; i < numberStrings.length; i++){
            myFloats.add(new Float(numberStrings[i]));
        }
    } catch (NumberFormatException nfe){
        //fail on any error
        return null;
    }
    return myFloats;
}

실제로는 null 대신 오류를 반환해야하며 일반적으로 여러 번 반환하는 것을 좋아하지 않지만 아이디어를 얻습니다.

반면에 문제를 무시하고 문자열을 구문 분석하려면 try / catch를 루프 내부에 다음과 같이 배치하십시오.

public static ArrayList parseAll2(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        try{
            myFloats.add(new Float(numberStrings[i]));
        } catch (NumberFormatException nfe){
            //don't add just this one
        }
    }

    return myFloats;
}

2
그래서 나는 "만약 당신이 단순히 나쁜 가치를 잡아 내고 가공을 계속하고 싶다면 그것을 안에 넣으십시오"라고 말했습니다.
Ray Hayes

5

이미 언급했듯이 성능은 동일합니다. 그러나 사용자 경험이 반드시 동일한 것은 아닙니다. 첫 번째 경우에는 빠르게 실패하지만 (즉, 첫 번째 오류 이후) try / catch 블록을 루프 안에 넣으면 주어진 메소드 호출에 대해 생성 된 모든 오류를 캡처 할 수 있습니다. 일부 형식화 오류가 예상되는 문자열 값 배열을 구문 분석 할 때 모든 오류를 사용자에게 제시하여 하나씩 수정하려고 할 필요가없는 경우가 있습니다. .


이것은이 특별한 경우에는 사실이 아니지만 (캐치 블록으로 돌아갑니다) 일반적으로 명심하는 것이 좋습니다.
Michael Myers

4

그것의 전부 또는 아무것도 실패하면, 첫 번째 형식이 의미가 있습니다. 고장이 아닌 모든 요소를 ​​처리 / 반품하려면 두 번째 양식을 사용해야합니다. 그것들은 방법 중에서 선택하기위한 나의 기본 기준이 될 것입니다. 개인적으로, 아니면 아무것도 아니면, 나는 두 번째 양식을 사용하지 않을 것입니다.


4

루프에서 수행해야 할 작업을 알고 있다면 try catch를 루프 외부에 둘 수 있습니다. 그러나 예외가 발생하자마자 루프가 종료되고 항상 원하는 것은 아닐 수도 있음을 이해하는 것이 중요합니다. 이것은 실제로 Java 기반 소프트웨어에서 매우 일반적인 오류입니다. 사람들은 대기열 비우기와 같은 여러 항목을 처리해야하며 가능한 모든 예외를 처리하는 외부 try / catch 문에 잘못 의존합니다. 또한 루프 내에서 특정 예외 만 처리하고 다른 예외가 발생할 것으로 예상하지 않을 수 있습니다. 그런 다음 루프 내에서 처리되지 않은 예외가 발생하면 루프가 "사전"되고 루프가 조기에 종료되고 외부 catch 문이 예외를 처리합니다.

큐가 큐를 비우는 역할을하는 경우 루프가 실제로 비우기 전에 루프가 종료 될 수 있습니다. 매우 일반적인 결함.


2

귀하의 예에서는 기능상의 차이가 없습니다. 첫 번째 예제가 더 읽기 쉽습니다.


2

내부 버전보다 외부 버전을 선호해야합니다. 이것은 규칙의 특정 버전 일뿐입니다. 루프 외부로 이동할 수있는 모든 것을 루프 외부로 이동하십시오. IL 컴파일러와 JIT 컴파일러에 따라 두 버전의 성능 특성이 다를 수 있습니다.

또 다른 메모에서 float.TryParse 또는 Convert.ToFloat를 볼 수 있습니다.


2

try / catch를 루프 안에 넣으면 예외 후에 계속 반복됩니다. 루프 외부에 배치하면 예외가 발생하자마자 중지됩니다.


글쎄, 예외시 null을 반환하므로 내 경우에는 처리가 계속되지 않습니다.
Michael Myers

2

내 관점은 적절한 예외 처리를 보장하기 위해 try / catch 블록이 필요하지만 그러한 블록을 만드는 것은 성능에 영향을 미칩니다. 루프에는 집중적 인 반복 계산이 포함되므로 try / catch 블록을 루프 안에 넣지 않는 것이 좋습니다. 또한이 조건이 발생하는 경우가 종종 있으며 "Exception"또는 "RuntimeException"이 발생합니다. 코드에서 런타임 예외가 발생하지 않도록해야합니다. 다시 말하지만 대기업에서 근무하는 경우 해당 예외를 올바르게 기록하거나 런타임 예외가 발생하지 않도록해야합니다. 이 설명의 요점은PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS


1

try / catch에 대한 특수 스택 프레임을 설정하면 추가 오버 헤드가 추가되지만 JVM은 사용자가이를 반환하고이를 최적화한다는 사실을 감지 할 수 있습니다.

반복 횟수에 따라 성능 차이는 무시할 수있을 것입니다.

그러나 나는 루프 외부에 그것을 가지고 있으면 루프 몸체가 더 깨끗해 보인다는 것에 동의합니다.

유효하지 않은 숫자가있는 경우 종료하지 않고 처리를 계속하고 싶을 가능성이 있다면 코드가 루프 안에 있어야합니다.


1

내부에 있으면 외부의 한 번과 달리 try / catch 구조의 오버 헤드가 N 번 발생합니다.


Try / Catch 구조가 호출 될 때마다 메소드 실행에 오버 헤드가 추가됩니다. 구조를 처리하는 데 필요한 약간의 메모리 및 프로세서 틱. 루프를 100 번 실행하고 가설을 위해 비용이 try / catch 호출 당 1 틱이라고 가정하고 루프 내부의 Try / Catch가 100 틱이면 비용은 1 틱이 아니라고 가정합니다. 루프 외부.


오버 헤드에 대해 조금 자세히 설명해 주시겠습니까?
Michael Myers

1
알았지 만 제프리 엘 휘 틀리지 (Jeffrey L Whitledge)는 추가적인 오버 헤드 가 없다고 말합니다 . 당신은 소스를 제공 할 수 있습니까?
마이클 마이어스

비용은 작고 거의 무시할 수 없지만 비용이 많이 듭니다. 다음은 .NET에 관한 Programmer 's Heaven의 블로그 기사 ( tiny.cc/ZBX1b )와 JAVA ( tiny.cc/BV2hS )에 관한 Reshat Sabiq의 뉴스 그룹 게시물
Stephen

알겠습니다 ... 이제 문제는 try / catch를 입력 할 때 발생하는 비용 (이 경우 루프 외부에 있어야 함)이거나 try / catch 기간 동안 일정합니까 (이 경우에는 루프 안에 있어야합니다)? 당신은 그것이 # 1이라고 말합니다. 그리고 나는 여전히 비용 있다고 확신하지 않습니다 .
Michael Myers

1
이 Google 도서 ( tinyurl.com/3pp7qj )에 따르면 "최신 VM은 페널티가 없습니다".
마이클 마이어스

1

예외의 요점은 첫 번째 스타일을 장려하는 것입니다. 가능한 모든 오류 사이트에서 오류 처리를 즉시 통합하지 않고 오류 처리를 한 번에 통합하고 처리 할 수 ​​있습니다.


잘못된! 예외의 요점은 "오류"가 발생할 때 채택 할 예외 동작을 결정하는 것입니다. 따라서 내부 또는 외부 try / catch 블록을 선택하는 것은 실제로 루프 처리를 처리하려는 방법에 따라 다릅니다.
기즈모

"오류 발생"플래그를 전달하는 등의 예외 전 오류 처리에서도 동일하게 수행 할 수 있습니다.
wnoise

1

안으로 넣다. 처리를 계속 (원하는 경우)하거나 클라이언트에게 myString 값과 잘못된 값을 포함하는 배열의 색인을 알려주는 유용한 예외를 처리 할 수 ​​있습니다. 나는 NumberFormatException이 이미 나쁜 가치를 말해 줄 것이라고 생각하지만 원칙은 모든 유용한 데이터를 예외에 넣는 것입니다. 이 시점에서 프로그램의 디버거에서 흥미로운 점을 생각해보십시오.

치다:

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

필요할 때 가능한 많은 정보를 가진 이와 같은 예외를 정말로 감사하게 생각할 것입니다.


예, 이것은 또한 유효한 포인트입니다. 필자의 경우 파일에서 읽는 중이므로 구문 분석에 실패한 문자열 만 있으면됩니다. 그래서 다른 예외를 던지는 대신 System.out.println (ex)을 수행했습니다 (법적 인만큼 많은 파일을 구문 분석하고 싶습니다).
Michael Myers

1

0.02c예외 처리 위치를 지정하는 일반적인 문제를 볼 때 두 가지 경쟁 고려 사항에 대해 내 자신을 추가하고 싶습니다 .

  1. try-catch블록 의 책임을 "더 넓게" (즉, 귀하의 경우 루프 외부에서) 나중에 코드를 변경할 때 기존 catch블록에서 처리하는 행을 실수로 추가 할 수 있음을 의미합니다 . 우연히 귀하의 경우 명시 적으로 잡기 때문에 이것은 가능성이 적습니다.NumberFormatException

  2. try-catch블록 의 책임이 "좁을수록" 리팩토링이 더 어려워집니다. 특히 (귀하의 경우와 같이) catch블록 내에서 "로컬이 아닌"명령어를 실행하는 경우 ( return null문).


1

그것은 실패 처리에 달려 있습니다. 오류 요소를 건너 뛰려면 내부에서 시도하십시오.

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

다른 경우에는 외부 시도를 선호합니다. 코드는 더 읽기 쉽고 더 깨끗합니다. 아마도 null을 반환하는 경우 오류 사례에서 IllegalArgumentException을 발생시키는 것이 좋습니다.


1

나는 $ 0.02를 넣을 것이다. 때로는 코드를 나중에 처음으로 완벽하게 작성하는 사람이 있기 때문에 나중에 코드에 "최종"을 추가해야하는 경우가있다. 이러한 경우, 시도 / 캐치를 루프 외부에 두는 것이 더 합리적입니다. 예를 들면 다음과 같습니다.

try {
    for(int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
    }
} catch (NumberFormatException ex) {
    return null;
} finally {
    dbConnection.release();  // Always release DB connection, even if transaction fails.
}

오류가 발생하거나 그렇지 않은 경우 데이터베이스 연결을 해제하거나 원하는 다른 유형의 리소스를 한 번만 선택하십시오.


1

위에서 언급되지 않은 또 다른 측면은 모든 try-catch가 스택에 약간의 영향을 미치므로 재귀 적 방법에 영향을 줄 수 있다는 사실입니다 .

"outer ()"메소드가 "inner ()"메소드를 호출하면 (재귀 적으로 호출 될 수 있음) 가능한 경우 "outer ()"메소드에서 try-catch를 찾으십시오. try-catch가 내부 메소드에있을 때 약 6,400 프레임에서, 외부 메소드에있을 때 약 11,600에서 성능 클래스에서 사용하는 간단한 "스택 충돌"예제가 실패합니다.

실제로는 합성 패턴을 사용하고 크고 복잡한 중첩 구조가있는 경우 문제가 될 수 있습니다.


0

각 반복에 대해 예외를 포착하거나 반복이 발생한 예외를 확인하고 반복에서 모든 예외를 포착하려면 try ... catch를 루프 안에 넣으십시오. 예외가 발생하면 루프가 중단되지 않으며 루프 전체의 각 반복에서 모든 예외를 포착 할 수 있습니다.

루프를 중단하고 예외가 발생할 때마다 예외를 검사하려면 try ... catch out the loop를 사용하십시오. 그러면 catch 후 루프가 깨지고 명령문이 실행됩니다 (있는 경우).

그것은 모두 당신의 필요에 달려 있습니다. 예외가 발생하면 결과가 모호하지 않고 루프가 중단되어 완전히 실행되지 않으므로 배포 중에 루프 내에서 try ... catch를 사용하는 것이 좋습니다.

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