»
S
I
D
E
B
A
R
«
중복 제거: Master Method
Nov 29th, 2009 by Wegra Lee

이번엔 널리 알려져서 누구나 다 알고 있을법한 (하지만 의외로 안지키는 사람도 많은) 방법을 소개한다.

Problems & Constraints

  1. 메서드를 오버로딩 해야한다.
  2. 입력 인자의 타입은 동일하며 그 유무만이 다르다.
    e.g.> doSomething(a, b) and doSomething(a, b, c)

Solution

가장 많은 인자를 받는 메서드를 Master Method 로 정하고, 다른 메서드에서는 부족한 인자를 default 값으로 채워 Master Method 를 호출한다. 생성자(constructor) 구현 시에도 동일한 규칙이 적용된다.

Examples

int read(byte[] buf)
{  .. // tens of lines
}

int read(byte[] buf,  int offset, int length)
{  .. // tens of lines
}

위에서 첫 번째 메서드는 두 번째 메서드의 특수한 형태로, offset 을 0으로, length 를 buf 의 size 로 해서 호출한 것과 완벽히 동일하게 동작한다. 따라서 첫 번빼 메서드는 아래와 같이 구현할 수 있다.

int read(byte[] buf)
{ return read(buf, 0, buf.size); // redirect to Master Method with appropriate parameters
}

Notes

Master Method 는 코드 중복 최소화 외에도, 오버로딩된 메서드들을 가지고 있는 클래스를 상속할 때 개발자 실수를 최소화 시킬 수 있다는 장점도 있다. 예를 들어, 오버로딩된 메서드 a, b, c 를 정의하고 있는 클래스 A 가 있다. 이 때 이를 상속한 클래스 B 에서 기능을 약간 변경하고자 할 때, Master Method 가 없다면 메서드 a’, b’, c’ 를 잊지 말고 모두 정의해줘야 한다. 실수로 무엇 하나를 빼먹었다면 의도치 않은 동작을 하게될 것이고, 그 원인을 찾기는 쉽지 않을 것이다. 하지만 만약 a 가 Master Method 였다면, a’ 만 재정의하면 문제를 미연에 예방할 수 있다. 따라서 Master Method 를 정하고, 어느 메서드가 Master 인지 명시하는 것이 좋다.

또, C++ 의 경우 Google 은 default parameter 를 흉내낼 목적으로는 사용하지 말기를 권고한다[4]. 그 단점을 아래와 같이 기술하고 있으니 참조하기 바란다.

One reason to minimize function overloading is that overloading can make it hard to tell which function is being called at a particular call site. Another one is that most people are confused by the semantics of inheritance if a deriving class overrides only some of the variants of a function. Moreover, reading client code of a library may become unnecessarily hard because of all the reasons against default function parameters.

If you want to overload a function, consider qualifying the name with some information about the arguments, e.g., AppendString(), AppendInt() rather than just Append().


References

  1. 권장 리팩터링 순서Recommended Sequence for Refactoring (wegra.org)
  2. 중복 제거: God Method (wegra.org)
  3. 중복 제거: Convert & Redirect (wegra.org)
  4. Google C++ Style Guide : Function Overloading (Google)
중복 제거: Convert & Redirect
Nov 26th, 2009 by Wegra Lee

계속해서 Recommended Sequence for Refactoring[1] 의 가장 첫 단계인 Remove duplications 에 적용할 수 있는 리펙토링 기법 하나를 더 소개한다. 먼저 소개한 God Method[2] 와는 얼핏 유사한 상황 같지만 분명한 차이가 있다.

Problems & Constraints

  1. 복수의 동일 목적 함수가 입력 인자의 타입만이 다르다.
  2. 입력 인자들 사이에선 서로 타입 변환이 가능하다. 즉 값(value)은 같으나 표현 형태만 다르다.

Solution

하나의 마스터 함수만 구현은 유지하고, 다른 함수들은 받은 입력의 타입만 변화시켜 마스터 메소드를 호출한다.

