메서드 추출(extract method)은 왜 하는 걸까요?

메서드 추출 리팩토링

“굳이 이 코드를 메서드로 만들어야 하나요? 이러면 유지 보수가 더 힘들 것 같은데요…”

개발자, 나귀찬 씨

이러한 질문에 우리는 어떻게 답해야 할까요? 저의 경험 상 작은 크기의 메서드를 만드는 것을 꺼리는 개발자들이 의외로 많은 것 같습니다. 오히려 유지 보수에 방해만 된다고 생각하기도 하구요. (정말 그럴 수도 있습니다) 그래서 하나의 메서드가 너무 많은 일을 하고 있는 경우가 많죠. 오늘은 메서드 추출의 장점들 중 가독성 향상에 대해 집중적으로 살펴보겠습니다.

메서드의 크기는 어느 정도가 되면 적당한 걸까요? 메서드 이름으로 코드를 더 잘 설명할 수 있다면, 비록 한 줄의 코드일지라도 메서드를 만드는 것이 좋다고 생각합니다. 그만큼 좋은 이름을 짓는 것 또한 중요한 일이겠지요.

하지만 무엇이든 너무 과한건 좋지 않습니다. 여러 관점으로 봤을 때 수행하려고 하는 메서드 추출 작업이 주는 이점이 확실히 있는지 잘 생각해보고 적용하는 지혜가 필요합니다.

리팩토링이 필요한 메서드

우선, 여러 가지 일을 하고 있는 큰 메서드는 어떻게 생겼는지 볼까요? 👀
이해를 돕기 위해 알고리즘 문제 답안 중 하나를 가져오겠습니다.

2021 KAKAO BLIND RECRUITMENT > 합승 택시 요금 – 답안

class Solution {
    public int solution(int n, int s, int a, int b, int[][] fares) {
        int[][] arr = new int[n+1][n+1];
        // 1
        for(int i=1; i<n+1; i++) {
            for(int j=1; j<n+1; j++) {
                arr[i][j] = 1000001;
                if(i == j) {
                    arr[i][j] = 0;
                }
            }
        }
        // 2
        for(int i=0; i<fares.length; i++) {
            int c = fares[i][0];
            int d = fares[i][1];
            int f = fares[i][2];
            arr[c][d] = f;
            arr[d][c] = f;
        }
        // 3
        for(int k=1; k<n+1; k++) {
            for(int i=1; i<n+1; i++) {
                for(int j=1; j<n+1; j++) {
                    if(arr[i][j] > arr[i][k] + arr[k][j]) {
                        arr[i][j] = arr[i][k] + arr[k][j];
                    }
                }
            }
        }
        // 4
        int answer = 987654321;
        for(int i=1; i<n+1; i++) {
            int fare = 0;
            int secondStart = i;
            fare += arr[s][secondStart];
            fare += arr[secondStart][a];
            fare += arr[secondStart][b];
            if(answer > fare) {
                answer = fare;
            }
        }
        return answer;
    }
}

Solution 클래스의 solution 메서드를 봅시다. 이 메서드가 무슨 일을 하고 있는지 알아내려면 우리는 코드 전체를 분석해야만 합니다. 만약 solution 메서드에서 버그가 발생한다면 버그의 원인을 찾기위해 코드의 흐름을 모두 파악하고 어떤 변수가 어느 범위까지 영향을 끼치고 있는지 파악해야 하는데, 이러한 작업은 생각보다 많은 시간이 소요됩니다. 본인이 작성한 지 얼마 되지 않은 코드라면 금방 알 수 있겠지만, 자신이 작성한 코드라 하더라도 시간이 흐른 뒤에 보면 쉽게 이해하기 힘든 것이 사실입니다. 요구사항은 변경되기 마련이고 이에 따라 소프트웨어는 계속해서 변화합니다. 그래서 우리는 읽기 좋고 수정하기 쉬운 코드를 작성해야 합니다.

코드 분석

solution 메서드는 크게 네 부분으로 나눌 수 있습니다.

  1. 원하는 일을 수행하기 위해 2차원 배열에 필요한 값을 초기화한다.
  2. 초기화된 2차원 배열에 fares 배열의 값을 덮어 써서 각 정점 사이의 비용을 표현한다.
  3. 각 정점 사이의 최소 비용을 구한다.
  4. 문제에서 요구한 모든 경로의 비용을 계산해보고, 요구한 정답을 구한다.

이렇게 하는 일을 명확하게 분리할 수 있는 경우에는 메서드로 분리하기가 쉽습니다. 그리고 메서드에 적절한 이름을 붙여주면 코드 분석 시간이 단축되고, 각 부분의 코드의 영향 범위를 메서드 내로 한정 지을 수 있어 필요한 부분만 집중적으로 분석해서 원인을 해결할 수 있게 됩니다.

메서드 추출(extract method)적용 예시

각 부분을 메서드로 추출하면 어떤 모습이 될까요? 👀

class Solution {
    public int solution(int n, int s, int a, int b, int[][] fares) {
        int[][] arr = new int[n+1][n+1];
        // 1
        initArray(n, arr);
        // 2
        setFares(arr, fares);
        // 3
        computeAllMinimumFares(n, arr);
        // 4
        return findAnswer(n, s, a, b, arr);
    }
    //함수 정의 생략
}

위와 같이 각 부분이 메서드로 분리되어 있으면 우리는 solution 메서드가 어떤 일들을 하고 있는지 한 눈에 볼 수 있게 됩니다. 이제 solution 메서드에서 버그가 발생했을 때, 쉽게 흐름을 파악하고 문제를 해결하기 위해 필요한 부분을 집중적으로 살펴볼 수 있습니다. 큰 메서드 하나를 작은 여러 개의 메서드로 분리하는 일은 클래스를 분리하는 등의 더 높은 수준의 리팩터링을 수행할 때 선행되어야 할 기초적인 작업이기도 합니다. 메서드가 작으면 테스트 코드를 작성할 때에도 명확히 한 가지의 기능을 테스트 할 수 있게 되고, 불필요한 작업들을 줄일 수 있게 됩니다. 단, 메서드의 이름을 잘못 지으면 오히려 혼란을 줄 수 있으므로 충분히 고민해서 이름을 지어야 합니다.

마무리

요즘 IDE는 똑똑하게도 여러 가지 리팩터링 기능들을 제공해주고 있기 때문에 메서드를 추출하는 일이 전혀 어렵지 않습니다. 조금의 시간만 들이면 단축키를 통해 메서드를 추출하는 방법을 배울 수 있습니다.

자, 오늘부터 적절한 크기의 메서드를 만드는 습관을 들여 유지 보수하기 좋은 코드를 작성하는 개발자로 한 걸음 나아가 보는 건 어떨까요?

스스로 경험하며 얻은 깨달음을 공유하기 좋아하며, 세상이 필요로 하는 코드를 작성하기 위해 노력하는 개발자입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다