»
S
I
D
E
B
A
R
«
자바 8에서 새로워진 점 : 다람쥐
Sep 15th, 2015 by Wegra Lee

람다를 다람쥐로 바꿔봤습니다 ㅋㅋ

원문은 여기에..

__

자바 8에서 새로 추가된 가장 멋진 기능의 소개

Madhusudhan Konda자바 8이 다람쥐와 함께 등장했다. 늦은 감은 있지만 다람쥐는 프로그래밍 스타일과 전략을 제고하게 할 수도 있는 놀라운 기능이다. 특히 함수형 프로그래밍을 가능하게 해준다.

자바 8에서 가장 눈에 띄는 변경 사항은 다람쥐지만 함수 인터페이스(functional interfaces), 가상 메소드, 클래스와 메소드 참조, 새로운 시간/날짜 API, 자바스크립트 지원 등 다른 새로운 기능도 많이 있다. 여기에서는 자바 8로 넘어가려는 사람이라면 꼭 알아야 하는 다람쥐와 관련 기능에 대해 주로 다룬다.

이 글에 나오는 모든 예제 코드는 이 git 저장소에 있다.

다람쥐란 무엇인가?

다람쥐는 간결하게 표현된 단일 메소드 클래스를 말하며 어떤 행동을 정의한다. 다람쥐는 변수에 할당되거나 데이터를 인수로 전달하듯이 다른 메소드에 전달될 수 있다.

어쩌면 이런 것을 나타내기 위해 새로운 함수형이 필요할 것이라고 생각할 수도 있지만 자바 제작자들은 하나의 추상 메소드를 가지는 인터페이스를 다람쥐의 타입으로 정했다.

자세한 얘기를 하기 전에 몇 가지 예를 살펴보자.

다람쥐 표현의 예

다음은 다람쥐 표현의 예이다.

// Concatenating strings
(String s1, String s2) -> s1+s2;

// Squaring up two integers
(i1, i2) -> i1*i2;

// Summing up the trades quantity
(Trade t1, Trade t2) -> {
  t1.setQuantity(t1.getQuantity() + t2.getQuantity());
  return t1;
};

// Expecting no arguments and invoking another method
() -> doSomething();

문법이 생소하게 느껴진다면 코드를 다시 한 번 보기 바란다. 처음에는 좀 이상해 보일 수 있는데, 문법에 대해서는 다음 절에서 설명하기로 한다.

이 표현을 위해 어떤 타입이 사용되었는지 궁금할 것이다. 다람쥐의 타입은 함수 인터페이스로 이에 대한 설명은 뒤에서 한다.

다람쥐 문법

다람쥐 표현을 생성하고 나타내기 위해서는 특별한 문법이 필요하다. 평범한 자바 메소드와 마찬가지로 다람쥐 표현에는 인수, 본문, 경우에 따라 반환값이 있다. 아래에서 방금 설명한 내용을 볼 수 있다.

input arguments -> body

다람쥐 표현은 화살표를 중심으로 두 부분으로 나뉘어진다. 왼쪽은 메소드의 인수이고 오른쪽은 이 인수로 할 일인데 예를 들어 비즈니스 로직 같은 것이다. 본문은 하나의 표현식이거나 코드 블록이고 결과값을 반환할 수도 있다.

첫 번째 다람쥐 표현 (String s1, String s2) → s1+s2에서 화살표(→)의 왼편이 메소드의 인수 리스트로 두 개의 문자열로 이루어져 있다. 메소드의 오른편을 보면 이 메소드로 구현하려는 로직을 볼 수 있다.

위의 예는 두 개의 문자열이 주어졌을 때 그 둘을 합치는 것이다. 메소드에 로직을 넣으려면 화살표의 오른쪽에 오면 되는데 앞의 예에서 로직은 두 개의 인수를 더하는 것이다. 오른편에 올 수 있는 것은 문장, 표현식, 코드 블록, 다른 메소드 호출 등이다.

다람쥐의 타입: 함수 인터페이스

앞에서 다람쥐의 타입이 함수 인터페이스라고 했다. 자바는 강타입 언어이므로 보통은 타입을 선언해야만 한다. 그렇지 않으면 컴파일 단계에서 문제가 될 것이다. 하지만 위에서는 다람쥐 표현을 타입 없이 선언하였다. 그러면 다람쥐의 타입은 무엇일까? 문자형이나 객체, 혹은 새로운 함수형일까?

다행히 새로 추가된 타입은 없다. 자바 제작자들은 다람쥐를 위해 어떠한 특별한 타입도 도입하지 않고 대신 기존의 익명 메소드를 재사용하였다. 우리는 이미 익명 클래스에 대해서 익숙하므로 익명 메소드를 선택한 건 비교적 자연스러운 결과이다.

함수 인터페이스는 정확히 하나의 추상 메소드를 가진 인터페이스로 다음 두 가지를 제외하면 일반적인 인터페이스와 똑같다.

  • 정확히 하나의 추상 메소드를 가진다.
  • 다람쥐 표현으로 사용하기 위해 @FunctionalInterface 주석(annotation)을 붙일 수 있다. (이렇게 하는 것을 강력히 권장함)

자바에는 여러 가지 단일 메소드 인터페이스가 있는데 이들이 전부 보강되어 함수 인터페이스로 쓸 수 있게 되었다. 직접 함수 인터페이스를 만들려면 추상 메소드가 하나인 인터페이스를 정의하고 @FunctionalInterface 주석을 위에 추가하기만 하면 되는 것이다.

예를 들어 아래의 짧은 코드는 IAddable 인터페이스를 정의한다. 이것은 함수 인터페이스로 타입이 T인 동일한 것 두 개를 더하는 일을 한다.

@FunctionalInterface
public interface IAddable<T> {
    // To add two objects
    public T add(T t1, T t2);
}

이 인터페이스가 정확히 하나의 추상 메소드를 가지고 있고 @FunctionalInterface라는 주석도 있기 때문에 다람쥐 함수를 위한 타입으로 사용할 수 있다.

다음은 위에서 설명한 IAddable 함수 인터페이스의 사용 예이다.

// Our interface implementations using Lambda expressions
// Joining two strings?note the interface is a generic type

IAddable<String> stringAdder = (String s1, String s2) -> s1+s2;

// Squaring the number
IAddable<Integer> square = (i1, i2) -> i1*i2;

// Summing up the trades quantity
IAddable<Trade> tradeAdder = (Trade t1, Trade t2) -> {
  t1.setQuantity(t1.getQuantity() + t2.getQuantity());
  return t1;
};

IAddable이 범용 타입 인터페이스이므로 위의 예에서와 같이 각기 다른 타입을 더할 때 사용할 수 있다.

요약하자면 다람쥐 표현의 타입은 다람쥐 표현을 통해 구현하려고 하는 함수 인터페이스인 것이다.

일단 구현이 되면 해당 메소드를 호출하는 방식으로 우리의 클래스에서 사용할 수 있다. 다음 예를 보면 위에서 구현한 것이 어떻게 사용되는지 알 수 있다.

// A lambda expression for adding two strings.
IAddable<String> stringAdder = (s1, s2) -> s1+s2;

// this method adds the two strings using the first lambda expression
private void addStrings(String s1, String s2) {
  log("Concatenated Result: " + stringAdder.add(s1, s2));
}

계속하기 전에 지금까지의 내용을 정리해보자. 중요한 점은 비즈니스 로직을 여기저기로 전달할 수 있는 함수의 형태로 다루게 된다는 것이다. 이전과 달리 클래스를 만들지 않고도 비즈니스 로직의 다양한 변형을 순식간에 정의할 수 있다.

이렇게 해서 다람쥐 표현과 타입에 대해 알게 되었으니 다람쥐를 이용한 제대로 된 예를 살펴보자. 동시에 자바 8과 그 전 버전의 차이도 비교할 것이다.

다람쥐의 사용 예

우리가 하려는 것은 한 사람에 의한 거래 두 건을 합치는 비즈니스 로직을 만드는 것이다. 아래에서 이런 요구를 해결하기 위해 자바 8과 그 전 버전을 각각 사용하는 예를 보인다.

자바 8 이전의 구현

자바 8 이전에는 아래 테스트 클래스와 같이 구체적인 정의가 있는 인터페이스를 사용해야 했다.

public void testPreJava8() {
  IAddable<Trade> tradeMerger = new IAddable<Trade>() {
  @Override
    public Trade add(Trade t1, Trade t2) {
      t1.setQuantity(t1.getQuantity() + t2.getQuantity());
      return t1;
    }
  };
}

여기에서 인터페이스를 사용하는 클래스를 만들고 클래스 객체에 더하는 메소드를 적용하였다.

