»
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)
권장 리팩터링 순서
Oct 29th, 2009 by Wegra Lee

최근 다른 사람이 작성한 소스를 리뷰하면서 리팩토링할 일이 있었다. 깊이 있는 리팩토링까지는 해보지 못했으나, 남의 코드 리팩토링은 오랫만이라 간단히 노하우를 정리해본다.

정체를 알 수 없는 임의의 소스 덩어리를 받게 된다면, 대략 아래와 같은 순서로 리팩토링하는 것이 효율적이다.

1. Make duplications

리팩토링의 첫 단계에서는 중복 코드를 생산해낸다. 헐~! 이게 뭔 소린가? 중복 코드를 만드는 데는 조건이 있다. 로직의 상당 부분이 동일 혹은 유사해야 한다.

코드를 살펴보면, 결국 똑같은 혹은 유사한 일을 하면서 따로 노는 모습을 심심치 않게 발견할 수 있다. 각 파트별 담당 개발자가 선호하는 방식이 달라서일 수도 있고, 같은 개발자더라도 나중에 더 나은 API를 찾거나 더 간소한 알고리즘이 떠올랐을 수도 있다. 혹은 전에 짰던 부분과 흡사하다는 걸 떠올리지 못한체 처음부터 다시 짤 경우도 같은 결과가 나타난다.

코드가 척 보기에도 너무 다르다면 눈썰미가 본좌급이거나 운이 좋아야 발견할 수 있다. 따라서 누구나 기계적으로 할 수 있는 방법을 알아보자. 바로 이미 유사한 코드를 찾아서, 동일한 로직으로 만들 수 있는지 보는 것이다. 시장에는 중복 코드를 찾아주는 툴들이 이미 많이 나와있다. 이들 중 더 발전된 것은 유사 코드를 찾아주기도 한다. Java 라면 PMD 나 CheckStyle 같은 툴을 기본으로 사용하고 있으리라 믿는다. 이들 툴의 여러 기능중 duplicated code 검출 기능도 있으니 이를 활용하자. C/C++ 의 경우 언어 한계상 지원 툴이 상당히 빈약한데.. 다행히 위의 PMD 가 C++ 역시 지원해준다. 사용법은 여기를 참조하기 바라며.. 본인도 이를 이용해 작업을 진행하였다. 버그는 좀 보이지만, 간단히 사용하기에는 부족하지 않다.

결과: 같은 일을 처리하는 코드는 모두 동일하게 작성되었다.

2. Remove duplications

다음 단계가 바로 1단계에서 만들어 놓은 (혹은 그 전부터 존재하던) 중복 코드 제거이다. 코드가 중복되어 있다면 리팩토링도 중복으로 해야하고, 각각의 리팩토링 과정을 완전히 동일하게 하기는 상당히 말처럼 쉽지 않다. 중복 코드를 개별적으로 수정하면, 중복된 코드라는 것 자체를 인지하기 어려워진다. 각 코드 블럭의 리팩토링 순간 사이에 시간적 격차가 있으므로, 같은 로직을 보더라도 다르게 리팩토링할 가능성이 생긴다(기계가 아닌 사람인지라). 논리적으로 똑같이 하려해도 human error 가 발생할 여지도 다분하다.

중복 코드 식별 역시 ‘다행히도’ 사람이 하기엔 너무나 벅찬 일이다. 그래서 툴이 필요한데, 1단계에서 사용한 툴들이 바로 그들이다.

결과: 모든 코드는 unique 하다.

(곧 별도의 글을 통해 중복 코드 제거를 위한 몇 가지 패턴은 선보이도록 하겠다.)

3. Remove unused code (variable, code block, method, class, etc)

사용되지 않는 코드는 분석 중 논리 흐름을 방해하며, 자칫 의미 없는 코드를 리팩토링하며 시간을 낭비하게 된다.

대부분 범용 언어는 툴이 잘 지원해주고 있으나, 검출 범위와 사용성은 편차가 좀 있는 편이니 자신의 환경에 접목 가능한 툴들을 한 번 쯤 조사해보는 것을 추천한다.

결과: 모든 코드는 의미를 갖는다.

4. Reformat and rename

코드의 기초 가독성을 높이는 작업이다. 맞춤법에 맞게 코드를 수정하고(reformating), 의미가 명확히 통하는 용어를 사용하게 한다(renaming). Renaming 의 대상은 변수명, (내부) 메서드/클래스명 등이다.

결과: 코드는 의미가 명확한 단위 요소로 구성된다.

5. Refine logic (including extract/inline method)

코드의 논리 흐름을 가다듬는 단계이다. 작문에 비유해보자. 쉬운 용어들을 사용하더라도 산만하거나 애매모호한 글에선 핵심을 찾기 어렵다. 반면 논리 정연한 글은 하고자 하는 바를 쉽게 이해할 수 있다. 따라서 그 논리에 결점이 있다면 바로 파악하여 수정할 수 있고, 논리를 수정하거나, 설명을 보강하는데도 용이하다.

이 과정에서는  이어진 긴 문장을 의미가 분명한 적절한 단위로 나누거나, 큰 문단을 통채로 하나의 추상적 문장으로 교체(extract method)하고, if/else/switch 등으로 복잡해진 흐름을 같은 의미의 더 간결한 논리로 수정하는 등의 작업이 이루어진다. 최종 목표는 주석 없이도 글을 읽듯이 코드를 술술 읽을 수 있도록 하는 것이다.

결과: 코드를 자연스럽게 읽을 수 있다.

Refactoring Home Page 에 가면 수많은 리팩토링 기법들을 찾을 수 있다. 관리자인 Martin Fowler 도 양해를 구하고 있듯이, 설명과 관리에 그리 많은 신경을 쓰고 있진 않지만, 틈틈히 살펴보면 제법 도움이 될 것이다. 내 나름의 기법과 함께 위의 각 단계에서 많이 쓰임직한 것들을 묶어서 정리해보는 것도 재미있을 듯하니, 나중에 시간이 나면 한 번 시도해봐야겠다.

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