Examples

다음의 두 함수는 다른 입력을 받지만 목적은 같다.

toDate(long  timestamp)
{  .. // tens of lines
}

toDate(DateTime dateTime)
{  .. // tens of lines
}

long 타입의 timestamp 를 DateTime 으로 변환시켜 (convert) Date 용 함수를 호출한다.

toDate(long timestamp)
{  DateTime dateTime = new DateTime(timestamp); // convert
return ConvertDateTimeToServerDate(dateTime); // then, redirect
// no more duplicated lines
}

toDate(DateTime dateTime) // master method
{  .. // tens of lines (unchanged)
}


References

  1. 리팩터링 권장 순서 (wegra.org)
  2. 중복 제거: God Method (wegra.org)
중복 제거: God Method
Nov 25th, 2009 by Wegra Lee
1.1. Problems & Constraints복수의 유사 목적 함수가 존재하고 구현 로직이 거의 동일하다. 극히 일부만 다른 코드가 여러 함수에 중복되어 있어, 향후 로직/결함 수정 시 human error 를 유발시키기 쉽다.입력 인자 중 일부가 서로 베타적이어서 하나로 합쳐서 제공하거나, 하나의 함수에서 다른 함수를 직접적으로 호출할 수 없다.Public API 가 이미 고정되어 있어, 서로 다른 인자들을 묶는 공통 타입을 만들고 함수들을 하나로 통합시키는 방법을 사용할 수 없다.혹은, 공통 타입이 존재하나 그 중 일부에 대해선 아무런 지원 계획이 없어, 이를 명확히 알리기 위해 복수의 독립적인 API 를 제공하고 싶다.1.2. Solution하나의 God Method(모든 상황에 필요한 인자의 합집합 받으며, 입력 조합에 반응하여 동작함)을 ‘내부’에 두고, 공개 메서드에서는 인자를 적절히 조합하여 이 God Method 를 호출한다.1.3. CautionsGod Method 는 하나의 기능에만 특화된 메서드에 비해 로직이 복잡하므로, 일반적인 경우에는 지양해야할 어프로치다.’중복량 vs. 복잡도 증가’ 를 잘 판단하여 적용 여부를 결정해야 하며, 적용하더라도 내부(private) 메서드에 한정 것이 좋다.1.4. Example아래의 세 함수들은 입력 인자 중 하나만이 다르며, 구현에서도 총 70라인 중 단 한 라인(CreateHttpRequestN 호출부)만이 다르다.DoSomething(..){       ..requestString = CreateRequestString(.., null, null,  ..);..}DoSomething(Circle circle, ..){       ..requestString = CreateRequestString(.., null, circle,  ..);..}DoSomething(Rectangle rectangle, ..){       ..requestString = CreateRequestString(.., rectangle, null,  ..);..}CreateHttpRequestN 함수에 필요한 모든 인자를 받아들이니 God Method 를 만들어 구현을 하나로 모은다.DoSomething_God // 새로 추가된 God Method(       Rectangle rectangle, Circle circle, ..){       ..requestString = CreateRequestString(.., rectangle, circle,  ..); // 받은 인자를 bypass..}DoSomething(..){       // 전후 중복 라인 사라짐return DoSomething_God(null, null,  ..); // God Method 호출}DoSomething(Circle circle, ..) const{return DoSomething_God(null, circle,  ..);}DoSomething(Rectangle rectangle, ..) const{return DoSomething_God(rectangle, null,  ..);}God Method 를 자세히 보면, public API 와 달리, Rectangle 와 Circle 인자를 포인터로 받고 있다. 이는 Rectangle, Circle 중 아무것도 사용하지 않는 메서드를 포용하기 위해서이다.
Problems & Constraints
복수의 유사 목적 함수가 존재하고 구현 로직이 거의 동일하다. 극히 일부만 다른 코드가 여러 함수에 중복되어 있어, 향후 로직/결함 수정 시 human error 를 유발시키기 쉽다.
입력 인자 중 일부가 서로 베타적이어서 하나로 합쳐서 제공하거나, 하나의 함수에서 다른 함수를 직접적으로 호출할 수 없다.
Public API 가 이미 고정되어 있어, 서로 다른 인자들을 묶는 공통 타입을 만들고 함수들을 하나로 통합시키는 방법을 사용할 수 없다.
혹은, 공통 타입이 존재하나 그 중 일부에 대해선 아무런 지원 계획이 없어, 이를 명확히 알리기 위해 복수의 독립적인 API 를 제공하고 싶다.
Solution
하나의 God Method(모든 상황에 필요한 인자의 합집합 받으며, 입력 조합에 반응하여 동작함)을 ‘내부’에 두고, 공개 메서드에서는 인자를 적절히 조합하여 이 God Method 를 호출한다.
Cautions
God Method 는 하나의 기능에만 특화된 메서드에 비해 로직이 복잡하므로, 일반적인 경우에는 지양해야할 어프로치다.
‘중복량 vs. 복잡도 증가’ 를 잘 판단하여 적용 여부를 결정해야 하며, 적용하더라도 내부(private) 메서드에 한정 짓는 것이 좋다.
Example
아래의 세 함수들은 입력 인자 중 하나만이 다르며, 구현에서도 총 70라인 중 단 한 라인(CreateHttpRequestN 호출부)만이 다르다.
DoSomething(..)
{       ..
requestString = CreateRequestString(.., null, null,  ..);
..
}
DoSomething(Circle circle, ..)
{       ..
requestString = CreateRequestString(.., null, circle,  ..);
..
}
DoSomething(Rectangle rectangle, ..)
{       ..
requestString = CreateRequestString(.., rectangle, null,  ..);
..
}
CreateHttpRequestN 함수에 필요한 모든 인자를 받아들이니 God Method 를 만들어 구현을 하나로 모은다.
DoSomething_God // 새로 추가된 God Method
(       Rectangle rectangle, Circle circle, ..)
{       ..
requestString = CreateRequestString(.., rectangle, circle,  ..); // 받은 인자를 bypass
..
}
DoSomething(..)
{       // 전후 중복 라인 사라짐
return DoSomething_God(null, null,  ..); // God Method 호출
}
DoSomething(Circle circle, ..) const
{
return DoSomething_God(null, circle,  ..);
}
DoSomething(Rectangle rectangle, ..) const
{
return DoSomething_God(rectangle, null,  ..);
}
God Method 를 자세히 보면, public API 와 달리, Rectangle 와 Circle 인자를 포인터로 받고 있다. 이는 Rectangle, Circle 중 아무것도 사용하지 않는 메서드를 포용하기 위해서이다.