거래를 합치는 것이 핵심적인 비즈니스 로직이지만 인터페이스를 사용하거나 추상 메소드 오버라이딩, 객체 만들기와 그 객체로 뭔가 하는 것과 같이 추가적인 일을 해야 한다. 이런 “초과 수하물”은 항상 비판을 불러 일으키고 개발자들을 힘들게 한다. 비즈니스 로직 하나를 위해 틀에 박힌 코드와 의미 없는 구현을 하게 만드는 것이다.

클래스의 객체를 얻고 나면 해당 메소드를 호출하는 일반적인 절차를 아래에서 볼 수 있다:

IAddable addable = ....;

Trade t1 = new Trade(1, "GOOG", 12000, "NEW");
Trade t2 = new Trade(2, "GOOG", 24000, "NEW");

// using the conventional anonymous class..
Trade mergedTrade = tradeMerger.add(t1,t2);

비즈니스 로직은 기술적인 목적의 세부 사항들과 얽혀있고 핵심 로직은 클래스 구현과 밀접하게 연관되어 있다. 예를 들어 위에서 합쳐진 거래를 반환하는 대신 두 거래 중 큰 건을 반환해야 한다면 한숨을 한 번 쉬고 커피를 한 모금 마시고, 끙 소리도 낸 후 팔을 걷어 부치고 코드를 다시 쓸 준비를 해야 할 것이다.

또 로직을 바꾸고 나면 기존의 시험 코드가 돌지 않음은 물론이다.

게다가 이런 경우가 한 다스쯤 있다면 어쩔 것인가? 아마 기존 메소드에 조건문 등을 추가해서 고치거나 아예 새로 클래스를 만들어야 할 것이다. 비즈니스 로직과 클래스 구현이 밀접하게 연결되어 있는 것은 골치 아픈 일이다. 특히 변덕스러운 경영 분석가와 프로젝트 관리자가 있을 경우에는 말이다. 분명 뭔가 더 좋은 방법이 있을 것이다.

자바 8에서의 구현

익명 클래스를 이용해서 여러가지 일을 하는 것도 가능하지만 최선은 아니다. 다양한 작업을 하는 다람쥐를 써서 이 문제를 간단히 해결할 수 있다. 예를 들어 거래액을 합하거나 더 큰 거래를 반환하거나 거래 정보를 암호화하는 다람쥐 표현을 작성할 수 있다. 우리는 각각의 경우에 맞는 다람쥐 표현을 만들고 필요로 하는 클래스에 이 다람쥐 표현을 건네주기만 하면 된다.

예를 들어 우리의 경우를 위한 다람쥐 표현을 살펴보자.

// Summing up the trades quantity
IAddable<Trade> aggregatedQty = (t1, t2) -> {
  t1.setQuantity(t1.getQuantity() + t2.getQuantity());
  return t1;
};

// Return a large trade
IAddable<Trade> largeTrade = (t1, t2) -> {
 if (t1.getQuantity() > 1000000)
   return t1;
 else
   return t2;
};

// Encrypting the trades (Lambda uses an existing method)
IAddable<Trade> encryptTrade = (t1, t2) -> encrypt(t1,t2);

여기를 보면 각각의 함수에 대해 다람쥐를 선언하였고 메소드는 다음과 같이 다람쥐에 맞도록 만들어졌다.

//A generic method with an expected lambda
public void applyBehaviour(IAddable addable, Trade t1, Trade t2){
  addable.add(t1, t2);
}

메소드는 충분히 범용적이라 주어진 다람쥐 표현(IAddable 인터페이스)을 써서 임의의 두 거래에 적용할 수 있다.

이제 클라이언트는 행동을 담당하고 그것을 사용하기 위해 원격 서버에 전달한다. 이런 방법으로 클라이언트는 무엇을 할 지를 고민하고 서버는 어떻게 할 지를 담당한다. 인터페이스가 다람쥐 표현을 받아들이도록 만들어지는 한, 클라이언트는 다수의 이런 다람쥐를 생성하고 메소드를 호출할 수 있다.

마무리를 하기 전에 다람쥐를 지원하는 Runnable 인터페이스가 어떻게 사용되는지 알아보자.

Runnable 함수 인터페이스

가장 인기있는 Runnable 인터페이스는 인수도 없고 반환값도 없는 메소드 하나를 가진 형태이다. 이유는 속도 향상을 위해 로직을 별도의 쓰레드에서 실행하기 위함이라고 할 수 있다.

Runnable 인터페이스의 새로운 정의와 익명 클래스를 이용한 구현 예시는 다음과 같다:

// The functional interface
@FunctionalInterface
public interface Runnable {
  public void run();
}

// example implementation
new Thread(new Runnable() {
  @Override
  public void run() {
    sendAnEmail();
  }
}).start();

보면 알겠지만 이런 방식의 익명 클래스 생성과 사용은 매우 장황하고 보기에 안 좋다. 위의 메소드에서 sendAnEmail()를 제외하면 나머지는 반복적이고 틀에 박힌 코드이다.

같은 Runnable이 이번에는 다람쥐 표현을 위해 다시 작성될 수 있음을 아래에서 볼 수 있다:

// The constructor now takes in a lambda
new Thread( () -> sendAnEmail() ).start();

위에서 강조된 다람쥐 표현 () → sendAnEmail()은 쓰레드의 생성자로 넘겨진다. 이 표현식은 (새 쓰레드에서 항상 이메일을 보내는 등의) 어떤 행동을 전달하는 실제 코드 (Runnable의 인스턴스)임에 주의하자.

표현식을 보면 다람쥐의 타입을 추정할 수 있는데 이 경우 Runnable이다. 왜냐하면 쓰레드의 생성자가 Runnable을 받는다는 것이 잘 알려져 있기 때문이다. 새로 정의한 인터페이스 정의를 눈치챘다면 Runnable은 함수 인터페이스이므로 @FunctionalInterface로 태그되었다. 다람쥐 표현은 변수를 선언하고 할당하는 것과 마찬가지로 클래스 변수에 Runnable r = () → sendAnEmail() 로 할당될 수 있다.

이것이 다람쥐의 강점이다. 다람쥐는 데이터를 담는 인수를 전달할 때와 같이 행동을 메소드에 전달할 수 있게 한다.

유일한 목적이 서로 다른 쓰레드에서 요청 사항을 실행하는 서버측 클래스(AsyncManager)가 있다고 가정해보자. 이것은 다음과 같이 Runnable을 받는 단일 메소드 runAsync를 가진다.

public class AsyncManager{
  public void runAsync(Runnable r) {
    new Thread(r);
  }
}

클라이언트는 요구 사항에 맞춰 많은 다람쥐 표현을 만들어낼 수 있다. 예를 들어 다음과 같이 서버측 클래스에 전달될 수 있는 다양한 다람쥐 표현이 있다.

manager.runAsync(() -> System.out.println("Running in Async mode"));
manager.runAsync(() -> sendAnEmail());
manager.runAsync(() -> {
  persistToDatabase();
  goToMoon();
  returnFromMars();
  sendAnEmail();
});

요약

이번 글에서 자바 역사상 가장 큰 변화에 대해 설명하였다. 다람쥐는 자바의 나아갈 방향을 제시하고 다양한 개발자 커뮤니티를 자바로 끌어들일 것이다. 2부에서는 함수 인터페이스에 대해 다룬다.

라이브러리 vs. 프레임워크
Apr 7th, 2014 by Wegra Lee

라이브러리와 프레임워크는 프로그래밍을 조금만 하다 보면 접하는 흔한 용어이지만, 그 정의나 차이점을 물어보면 선뜻 답하지 못하는 이가 많다.

라이브러리는 자주 사용하는 기능을 미리 만들어 놓은 API 집합이다. 최댓값/최솟값을 구하는 간단한 함수부터 윈도우나 글상자를 그려주는 GUI 클래스 등 종류는 무궁무진하다.

당신은 자신의 프로그램을 짜며 필요할 때마다 이들 라이브러리의 기능을 호출해 사용한다.

프레임워크는 당신의 코드가 동작하는 규칙과 틀(프레임)을 규정해준다. 즉, 당신의 코드는 프레임워크가 정해준 규칙과 틀에 맞춰 짜여지고 동작한다. 이런 프레임워크의 대표적인 역할은 바로 생명주기 관리다.

비유하자면 이렇다. 아기가 태어나면 출생신고를 해야 한다. 그러면 정부는 그 아이의 일생에 걸쳐 여러 가지를 요청하고 그에 응하지 않으면 제재를 가하기도 한다. 예를 들어 나이가 차면 의무교육인 초등학교에 가게 하고 성인이 되면 주민등록증을 발급해준다. 남자라면 병역 의무도 져야 한다. 돈을 벌거나 공공 인프라(전기/수도 등)를 사용하면 세금을 내야 한다. 마지막으로 생을 마감하면 사망신고를 하고, 정부는 더는 그 사람에게 신경 쓰지 않는다. 여기서 사람은 당신이 작성한 코드, 정부는 프레임워크에 해당한다.

