코드의 논리 오류 잡아내기

정순동·2024년 3월 7일

알고리즘

목록 보기
31/33

우리가 코드를 작성하고 실행할 때 예상치 못한 오류를 마주쳤다고 가정하자. 코드가 간단한 경우든 복잡한 경우든 우리는 해당 오류를 해결해야하고 이를 해결해야한다.

프로그램에서 발생하는 문법 오류논리 오류를 찾아 바로잡는 과정을 디버깅(debugging)이라한다.

문법 오류는 우리가 굳이 찾을 필요가 없다. 컴파일러가 자동으로 찾아 주므로 테스트할 때 문제가 되지 않는다.

논리 오류가 문제다. 논리 오류는 우리가 작성한 코드가 우리의 의도대로 수행되지 않는 경우이다.

public static int sum(int x, int y) {
	return x * y; // 곱하기를 사용!
}

public static void main(String[] args) {
	System.out.println("10과 5의 합은 " + sum(10, 5) + "이다.");
}

위 코드는 문법적 오류없이 정상적으로 동작하나 결과가 "10과 5의 합은 50이다"라는 엉뚱한 답을 하게된다. 그 이유는 sum()메서드의 'x * y'부분에서 발생하는데 이를 '논리 오류'라고 한다. 'x + y'가 되어야 한다.

이런 논리 오류는 코드가 복잡한 경우 발견하기도 쉽지 않고 가끔은 일어난지도 모르는 경우가 있다.

디버깅의 중요성

코딩 테스트 도중 떨어진 사람들은 보통 디버깅을 할 줄 모르거나 비효율적인 방식으로 진행하여 시간을 다 잡아먹고 탈락하게 되는 경우가 많다.

많은 사람들이 문법을 배우는 과정에서 디버깅을 가볍게 생각한다. 하지만 우리는 사람인지라 아무리 문법을 마스터했다고 해도 실수는 일어나기 마련이다.

그러므로 디버깅은 코딩 테스트나 코딩에 필요한 기술이고, 그냥 알아 두기만 하면 되는 것이 아니라 문제를 풀면서 반드시 해야 하는 과정이다. 반드시 디버깅을 익힌 후 코딩 테스트등에 응하자.

디버깅하는 법

디버깅을 하는 방법은 코드에서 디버깅하고자 하는 줄에 중단점(break point)을 설정하고, IDE의 디버깅 기능을 실행해 진행하면 된다.

구체적인 방법은 아래와 같다.

  1. 코드에서 디버깅하고자 하는 줄에 중단점을 설정한다. 이때 중단점은 여러 개 설정할 수 있다.
  1. IDE의 디버깅 기능을 실행하면 코드를 1줄씩 실행하거나 다음 중단점까지만 실행할 수 있으며, 이 과정에서 추적할 변수도 지정할 수 있다. 이 방법으로 변수값이 자신의 의도한 대로 바뀌는지 파악한다.
  1. 변숫값 이외에도 원하는 수식을 입력해 논리 오류를 파악할 수도 있다.

위 2,3에서 말하는 '변숫값 추적'은 이클립스나 인텔리제이에서
'Expressions' 기능을 활용하면 된다.

Expressions기능 (디버깅 툴)

IntelliJ, Eclipse는 모두 위 기능을 지원하는데 여기선 IntelliJ로 알아보자.

17행을 보면 빨간점으로 표시를 해 놓았는데 이게 중단점 즉 break point이다. 표시를 하는법은 저 위치를 왼클릭 하면 된다.

그 후 상단의 Run탭에서 Debug모드로 실행하거나, 우상단에서 벌레모양의 버튼을 누르면 디버그 모드로 실행이된다.

그러면 내가 설정한 중단점부터 실행이 잠시 멈추게 되고 여기서 현재 변수들에 들어있는 값이나 배열의 현 상태 등 많은 정보를 알 수 있다.

해당 중단점에서부터 여러 버튼을 클릭해서 다음 코드를 실행하거나 중단점 무시하기 등 여러 기능이 존재한다. 해당 기능들은 아래 블로그가 잘 설명해 두었으니 참조하면 좋겠다.

https://blog.naver.com/PostView.nhn?blogId=tangunsoft&logNo=222153922257


실제 일어나기 쉬운 4가지 논리 오류 상황

오류 1. 변수 초기화 오류

실제 코드를 작성할 때 for문 앞 뒤로 초기화를 제대로 하지 않은 경우 주로 발생하는 오류이다.

	public static void main(String[] args) {
        int[] arr1 = {1,2,3,4,5};
        int[] arr2 = {5,4,3,2,1};
        int answer = 0;
        for(int i = 0; i < arr1.length; i++) {
            answer += arr1[i] + arr2[i];
            System.out.println(answer);
        }
    }