지난달 포스팅한 Recommended Sequence for Refactoring[1] 의 가장 첫 단계인 Remove duplications 에 적용할 수 있는 리펙토링 기법이다.

곧 한 두개의 기법이 추가로 뒤따를 예정이다.

Problems & Constraints

  1. 복수의 유사 목적 함수가 존재하고 구현 로직이 거의 동일하다. 극히 일부만 다른 코드가 여러 함수에 중복되어 있어, 향후 로직/결함 수정 시 human error 를 유발시키기 쉽다.
  2. 입력 인자 중 일부가 서로 베타적이어서 하나로 합쳐서 제공하거나, 하나의 함수에서 다른 함수를 직접적으로 호출할 수 없다.
  3. Public API 가 이미 고정되어 있어, 서로 다른 인자들을 묶는 공통 타입을 만들고 함수들을 하나로 통합시키는 방법을 사용할 수 없다.
  4. 혹은, 공통 타입이 존재하나 그 중 일부에 대해선 아무런 지원 계획이 없어, 이를 명확히 알리기 위해 복수의 독립적인 API 를 제공하고 싶다.

Solution

하나의 God Method(모든 상황에 필요한 인자의 합집합 받으며, 입력 조합에 반응하여 동작함)을 ‘내부’에 두고, 공개 메서드에서는 인자를 적절히 조합하여 이 God Method 를 호출한다.