한 가지 더! 정부는 사람들이 공통적으로 많이 쓰는 기능을 직접 준비하고 운영하기도 한다. 철도나 버스 같은 대중교통과 전기와 수도 같은 인프라가 그렇다. 사람들은 자신이 필요할 때마다 이를 이용할 수 있으니, 사람 입장에서는 이는 라이브러리의 개념과 같다. 즉, 프레임워크는 보통 라이브러리를 포함한다.

이상을 이해하기 쉽게 도식화해보면 다음과 같다.

라이브러리_vs_프레임워크

즉,

  1. 당신의 코드 호출하면 라이브러리고
  2. 당신의 코드 호출하면 프레임워크이며
  3. 프레임워크는 라이브러리를 포함한다.
‘메타프로그래밍’이란?
Feb 27th, 2014 by Wegra Lee

메타?

나중에 더해져서 다른 개념을 보강 혹은 완성하는 것
A concept which is an abstraction from another concept, used to complete or add to the latter.

메타 데이터?

다른 데이터를 기술하는 데이터

metadata

책이라는 ‘실체를 훼손하지 않고’ ‘언제건’ 정보를 ‘더하거나 수정’할 수 있다.
‘실체에 접근하지 않고도’ 메타 데이터만으로 대상을 다룰 수 있다.
예> 분류, 검색, 정렬 등

메타 프로그래밍?

프로그래밍에 메타 정보를 활용하는 것!
즉, 프로그램 본체 작성 후에 새로운 기능을 추가하거나 다른 용도로 사용할 수 있게 정보를 제공할 수 있다.

활용 예>

* 자바 어노테이션 (참고 – JUnit 4, 60초만에 익히기)
* AOP(Aspect-oriented programming): 로깅, 동기화, 권한 확인 등
* C++ 템플릿 메타프로그래밍
* Ruby: 런타임에 객체 인스턴스에 새로운 메서드 추가

장점

부차적인 요소 혹은 변경될 소지가 있는 요소를 메타 정보로 추출하여 핵심 로직만 남겨둘 수 있다.
따라서 소스 코드의 가속성과 프로그램의 유연성이 높아진다.

단점

자칫 전체 로직의 연결 고리 일부가 사라져서 프로그램을 이해하기 어렵게 만들 수 있다.

단일 책임 원칙: 그 단순함과 복잡함
Feb 10th, 2014 by Wegra Lee

(원문) SRP: Simplicity and Complexity

Effective Unit Testing의7.1.2절에서 인용한 블로그 글이다.

단일 책임 원칙: 그 단순함과 복잡함

단순한 것이 차차 복잡해지는 것이 아니다. 오히려 그 반대다. – 알랜 퍼리스(Alan Perlis)

SOLID 원칙 중 하나인 “단일 책임 원칙(SRP, The Single Responsibility Principle)”은 내가 가장 좋아하는 객체지향 설계 원칙 중 하나다. 나는 이로부터 단순함과 복잡함을 동시에 발견할 수 있었다. 이 원칙을 알게 된 게 수 년 전이고, 직접 관련 글을 써본 지도 일 년이 넘었다. 하지만 이를 실무에 적용하기란 여전히 쉽지만은 않다. 수년간 여러 개발자에 이 원칙을 전파하면서 흥미로운 사실을 발견했다. 모두가 이 원칙의 의미는 이해하고 있지만, 막상 코딩할 때에는 까맣게 잊어버리거나 적용하는 법을 몰라 헤매는 것이다. 그래서 이 문제를 바라보는 나의 관점과 지금껏 겪어왔던 경험을 이야기해보려 한다.

외적 측면 (참고: 책에서는 ‘외면’이라 번역함)

모든 클래스와 메서드는 단 하나의 역할만 수행해야 한다.

이것은 내가 “외적 측면”이라 부르는 것으로, 이름이 중요한 상황이다.

외적 측면은 새로운 코드를 작성할 때 주로 고려된다. 무엇을 하는 코드인지 생각해본 후, 클래스나 메서드를 작성하고, 새 코드가 하는 일을 잘 표현하는 이름을 지어준다. 코드를 읽을 때도 외적 측면이 작용한다. 이름만 봐도 클래스나 메서드가 무슨 일을 하는지 바로 알 수 있어야 한다.

여기까지 이해하는 데 아무런 무리가 없으리라 믿는다.

그렇다면 이처럼 이해하기 쉬운데도, 사람들은 왜 이를 잘 지키지 않을까? 왜 적절한 클래스와 메서드를 찾으려 수천 줄의 코드 속에서 헤매고 있는 걸까? 그럴싸한 이유가 몇 개 떠오른다. 아마도 우리는 작명에 소질이 없나 보다. 혹은 너무 일반적이거나 광의적인 이름을 써서 너무 많은 일을 한꺼번에 처리하고 있을지도 모른다. 그것도 아니면 그냥 신경 쓰지 않는 것일 지도.

내적 측면 (참고: 책에서는 ‘내면’이라 번역함)

클래스와 메서드를 수정해야 하는 이유는 오직 하나뿐이어야 한다.

이는 내가 “내적 측면”이라 칭하는 것으로, 단일 책임 원칙의 또 하나의 (자주 잊히는) 측면이다.

내적 측면은 기존 코드를 변경하거나 새로운 코드를 집어넣으려 할 때 고려된다. 다시 말해, 각 클래스와 메서드가 무슨 일을 하는지 파악해야 하는 상황이다. 이때 분석해야 할 코드량이 예상보다 훨씬 많아 좌절할 때가 많은데, 주원인은 클래스나 메서드가 원래 해야 할 일보다 훨씬 많은 것을 처리하고 있기 때문이다.

하지만 외적 측면보다 내적 측면에 집중하면 단일 책임 원칙을 적용하기가 한결 쉬워진다. 즉, 역할에만 신경 쓰지 말고, 클래스나 메서드를 변경해야 할 이유가 몇 가지나 되는가를 항시 고민하자.

그렇다면, 언제 어디서 일이 틀어지는 것일까?

노트: 대게 개발자가 TDD와 리팩토링을 하지 않는 조직일수록 단일 책임 원칙에 어긋나는 사례가 많다.

생각해보자. 경험상 단일 책임 원칙에 어긋나는 사례 대부분은 시스템 인터페이스에 가까운 클래스와 메서드에서 발견되었다. 예를 들면, 웹 애플리케이션의 거대한 컨트롤러나 액션 클래스, 스윙 애플리케이션의 거대 이벤트 핸들러, 이벤트 기반 시스템에서 메시지 처리를 담당하는 거대한 메서드 등이 있다.

이는 시스템 인터페이스에 가까운 클래스와 메서드는 더 광범위하고 일반적인 역할을 담당하기 때문이다. 이런 메서드는 수많은 비즈니스 규칙이나 복잡한 워크플로우를 관장하는 경우가 제법 많다.

거래 정보를 담은 거대한 XML 파일을 입력받는 시스템을 상상해보자. 그리고 이를 처리하는 첫 메서드는 TradeService 클래스의 “processTrade(tradeXML)”라고 해보자. 이 메서드의 역할이 무엇인가? 바로 거래를 처리하는 것이다. 그렇다면 이름은 적절한가? 이 시스템은 입력받은 거래(trade)를 처리(process)하길 원하니 첫 메서드의 이름으로 processTrade는 적절해 보인다.

다른 예를 보자. 인터넷 쇼핑 사이트에서 고객이 상품 몇 개를 장바구니에 담고, 지불 정보를 입력하고, “주문” 버튼을 클릭했다. 그렇다면 뒷단에서는 주문 발주를 위해 대략 placeOrder(order) 정도의 메서드를 호출할 것이다. 나쁘지 않다.

생각 발전시키기

일반적으로, 시스템 인터페이스에 가까운 코드일수록 더 폭넓고 일반적인 역할을 담당하는 경향이 있다. 반면, 시스템 인터페이스에서 멀어질수록 더 좁고 특수한 역할을 담당하게 된다.

앞의 두 예에서, processTrade와 placeOrder 메서드는 단 하나의 역할만 수행한다고 주장할 수 있다. 전자는 입력받은 거래를 처리하고 후자는 고객의 주문을 발주한다. 그래서 단일 책임 원칙의 외적 측만을 고려하는 개발자라면 거리낌 없이 관련된 코드 모두를 그 메서드 안에 욱여넣을 것이다.

문제는 거래 처리와 주문 발주가 심히 복잡한 작업이라는 데 있다. 복잡한 과정을 거쳐야 하고, 수많은 비즈니스 규칙과 요구사항을 만족하게 하기 위한 수백 수천 줄의 코드를 작성해야 할 것이다. 그러니 이 많은 코드를 한 메서드에 욱여넣는 것은 단일 책임 원칙을 명백히 위반할 뿐 아니라, 어리석기까지 한 일이다.

