스파르타 이노캠/본과정

2023. 06. 13 화 - 숫자 야구 / Input issue / AI

haema_ 2023. 6. 13. 17:46
728x90

 

 숫자야구 코드 작성 시, 이전 알고리즘 문제를 풀 때 BufferedReader를 사용하는 것이 Scanner보다 더 빠르게 작동한다는 사실을 알게 되어서 BufferedReader를 사용해서 작성했었다.

이후 ChatGPT에 코드 리뷰를 요청해봤는데, Scanner 클래스를 사용해서 간편하게 입력받을 수 있다는 개선점을 제시해주었다.

 

ChatGPT가 Scanner를 추천해준 이유는 다음과 같았다.


BufferedReader와 InputStreamReader를 사용하여 사용자 입력을 받는 방식은 좀 더 저수준의 입출력 처리 방식입니다. 이는 몇 가지 단점을 가지고 있습니다.
  1. 복잡한 구현: BufferedReader와 InputStreamReader를 사용하면 입력을 받는 부분이 상당히 복잡해집니다. try-catch 블록을 사용하여 예외 처리를 해야하고, IOException을 처리해야 합니다. 또한 StringTokenizer를 사용하여 입력된 값을 분리해야 합니다. 이로 인해 코드가 복잡해지고 가독성이 떨어질 수 있습니다.
  2. 자료형 변환: BufferedReader를 사용하여 입력을 받을 경우, 사용자로부터 입력받은 값을 String으로 받게 됩니다. 이후에는 String을 int로 변환해야 하며, 이 과정에서 오류가 발생할 수 있습니다. 입력값이 올바른 형식이 아니거나 숫자가 아닌 경우에는 예외 처리를 해야 합니다.
  3. 코드 중복과 실수 가능성: BufferedReader를 사용할 때는 입력 처리를 위한 반복 코드가 필요합니다. br.readLine()으로 한 줄을 읽고, StringTokenizer로 분리하고, 각각의 토큰을 정수형으로 변환해야 합니다. 이러한 반복적인 작업은 오타나 실수를 유발할 수 있습니다.
반면에 Scanner 클래스는 이러한 단점을 극복하고 편리한 사용을 제공합니다.
  1. 간단한 구현: Scanner를 사용하면 nextInt()나 nextLine()과 같은 간단한 메서드를 사용하여 입력을 처리할 수 있습니다. 별도의 예외 처리가 필요하지 않습니다.
  2. 자료형 변환: Scanner는 입력된 값을 int, double, String 등과 같은 원하는 자료형으로 바로 읽어올 수 있습니다. nextInt()를 호출하면 정수를, nextDouble()을 호출하면 실수를 바로 얻을 수 있습니다. 이로 인해 입력값의 형식 오류가 발생할 확률이 줄어듭니다.
  3. 코드 중복 최소화: Scanner를 사용하면 입력 처리를 위한 반복적인 작업이 필요하지 않습니다. 메서드를 호출하여 한 줄로 입력을 처리할 수 있습니다. 이로 인해 코드 중복 가능성이 낮아지며, 가독성과 유지보수성이 향상됩니다.

따라서 BufferedReader와 InputStreamReader 대신 Scanner 클래스를 사용하여 사용자 입력을 받는 것이 보다 효율적이고 편리합니다.

 


답변을 읽다보니까 BufferedReader가 굉장히 좋지 않은 것처럼 묘사되길래, Scanner사용이 현업에서 더 일반적인 건지에 대한 궁금증이 생겨 추가 질문을 해보았다.

 

