Java 메소드에서 쓸모없는 반환을 피하려면 어떻게해야합니까?


115

이론적으로는 return두 개의 for루프에 중첩 된 문에 항상 도달 하는 상황이 있습니다.

컴파일러는 동의하지 않으며 루프 return외부의 명령문이 필요합니다 for. 현재의 이해를 뛰어 넘는이 방법을 최적화하는 우아한 방법을 알고 싶습니다. 시도한 break 구현 중 어느 것도 작동하지 않는 것 같습니다.

Attached는 임의의 정수를 생성하고 int 매개 변수로 메서드에 전달 된 범위 내에서 생성 된 두 번째 임의의 정수를 찾을 때까지 순환되는 반복을 반환하는 할당의 메서드입니다.

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
    }
    return 0; // Never reached
}

9
마지막 항목에 도달 while(true)하지 않은 경우 인덱싱 된 루프 대신를 사용할 수 있습니다 . 이것은 컴파일러에게 루프가 절대로 반환되지 않는다는 것을 알려줍니다.
보리스 스파이더

101
범위가 0 (또는 1 미만의 다른 숫자) ( oneRun(0)) 인 함수를 호출하면 도달 할 수없는 항목에 빠르게 도달하는 것을 볼 수 있습니다return
mcfedr

55
제공된 범위가 음수 일 때 반환됩니다. 또한 입력 범위에 대한 유효성 검사가 0이므로 현재 다른 방법으로 포착하고 있지 않습니다.
HopefullyHelpful

4
@HopefullyHelpful 물론 그게 진짜 대답입니다. 전혀 쓸모없는 반환이 아닙니다!
Mr Lister

4
@HopefullyHelpful 아니오, nextInt예외가 발생하기 때문에 range < 0반환에 도달 한 유일한 경우는 다음과 range == 0
같습니다.

답변:


343

컴파일러의 휴리스틱은 마지막 return. 절대 도달 할 수 없다고 확신한다면 throw상황을 명확히하기 위해 a 로 교체하겠습니다 .

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) {
        ...
    }

    throw new AssertionError("unreachable code reached");
}

135
가독성뿐만 아니라 코드에 문제가 있는지 확인할 수 있습니다. 발전기에 버그가있을 수 있다는 것은 완전히 그럴듯합니다.
JollyJoker

6
코드가 "도달 할 수없는 코드"에 도달 할 수 없다고 절대적으로 확신하는 경우 발생할 수있는 모든 엣지 케이스에 대해 일부 단위 테스트를 만듭니다 (범위는 0, 범위는 -1, 범위는 최소 / 최대 정수). 지금은 비공개 방법 일 수 있지만 다음 개발자는 그렇게 유지하지 않을 수 있습니다. 예상치 못한 값을 처리하는 방법 (예외 발생, 오류 값 반환, 아무것도하지 않음)은 메서드 사용 방법에 따라 다릅니다. 제 경험상 일반적으로 오류를 기록하고 반환하기를 원합니다.
Rick Ryker 2017

22
아마도 그럴 것입니다throw new AssertionError("\"unreachable\" code reached");
Bohemian

8
나는 프로덕션 프로그램에서 내 대답에 넣은 것을 정확하게 작성합니다.
John Kugelman

1
주어진 예의 경우 throw도달 할 수없는 a를 단순히 추가하는 것보다 처리하는 더 좋은 방법이 있습니다. 너무 자주 사람들은 근본적인 문제에 대해 너무 많이 생각하지 않고 "모든 것을 지배하는 하나의 솔루션"을 신속하게 적용하기를 원합니다. 그러나 프로그래밍은 단순한 코딩 그 이상입니다. 특히 알고리즘과 관련하여. 주어진 문제를 (주의 깊게) 검사하면 외부 for루프 의 마지막 반복을 고려 하여 "쓸모없는"반환을 유용한 반환으로 바꿀 수 있음을 알게 될 것입니다 ( 이 답변 참조 ).
a_guest

36

으로 @BoristheSpider 지적 당신은 확실히 두 번째 할 수 있습니다 return문이 의미에 도달 할 수 있습니다 :

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    int count = 0;

    while (true) {
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
        count++;
    }
}

잘 컴파일 및 실행됩니다. 그리고 만약 당신이 ArrayIndexOutOfBoundsException어떤 것을 명시 적으로 던질 필요없이 구현이 의미 적으로 잘못되었음을 알게 될 것입니다.


1
정확히 : 조건이 실제로 루프를 제어하는 ​​데 사용되지 않으므로 거기에 있으면 안됩니다. 그러나 나는 이것을 for(int count = 0; true; count++)대신 쓸 것입니다.
cmaster-monica reinstate monica

3
@cmaster : 당신은 생략 할 수 있습니다 true:for(int count = 0; ; count++) …
홀거

@Holger 아. 이것이 자바에 관한 것이기 때문에 확실하지 않았고 C / C ++에 훨씬 더 익숙한 이후로 몇 년 동안 그 언어를 건드리지 않았습니다. 그래서를 사용하는 것을 보았을 때 while(true), 나는 자바가 이와 관련하여 조금 더 엄격하다고 생각했습니다. 걱정할 필요가 없다는 것을 알게되어 기쁩니다 ... 사실, 생략 된 true버전이 훨씬 더 좋습니다. 볼 조건이 전혀 없다는 것을 시각적으로 분명하게 보여줍니다. :-)
cmaster-monica reinstate monica