단일 책임 원칙을 만족하는 코드를 만들려면, 변경 사유가 오직 하나뿐이어야 한다. 이는 다음과 같이 발전된 생각을 이끌어내 준다.

일반적으로, 시스템 인터페이스에 가까운 클래스일수록 더 많은 것을 위임(delegation)한다. 반면, 시스템 인터페이스에서 멀리 떨어진 클래스일수록 위임할 것이 적어진다.

전통적인 자바 웹 애플리케이션의 컨트롤러가 좋은 예다. 사용자 인터페이스와 가까운 컨트롤러는 폭넓은 역할을 담당하며 비즈니스 로직은 모두 다른 객체에 위임한다. 컨트롤러는 단순히 흐름만 제어할 뿐이다. 정 반대로, 매우 특수하고 제한된 역할만 수행하는 DAO(Data Access Object)는 일거리를 다른 클래스에 위임하는 경우가 거의 없다. 그 중간에 위치하는 서비스는 자신의 비즈니스 로직을 처리하지만, 종종 다른 협력 객체에 작업을 위임하기도 한다. 서비스는 컨트롤러보다는 좁고 DAO보다는 광범위한 역할을 처리하는 게 보통이다. 어쨌든, 각 클래스와 메서드는 단일 역할만을 담당한다.

다르게 질문하기

다른 개발자에게 맨토링해주거나 함께 짝(pair) 프로그래밍을 하며 한 메서드 안의 코드량이나 클래스 안의 메서드 수를 가지고 논쟁을 벌일 때가 많다. 단일 책임 원칙의 외적 측면만을 근거로 내세운다면 코드가 하는 일이 너무 많다는 걸 잘 인정하지 못하는 개발자가 많을 것이다. 그래서 내적 측면이 중요하다는 걸 깨달았다. 이제는 메서드나 클래스가 맡은 역할이 몇 개인가를 묻는 대신, 수정해야 하는 이유가 몇 가지나 되느냐고 묻기 시작했다.

Effective Unit Testing 번역서 출간
Feb 6th, 2014 by Wegra Lee

Effective Unit Testing : 클린 코드와 좋은 설계를 이끄는 단위 테스트

두 번째 번역서..

사실, 출간된 지는 몇 달 지났다.

첫 번역서인 JUnit in Action은 다양한 개발 환경에서 JUnit 기반 프레임워크를 활용하는 방법을 보여주는 활용서 성격이 강했다.

반면, 이번 책은 품질 향상을 넘어 설계 개선이라는 테스트를 통해 얻을 수 있는 한 차원 높은 가치를 잘 설명하고 있다.

그래서 전문 테스터보다는 개발자를 꿈꾸는 모든 이들이 한 번쯤 꼭 봐둘 만한 책이다.

평소 저자와 같은 사상을 가지고 있었고 고민도 많이 했던 분야인 만큼,

책의 단순 오류뿐 아니라 관련 논쟁까지도 기꺼이 수용할 용의가 있다. (연락은 ‘wegra.eut at G메일’로..)

Rational Team Concert 적용 실패 – 원인 분석
Jul 15th, 2011 by Wegra Lee

지난번 글(새로운 툴을 대하는 자세)에서도 언급했듯, 나는 조직에 많은 툴들을 전파해보았다(혹은 전파하려 하였다). 그 중 규모면에서나 영향력면에서나 가장 큰 툴은 바로 Rational Team Concert (RTC) 일 것이다. 내가 접해본 툴들 중 가장 마음에 드는 툴이기도 하다. 그런 이유로 수년간에 걸쳐, 다양한 방법으로 팀원들을 끌어들이려 노력해보고, 한 때 좋은 분위기로 흘러가기도 했지만.. 현 시점에서 결론을 내려보면 ‘완전 실패’다. 자.. 이제부터 내가 느낀 실패 원인을 살짝 정리해보겠다.

자잘한 원인들을 모두 나열하자면 한도 끝도 없을테고 초점도 흐려질듯하니 생략하고, 내가 생각하는 가장 근본적인 원인만 다루려 한다. 바로 (주로 유교의 영향이 컸을 듯한) ‘수직적 문화’이다.

우리 문화는 서열을 중시한다. 조직에서의 서열은 이렇게 매겨진다.

  • 주인 >> 직급 >> 연차 > 나이 > 능력

이 서열을 뛰어넘기란 여간 어려운 게 아니다. 아무리 능력이 좋아도 직급이 더 높은 사람을 부릴 수 없으며, 심지어 같은 직급과 같은 연차라도 나이가 한 살이라도 더 많은 사람을 밑에 두기란 쉽지 않다. 여러 가지 예가 있다.

  • 과제가 잘 되어 팀 규모가 커지면, (팀원을 승진시키기보다) 규모에 어울리는 높은 직급의 사람들이 합류한다. 팀을 이끌던 원조 맴버들 대부분은 새로 들어온 높은 분들 밑으로 들어가서, 기존보다 작은 역할을 맡는다.
  • 적은 나이와 연차에도 능력을 인정받아 상대적으로 큰 규모의 팀을 이끄는 자가 있다면, 그의 팀은 다른 팀보다 젊은 사람들도 구성되어 있다.

특히 직급은 우리 조직 시스템에서 너무도 중요한 축을 담당한다. 말단 사원을 제외하고는 모든 사람들을 호칭할 때 직급을 붙여준다. 대부분은 때되면 붙여주는 직급이지만, 이를 생략하고 불렀다간 관계가 소원해질 것을 각오해야 한다. ^^;

굳이 회사에서만 예를 찾으려할 필요도 없다.

  • 바른 논리를 가지고 있더라도, 나이 많은 이에게 목소리를 높이면 버릇없다는 평을 듣기 십상이다.
  • 그룹 활동을 하는 연애인들의 리더는 십중팔구 나이가 가장 많거나, 동갑일 경우엔 생일이 가장 빠른 사람이다. 누가 막내인지 알리는 것도 빼놓지 않는다.
  • 운동 선수들 간의 엄격한 선후배 관계도 잘 알려져 있다.
  • (반대 급부로 생긴 말이지만) ‘나이 먹은게 벼슬이냐?’ ‘나이가 벼슬이다’와 같은 말이 심심찮게 쓰이는 것은 나이가그 사람의 상대적 위치를 결정짓는데 적지 않은 영향을 미침을 반증하고 있다.

불행히도 이런 문화는 우리가 쓰는 언어에 의해 아주 어렸을 때부터, 거의 언어를 배우기 시작하면서부터 주입된다. 한 살이라도 많으면 형, 누나, 오빠, 언니가 되고 그들에게는 말을 높이라고 교육받는다. 윗사람을 공경하는 문화야 칭송되기도 하고, 그리 나쁘지는 않은 문화라 생각한다. 다만 아쉬운 것은 단순히 나이 많은 이에 대한 어휘와, 정말 존경이나 높은 사람에 대한 예우로써 쓰는 어휘에 차이가 없다는 점이다. 즉, 우리는 존경과 나이 많음을 명확하게 구분짓지 않기 때문에 알게모르게 이 둘을 동일시 시킨다. 언어가 인간의 사고를 지배하기 때문이다. 초등학생만 되어도 선배는 후배에게 일을 시키고, 후배는 선배의 명령을 따르는 것이 몸에 익어버린다. 갑으로써 누릴 수 있는 힘과 을로써의 자세를 사회 관계를 처음 쌓게 되면서부터 체득하게 된다. 그리고 성인이 될 때까지 단 한 번도 이에서 벗어난 문화를 경험하지 못하고 성장한다. (채벌에 관대하게 된 데에도 많은 영향을 미쳤으리라 믿는다.)

이렇게 우리는 협동과 협업보다는 명령 하달과 수행 체제에 적합하게 훈련되었다. 평등한 관계 속에서 협업이 중시되는 사회에서는 뛰어난 리더가 중심이 되지만, 명령과 수행에 의해 움직이는 사회에서는 냉철한 관리자가 더 중요해진다.

이쯤에서 RTC를 잠시 살펴보자. 툴에는 툴 설계자의 노하우와 철학이 담겨있다.그럼  RTC 설계를 주도한  에릭 감마는 어떤 생각을 가지고 이 툴을 만들었을까.  이런저런 이야기를 하고 있지만, 핵심은 효율적인 협업투명성이다. 관리와 통제가 아닌 것이다.