현업에서는 입력 처리에 Scanner보다는 BufferedReader를 더 많이 사용하는 경향이 있습니다. 이는 다음과 같은 이유로 설명됩니다:
  1. 성능: BufferedReader는 내부적으로 버퍼를 사용하여 입력을 처리하기 때문에 빠른 속도를 제공합니다. 반면에 Scanner는 각각의 호출마다 입출력 작업을 수행하므로 성능면에서는 BufferedReader보다 느릴 수 있습니다. 따라서 대량의 입력을 처리하는 경우에는 BufferedReader가 성능상 이점을 가집니다.
  2. 입력 형식: BufferedReader는 String으로 입력을 받기 때문에 사용자로부터 입력된 값을 원하는 형식으로 변환해야 합니다. 이는 Integer.parseInt()와 같은 변환 작업을 추가로 수행해야 하는 번거로움을 초래할 수 있습니다. 반면에 Scanner는 다양한 자료형으로 입력을 바로 읽을 수 있어서 사용자 입력 처리가 더 편리합니다.
  3. 예외 처리: BufferedReader를 사용할 때는 입력 처리 중에 IOException과 같은 예외를 처리해야 합니다. 이에 반해 Scanner는 예외 처리를 자동으로 처리하므로 예외 처리에 대한 부담이 줄어듭니다.
  4. 다양한 입력 소스: BufferedReader는 InputStreamReader와 함께 System.in, 파일, 소켓 등 다양한 입력 소스에 대해 사용될 수 있습니다. Scanner는 기본적으로 System.in에 대한 입력을 처리하는 데에 특화되어 있습니다.

따라서 대부분의 경우, 입출력 성능과 예외 처리 등을 고려하여 BufferedReader가 선호되는 선택이지만, 입력 처리가 간단하고 다양한 자료형으로 쉽게 변환할 수 있는 상황에서는 Scanner를 사용하는 것이 더 편리할 수 있습니다. 선택은 상황과 요구사항에 따라 달라질 수 있습니다.


 그 뒤에 몇 가지 추가 질문을 해본 결과, 결론적으로는 숫자 야구의 입력 처리 부분이 간단한 형식이고, 이 정도 간단한 코드에 굳이 힘들게 예외 처리를 해가면서 BufferedReader를 쓸 이유가 없다는 맥락이었다.

물론 AI가 생성해주는 답변을 완벽하게 신뢰할 수는 없겠지만, 학습 시에 많은 도움을 주기는 한다. 단순히 '올바른 코드를 짜줘' 같은 요구가 아니라 어떤 코드나 기능의 문제점이 뭔지, 왜 그런지 등을 추가적으로 질문하다 보면 확실히 머리에 들어오고, 이해가 된다.

 여담으로 최근에는 Bard만 이용했었는데, ChatGPT를 간만에 이용해보니 확실히 ChatGPT의 답변 퀄리티가 아직은 훨씬 높은 느낌이다.

 

 Bard는 아직까지는 단순한 정도의 문답 형식 수준으로 커뮤니케이션이 가능했다. 예를 들면 이전 질문의 내용에 덧붙여서 조금 더 상세한 답변을 요구한다거나, 질문 중 일부를 수정해서 되묻는 등의 요구에서는 만족스러운 답변을 내주지 못했다.

 반면에 ChatGPT는 코드 전체를 면밀히 분석해주는 것은 물론이고, 해당 코드에서 발생 가능한 예외 중 처리하지 못한 예외를 파악해서 알려주기도 했다. 또 앞의 질답을 바탕으로 추가적인 질문이 가능해서, 뒤로 갈수록 질문 자체를 많이 구체화 할 수도 있었다.

 

 한 가지 아쉬웠던 점은 Bard건 ChatGPT건 답변 자체를 너무나도 당연한 사실처럼 말하기 때문에, 틀린 내용을 답해주더라도 내가 그 부분에 대해 모르면 이게 틀린 답인지 맞는 답인지 모른다는 거다. 실제로 어떤 두 가지를 비교해달라고 질문했을 때 아예 반대로 설명해주는 경우도 있었고, 나머지는 다 똑바로 설명하다가 한 줄만 교묘하게 반대로 되어있는 경우도 있었다.

 