배열1,2의 같은 위치 요소를 더한 다음 출력하는 메서드이다.
arr1[0] + arr2[0] = 6, arr1[1] + arr2[1] = 6으로 모두 6이 출력되는 것을 기대한 메서드인데, 실제 출력하면 6의 배수처럼 6,12,18... 등으로 출력된다.

그 이유는 위에서 찾아 볼 수 있듯 answer를 초기화하지 않았기 때문이다. 이런 경우는 쉽게 찾을 수 있지만. 코드가 길어지는 경우는 발견하기 어려울 수 있다 따라서 이럴 때 break point를 설정하여 변수들을 유심히 살펴보자.

아래에서 볼 수 있듯 i = 1이다. 즉 for문이 2번 째 돌고 있다는 소리인데 answer변수가 12로 표시돼 있다. 이는 이전 첫번째 for문에서 사용한 answer가 그대로 넘어와서 또 6 + 6으로 된것이라 풀이할 수 있고 오류를 잡아낼 수 있다.

오류 2. 반복문 범위 설정 오류

초보나 숙련자나 모두 쉽게 헷갈리며 실수하기 쉬운 반복문 범위 설정 오류이다.

public static void main(String[] args) {
        int[] arr1 = new int[1000];
        int[] arr2 = new int[1000];

        for (int i = 1; i < 100; i++) {
            arr1[i] = (int) (Math.random() * Integer.MAX_VALUE);
            arr2[i] = arr1[i] * 2;
        }

        for (int i : arr2) {
            System.out.print(i + "  ");
        }
    }

위 코드는 그저 arr1의 모든 요소를 랜덤한 숫자로, arr2의 모든 요소는 arr1의 각 요소의 2배로 만들어서 채우는 간단한 코드인데, 실제 실행 해 보면 arr1,2의 각 0번 인덱스 요소와, 100번 인덱스 이후 요소들에 대한 값이 모두 0인 것을 확인 할 수 있다.

이는 배열이 0부터 시작한다는것을 잊어버린경우와, for문에 1000을 넣어야 하는데 100을 넣어버린 경우이다.

또한 어떤이는 <=, >=, <, >를 잘못 사용하여 마지막 요소가 0이거나 인덱스 범위 초과 오류를 일으키기도 하니 꼼꼼하게 살펴보며 코딩하도록 하자.

오류 3. 자료형 범위 오류 찾기

앞선 예시인 arr1,arr2 요소 채우기에는 다른 오류가 하나 더 있었다 바로 arr2배열에서 나타나는 오류인데 바로 요소의 값이 -가 돼 있는것이다.

분명 Integer.MAX_VALUE라는 상수를 통해 상한값을 int형 변수에 저장할 수 있는만큼 가져왔지만, 우리는 arr2에 대입할때 x2를 해서 넣었다.

이렇게 되면 큰 숫자의 경우 int형의 최대값은 21억~~값을 초과하는 경우가 생기고 그로 인해 예기치 못한 값들을 받게 되는것이다. 이를 오버플로우라고 하며

우리가 이를 방지하기 위해서는 그저 long자료형을 사용하면 된다. long은 int형에비해 저장할 수 있는 값의 범위가 훨씬 크기 때문에 웬만하면 오버플로우가 일어나지 않는다.

그렇다고 Integer.MAX_VALUE부분을 Long.MAX_VALUE로 고치면 똑같은 오류가 날것임은 기억하자.

오류 4. 잘못된 변수 사용 오류 찾기

"사과" 라는 값을 저장한 apple 변수를 출력해야 하는데
"청산가리" 라는 값을 저장한 kcn 변수를 출력하는 경우를 예로 들 수 있다.

예상하듯 이상한 문장이 만들어질 것이고, 논리적 오류를 발생시킨다.

	System.out.println("철수는 " + kcn + "가 들어간 음식을 좋아해!");
    // 철수는 청산가리가 들어간 음식을 좋아해!

이렇게 흔히 발생하는 오류 4가지의 경우를 알아봤는데 여기서 가장 흔하게 일어나는 오류는 3번으로 int형의 변수는 -21억 ~ 21억 사이의 값을 보통 저장하는데, 총 42억 범위로 넓어보이지만 실제로는 오버플로우 하는 경우가 흔히 생긴다.

따라서 코딩 테스트 등 변수의 크기에 큰 제한이 없는 경우는 애초에 long형으로 변수를 만들어 해당 변수에 대입하는 수만 int형과 비슷하게 넣자.

0개의 댓글