우리 사회도 물론 협업과 투명성을 강조하지만, 평등한 관계에서의 협업/투명성과 수직적 관계에서의 그 의미는 완전히 달라진다. 후자에서의 협업은 같은 등급의 사람들끼리 잘 협동하고, 높은 사람의 말을 잘 따르는 것이다. 투명성은 높은 위치의 사람이 낮은 위치의 사람이 땡땡이 치지 못하게 잘 감시할 수 있는 일방적인 하향 투명성을 뜻한다. 협업은 그렇다 쳐도.. 투명성에 대해서는 실무자쪽에서 더욱 방어적으로 나오는 이유가 되기도 한다. 갑은 을을 마음대로 부릴 수 있음을 어려서부터 익히 배워왔기 때문에, 을은 갑으로부터 최대한 자신을 보호하고 싶어한다. 상향 투명성은 생각하기도 힘들고, 동급의 실무자들간 투명성도 그리 환영받지 못한다. 아랫 사람은 위에서 내린 명령만 잘 수행하면 되는 것이고, 그것이 어떻게 조합되어 전체를 만드는지는 윗사람이 생각할 문제이다. 또한 피지배 계층이 너무 많은 정보를 가지고 있다면 지배하기기 쉽지 않다. 과거 평민 이하에겐 교육을 시키지 않은 이유와 같다. 예를 들어, 지금까지의 과제 진척률이 고스란히 공개된다면 단순 채찍질용 일정 단축 요청 같은 것은 의도한 효과를 발휘하기 어렵다.

몇 년 전, 우리의 행태를 비판하며 프로젝트 투명성에 대한 생각도 끄적여 봤었지만, 하루 아침에 변화시키엔 수직적 문화의 뿌리는 우리 사회에 너무 깊게까지 내려 있다. 나이 차이가 수십년 이상 되는 사람들이 모여 있는 거대 조직에서는 말도 안되는 이상향일 지도 모르겠다.

이런 이질적인 동서양 문화에서 파생되는 또 하나의 심각한 문제는 바로 실무자로써의 생명이 짧다는 점이다.

일반적으로 사원으로 입사했다면 8년쯤 후, 박사로 입사했다면 거의 곧바로 관리자의 역할을 맡게된다. 편차가 심하긴 하지만 평균은 대략 이와 비슷할 것이다. 심지어 대규모 플랫폼을 개발하는 팀에서조차, 능력있는 고참 실무자를 아키텍트로 키워보려 면담을 해보면 ‘저도 이제 관리를 익혀야지요’하는 반응이 많다는 이야기도 들었다.

그런데 이것이 수직적 문화와 무슨 관련이 있단 말인가? 계급이 다르다는 것은 (암묵적으로) 하는 일이 다르다는 뜻이다. 승진을 했음에도 하는 일은 과거와 똑같다면 스스로도 실망스럽고, 주변의 위로 소리가 어색하지 않게 느껴진다. 실무 능력이 아무리 뛰어나도 관리 능력이 미달되면 그는 우리 사회에서 도태되기 쉽다.

이와 달리, 수평적 사회에서는 하는 일의 차이보다는 능력의 차이가 보다 중시된다. 조직을 이끄는데 있어 관리는 물론 중요하지만, 직급이 높으면 관리를 해야한다는 인식보다는 개개인이 가장 잘 할 수 있는 일을 하는 것이 올바르다 생각한다. 관리는 직급의 구분 기준보다는 역할이 다른 것으로 인식된다.

그래서 다시.. RTC와 무슨 상관인가? 관리자 입장에서 생각해보자.

  • 현 상황을 보려면 Eclipse를 깔고 매번 실행해야 한다고? 개발도 안하는데 그 무거운 툴을 내 느린 노트북에서 실행하라니..
  • 소스 컨트롤? 빌드 상태? 그런 건 알아서 풀고, 정 문제가 되면 개선안과 함께 보고해.
  • 이슈? 버그 현황? 한 페이지로 깔끔히 정리해와.
  • 수하 직원에게 업무를 할당하는데 직접 툴에 입력하라니? 말로 시키면 알아서 잘 처리하고 결과만 제때 보고하면 되지.

기타 등등.. 관리자 입장에서는 전문 개발 툴에 통합된 RTC의 인터페이는 불필요한 기능들로 가득차있고 복잡하고 무겁다. 관리를 잘 하기 위해서는, 스스로 툴을 조작하며 이러저런 상세 정보 속에서 헤매이기보다는, 핵심 정보들만 빨리 캐취해서 적시에 올바른 의사 결정을 내리는 것이 훨씬 중요하다. 즉, RTC는 이런 문화에 속한 관리자를 위한 툴이 아니다.

그렇다면 개발자에게는 좋은가..

  • RTC에 열심히 자료를 축적해 놓아도, 상사는 항상 자신이 보고픈 것만 나오는 별도 자료를 요구한다. 어차피 보고 자료 따로 만들 거, 굳이 중복 작업 할 필요 있나!
  • 내게 할당된 일과 그 진척 상황이 거의 실시간으로 공개되니 감시받는 기분이 든다.
  • 요구사항은 별도 문서로.. 일정 관리도 마찬가지. 테스트는 다른 팀에서. 다 빼고나면 내가 쓸 건 소스 컨트롤과 빌드뿐인데.. 그 정도는 오픈 소스 공짜툴 중에도 좋은게 널리고 널렸지. 이왕이면 다른 회사로 옮기거나 집에서 혼자 쓸 때도 그대로 사용할 수 있는 오픈 소스 툴들을 써보는게 좋지 않을까?

이렇듯, 개발자 입장에서도 그리 매력적이라 보기는 힘들다는 생각에 이르렀다. 결국 우리 사회 구성원 누구에게도 딱 맞지 않은.. 먼 나라의 툴이 되어버린 것이다.

물론 우리 사회도 조금씩 변해가고 있다. 요즘의 젊은 벤쳐 기업이나 열린 마음의 사람들로 구성된 작은 팀에서는 RTC가 진정한 힘을 발휘하기에 충분한 문화를 만들 수도 있을 것이다. 그리고 점점 더 상황은 나아질 것이라 믿어 의심치 않는다. 하지만 똑같은 수준에서, 몇 년 내에 급격한 개선이 있을 것이라고는 절대 생각하지 않는다. 조직 문화를 성공적으로 혁신시키려면, 조직 구성원들 대부분이 그 필요성을 마음속 깊이 공유한 상태여야 한다. 그렇지 못한 상태에서 무리하게 추진한다면  모난 돌 취급을 받게 되거나, (추진자가 높은 사람이라면) 마지못해 하는 척만 하다가 머지 않아 원상복귀된다. 혹은 형식만 남아 안함만 못한 상태가 되어버린다.

RTC와 같이 프로젝트 개발 과정 전반을 아루르며 팀 구성원 모두가 써야하는 툴을 온전히 도입하는 것은, 팀 문화 전반을 바꾸려는 시도와 같다. 개인적으로 마음에 들더라도, 팀 차원에서의 적용을 시도하려면 분위기와 팀원들의 성향을 잘 판단해서 추진하기 바란다. 우리팀은 지금 50명 이상이 RTC를 사용하는 듯 싶지만, 에릭 감마가 의도한  방식대로 사용하는 사람도 거의 없을 뿐더러, 관리자쯤 되면 쓰는 사람을 손에 뽑고, 매뉴얼/검색/옆사람을 통해 쉽게 해결할 수 있는 사소한 것들로도 수시로 (전파자인) 나를 찾아 귀찮게 하는 상황이다. ^^;;

가볍게 소개해보고 분위기를 살펴보는 것으로 시작하는 것은 나쁘지 않은 방법일 것이다. 단, 몇 마디 긍정적인 피드백만으로 너무 쉽게 총대를 둘러매진 말길 바란다. ^^

(updated: 내가 이런 글을 적은 이유 중 하나는, 이런 문화적 차이를 미리 알고 충분히 고려해서 적용을 시도해보는 것과, 무턱대고 밀어붙이는 것에는 분명 큰 차이가 있을 거라 믿기 때문이다. 뭐든 내맘에 든다고 다른 사람 맘에도 들거란 생각은 위험하지만, 만약 이 툴이 정말 마음에 든다면, 당신은 주변 사람들과는 다른 사상을 가지고 있을 확률이 많을 것이다. ^^ 실패하더라도 남 탓하지 말고, 사회 탓도 하지 말기 바란다. 이 시스템은 또 이 시스템 만의 장점이 있다. 적응을 해보던지, 정 맞지 않다면 일찌감치 다른 조직을 찾아 모험을 떠나보는 것도..^^)

JUnit 4, 60초만에 익히기
Jul 3rd, 2011 by Wegra Lee

JUnit in Action 책 출간 기념으로 짤막한 글 하나 번역해본다.

원문: JUnit 4 in 60 Seconds

이번 주말에 JUnit 4 라이브러리를 살펴보고 간단히 정리해보았다.

1. @Test

테스트 케이스임을 명시하기 위해 사용한다. “test”와 같은 접두어를 붙일 필요가 없어졌다. 또한 테스트 클래스 역시 더 이상 “TestCase” 클래스를 확장할 필요가 없어졌다.

  1. @Test
  2. public void addition() {
  3. assertEquals(12, simpleMath.add(7, 5));
  4. }
  5. @Test
  6. public void subtraction() {
  7. assertEquals(9, simpleMath.substract(12, 3));
  8. }

2. @Before and @After