그래서 잘못 설명한 거 아니냐고 되물어보면, 잘못 설명했다고 대답한다..... 알면서 왜 그러니ㅡㅡ

 

어쨌든 학습 시 AI에 전적으로 의존하기만 하는 것은 절대로 좋지 않은 것 같다.

 


 

코드 작성은 예외 처리에 익숙해지기 위해 그대로 BufferedReader를 사용해서 진행했다.

 

작성한 숫자 야구 코드 내 발생 가능한 Exception 정리

NoSuchElementException e

BufferedReader로 입력받은 값을 StringTokenizer를 사용해서 공백을 구분자로 토큰화. 각 토큰을 정수화하여 변수에 각각 대입하도록 작성했는데, 공백을 기준으로 분리한 토큰이 총 3개가 아닐 경우 예외 처리

 

NumberFormatException e

입력받은 값이 Integer.parseInt로 정수화 될 수 없는 값일 때 발생하는 예외.

작성한 코드에서는 각 토큰이 두 자리 이상의 숫자일 경우 또한 동일 예외로 처리했다.

 

IOException e

입출력 시의 시스템 오류 등의 예외 처리


전체 코드

import java.util.*;
import java.io.*;
public class Baseball {
public static void main(String[] args) throws IOException {
int [] targetNum = ran();
int count=10;
int tr=0;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(count>0) {
System.out.println("세 자리 숫자를 한 칸씩 띄워서 입력하세요. 남은 기회 "+count+"");
try{
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
if (st.countTokens() != 3) {
throw new NoSuchElementException(); // 토큰 개수가 3이 아닌 경우 예외 발생
}
int a, b, c;

try {
a = Integer.parseInt(st.nextToken());
b = Integer.parseInt(st.nextToken());
c = Integer.parseInt(st.nextToken());
if(a>9||b>9||c>9){
throw new NumberFormatException(); // 입력 중 두 자리 이상의 숫자인 경우 예외 발생
}
} catch (NumberFormatException e) {
throw new NumberFormatException(); // 정수로 변환할 수 없는 토큰이 있는 경우 예외 발생
}
int strike=0;
int ball=0;
tr+=1;
if(a==targetNum[0]){
strike++;
} else if(a==targetNum[1] || a==targetNum[2]){
ball++;
}
if(b==targetNum[1]){
strike++;
} else if(b==targetNum[0] || b==targetNum[2]){
ball++;
}
if(c==targetNum[2]){
strike++;
} else if(c==targetNum[0] || c==targetNum[1]){
ball++;
}
if(strike==3){
System.out.println("정답입니다! 총 시도 횟수 : "+ tr + "");
count=0;
} else if(count==1 && strike != 3 ){
System.out.println("아쉽게도 기회를 모두 소진하셨습니다");
System.exit(0);
}
else {
System.out.println(strike + "S " + ball + "B");
}
count--;
} catch (NoSuchElementException e) {
System.out.println("입력 값이 올바르지 않습니다. 세 자리 숫자를 한 칸씩 띄워서 입력하세요.");
} catch (NumberFormatException e) {
System.out.println("입력 값이 올바르지 않습니다. 세 자리 숫자를 입력하세요.");
} catch(IOException e){
System.out.println("입력 중 오류가 발생했습니다. 다시 입력해주세요.");
}
}
try{
br.close();
} catch (IOException e) {
System.out.println("입력 스트림을 닫는 도중 오류가 발생하였습니다.");
}
}

public static int[] ran() {
Random ran = new Random();
int tmp = ran.nextInt(10);
int[] targetNum = {tmp, -1, -1};
tmp = ran.nextInt(10);
while (targetNum[0] == tmp) {
tmp = ran.nextInt(10);
}
targetNum[1] = tmp;
tmp = ran.nextInt(10);
while (targetNum[0] == tmp || targetNum[1] == tmp) {
tmp = ran.nextInt(10);
}
targetNum[2] = tmp;
return targetNum;
}
}

반응형