Cautions

God Method 는 하나의 기능에만 특화된 메서드에 비해 로직이 복잡하므로, 일반적인 경우에는 지양해야할 어프로치다.

‘중복량 vs. 복잡도 증가’ 를 잘 판단하여 적용 여부를 결정해야 하며, 적용하더라도 내부(private) 메서드에 한정 짓는 것이 좋다.

Examples

아래의 세 함수들은 입력 인자 중 하나만이 다르며, 그에 따라 달라지는 코드 라인수는 전체 코드 구현에서 비중이 작다. 혹은 중복 코드의 절대량이 많다.

public Object DoSomething(.. /* series of params */)
{  .. // tens of lines
MakeSomething(.., null, null,  ..);
.. // tens of lines
}

public Object DoSomething(Apple apple, .. /* series of params */)
{  .. // tens of lines
MakeSomething(.., apple, null,  ..);
.. // tens of lines
}

public Object DoSomething(Orange orange, .. /* series of params */)
{   .. // tens of lines
MakeSomething(.., null, orange,  ..);
.. // tens of lines
}

DoSomething 함수에 필요한 모든 인자를 받아들이니 God Method (DoAnything) 를 만들어 구현을 하나로 모은다.

private Object DoAnything(Apple apple, Orange orange, ..) // newly introduced god method
{  .. // tens of lines
MakeSomething(.., apple, orange, ..); // bypass the given params
.. // tens of lines
}

public Object DoSomething(..)
{  // no more duplicated lines
return DoAnything(null, null,  ..); // invoke the god method
}

public Object DoSomething(Apple apple, ..)
{
return DoAnything(apple, null,  ..);
}

public Object DoSomething(Orange orange, ..)
{
return DoAnything(null, orange,  ..);
}


References

  1. 권장 리팩터링 순서 (wegra.org)
  2. 중복 제거: Convert & Redirect (wegra.org)
[개발자 역량] 난 디버깅 킹왕짱
Sep 30th, 2009 by Wegra Lee

디버깅을 잘 한다.

나는 과거 종종 그런 얘기를 들은 적도 있고, 또 스스로도 제법 잘 한다고 생각한다. (최근 몇 년간은 직접적인 개발과는 거리가 좀 있는 업무들을 주로 수행해 왔다.)

그런데 ‘디버깅을 잘한다’는 말에는 어떤 의미가 포함되어 있을까.

아무 개념 없이 엉터리로 짠 남의 코드에서 산발적으로 발생하는  미지의 문제를 소스 코드만 추적해서 잘아낼 수 있으면 디버깅을 잘 하는 것일까? 분명 이런 능력은 대단한 것겠지만.. 현실적으로는 ‘운이 좋았다’ 이상을 기대하긴 어렵다. 주변에서도 능력 있는 개발자가 다른 프로젝트 합류 후 ‘내가 할 수 있는 일이 없다’고 푸념하고 경우를 몇 번 보았다. 이유는 몇 년 동안 들여다보던 사람이 아니면 도저히 손 댈 엄두가 안나는 코드를 사용하고 있기 때문이었다. 종속된 모듈과 프로젝트가 많고 덩치도 만만치 않아 새로 깔끔히 만들 수도 없다는 것이다. 동작하는 코드를 새로 만들 시간을 달라는 요청은 관리자들에게 쉽게 묵살당한다.

결국 아무리 능력 좋은 개발자도 대상 소프트웨어의 상태/환경에 따라 발휘되는 능력은 극심한 편차를 보이기 마련이다. 좀 더 정리하여 이야기하면, 커버할 수 있는 임계점 이하까지는 쉽게쉽게 문제를 찾아내지만, 그 한계를 넘어서면 거의 컨트롤이 불가하여 평범한 개발자와 별 차이를 보이지 못하게 된다. 이는 인간이 머릿속에 한 번에 담고 분석/추적할 수 있는 정보량이 그리 많지 않기 때문이다.