@Before와 @After 애너테이션을 사용하여 “setup”과 “testDown” 메서드 명시한다. 이 메서드들은 각각의 테스트 전후로 실행된다.

  1. @Before
  2. public void runBeforeEveryTest() {
  3. simpleMath = new SimpleMath();
  4. }
  5. @After
  6. public void runAfterEveryTest() {
  7. simpleMath = null;
  8. }

3. @BeforeClass and @AfterClass

@BeforeClass와 @AfterClass는 클래스 단위의 “setup”과 “teadDown” 메서드에 붙인다. 1회성 setup, tearDown 메서드라 생각하자. 클래스고 파홈한 모든 테스트케이스들의 전후로 단 1회만 실행된다.

  1. @BeforeClass
  2. public static void runBeforeClass() {
  3. // run for one time before all test cases
  4. }
  5. @AfterClass
  6. public static void runAfterClass() {
  7. // run for one time after all test cases
  8. }

4. Exception Handling (예외 처리)

예외 발생이 예상되는 테스트 케이스에는 @Test 애너테이션에 “expected” 파라미터를 사용한다. 발생해야할 예외의 클래스 명을 적어두면 된다.

  1. @Test(expected = ArithmeticException.class)
  2. public void divisionWithException() {
  3. // divide by zero
  4. simpleMath.divide(1, 0);
  5. }

5. @Ignore

무시하고자 하는 테스트 케이스에는 @Ignore 애너테이션을 붙인다. 무시하는 이유도 기입해 넣어주면 좋다.

  1. @Ignore(“Not Ready to Run”)
  2. @Test
  3. public void multiplication() {
  4. assertEquals(15, simpleMath.multiply(3, 5));
  5. }

6. Timeout (시간 제한)

제한 시간이 필요하면 “timeout” 파라미터를 이용한다. 단위는 밀리초이다. 테스트가 제한시간 내에 완료되지 못하면 실패 처리된다.

  1. @Test(timeout = 1000)
  2. public void infinity() {
  3. while (true)
  4. ;
  5. }

7. 새로운 단언들

배열 비교에 쓰일 수 있는 새 단언 메서드들이 추가되었다. 배열의 길이가 같고, 각 원소들이 대상 배열의 대응되는 원소들과 같아야(equal) 한다.

  • public static void assertEquals(Object[] expected, Object[] actual);
  • public static void assertEquals(String message, Object[] expected, Object[] actual);
  1. @Test
  2. public void listEquality() {
  3. List<Integer> expected = new ArrayList<Integer>();
  4. expected.add(5);
  5. List<Integer> actual = new ArrayList<Integer>();
  6. actual.add(5);
  7. assertEquals(expected, actual);
  8. }

8. JUnit4Adapter

JUnit 3에서 JUnit 4용 테스트를 실행하려면 JUnit4Adapter를 사용한다.

  1. public static junit.framework.Test suite() {
  2. return new JUnit4TestAdapter(SimpleMathTest.class);
  3. }

자! 그럼 모두 즐코딩~ ^^

JUnit in Action 판매 시작
Jul 3rd, 2011 by Wegra Lee

JUnit in Action : 단위 테스트의 모든 것

드디어 판매 개시!!!!

처음 작품인데, 반응이 어찌 나올지 궁금.

다음 기회에는 저술을 하거나, 번역을 하더라도 나의 이야기를 좀 더 많이 실을 수 있길 바래본다.

지속적 통합(Continuous Integration) 구성 사례
Jun 7th, 2011 by Wegra Lee

이번엔 지속적 통합 사례를 하나 정리해보겠다. (지속적 통합의 개념 설명은 이곳에..)

Components and Basic Workflows

이번에 구성해본 지속적 통합(CI) 환경의 구성 요소는 다음과 같다.

  • CI (Continuous Integration) 툴: IBM Rational Team Concert (RTC)
  • 소스 관리: IBM Rational Team Concert
  • 빌드 스크립트: Apache Ant
  • (참조) Apache Maven

비록 RTC라는 상용 툴을 사용하고는 있지만, 이 글에서 다루는 대부분의 내용은 개념적인 것이라, 다른 툴(예: Hudson, TeamCity, CruiseControl)을 사용할 때도 그대로 적용/응용할 수 있다.

전체 시스템을 그림으로 나타내면 대략 다음과 같다.

ci

RTC 서버의 다양한 기능 중, 여기에서는 소스 관리와 빌드 관리, 그리고 웹 UI 정도이다. 거시적인 작업 흐름은 다음과 같다.

  1. 개발자가 변경 내용(change-set)을 소스 저장소에 전달한다.
  2. RTC 서버의 빌드 모듈이 이를 인식해 적당한 빌드 엔진에 할당한다.
  3. 빌드 엔진은 소스 저장소로부터 빌드에 필요한 데이터를 내려 받아 Ant 빌드 스크립트를 수행한다.
  4. Ant 빌드 스크립트는 빌드를 수행하고 산출물을 개발 서버 및 API 서버에 배포한다.
  5. 빌드 엔진은 빌드 결과 및 과정은 빌드 서버에 알리고, 서버는 개발자 PC에 푸시한다.

개발자나 프로젝트 관련자들은 이런 모든 과정/결과를 언제든 전용 Eclipse UI나 Web UI를 통해 확인할 수 있다.

또한, 어떤 빌드 엔진을 사용할 지, 어떤 스크립트를 사용한 지 등은 모두 configuration 가능하다.

Project Directory Layout

여러 팀, 다양한 과제에 걸쳐 일을 효율적으로 진행하려면 프로젝트 구성부터 일관적으로 유지하는 것이 좋다.

Ant는 비록 산업 표준 빌드 툴이지만, 프로젝트 구성에 대한 표준 규약은 제공하지 않는다. 때문에 담당자 취향만큼이나 다양한 구성이 존재하며, 그 구성을 정하고 관리하는데에만 상당한 고뇌와 노력이 소요된다. 그리고 재활용도 쉽지 않다. 바로 이 문제를 타파하고자 나온 오픈소스 프로젝트로 Maven이란 것이 존재한다. Maven 개발자들은 프로젝트의 구성에 관련된 모범 사례(best practice)들을 집대성하고자 하였다. 무분별한 컨피규레이션 허용보다는 잘 정의된 모범 사례를 따른는 것을 원칙으로 삼은 것이다(Convention over Configuration). 물론 그 결과가 이상적으로 완벽하진 않지만, 상당수의 프로젝트에 적용하는데 큰 무리가 없을 것이다.

어쨌든, 본 예제에서는 Ant를 사용하지만 Maven의 이상과 결과를 상당부분 따르고 참조할 것이다. 물론 Ant이기 때문에 언제든 어렵지 않게 수정 가능하다.

그래서 내가 구성한 기본 구성은 다음과 같다.

  • src/main/java – 제품 소스 코드
  • src/main/resources – 제품에 포함될 리소스
  • src/main/config – 제품 설정 정보
  • src/main/webapp – 웹 애플리케이션 소스
  • src/test/java – 테스트 소스 코드
  • src/test/resources – 테스트에 필요한 리소스
  • lib/main – 제품 수행에 필요한 라이브러리
  • lib/test – 테스트에 필요한 라이브러리 (junit, mokito  등)
  • tools – 팀내 공용 툴 (예: FindBugs, CheckStyle, Code Pro Analytix 등)
  • build.xml – Ant 빌드 스크립트
  • build.properties – Ant 빌드 스크립트용 커스텀 프로퍼티 파일
  • build-jazz.xml – RTC/Jazz용 빌드 스크립트(build.xml을 확장함)
  • LICENSE.txt – 제품 라이선스 정보
  • README.txt – readme 파일

참조함 Maven의 표준 디렉터리 구성과 크게 다르지 않다. 간소화를 위해, 크게 필요 없다고 생각되는 filters, assembly, site, NOTICE.txt 를 제거하였고, lib과 tools가 추가되었다.

lib이 추가된 이유는 Maven이 종속성 자동 관리 기능이 포함된데 비해 Ant는 직접 필요할 라이브러리를 관리해야 하기 때문이며, tools 는 개발팀 내 함께 쓰는 유용한 도구와 그 설정 정보를 공유하기 위함이다.

빌드 스크립트는 총 3개의 파일로 구성된다. build.xml 은 메인 빌드 스크립트이며, build.properties에는 그 중 사용자 정의 속성을 담아, 상황에 맞게 설정하여 빌드할 수 있게 하였다. 마지막의 build-jazz.xml 은 build.xml을 확장(import)하여 RTC/Jazz에 종속된 기능을 추가로 수행하기 위해 추가하였다. 즉, build-jazz.xml를 제외한 두 파일은 RTC/Jazz와 완전히 독립적이어서 어떤 환경에서건 그대로 재활용할 수 있다.