3
"for"로 전환하고 싶지 않습니다. "while-true"는 내부의 무언가가 깨지지 않는 한, 블록이 무조건 반복 될 것으로 예상되는 명백한 플래그입니다. 조건이 생략 된 "for"는 명확하게 전달되지 않습니다. 더 쉽게 간과 될 수 있습니다.
Corrodias

1
@ l0b0 경고가 코드 품질과 관련이 있다는 점을 고려하면 전체 질문이 코드 검토에 더 좋을 것입니다.
Sulthan

18

두 개의 for루프를 분리하는 것에 대해 질문 했으므로 레이블을 사용하여 수행 할 수 있습니다 (아래 예제 참조).

private static int oneRun(int range) {
    int returnValue=-1;

    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    OUTER: for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                returnValue = count;
                break OUTER;
            }
        }
    }
    return returnValue;
}

returnValue초기화되지 않은 상태로 사용할 수 있으므로 컴파일에 실패하지 않습니까?
pts

1
가능성이 높습니다. 게시물의 목적은 원본 포스터가 이에 대해 질문했듯이 두 루프에서 벗어나는 방법을 보여주는 것이 었습니다. OP는 실행이 메소드의 끝까지 도달하지 않을 것이라고 확신했기 때문에 returnValue를 선언 할 때 임의의 값으로 초기화하여 가져온 내용을 수정할 수 있습니다.
David Choweller 2017

4
라벨은 어떤 식 으로든 피하는 것이 더 낫지 않습니까?
Serverfrog

2
나는 당신이 gotos를 생각하고 있다고 믿습니다.
David Choweller

4
고급 (-ish) 수준의 프로그래밍 언어로 된 레이블. 물론 야만적 ...
xDaizu

13

단언은 좋은 빠른 해결책입니다. 일반적으로 이런 종류의 문제는 코드가 너무 복잡하다는 것을 의미합니다. 코드를 볼 때 배열이 이전 숫자를 보유하는 것을 원하지 않는 것이 분명합니다. 당신은 원하는 Set:

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);
previous.add(randomInt);

for (int count = 1; count <= range; count++) {
    randomInt = generator.nextInt(range);
    if (previous.contains(randomInt)) {
       break;
    }

    previous.add(randomInt);
}

return previous.size();

이제 우리가 반환하는 것은 실제로 세트의 크기입니다. 코드 복잡성이 2 차에서 선형으로 감소했으며 즉시 더 읽기 쉽습니다.

이제 우리는 해당 count인덱스 가 필요하지 않다는 것을 알 수 있습니다 .

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);

while (!previous.contains(randomInt)) {          
    previous.add(randomInt);      
    randomInt = generator.nextInt(range);
}

return previous.size();

8

반환 값은 외부 루프의 변수를 기반으로하므로 외부 루프의 조건을 변경 count < range한 다음 함수 끝에서이 마지막 값 (방금 생략)을 반환 할 수 있습니다.

private static int oneRun(int range) {
    ...

    for (int count = 1; count < range; count++) {
        ...
    }
    return range;
}

이렇게하면 도달 할 수없는 코드를 도입 할 필요가 없습니다.


하지만 일치 항목이 발견 될 때 루프의 두 계층에서 벗어날 필요가 없을까요? 하나의 for 문으로 줄이는 방법이 보이지 않습니다. 새로운 난수를 생성하고 다른 난수를 생성하기 전의 번호와 비교해야합니다.
올리버 베닝

여전히 두 개의 중첩 된 for 루프가 남아 ...있지만 ( 두 번째 루프는 생략했습니다 ) 외부 루프는 마지막 반복에 의해 감소되었습니다. 이 마지막 반복에 해당하는 케이스는 마지막 return 문에 의해 개별적으로 처리됩니다. 이것이 귀하의 예제에 대한 우아한 솔루션이지만이 접근 방식을 읽기가 더 어려워지는 시나리오가 있습니다. 예를 들어 반환 값이 내부 루프에 종속 된 경우 끝에 단일 return 문 대신 추가 루프가 남게됩니다 (외부 루프의 마지막 반복에 대한 내부 루프를 나타냄).
a_guest

5

예를 들어 "result"와 같은 임시 변수를 사용하고 내부 반환을 제거합니다. 적절한 조건으로 while 루프에 대한 for 루프를 변경하십시오. 나에게 함수의 마지막 문장으로 하나의 리턴만을 갖는 것이 항상 더 우아합니다.


글쎄, 외부 while 조건이 될 수 있다면 매우 내부처럼 보입니다. for에 해당하는 시간이 항상 있다는 것을 명심하십시오 (항상 모든 반복을 수행 할 계획이 아니기 때문에 더 우아함). 이 두 개의 중첩 된 for 루프는 확실히 단순화 할 수 있습니다. 모든 코드는 "너무 복잡한"냄새가납니다.
데이비드

