본문 바로가기

Programming/Java

[기본-4] 제어문

목차

  • 선택문
  • 반복문

 

선택문

1) if문

if문은 조건이 성립되는 경우에만 원하는 처리를 하고자 할 때 사용한다. 

 

int num = 100;
if(num == 100) {
    System.out.println("100입니다.");
}

 

 

동시에 다른 조건도 비교하여 별도의 처리를 하고자 할 때는 else if를 추가한다.

 

int num = 100;
if(num == 100) {
    System.out.println("100입니다.");
} else if(num == 90) {
    System.out.println("90입니다.");
}

 

만약 조건이 아닐 경우에 별도의 처리가 필요할 때는 else 절을 작성한다.

 

int num = 50;
if(num == 100) {
    System.out.println("100입니다.");
} else if(num == 90) {
    System.out.println("90입니다.");
} else {
    System.out.println("if절과 else if절에서 조건에 성립되지 않아 else 절을 수행");
}

 

 

if 절은 필수로 작성해야하며 else if 절은 없거나 여러번 올 수 있다. else 절은 없거나 마지막에 한 번만 올 수 있다.

가장 흔한 예로 학점 평균을 출력하는 프로그램은 다음과 같다.(+학점은 생략)

 

int avg = 75;

if(avg >= 90) {
    System.out.println("A학점 입니다.");
} else if(avg >= 80) {
    System.out.println("B학점 입니다.");
} else if(avg >= 70) {
    System.out.println("C학점 입니다.");
} else if(avg >= 60) {
    System.out.println("D학점 입니다.");
} else {
    System.out.println("F학점 재수강 입니다.");
} 

 

 

논리 연산자를 이용하여 하나의 if 절에서 여러 조건을 넣어 비교할 수도 있다. 

다음은 윤달을 체크하는 예제이다.

 

int year = 2000;

// 윤달을 체크한다.
if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
    System.out.println("윤달입니다.");
}

 

 

개인적인 생각으로 if문의 단점 중에 하나는 사용하기 나름이지만 else 절을 남발할 경우 유지보수가 어려운 소스코드가 된다는 것이다.

validation 체크 등 단일 if절과 return을 사용하여 처리할 수 있는 상황에서 그렇지 않을 경우까지 생각을 하여 무조건 else 절을 넣게 되면 유지보수가 어려운 코드가 되버린다. 

 

다음은 유지보수가 어려워지는 else 지옥에 대한 샘플 예제이다. else 지옥이라는 것은 필자가 붙여준 별명이다. 실제로 10년 가까이 된 레거시 소스를 보면 이런식으로 된 소스를 많이 봤었다. 이런 코드는 가독성이 나빠서 보기 어렵고, 기존 유지보수 하는 개발자도 이해하기가 쉽지 않을 것이다. 실제로 저런 else 지옥으로 인하여 depth가 10depth 이상 들어가는 소스코드도 흔하게 봤다. 이런 코드 중간에 다른 요건이 추가 된다면? 혹은 몇몇 부분에서 동일한 로직을 수행해야 한다면? 내부 블록으로 감싼 코드에 대해서 검증이 필요할 것이다. 아주 골치아픈 일이 될 것이다.

 

// 이번 예제에서는 별도의 VO를 작성하지 않기위해 Map으로 대체하였다.

Map<String, String> map = new HashMap<>();

// 9999라는 응답코드를 받았을 때 
if("9999".equals(map.get("RES_CODE"))) {
    System.out.println("시스템장애일 경우에 처리하는 로직 수행");
    
// return 처리하여 아래 else 문을 작성하지 않아도 되는 상황!    
} else {
    
    // 요건이 변경되어 오류 처리가 추가 되었다.
    if("Y".equals(map.get("NETWORK_TIMEOUT"))) {
        System.out.println("타임아웃 발생으로 인하여 별도의 로직 수행!");
    
    // return으로 처리가 가능한 로직을 또다시 else 절을 추가한다.
    } else {
    
        
        if(!"05".equals(map.get("eci"))) {
        
            System.out.println("승인불가!");
        
        // 여기서 또 조건이 추가된다면??? 그래도 else문을 추가할 것인가?
        // validation 등으로 즉시 리턴되지 않고 else 절 처리가 간단한 경우는 else를 사용하지만
        // else 문을 남발하는 것은 유지보수가 어려운 소스코드를 생산하게 되므로 회피해야 한다!
        } else {
            // 이제 그만... 이런 소스가 운영중일때 만약 중간에 조건문이 추가된다면??? 내부 블록에 대한 검증이 필요해진다.
            
            System.out.println("정상일 때 로직 수행");
        }
        
    
    }
    

}

 

 

반면 else 문이 필요 없는 상황에서 회피할 경우 아래와 같아진다.  else if 절까지는 상관없다. else절을 메소드 전체에 묶는 패턴은 피해야 한다.

// 이번 예제에서는 별도의 VO를 작성하지 않기위해 Map으로 대체하였다.