중요한 것은 디버깅 대상을 단순 명료하게 유지하는 능력이다. 또는 복잡한 대상을 단순하게 변형하는 능력이다. 구체적인 방법들을 떠올려보자.

  • 컨벤션을 잘 따른다. 예외 상황을 신경쓸 필요가 없으므로 개발자는 context 에 집중할 수 있게 된다.
  • 읽을 수 있는 코드를 작성한다. 해석하는 것과 읽는 것의 차이를 생각해보자.
  • Refactoring 을 통해 코드를 항시 명쾌하게 유지한다.
  • Testability 를 생각해 디자인한다. 문제가 명확하지 않다면 테스트를 통해 하나하나 문제 쉽게 좁히기 쉽다. 디자인이 잘 되어 있다면 모듈 하나하나를 분리해 독립적으로 검증할 수도 있을 것이다. TDD 같은 실천법을 활용하는 것도 적극 추천한다.

이상은 순수한 소스 코드 & 설계의 관점에서의 디버깅 능력 향상 방법들이었다. 유지보수를 위한 지침과 동일하다고 볼 수 있다. 그럼 다른 관점에서는 또 어떤 방법들이 있을까?

  • 다양한 결함 분석 툴을 활용해 잡을 수 있는 문제들을 미리 잡아 놓는다. 사소한 문제들, 혹은 툴이 잘 잡아주는 결함들을 미리 제거해 두면 잠재한 문제의 범위가 좁혀지고, 그만큼 더 집중할 수 있게 된다. (디버깅은 범죄 수사와 마찬가지로, 문제 범위를 좁히고 그에 집중해 파헤쳐보고, 잘못 집었다 싶으면 되돌아가 다른 가능성에 집중에 파헤쳐보는 스타일로 진행된다.)
  • 평소의 좋은 로깅 습관이 디버깅을 높기도 한다. 더욱 효율적인 로깅을 원한다면 역시 좋은 로깅 솔루션을 도입하는 것이 좋다. 주변에 찾아보면 다양한 로깅 프레임워크들을 쉽게 구할 수 있을 것이다. 이들은 로그를 다양한 목적에 맞게 구분짓고, 상황에 맞게 조작/필터링 등을 쉽게 할 수 있도록 도와준다.
  • 디버깅 툴의 파워 유저가 되자. 이러저러한 모든 것을 만족해도 풀지 못하는 문제가 있을 수 있다. 또는 어쩔 수 없이 좋지 않는 코드를 디버깅해야 하는 경우도 많다. 따라서 디버깅 툴을 확실히 활용할 수 있는 능력은 기본으로 갖춰두어야 한다.
  • 개발툴/언어 외적인 보조 방법을 활용한다. 예를 들어 aspect 언어를 활용할 줄 안다면 디버거로 쉽게 잡아내지 못하는 복잡한 상황까지도 아주 간단히 연출해낼 수 있다.

이상은 소스 코드 외적인 방법이었지만, 개인이 충분히 할 수 있는 것들이다. 그럼 마지막으로 소셜 개발자라면 또 어떠한 방법들을 생각해볼 수 있을지 나열해보겠다.

  • 유사 도메인의 구루, 혹은 전문 커뮤니티 활용한다.
    오늘날 대부분의 프로젝트는 다수의 외부 라이브러리/플랫폼을 활용하기 때문에, 이 능력의 중요성은 과거보다 훨씬 커졌다. 내가 겪는 대부분의 문제는 누군가 이미 겪어서 그 해결책(혹 ‘어쩔 수 없다’일지라도)이 공개되어 있다. 대답을 찾지 못하더라도 질문을 올리면 늦어도 몇 일 내에 만족할만한 답을 찾을 수 있을 것이다.
    테스트 방법이나 설계에 대한 조언도 쉽게 구할 수 있다. 물론 앞서의 경우보다는  훨씬 복잡한 이슈라 자신이 처한 상황을 명확히 기술해주어야 좋은 답을 얻을 가능성이 그만큼 높아진다.
    중요한 부분이 아니라면 소스 코드를 공개해
  • 만든 제품에 대해 피드백을 받을 수 있는 커뮤니티를 만들고, 가능하다면 오픈소스화 한다.
    직접 디버깅 하는 것만이  꼭 능력은 아니다. 더 궁극적인 목적은 결함 없는 제품을 만드는 것이 아닌가? 그러려면 프로젝트가 남의 관심을 받을 만큼 충분히 유용해야하고, 그들의 요구사항을 적극 반영하며 기여자(contributor)들과 지속적으로 좋은 관계를 유지해야 한다.

