[Java] 반복문 속 switch, 왜 빠져나오지 못할까? — 반복문 속 메뉴 로직의 깔끔한 구조와 탈출 전략
들어가며
계산기 프로젝트를 진행하면서 생긴 궁금증에 대해서 생각해본 내용을 정리해보았다.
프로그램을 작성하다보면, 특히 콘솔기반의 자판기, 은행등의 토이 프로젝트는 사용자에게 메뉴를 반복하여 보여주고 입력을 받기야 하기 때문에 while(true)와 같은 무한 반복문을 사용한다.
예를 들어
while (true) {
System.out.println("1. 기능1번");
System.out.println("2. 종료");
int menu = scanner.nextInt();
// 처리 로직
}
이런 식의 출력을 통하여 메뉴 선택을 요구한다.
반복문과 switch는 궁합이 좋지 않다
입력받은 메뉴를 처리하기 위해, 자주 등장하는 구조가 아래와 같은 반복문 속의 switch문이다. 메뉴 입력을 받고 기능을 나눌때 자주 사용되지만, 반복문과 결합되면 switch내부에서 전체 프로그램의 흐름을 제어하기 어려운 문제가 생긴다.
while (true) {
int menu = scanner.nextInt();
switch (menu) {
case 1:
while (true) {
// 내부 루프 = 기능처리
if (scanner.next().equals("n")) break; //내부 루프 탈출용
}
break;
case 2:
//프로그램 종료를 원하는데 break는 switch를 탈출
break; // 여전히 외부 while이 작동
}
}
문제는 사용자가 프로그램 사용을 끝내고 프로그램도 종료하고 싶을 때 외부 while 루프를 벗어날 방법이 마땅치 않다는 것이다. switch 블록에서의 break는 내부 루프만 빠져나오기 때문이다.
또한, 1번 메뉴를 선택하여 기능을 수행할 때, 내부 로직에 의해서 추가 반복문이 필요한 경우도 있다. 사용자가 내부 반복문에서 n을 입력하도록 하여 반복을 종료하려 해도, 외부 반복문을 탈출은 어렵다.
정리하자면, 아래와 같은 이중 반복문 탈출 문제가 생긴 것이다.
- 내부에서
break를 사용하면 내부 루프만 빠져나간다.- 외부 루프까지 종료하고 싶을 때
break만으로는 불가능하다.
이럴때, 외부반복문을 어떻게 종료 가능할까?
방법1 : return 사용
아래와 같이 return문을 사용하여, 아예 main메소드를 종료시키는 방법이 있다. 종료를 원하는 동작이므로 정상적으로 작동한다.
while (true) {
//생략
switch (menu) {
case 1:
while (true) {
//연산 로직 수행
...
System.out.println("계속할까요? (y/n)");
if (scanner.next().equals("n")) break;
}
break;
case 2:
return; //main() 종료
}
이방식의 장점은 프로그램이 즉각적으로 종료되므로 직관적이다
단점은 프로그램 전체가 종료되지만 너무 직접적이라는 것이다
또한, 정리 작업이나 이후 종료 로직을 처리 못하는것도 문제이다
작은 규모에선 괜찮을지도 모르지만 유지보수나 테스트면에서도 불리하다.
✅ 장점
- 직관적이다
- 코드가 간단하다
❌ 단점
- 정리 작업이나 이후 종료 로직을 처리하지 못한다
- 코드 중간에
return이 있다면, 어디서 종료되는지 추적이 어렵다- 리팩터링 시 복잡도 증가 →
return위치에 따라 버그발생 가능성이 있다
방법2 : 플래그 변수 사용
두번째 방법은 flag를 사용하여, while문에 직접 변수를 주는 방식이다.
while(true)에서, true가 아닌, boolean 변수를 사용하고 조건에 맞게 switch 문에서 변수의 값을 변경하여 자연스럽게 while 반복문을 탈출 하게 한다.
boolean isRunning = true;
while (isRunning) {
System.out.println("1. 계산기");
System.out.println("2. 종료");
int menu = scanner.nextInt();
switch (menu) {
case 1:
boolean calc = true;
while (true) {
System.out.println("기능 실행 중 (n 입력 시 나가기, exit 입력 시 전체 종료)");
String input = sc.next();
if (input.equals("exit")) {
isRunning = false;
break;
}
if (input.equals("n")) break;
}
case 2:
isRunning = false;
break;
}
}
위와같은 방식을 사용하면, 반복문 내부의 반복문에서도 중간에 외부 반복문의 흐름을 제어 가능하다는 장점이 있다. 하지만 프로그램이 길어지거나, 플래그가 많아지면 복잡성이 증가한다.
장단점을 정리해보면 아래와 같다.
✅ 장점
- 직관적이고 가독성 좋다
- 여러 단계 종료 조건을 독립적으로 제어 가능하다
- 외부 반복문 탈출 여부를 명확하게 컨트롤 가능
exit등 사용자 입력 기반 제어 가능
❌ 단점
- 플래그 변수가 많아지면 코드 추적이 어렵다
- 중첩 구조가 많아질 경우 복잡성이 증가한다.
방법3 : 라벨을 활용한 break
outer:
while (true) {
System.out.println("1. 계산기");
System.out.println("2. 종료");
int menu = scanner.nextInt();
switch (menu) {
case 1:
while (true) {
System.out.println("계산기 작동 중...");
if (scanner.next().equals("exitAll")) {
break outer; // 바깥 루프까지 탈출
} else {
break; // 내부만 탈출
}
}
break;
case 2:
break outer;
}
}
✅ 장점
- 지정한 반복문만 명확하게 탈출 가능하다
- 중첩 루프에서 편하게 사용 가능하다
- 간단한 코드에서는 명확하다
❌ 단점
- 라벨 사용은 권장되지 않음 (가독성 저하, 유지보수 어려움)
- 코드가 길어질수록 단점이 부각된다
- 남용시
goto문과 같은 단점이 생긴다
방법4 : 구조적 개선 - if문을 사용하는 방법
문제의 원인인 switch문이 탈출하기 어려운 구조 자체를 개선하는 방법이 있다. 애초에 문제가 되지 않는 구조로 설게하는것이 가장 근본적인 해결책이라고 볼 수 있다.
switch가 아닌, if문을 사용하면, break와 continue를 사용하여, 흐름 제어가 가능하다.
while (true) {
//생략
System.out.println("1. 기능수행\n2. 종료");
int menu = sc.nextInt();
if(menu == 1){
//기능수행
}
else if(menu ==2){
//종료 로직
break; //while 반복문 종료
}
}
✅ 장점
- 구조가 명확해진다
- 루프 탈출보다 더 명확하게 흐름제어가 가능하다
- 다양한 조건도 조합 가능하다
❌ 단점
if-else가 많아지면 가독성이 저하된다- 메뉴가 많아질수록 유지보수가 어렵다
각 방법의 장단점 비교
| 방식 | 장점 | 단점 |
|---|---|---|
return |
코드가 간결하고 즉시 빠져나옴 | main()이 종료되어 제어가 어려움 |
플래그 |
반복문 종료조건을 명시적으로 표현가능 | 플래그가 많아지면 복잡도가 증가 |
라벨 |
특정 상황에서 가장 직관적이다 | 남용시, 코드 구조 파악이 어려움 |
if |
흐름 제어가 유연, 명확한 조건 처리 | if-else많아질수록 복잡 |
그래서 정답은 ?
다양한 방법이 있고, 각 방법마다 장단점이 존재한다. 중요한점은, 어떤 방법을 선택하여 적용할까에 대한 고민이다.
처음에 이 문제에 대해 고민을 하고, 여러 방법을 찾아보게된 이유는 가장 많이 쓰이거나, 가장 좋은 방법을 찾고 싶었기 때문이었다.
여러가지 방법을 사용해본 결과, 방법은 여러가지가 있고 각 방식의 장단점이 우열을 가리기 어렵다고 생각하였다.
이것이 좋고 저것은 안좋은것이 아니다. 만약 코드가 정말 짧은경우라면, label 사용이 가장 명확하고 편리한 방법이 되는것이다. 조건 분기가 적다면 switch-case 사용이 한눈에 보기 좋은 코드일 수 있다. 각 해결 방법이 장단점이 존재하기 때문에 상황에 따라 적절하게 적용하는것이 좋다는 것이다.
그렇지만 구조적인 관점에서 생각해본다면, 이런 흐름 제어 방식들은 근본적인 해결책이라기보다는 임시방편에 가까울수도 있는것이다. 궁극적으로는 처음부터 흐름이 복잡해지지 않도록 설계하는 것, 즉 구조 자체를 단순하고 명확하게 만드는 것이 가장 바람직한 방향일 수 있다
위의 예시 코드로 살펴보면, switch-case를 탈출하기 위해 좋은 방법을 고민하는것이 아닌, 애초에 while 안에 switch를 사용하지 않는것이 가장 간단한 방법이라는 것이다.
결국 가장 중요한 것은 하나의 방식을 고집하는 것이 아니라, 다양한 방법의 장단점을 이해하고, 상황에 맞게 적용하는 유연한 사고를 하는것이다.
때로는 기술로 문제를 해결할 수 있을것이고, 어떤 경우에는 구조설계 자체를 다시 해야할 수도 있다.
여러 방법중 하나의 선택, 구조적 설계 등 모든 고민의 과정이 더 나은 코드로 이어진다. 좋은 코드를 만들기 위해 정답 하나가 아닌 다양한 시도와 적절한 선택이 가장 중요하다.
댓글남기기