Map<String, String> map = new HashMap<>();

// 9999라는 응답코드를 받았을 때 
if("9999".equals(map.get("RES_CODE"))) {
    System.out.println("시스템장애일 경우에 처리하는 로직 수행");
    return map; // return 처리하여 아래 else 문을 작성하지 않아도 되는 상황!    
}
    
// 응답메시지를 확인하여 오류 처리가 추가 되었다고 가정한다.
if("Y".equals(map.get("NETWORK_TIMEOUT"))) {
    System.out.println("타임아웃 발생으로 인하여 별도의 로직 수행!");
    return map;
}    

// 요건이 추가 되었다고 가정한다.
if(!"05".equals(map.get("eci"))) {
    System.out.println("결제 승인불가! 별도 로직 수행");
    return map; // return 타입은 메서드 반환타입에 맞게..
}

System.out.println("정상일 때 로직 수행.....");

 

위의 둘 중에 어떤 소스코드가 더 쉬워보이는가? else 절은 필요한 상황에서 사용하고 남발해서는 안된다는 예제를 보여주고 싶어 샘플 예제를 작성하게 되었다.

 

 

2) switch문

switch문은 if문과 비슷하지만 switch절 옆에 판별할 값이나 변수가 오고, 조건은 case 절을 통해서 작성한다. 또한 if문의 else절과 같은 기능을 하는 default 키워드가 제공된다. String 타입은 자바 8 이후에나 입력받을 수 있게 되었다. 

 

 

int avg = 80;

switch(avg / 10) {
    case 10 :
    case 9 :
        System.out.println("A학점 입니다");
        break; // break가 없는 경우 아래 조건을 확인하게 된다. (default 존재시 default를 수행함)
    case 8 :
        System.out.println("A학점 입니다");
        break;
    case 7 :
        System.out.println("C학점 입니다");
        break;
    case 6 : 
        System.out.println("D학점 입니다");
        break;
    default : 
        System.out.println("축하합니다. 재수강 입니다.");
}

 

 

break 키워드는 처리 후 반드시 작성하여 빠져나가게 해야한다. 

만약 default 조건이 있는 데 상위 조건을 성립하여 로직 수행 후 break를 하지 않는 경우 default절의 조건을 수행하게 되어 디버깅이 어려운 문제가 발생하게 된다.

 

 

int n = 9;
int result = 0;
switch(n) {
    case 9 :
        System.out.println("9");
        result = 9;
    case 8 :
        System.out.println("8"); // 8은 실행되지 않을 것 같지만 break 절을 만나기 전까지 계속 처리된다.
        result = 8;
    default:
        System.out.println("기본"); 
        result = 1;
}

 

 

break 키워드가 있었으면 result는 9였겠지만 위의 결과는 1이 된다. 만약 case 8에서 break가 있었으면 result는 8이 될 것이다. switch문을 작성할 때는 break를 반드시 확인하자.

 

 

반복문

 

1) for문

for문의 형식은 다음과 같다

for ( [시작값 정의] ; [반복을 수행할 조건] ; [증감값정의]) 

여기서 세미콜론은 필수이며 각 [] 안에 주어진 조건은 생략해도 오류가 아니다. 

* 참고사항으로 for( ; ; )는 while(true)와 같다. (But, 가독성 때문에 for문을 이렇게 사용하지는 않고 while문으로 대체한다.)

 

다음은 1부터 10까지의 합을 구하는 예제이다.

int sum = 0;
// 1부터 10까지의 합을 구한다.
for(i = 1; i <= 10; i++) {
    sum += i;
}

 

 

반복문은 중복해서 사용할 수도 있다. 

다음은 2단부터 9단까지 수행하는 구구단 예제이다.

for(int i=2; i<10; i++) {
    for(int j=1; j<10; j++) {
        System.out.println(i + " x " + j + " = " + i*j);    
    }
}

 

 

* 이중 for문은 O(n^2)의 성능을 나타내므로 입력값이 클 경우 회피해야 한다.

 

2) while문

while 문은 for문 처럼 반복문을 수행할 때 사용한다. 단순히 조건만 작성하므로 필요에 맞게 for문이나 while문을 사용하면 될 것이다.

int n = 0;
int sum = 0;

// 1부터 10까지의 합을 구한다.
while(n <= 10) {
    sum += ++n;
}

 

3) do-while문

do-while문은 while문과 동일하나 최초에 처리할 로직이 필요한 경우 사용하면 유용하다.

int n = 0;
int sum = 0;
do {
    sum += ++n;
} while(n > 0 && n <= 10); // 조건을 처리하기 전에 한번 실행되므로 n은 0보다 크다.

 

 

 

* 프로그래밍 입문 부분에 해당하며, 다른 프로그래밍 언어를 배웠다면 (C, C++) 차이점이 없으므로 해당 포스트는 간결하게 마무리 합니다.

* 이번 포스팅에서는 label 키워드에 대한 설명은 생략하였습니다.