메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

리팩토링(5) - 리팩토링 맛보기

한빛미디어

|

2005-08-26

|

by HANBIT

13,351

저자: 임백준
출처: 임백준의 소프트웨어 산책(2005, 한빛미디어) 중 제3장 리팩토링



* 리팩토링(1) - 과거와 대결하는 프로그래머의 무기
* 리팩토링(2) - 복잡성에 대한 두려움
* 리팩토링(3) - 단순함의 미학
* 리팩토링(4) - 리팩토링의 탄생

필자의 책장에 꽂혀있는 1999년 판 <리팩토링> 원서에는 대략 70여 개의 리팩토링 기법이 소개되어 있다. 그 후로 많은 시간이 지난 지금까지 전 세계의 프로그래머들은 다양한 실전 경험을 통해서 새롭고 신선한 리팩토링 기법을 더 많이 발굴했다. 그렇게 많은 기법을 일일이 설명하는 것은 가능하지도 않지만 이 책의 목적과 맞지 않다. 따라서 여기에서는 여러 가지 기법 중에서 이해하기 가장 쉬우면서 실전 프로그래밍에서의 효과도 큰 ‘메쏘드 추출(Method Extraction)’ 기법을 선택해서 가볍게 살펴보도록 하겠다. 이 글의 목적은 여러분에게 리팩토링 기법을 설명하려는 것이 아니기 때문에 리팩토링을 구체적으로 익히고 싶은 사람이라면 이 글이 아니라 <리팩토링>을 읽어야 할 것이다.

파울러도 말했다시피 리팩토링 기법을 설명할 때 너무 쉽고 간단한 예를 드는 것은 리팩토링이 필요한 이유를 설득력 있게 보여줄 수 없어서 곤란하다. 하지만 그렇다고 해서 너무 복잡한 예를 사용하면 본래의 의도보다 예제 프로그램의 내용을 이해하는데 정력을 빼앗기게 되므로 곤란하다. 따라서 예제는 그 중간의 어느 지점에서 잘 선택되어야 하는데, 이 책의 목적상 여기에서는 이해하기 쉬운 코드 쪽에 무게를 두었다. 간단한 예를 보여주기 위해서는 파울러가 <리팩토링>에서 사용한 비디오 대여점의 대여 가격을 산출하는 코드가 좋긴 하지만 그대로 사용할 수는 없으므로, 간단한 주식 브로커(stock broker) 코드를 작성해 보았다.

프로그래밍에 입문하는 독자를 고려해서 코드는 일부분이 아니라 필자가 컴파일하고 테스트할 때 사용한 전체 내용을 실어 놓았다. 컴파일과 실행은 윈도우XP PC에서 JDK 1.5.0을 이용했다. 다시 말하지만 이 글의 목적은 리팩토링을 ‘공부’하자는 것이 아니므로 너무 진지해질 필요는 없을 것이다. 불필요한 부분은 건너뛰고 주로 Broker 객체의 getPrice 메쏘드를 읽어보기 바란다.

프로그램을 실행하는 main 함수는 Broker 객체를 생성한 다음, 루슨트(LU)와 노텔(NT)의 주식시세를 화면에 출력하기 위해서 Broker 객체에게 현재의 주식 값을 물어본다. Broker 객체의 생성자(constructor)를 호출할 때 자신이 주식의 매수자(buyer)인지 매도자(seller)인지를 밝히고 있는데, Broker 객체는 해당 주식의 현재 시세를 계산할 때 사용자가 매수자인지 매도자인지에 따라서 적절한 프리미엄(premium)을 적용해서 가격을 조절한다.

이 예제에서 Broker 객체가 가격을 조절하도록 한 부분, 즉 매수자에게는 가격을 약간 높여서 부르고 매도자에게는 가격을 약간 낮춰서 부르도록 한 것은 반드시 실제의 관행이 그래서가 아니라 알고리즘을 약간 ‘지저분하게’ 만들기 위해서이다. 알고리즘에 아무런 얼룩이 없으면 리팩토링의 필요성이 줄어들기 때문에 일부러 그렇게 했다. 어쨌든 ‘브로커’라고 불리는 사람들은 주식을 사거나 파는 고객의 주문을 ‘고객의 입장에서’ 성실하게 수행하는 대가로 일정한 수수료를 받는 사람들이므로 고객에게 조작된 시세를 알려주는 방식으로 수익을 올리지는 않을 것이다.

하지만 현실은 꼭 그렇지도 않다. 미국의 월스트리스(wall street)에서는 마쉬(Marsh & McLennan)라는 보험 브로커 회사가 보험을 사려는 고객들에게는 최선의 보험을 찾아주는 대가로 수수료를 받고, 보험을 파는 대형 회사들로부터는 (가격이나 보험약관에 상관없이) 그 회사의 보험을 팔아주는 대가로 어마어마한 규모의 뒷돈(kickback)을 받아 챙기다가 적발되었다. 다시 말해서 그들은 자신의 이익을 위해서 고객을 속인 것이다. 이와 비슷한 예는 마쉬에 국한되지 않는다. 골드만삭스나 JP 모건 같은 큰 금융회사의 직원들이 회사의 이익을 위해서 고객에게 잘못된 정보를 흘리다가 적발되는 것은 어제오늘의 일이 아니다. 어느 나라보다도 투명한 시스템을 자랑으로 내세우는 미국 자본주의이지만, 내면의 실상은 그리 자랑할 만한 모습이 못되는 것이 사실이다. 우리나라의 모습은 어떤지 궁금하다.


public class Test 
{
    public static void main (String[] args) 
    {
        Broker myBroker = new Broker (Broker.SELLER);
        System.out.println ("Lucent stock price: " + myBroker.getPrice("LU"));
        System.out.println ("Nortel stock price: " + myBroker.getPrice("NT"));
    }
}