디버깅 하나 얘기하면서 참 많은 것을 훑었다. 오버라고 생각할 수도 있겠지만, 개발이라는 것이 그리 딱딱 떨어지는 것이 아니고.. 이것이 현실이다. 오히려 위에 나열할 것 외에도 수많은 요소들이 훌륭한 디버거를 만드는 관여될 것이 분명하다.

자!! 그럼 지금까지 나온 좋은 디버거로서의 자질은 정리해보자.

  • 명확하고 논리 정연한 코드 작성 능력
  • 꾸준한 코드 관리
  • Testable 디자인 능력
  • 다양한 분석 툴 활용 능력
  • 좋은 로깅 습관 & 로깅 툴 활용 능력
  • 디버깅 툴 활용 능력
  • 주변 전문가, 커뮤니티, 웹 상의 정리된 지식 활용 능력
  • 외부 기여자를 끌어모아 적극적인 피드백을 이끌어 내는 능력
TDD, Refactoring and Scrum – Part II
Sep 9th, 2009 by Wegra Lee

내가 지금까지 접해본 여러 이론/실천법 들을 통틀어 가장 맘에 드는 것들을 세 가지 고르라면 TDD, Refactoring, Scrum, 이렇게 세 가지를 뽑겠다. (우리 조직에선 이 중 단 하나도 하지 않고 있어서 참으로 답답하고 안타깝다.) 다른 사람들과는 좀 다른 관점일 수 있겠는데, 이들에 애착이 생기는 내 나름의 이유는 간략히 정리해본다.

  • Part I – TDD
  • Part II – Refactoring (this article)
  • Part III – Scrum (not available yet)

Refactoring – Refactoring은 현재의 잘못된 점이나 부족한 부분을 식별하여 더 나은 형태로 개선하는 작업이다. 따라서 Refactoring 은 일종의 회고이며, 그 주된 관점은 설계/코드의 효율성, 가독성, 명확성 등이다. 사람이나 조직이 회고를 통해 교훈을 얻고 성장하듯, 개발자도 Refactoring 을 통해 역량을 키워나간다. 여건이 되지 않으면 혼자서라도 틈틈히 수행해야 하며, 다행히 지도해줄 경험 많은 동료가 함께한다면 그 효과는 배가된다. 팀의 리더라면 팀원간의 이런 교류가 자연스레 일어날 수 있도록 이끌어 주어야 한다.

Refactoring은 개발 기간 내내 리듬을 가지고 정기적으로 수행되어야 한다. 리펙토링 문화가 충분히 자리잡지 못했다면 프로세스에 명시해두는 것이 좋다. 시점은 매 반복 주기(iteration/milestone)가 끝나고 다음 주기가 시작되는 시점이다. 새로운 기능을 넣기 전에 기반을 단단히 다진다거나, 구조적 문제를 조기에 다잡아 추후 대규모 수정을 예방한다는 점도 물론 중요하지만, 사람(개발자)의 역량 성장 측면에서도 반드시 요구되는 프로세스이다.

