복사해서 붙여 넣으면 끝!

중복 코드가 나쁘다는 것은 익히 알고 있지만 문제는 중복 코드가 자주 나타난다는 점이다. 여러가지 이유가 있겠지만 가장 큰 이유는 복사해서 붙여 넣기가 너무 쉽기 때문이다. 조금 다른 코드를 작성하는 경우에도 복사해서 붙여 넣고 일부만 수정해 주면 끝이다. 즉, 중복을 만드는 것은 매우 쉬운 일인데 상대적으로 중복을 없애기는 어렵다. 중복은 머리가 없어도 만들 수 있지만 중복을 없애는 것은 머리가 없이 할 수 없는 일이다.


중복에 대한 인식

중복에 대해서 크게 문제 삼지 않는 경우이다. 중복 코드에 대한 인식 문제는 프로그래밍에 대한 자세와 연관이 깊다. 코드를 만들고 돌아가면 끝이고 다시는 그 코드를 들여다 보고 싶지 않은 개발자들이 보통 이런 사고를 가지고 있다.


객체지향 설계 및 디자인 패턴에 대한 지식 부재

요컨데 중복 코드를 제거하는 도구와 정형화 된 방법들에 대한 이해가 부족하기 때문이다. 객체지향의 겉모습은 온톨로지(Ontology)를 기반으로 한 실제 세계에 대한 인식을 컴퓨터 상에 모사한 것으로 보이지만, 깊숙히 들여다 보면 중복을 효율적으로 제거하는 도구들로 채워져 있다. 문제는 객체지향을 이해하는 것보다 코드를 만드는 일이 더 선행된다는 점이다. 즉, 중복을 제거할 수 있는 도구는 모른체 코드를 만들기 시작한다. 당연히 코드는 중복으로 넘쳐날 수 밖에 없다.


발견하기 어려운 경우

중복 코드가 너무 멀리 있는 경우, 중복 코드를 만든지 너무 오래된 경우, 중복 코드가 너무 짧은 경우, 중복 코드가 다른 코드에 뭍혀 있는 경우, 중복 코드 블럭 중간에 다른 코드가 들어가 있는 경우 등이다. 아래 코드를 보자.

class Example{

    private int data;

    public Example(int data){

        this.data = data; // 중복

    }

    public void setData(int data){

        this.data = data; // 중복

    }

}

이 코드는 중복이 있다. 아래와 같이 고쳐 줘야 한다.

class Example{

    private int data;

    public Example(int data){

        setData(data); // 중복 제거

    }

    public void setData(int data){

        this.data = data;

        /* data 갱신시 수행할 일들 추가 */

    }

}

얼핏 보면 우스운 일이다. 단 한 줄의 코드고, 직접 수행하던 코드를 함수까지 써가면서 수정했다. 코드는 줄지도 않았고 오히려 함수 콜에 의한 연산만 증가했다. 하지만 기존의 코드는 명백한 중복 코드이다. 만약 data 값이 변경 되었을 때 해야 할 일이 생겼다면 어떻게 할 것인가? 기존의 코드에서는 생성자와 setter 함수 모두 그 일을 수행하도록 변경해야 할 것이다. 하지만 아래 코드에서는 setData() 함수 내부만 수정해 주면 된다. 코드는 한 줄 바뀌었지만 "data가 갱신 되었을 경우 해야 할 일"에 대한 코드가 들어가야 할 위치가 setter 함수 쪽으로 단일화 되었다. 단 한 줄의 코드가 중복인 경우라도 수정에 닫혀 있지 않다면 중복이다. 그리고 중복을 해결하면 코드가 짧아질 것이라는 선입견을 버려야 한다. 중복 코드를 없애는 것은 코드를 짧게 줄이는 것이 아니고 중복 코드가 발생시킬 수 있는 문제를 차단하는 것이다.


다 똑같은데 일부만 다른 경우

대표적인 예가 순회(방문) 코드일 것이다. 즉, 여러 객체들을 방문해서 어떤 작업을 수행하는 코드이다. 이 때 방문 코드가 매우 길어지면 상대적으로 방문해서 할 작업 코드는 짧아진다. 그러면 다른 작업이 추가되면 방문 코드는 복사해서 붙여 넣게 된다. 이런 형태의 코드들은 발견해도 바로 수정하기는 어렵다.


다른 코드와 섞여 있는 경우

중복 코드가 블럭 A와 블럭 B의 연속이라고 하자. 그리고 이 중복 코드가 두 군데 이상 존재하는데, 어느 한 쪽에서 블럭 A와 블럭 B 사이에 흐름과 관계 없는 코드를 집어 넣었다고 가정하자. 이런 경우에 중복 코드를 발견해 내기가 어려울 수 있다. 그리고 상황에 따라서는 삽입된 코드가 중복 코드들과 유사하거나 어떤 영향이 있는지를 알기 힘들어서 분리해 내기가 어려울 수도 있다.

List<String> list1 = new ArrayList<String>();

List<String> list2 = new ArrayList<String>();

List<String> list3 = new ArrayList<String>();

public void example(){

    for(int i = 0; i < list1.size(); i++){

        /* list1에 대한 연산 */

        /* list1 + list2 + list3에 대한 연산 */

    }

} 

list1에 대한 연산이 중복 코드일 경우, 아래에 있는 코드와의 관계를 재빨리 파악하기는 힘들다. 왜냐하면 둘 모두 같은 for 문에 묶여 있기 때문이다. 이 경우 코드를 유심히 들여다 보지 않고는 중복 코드가 있는지 발견하기 어렵다.


매개가 필요한 경우

이 경우가 해결하기 어려운 문제 중 하나이다. 분명 코드는 중복인 것처럼 보이지만 다들 조금씩 다르고 해결하기에는 쉬워 보이지 않는다. 아래 코드를 보자.

class Boundary{

    int northLimit = 100;

    int southLimit = 50;

    int eastLimit = 20;

    int westLimit = 10;

   

    public int getNorthLimit() { return northLimit; }

    public int getSouthLimit() { return southLimit; }

    public int getEastLimit() { return eastLimit; }

    public int getWestLimit() { return westLimit; }

    public void setNorthLimit(int northLimit) { this.northLimit = northLimit; }

    public void setSouthLimit(int southLimit) { this.southLimit = southLimit; }

    public void setEastLimit(int eastLimit) { this.eastLimit = eastLimit; }

    public void setWestLimit(int westLimit) { this.westLimit = westLimit; }

}

각 데이터들에 대해서 반복적인 패턴이 나타난다. 즉, 데이터 선언과 getter / setter 선언이 그것이다. 이러한 중복은 얼핏 해결이 불가능해 보인다. 완벽하게 동일한 코드가 아니고 유사한 코드들의 나열이기 때문이다. 이것을 해결하기 위해서는 매개체가 필요하다.

enum Direction{

    NORTH,

    SOUTH,

    EAST,

    WEST

    ;

    public static int size(){ return values().length; }

}

class Boundary{

    int[]boundaries = new int[Direction.size()];

    public int getLimit(Direction direction) { return boundaries[direction.ordinal()]; }

    public void setLimit(Direction direction, int limit) { boundaries[direction.ordinal()] = limit; }

}

이것이 중복의 해결책인 이유는 다음과 같다. 만약 Direction이 추가되었을 경우, Boundary 클래스는 수정이 전혀 필요하지 않게 된다. 기존의 코드에서는 새로운 데이터가 추가될 때 getter/setter가 추가 되어야 했다는 점을 주목하자. 이렇게 매개체가 필요한 형태의 코드들은 중복을 발견해 내기가 어렵다.

Posted by 이세영2
,