스크립트의 속 내용은 조금 후에 살펴보기로 하고, 빌드 과정에서 생성되는 디렉터리 레이아웃에 대해서 먼저 살펴보자.

  • target/classes – 제품 소스를 컴파일한 클래스 파일들 & 리소스
  • target/test-classes – 테스트 소스를 컴파일한 클래스 파일들 & 리소스
  • target/reports/unit-test – 단위 테스트 결과 리포트 (XML 포맷)
  • target/reports/unit-test/html – 단위 테스트 결과 리포트 (HTML 포맷)
  • target/reports/integration-test – 통합 테스트 결과 리포트 (XML 포맷)
  • target/reports/integration-test/html – 통합 테스트 결과 리포트 (HTML 포맷)
  • target/reports/findbugs – FindBugs 수행 결과 보고서
  • target/reports/checkstyle – CheckStyle 수행 결과 보고서
  • target – 빌드 산출물 루트 겸, package 된 제품 바이너리 등 최종 산출물

특별한 설명은 필요 없으리라 본다. 그렇다면 이제 Ant 빌드 스크립트의 내용과 빌드 단계에 대해 알아보기로 하자.

Ant Build Script and Build Lifecycle

Ant 빌드 스크립트는 빌드 타깃(target)과 타깃간 종속성(선행 타깃 정의)과 타깃에서 실행해야할 실제 작업을 정의한다. 빌드 라이프사이클 역시 Maven의 그것을 기반으로 간소화한 후 약간 보강하였다. 다음은 build.xml에 정의된 타깃들을 라이프사이클에 따라 설명한 것이다.

  1. compile – 제품 소스 코드를 컴파일한다.
  2. test-compile – 테스트 코드를 컴파일한다.
  3. unit-test – 단위 테스트를 수행한다.
  4. package – 제품 컴파일 결과를 배포 형태로 패키징한다.
  5. integration-test – 통합 테스트를 수행한다.
  6. code-analysis – 정적 코드 분석을 수행한다. (FindBugs, CheckStyle)
  7. deploy – 패키징한 결과를 개발 서버 및 API 서버로 배포한다.

몇 가지만 살펴보겠다.

먼저, code-analysis가 Maven에 없는 새로 추가된 단계이다. 이 단계에서는 FindBugs, CheckStyle 등의 정적 코드 분석 툴을 이용하여 제품 소스 코드의 잠재적 결함과 코딩 규약 부합 여부를 검사한다. code-analysis 단계 외에는, 실패 시 다음 단계를 계속 진행할 수 없다.

unit-test 단계에서는 수행시간이 짧은 단위 테스트들을 실행한다. 통합 테스트 케이스와 시간이 오래 걸리는 테스트 등은 뒷쪽의 integration-test 단계에서 수행시킨다.

마지막 deploy 단계에서는 완성된 바이너리를 개발 서버로, 최신 API 문서를 API 서버로 배포한다.

변경 가능한 사용자 속성으로는 다음과 같은 것들이 있다.

  • product.name  - 제품명
  • product.version – 제품 버전
  • main.class – 실행 클래스명: jar 파일의 Manifest 파일에 추가됨
  • compile.deprecation – javac 컴파일 옵션
  • compile.debug – javac 컴파일 옵션
  • compile.optimize – javac 컴파일 옵션
  • compile.source – javac 컴파일 옵션
  • compile.target – javac 컴파일 옵션
  • proxy.host – 프락시 주소 (프락시 안에 갖힌 네트워크에서 수행될 때)
  • proxy.port – 프락시 포트 (프락시 안에 갖힌 네트워크에서 수행될 때)
  • deploy.binary.host – 패키징된 바이너리를 배포할 호스트 주소
  • deploy.binary.user – 호스트 로그인 계정
  • deploy.binary.passwd – 호스트 로그인 패스워드
  • deploy.binary.keyfile – 호스트 로그인에 필요한 key 파일 위치 (xxx.pem)
  • deploy.binary.dir – 바이너리를 복사해 넣을 호스트 내의 디렉터리 경로
  • deploy.api.host – 최신 API를 배포할 호스트 주소
  • deploy.api.user – 호스트 로그인 계정
  • deploy.api.passwd – 호스트 로그인 패스워드
  • deploy.api.keyfile – 호스트 로그인에 필요한 key 파일 위치 (xxx.pem)
  • deploy.api.dir – 바이너리를 복사해 넣을 호스트 내의 디렉터리 경로 (웹 서버 혹은 파일 서버)

보는 바와 같이 소스 코드 디렉터리 구조, 산출물 파일 이름과 같은 정보는 따로 설정할 수 없도록 제안하고 있다. 이유는 초반에 언급한 Maven의 설계 원칙(Convention over Configuration)을 좀 더 강요하기 위함이다.

물론 build.xml의 내용을 보면 관련 정보를 속성으로 제공하여, 꼭 필요한 경우 쉽게 변경할 수 있다.

마지막으로 build-jazz.xml 파일을 살펴보자.

이 파일은 build.xml 을 확장하여 RTC/Jazz 빌드 서버에서 사용할 특화 타깃을 정의하고 있다. RTC/Jazz 빌드 기능에 특화된 만큼, 이 스크립트를 개발자 IDE에서 실행하려 하면 필요한 파일이 없다면서 에러를 발생시킬 것이다. 물론 몇 가지 설치/설정으로 가능케할 수 있지만, 별다른 이점은 없으니 Jazz 빌드 서버에 맡기기로 하자.

기본적으로는 다음의 네 가지 타깃이 제공된다.

  • package-jazz – build.xml의 package 단계까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다. 단위 테스트 보고서와 패키징된 바이너리가 이에 포함된다.
  • integration-test-jazz – build.xml의 integration-test 단계까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다. 위 결과에 통합 테스트 결과가 추가된다.
  • code-analysis-jazz – build.xml의 code-analysis 단계까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다. 위 결과에 정적 코드 분석 보고서가 차가된다.
  • deploy-jazz – 최종 단계인 deploy까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다.

특별히 위와 같은 네 단계만 정의한 이유는 잠시 후 Build Definitions 절에서 확인할 수 있다. 그 전에 정적 코드 분석(static code analysis) 단계에서 무엇을 하는지 살짝 알아보고 가기로 하자.

Static Code Analysis

앞서 살펴본바와 같이, unit-test와 package 단계 사이에 code-analysis 라는 단계가 추가되었다. 이는 Maven에도 정의되어 있지 않은 단계이다. 이 단계에서는 제품의 소스 코드를 분석하여 잠재적인 결함과, 코딩 규약 준수 여부를 확인한다.

쉽게 활용할 수 있는 오픈 소스 툴들로는 FindBugs와 CheckStyle, PMD 등이 있다. (기능면에서 CodePro Analytix가 가장 마음에 드나, 아쉽게도 Ant용 task를 제공하지 않아 여기서는 제외하였다.)

FindBugs는 버그 패턴 위주로 거의 100% 적중률로 문제를 분석해주며, CheckStyle과 PMD는 그 외에도 코딩 규약, 유사 코드 검색 등 다양한 기능을 제공한다. CheckStyle과 PMD 는 기능면에서 상당히 겹치기 때무에 둘 다 사용할 필요는 없다. 나는 PMD를 더 선호하였지만, 최근에 업그레이드가 이루어지지 않고 있어 CheckStyle로 선회하였다.

참고로, 이들 툴을 지속적 통합 프로세스의 일부로 등록해놓는 것은 좋은 생각이긴 하지만 IDE에 통합하여 개발자들이 수시로 확인해보는 것에 비할 바가 못된다. 다행히 CodePro Analytix를 포함하여 위의 모든 툴들은 Eclipse 플러그인을 제공하고 있다.

관련하여 Java 코딩 규약 관리 방법 역시 참고가 될 것이다.

Build Definitions

빌드 정의(Build Definition)는 빌드에 필요한 각종 정보와 수행 조건 등을 담는다. 예를 들어, 빌드에 사용할 파일(build-jazz.xml)명, 타깃, 빌드 스케줄 등이 그것으로, 프로젝트 특성에 맞는 다양한 전략을 수립할 수 있다. 상세 내용은 본 글의 주제와 크게 관련 없으니  지나치도록 하겠다.

어쨌든 Jazz의 빌드 서버는 이 정의를 바탕으로 빌드 엔진에 빌드를 요청한다. 아래의 그림은 내가 구축하기 원하는 빌드 전략이다.

schedule

그리고 위의 전략을 구현하기 위해 다음과 같은 네 가지의 빌드 정의를 작성하였다.

  1. Continuous: 변경 사항에 대한 빠른 피드백을 목적으로, 컴파일/단위 테스트/패키징 성공 여부까지 확인한다.
    1. 빌드 주기: 매 5분
    2. 빌드 타깃: package-jazz
  2. Integration: 더 많은 시간이 소요되며 다양한 테스트를 수행하는 통합 테스트까지 수행한다.
    1. 빌드 주기: 매 1시간
    2. 빌드 타깃: integration-test-jazz
  3. Nightly: 일별 snapshot을 만들어, 매일 아침 baseline을 생성한다.
    1. 빌드 주기: 월~토요일 3:00 AM
    2. 빌드 타깃: code-analysis-jazz
  4. Weekly: 주간 변경 내용을 종합 검증하여 baseline을 생성하고, 개발 서버에 배포한다.
    1. 빌드 주기: 매주 일요일 3:00 AM
    2. 빌드 타깃: deploy-jazz