배우고 경험하고 배우고 경험하고.. 두 과정이 주기적으로 반복되어야 머리로 익힌 것이 체화되고, 그것이 실생활에서 어떤 가치가 있는지 진정한 의미를 깨닫게 된다. 처음에 좋아보였던 것도 생각지 못했던 숨은 이슈나 과소 평가했던 측면 때문에 실제로는 역효과가 더 큰 경우도 많다. 또한 체험 없이 계속해서 머리로만 익히다 보면 모든 것이 쉽고 단순해 보이는 경향이 있다. 이렇게 해서 다음 리펙토링을 수행할 때에는 이전 수행 시보다 향상된 시각으로 구조를 바라볼 수 있게 된다. 설계와 코드를 보는 눈이 향상되고, 시행착오가 줄어들고, 다양한 경험을 통해 확신을 가질 수 있다. 주기적인 Refactoring은 프로젝트가 진행될 수록 Refactoring의 품질과 그것을 수행하는 개발자 역량을 함께 성장시킬 수 있는 멋진 제도적 장치가 될 것이다.

Refactoring 시 주의점

리펙토링 시 가장 신경써야할 부분은 역시 side effect 다. 때문에 전문 툴 활용과 풍부한 테스트 케이스가 준비가 큰 도움을 준다. 툴의 Refactoring 지원 정도는 언어별로 편차가 심하다. Small Talk, Java 언어는 Refactoring 이 지원되지 않는 툴은 오래전에 자취를 감췄을 정도로 대중화되어 있고, C#, Visual Basic 등은 중간 정도.. C/C++ 계열은 아직 많이 부족하며 발전 속도도 더디다.

툴이 해주는 일은 다양한 리펙토링 기법들을 마우스 클릭 몇 번으로 관련된 모든 코드를 자동 변경해주는 편리함과, 이 때 발생할 수 있는 구조적 충돌을 사전 검증하여 side effect 을 최소화시켜주는 효과가 있다. 소프트웨어 규모가 커지면 수십명이 일주일 동안 해야할 변경량을 한 사람이 반나절 만에 처리할 정도로 극적인 효과를 볼 수도 있다.

툴이 구조적인 side effect를 예방해준다면, 테스트 케이스는 기능적인 side effect 를 예방해준다. 테스트 케이스가 충분치 않다면 변경의 잘못된 영향을 먼 훗날에야 발견하게 되고, 무엇이 그 원인이었는지 파악하기 어렵게 된다.

Refactoring 과 TDD
테스트의 대표 주자는 역시 TDD 이다. 현실적으로 거의 불가능하지만, TDD 를 100% 잘 따랐다면 작성된 코드 중 테스트 케이스가 커버하지 못하는 영역은 존재하지 않는다. 따라서 Refactoring 을 가장 안전하게 하기 위한 좋은 지침은 TDD 를 잘 따르는 것이다. 역으로 TDD 의 과정에서 새로운 테스트 케이스(요구사항)가 추가되면, 그 케이스를 만족시키기 위해 기존 코드를 Refactoring 해야한다. 이렇든 이 둘은 거의 바늘과 실 관계이다. 다만 Refactoring 없는 TDD 는 그 효과가 급감하지만, TDD 없이도 테스트 케이스는 작성할 수 있으므로 Refactoring 은 독자적으로도 충분한 가치가 있다.

TDD, Refactoring and Scrum – Part I
Aug 31st, 2009 by Wegra Lee

내가 지금까지 접해본 여러 이론/실천법 들을 통틀어 가장 맘에 드는 것들을 세 가지 고르라면 TDD, Refactoring, Scrum, 이렇게 세 가지를 뽑겠다. (우리 조직에선 이 중 단 하나도 하지 않고 있어서 참으로 답답하고 안타깝다.) 다른 사람들과는 좀 다른 관점일 수 있겠는데, 이들에 애착이 생기는 내 나름의 이유는 간략히 정리해본다.