@ Solomonoff'sSecret 귀하의 진술은 터무니 없습니다. 두 개 이상의 return 문을 "일반적으로"목표로해야 함을 의미합니다. 흥미 롭군.
데이비드

@ Solomonoff'sSecret 그것은 선호하는 프로그래밍 스타일의 문제이며, 지난주에 절름발이였던 이번 주 멋진 것이 무엇이든간에, 출구 지점이 하나 뿐이고 메서드가 끝날 때 (그냥 루프를 생각해 많은 사람들이 return result그 안에 뿌려졌고 이제 result그것을 반환하기 전에 발견 한 것을 한 번 더 확인하고 싶다고 생각 합니다. 이것은 단지 예일뿐입니다). 단점도있을 수 있지만 "일반적으로 단 한 번만 수익을 올릴 이유가 없다"와 같은 광범위한 진술은 물을 담지 못합니다.
SantiBailors

@David 다시 말해 보겠습니다 : 한 번만 반환해야하는 일반적인 이유는 없습니다. 특별한 경우에는 이유가 될 수 있지만 일반적으로 최악의 경우화물 숭배입니다.
Monica 복원

1
코드를 더 읽기 쉽게 만드는 문제입니다. 머릿속에 "이것이 있다면 우리가 찾은 것을 반환하고 그렇지 않으면 계속 진행하십시오. 우리가 무언가를 찾지 못하면이 다른 값을 반환하십시오"라고 말하면. 그러면 코드에 두 개의 반환 값이 있어야합니다. 항상 다음 개발자가 더 쉽게 이해할 수 있도록 코딩하십시오. 컴파일러는 우리 눈에 두 개처럼 보이지만 Java 메서드에서 반환 지점은 하나만 있습니다.
Rick Ryker 2017

3

아마도 이것은 코드를 다시 작성해야 함을 나타냅니다. 예를 들면 :

  1. 정수 0 .. 범위 -1의 배열을 만듭니다. 모든 값을 0으로 설정합니다.
  2. 루프를 수행하십시오. 루프에서 난수를 생성합니다. 목록에서 해당 인덱스를보고 값이 1인지 확인하십시오. 만약 그렇다면 루프를 중단하십시오. 그렇지 않으면 해당 인덱스의 값을 1로 설정하십시오.
  3. 목록에서 1의 수를 세고 해당 값을 반환합니다.

3

return 문이 있고 내부에 루프 / 루프가있는 메서드는 항상 루프 외부에 return 문이 있어야합니다. 루프 외부의이 명령문에 도달하지 않더라도. 이러한 경우 불필요한 return 문을 피하기 위해 메서드의 시작 부분, 즉 각 루프 앞뒤에 각 유형의 변수, 경우에 정수를 정의 할 수 있습니다. 루프 내부에서 원하는 결과에 도달하면 각 값을이 미리 정의 된 변수에 할당하고 루프 외부의 return 문에 사용할 수 있습니다.

rInt [i]가 rInt [count]와 같을 때 메서드가 첫 번째 결과를 반환하기를 원하므로 rInt [i]가 rInt [count]와 같을 때 마지막 결과를 반환하므로 위에서 언급 한 변수 만 구현하는 것만으로는 충분하지 않습니다. 한 가지 옵션은 원하는 결과를 얻을 때 호출되는 두 개의 "중단 문"을 구현하는 것입니다. 따라서 방법은 다음과 같습니다.

private static int oneRun(int range) {

        int finalResult = 0; // the above-mentioned variable
        int[] rInt = new int[range + 1];
        rInt[0] = generator.nextInt(range);

        for (int count = 1; count <= range; count++) {
            rInt[count] = generator.nextInt(range);
            for (int i = 0; i < count; i++) {
                if (rInt[i] == rInt[count]) {
                    finalResult = count;
                    break; // this breaks the inside loop
                }
            }
            if (finalResult == count) {
                break; // this breaks the outside loop
            }
        }
        return finalResult;
    }

2

도달 할 수없는 문이 발생하는 경우 예외를 throw해야한다는 데 동의합니다. 동일한 방법으로 더 읽기 쉬운 방식으로이를 수행하는 방법을 보여주고 싶었습니다 (Java 8 스트림 필요).

private static int oneRun(int range) {
    int[] rInt = new int[range + 1];
    return IntStream
        .rangeClosed(0, range)
        .peek(i -> rInt[i] = generator.nextInt(range))
        .filter(i -> IntStream.range(0, i).anyMatch(j -> rInt[i] == rInt[j]))
        .findFirst()
        .orElseThrow(() -> new RuntimeException("Shouldn't be reached!"));
}

-1
private static int oneRun(int range) {
    int result = -1; // use this to store your result
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range && result == -1; count++) { // Run until result found.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count && result == -1; i++) { // Check for past occurence and leave after result found.
            if (rInt[i] == rInt[count]) {
                result = count;
            }
        }
    }
    return result; // return your result
}

하나를 수행하기 때문에 코드는 비효율적 많은result == -1생략 될 수 수표 return내부 루프 ...
윌렘 반 Onsem
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.