class Broker 
{
    private String[] myStockList = {"LU", "NT", "CSCO", "DELL", "INTC"};
    private final double SELLER_PREMIUM = 0.98;
    private final double BUYER_PREMIUM  = 1.02;
    public  static final int SELLER     = 1; 
    public  static final int BUYER      = 2; 
    private int clientType;

    public Broker (int clientType)
    {
        this.clientType = clientType; 
    }

    public double getPrice (String symbol)
    {
        double price = 0;
        Stock stock = StockFactory.getStock (symbol);

        boolean isValidStock = false;
        for (int i = 0; i < myStockList.length; i++)
        {
            if (myStockList[i].equals (stock.getSymbol()))
            {
                isValidStock = true;
                break;
            }
        }

        if (!isValidStock)
        {
            return -1;
        }

        if (stock.isActive())
        {
            if (stock.isPremium())
            {
                if (this.clientType == SELLER)
                {
                    price = Market.getPrice (symbol) * SELLER_PREMIUM;
                }
                else
                {
                    price = Market.getPrice (symbol) * BUYER_PREMIUM;
                }
            }
            else
            {
                price = Market.getPrice (symbol);
            }
        }

        return price;
    }    
}

class StockFactory
{
    private static HashMap stockMap = new HashMap ();

    public static Stock getStock (String symbol) 
    {
        if (stockMap.containsKey(symbol))
        {
            return (Stock) stockMap.get(symbol);
        }           

        Stock stock = new Stock (symbol);
        stockMap.put (symbol, stock); 
        return stock;
    } 
}

class Stock 
{
    private String symbol;
    private boolean isPremium = true;

    public Stock (String symbol)
    {
        this.symbol = symbol;
    }

    public boolean isActive ()
    {
        return true;
    }

    public boolean isPremium ()
    {
        return isPremium;
    }

    public String getSymbol ()
    {
        return symbol;
    }

    public void setPremium (boolean isPremium)
    {
        this.isPremium = isPremium;
    }
}

class Market 
{
    public static double getPrice (String symbol)
    {
        if ("LU".equals(symbol))
        {
            return 80.0;
        }
        else if ("NT".equals(symbol))
        {
            return 45.0;
        }
        return 0.0;
    }
}


이 코드의 전체 내용을 Test.java라는 이름의 파일에 저장한 다음 컴파일 한다.


    javac Test.java


그리고 자바 명령어를 실행하면 화면에 출력되는 결과를 확인할 수 있다.


    java Test


이 예제에서 Market 객체는 미리 하드코드(hardcoded)된 가격을 리턴하고 있는데, 실제 프로그램이라면 네트워크나 데이터베이스를 이용해서 실시간으로 조회한 주식 가격을 리턴 할 것이다. 간단한 예를 제공하는 것이 목적이므로 여기에서는 프로그램의 성능, 예외 상황 처리, 멀티쓰레딩과 같은 측면에 대해서 아무런 고려를 하지 않았다. 예제를 읽으면서 그와 같은 ‘샛길’에 빠지지 않기를 당부한다. 이제 getPrice 메쏘드를 살펴보자. 워낙 간단한 예이기 때문에 이 메쏘드가 수행하는 일을 파악하는 것이 어렵진 않을 것이다. getPrice 메쏘드가 수행하는 업무를 정리해 보면 다음과 같은 몇 가지로 요약된다.

a. 인수로 전달된 symbol 문자열을 이용해서 StockFactory로부터 Stock 객체를 얻는다.

b. 주어진 symbol이 자기가 관리하는 주식 리스트에 포함되어 있는지 확인한다. 만약 포함되어 있지 않으면 -1을 리턴하고, 포함되어 있으면 계속 진행한다.

c. 주식이 현재 시장에서 거래되고 있는지 확인한다. (stock.isActive())

d. 주식이 프리미움 부과 대상인지 확인한다. (stock.isPremium())

e. 가격을 요청한 클라이언트가 매수자인지 매도자인지 확인한다.

f. 위의 세 조건에 따라서 Market 객체로부터 얻은 가격에 프리미움을 적용하거나 혹은 적용하지 않고 최종 가격을 산출해서 리턴 한다.

메쏘드가 언제나 하나의 구체적인 업무만 수행하도록 작성하고, 메쏘드의 이름이 업무의 내용을 잘 드러내도록 붙이는 습관만 들여도 그런 프로그래머가 작성한 코드는 보기에 훨씬 좋게 된다. 위에서 분류한 6개의 항목 중에서 getPrice라는 메쏘드 이름과 직접적으로 관련된 내용은 마지막에 있는 f 하나일 뿐이다. 다시 말해서 getPrice 메쏘드는 이름이 드러내지 않는 다른 여러 가지 일들을 하고 있기 때문에 커니건과 파이크가 말한 ‘간결성’의 원칙을 위배하고 있다.

프로그램은 대개 처음에는 간결하게 시작된다. 하지만 마치 때 묻지 않은 순수한 영혼이 세파에 시달리면서 점점 강퍅하게 변해 나가는 것처럼 간결한 프로그램은 여러 프로그래머의 손을 거치면서 할 일이 많아지고, 조건이 까다로워지고, 분량이 늘어난다. 메마른 영혼으로 살아가던 사람이 어떤 계기를 맞이해서 깊은 반성과 자기 성찰을 하게 되는 것처럼 담쟁이덩굴 같은 알고리즘으로 몸이 칭칭 둘러싸인 프로그램은 어느 순간 리팩토링을 절실하게 필요로 하게 된다. 그때 리팩토링의 세례를 받지 못하는 프로그램은 오래 살지 못하고 시들어 죽는다.
TAG :
댓글 입력
자료실

최근 본 상품0