TDD (Test Driven Development) – 보통 언급은 되지만 크게 부각되지 않는 장점. 종종 설명에서 아예 빠지기도 한지만, 내가 가장 높게 쳐주는 TDD의 장점은 바로 개발자들의 설계 능력의 향상’이다. 테스트는 곧 사용자의 사용 패턴을 시뮬레이션 한 것으로, 쉽게 테스트하기 위한 고민은 바로 쉽게 사용하기 위한 인터페이스를 고민하는 것이다. 이 과정을 몇 개월만 반복하더라도 테스트하기 쉬운 설계(testable design)와 그 반대의 케이스의 대표적인 사례(testable design anti-patterns)들을 한 다발 발견해낼 수 있을 것이다. 디자인 공부한답시고 UML 다이어그램 책이나 디자인 패턴 책을 열심히 파는 것보다, 자신이 개발중인 코드를 보고 테스트를 어떻게 할까를 고민해보는 것이 훨씬 효과만점이라고 자신있게 주장할 수 있다.

업계에선 이 부분이 잘 수행되지 않고 있는데, ‘테스트 == 설계’ 라는 관계가 잘 부각되지 않기 때문으로 보인다. (아쉽게도 팀원의 역량 향상을 등한시하는 매니저도 제법 있다.) 설계는 중요시하면서도 테스트는 등한시 하는 것이다. 개발자에게 설계를 잘 하라면서도 테스트는 외부 인력에 의존하는 경우가 너무 많다. 테스터들도 자신이 설계를 검증해야 한다는 것을 자각하지 못하고 있고, 혹 있다손 치더라도 설계에 대해 이러쿵 저러쿵 하는 것을 개발팀에서 그리 달가워하지 않는 경우도 종종 있다. 그리고 테스트를 너무 늦게 시작해 설계 문제가 드러나도 일정상 수정하지 못하는 안타까운 상황도 흔히 발생한다.

기본적인 테스트(유닛, 인티그레이션)는 가능한 모두 개발자들 스스로 수행하도록 하고, 외부 인력은 신뢰성, 고가용성, 보안, 스레드 안정성 등 전문 분야에 대해서 검증을 수행해 주는 것이 효율적이다. 개발자 테스트 단계에서도 단순 기능 검증이 아닌 테스트 용이성에 큰 비중을 주도록 꼭! 주지시켜야 한다.

이렇게 훈련된 개발자들은 나중에 혹 테스트 케이스를 작성할 시간이 부족하더라도 처음부터 상대적으로 수준 높은 설계를 만들어낸다. 신입사원들에게 별 쓸모 없는 프로세스나 테크닉 교육은 대폭 줄이고 TDD 나 한 두 달 시켜주면 현업에 훨씬 도움이 될 것이다. 대학과 확원에서도 이론과 테크닉에만 치우지지 말고 이런 실질적인 교육과 훈련에 좀 더 투자를 해야 한다.

다시 한 번 강조하는 것은 ‘개발자‘의 역량 향상이다. 그 효과는 영구적이고 사기 증진에도 많은 도움이 된다. 제품의 설계 향상 같은 단기적인 이점은 그에 비해 훨씬 가치가 적다.

TDD 에 관심이 있는 사람이라면 BDD (Behavior Driven Development – Click Here) 도 한 번쯤 꼭 참고해보도록 하자. TDD 의 발전 형태로 볼 수 있는데, 무엇을 어떤 식으로 테스트해야할 지에 대한 좋은 가이드라인을 제시해준다. 개념을 확실히 인지하고 있다면 전용 툴 지원  없이 전통적인 Unit Test Framework 만으로도 BDD 를 활용할 수 있다.

단순 API 테스트라면 boundary value analysis (Click Here) 와 equivalence partitioning (Click Here) 정도는 확실히 알아두자. 이 둘을 조합하면 많은 경우에 있어 기계적으로 test case 들을 뽑아낼 수 있다. SE, SQA, 혹은 전문 테스터로서 역량을 원한다면 이곳 (Click Here) 도 좋은 레퍼런스가 될 것이다.

»  Substance: WordPress   »  Style: Ahren Ahimsa