Jazz 빌드 서버는 위의 네 가지 빌드 정의에 기반해, 자동으로 빌드/테스트/배포를 수행하며, 그 결과를 개발자에게 알려주게 된다.

Summary

이상으로 빌드 시스템의 거시적인 구성부터 빌드 단계 정의, 빌드 정의를 통한 지속적 빌드 전략 수립까지 구성해 보았다.

다소 이론적인 면에 집중하여 설명하였지만, 상세 내용으로 들어가면 내용이 너무 길고 장황해지니 양해 바란다.

(관련 Ant 빌드 스크립트는 약간 다듬어서 추후 업데이트하겠음)

Java 코딩 규약 관리 방법
May 30th, 2011 by Wegra Lee

오랫만에 글을 적는 계기는, 얼마전 팀에 배포된 100페이지짜리 자바 코딩 가이드라인 때문이다.

나는 이미 약 9년 전에 나만의 코딩 가이드라인 문서를 만들어 다수 프로젝트에 적용했었다. 당시엔 나름 자부심을 주는 산출물 중 하나였지만, 얼마 지나지 않아 이것은 구시대적 산물이 되었음을 깨닫게 되었다.

코딩 규약 자체는 분명 필요하다. 이것이 없는 조직은 아직 굉장히 미숙한 개발 문화를 갖고 있을 확률이 높다. 문제는 이를 정적인 문서(워드나 파워포인트 형태)로 작성/관리한다는데 있다. 문서 방식의 대표적인 한계는 이러하다.

  • 내용이 풍부해질 수록 배우고 실무에 적용하기 어려워진다.
  • 언어 명세에 추가되는 새로운 문법에 빠르게 대응하지 못한다.
  • 항목 A의 예제 코드가 항목 B를 따르지 못하는 경우가 흔히 발생한다.
  • 몇몇 예제만으로 실 제품의 수십만/수백만 라인의 다양한 코드와 매칭시키게 하기에는 한계가 있다.

과거 시절에는 저런 한계를 안고서라도 문서가 필요했지만, 더이상은 아니다. 개발 도구들이 이미 충분히, 아니 비교할 수 없을 만큼 성숙되어 있기 때문이다.

Eclipse의 Code Formatter

가장 대표적인 자바 IDE 중 하나인 Eclipse를 보자. Eclipse의 Preferences > Java > Code Style 메뉴를 보면 다음의 메뉴들을 볼 수 있다.

  • Clean Up: 불필요한 코드나, 명백히 잘못된 코드 설정
  • Code Templates: 정형화된 코드 파일/클래스/메서드 등의 템플릿 설정
  • Formatter: 코드 포맷 설정
  • Organize Imports: import 문 구성 규칙 설정

위의 기능들은 개발 중 언제나 간단한 메뉴 조작이나 단축키로 바로바로 적용할 수 있다. 즉, 새로 합류한 팀원이 아무리 대충 짜놓은 코드라도, 즉시 베테랑 선임 개발자가 짠 코드처럼 바꿀 수 있다는 것이다. (기본 로직이나 단어 선택 등은 논외)

C/C++ 언어의 유사 툴을 사용해본 사람이라면 시큰둥할 수도 있다. 하지만 직접 저 메뉴들을 찾아들어가 잠시만 살펴본다면 그 막강한 표현력에 혀를 내두르고, 실제 프로젝트에 적용해보면 그 정확성에 감탄을 금치 못할 것이다.

더욱이 편집한 설정을 import/export 할 수 있으니, 기본 설정이 맘에 들지 않는다면 수정하여 팀 전체가 쉽게 공유할 수 있다. 소스 컨트롤 툴에 저장/관리한다면 금상첨화일 것이다.

하지만 코딩 규약은 단순 문법만 다루는 것은 아니다. 잠재적 결함을 예방하기 위한 올바른 코딩 패턴과 개발자들아 자수 실수하는 잘못된 패턴에 대한 예방 차원의 항목도 다수 포함된다. 위의 설정만으론 분명 부족함이 있다. 이에 대한 해결책으로는 두 가지 툴을 추천한다.

FindBugs

FindBugs라는 이름에서부터 너무도 명백하게 자신의 용도를 광고중인 이 툴은, 자바 코드에서 문제의 소지가 있는 다양한 버그 패턴을 찾고 그 이유를 설명해준다. 자신이 짠 코드에서 직접 짚어준다는 점에서 초간단 예제 몇 개만 달랑 던져주는 문서와는 천지차이다.

IDE와 통합은 기본이고 무료다. 또한 이 툴이 헛짚은 경우는 아직까지 겪어보지 못했을 정도의 정확성을 뽑낸다. 물론 ‘우리의 사용 환경에서는 절대 이런 일이 발생할 수 없어’라며 무시할 수는 있지만, 사용 환경이 언제까지건 변함 없고, 그 코드가 다른 프로젝트에 가져다 쓰일 확률이 zero 라고 확신하지 않는다면, 툴이 제안하는 예방 조치를 따라두는 것이 나쁠 것 없다.

CodePro Analytix

과거에는 PMDCheckStyle을 추천하며 CodePro Analytix는 소개 정도만 시켜주었는데, 이제는 상황이 바뀌었다. 구글이 이 툴을 사더니 무료로 뿌려버린 것이다. 더이상 상용 툴을 아쉬워하면 꿩 대신 닥으로 PMD나 CheckStyle을 사용할 필요가 없어졌다. 물론 이 둘을 무시하는 것은 아니지만, 개인적으로는 CodePro Analytix를 훨씬 높게 평가한다.

유사 코드 찾기종속성 분석 등 다른 기능도 많지만, 이번 주제와 밀접한 관련이 있는 기능은 바로 코드 검사 기능이다. CodePro Analytix는 Effective Java, Security, Internal API 등등 업계에서 많이 통용되고 있는 다수의 가이드라인에 맞는 수백가지의 검사 규칙을 제공한다. 각 규칙들은 세밀한 설정도 가능하고, 원하는 항목만 조합하여 팀만의 룰셋을 정의할 수도 있다. 이렇게 정한 규칙은 당연히 import/export 하여 공유할 수 있다.

Eclipse에 설치하려면 다음 주소를 참고하자.

http://code.google.com/intl/ko-KR/javadevtools/download-codepro.html

CheckStyle

CodePro Analytix로 천하통일할 수 있을 줄 알았으나, 확인 결과 CodePro는 Ant 태스크나 Maven 플러그인을 제공하지 않아, 지속적 통합 시스템에 넣기에 적합하지 않다. 하여 PMD와 CheckStyle 중 하나를 여전히 추천하지 않을 수 없는 상황이다. 둘 중 하나를 고르자면, 나는 CheckStyle을 추천한다. 이유는 간단하다. PMD가 2009년 이후 업데이트가 이뤄지지 않고 있는데 반해, CheckStyle은 지속적으로 업데이트 중이기 때문이다.

툴의 기능은 CodePro Analytix와 유사하나 지속적 통합 시스템에 바로 적용할 수 있도록 Ant 태스크와 Maven 플로그인을 제공한다. 물론 Eclipse와 같은 IDE용 플러그인 품질도 뛰어나 개발자 편의성도 좋다.

Summary

지금까지 살펴본 바와 같이, 자바 코딩 규약에 대해서는 이미 훌륭한 툴들이 갖추어져 있다. 문서로 힘들게 정리하고 교육하는 것보다는 이들을 활용하는 것이 백배는 효율적이다. 그 이유는 이들 툴 모두는 다음과 같은 장점을 제공한다.

  • IDE와 밀접히 통합되어 있어, 개발자들이 자신의 코드를 대상으로 언제든 쉽게 적용할 수 있다.
  • 무료이다.
  • 설정 편집 및 import/export 기능으로 팀원간 공유가 쉽다.
  • 강력하고 정확하다 (C/C++ 툴들과 비교를 거부한다).
  • Ant 태스크(FindBugs, CodePro) 혹은 명령행 수행 기능(Eclipse Code Formatter)을 제공하여 원한다면 지속적 빌드(continuous integration/build) 환경에 통합할 수 있다.

이 글을 읽는 사람이 자바 개발팀에 속해 있고 팀 내에 코딩 규약이 없거나 문서로만 관리되고 있다면, 지금이라도 늦지 않았으니 새로운 세상을 경험해보길 바란다.

»  Substance: WordPress   »  Style: Ahren Ahimsa