E D R , A S I H C RSS

Refactoring Discussion

Refactoring 과 관련된 토론, 질문/답변의 장으로 활용한다.

현재 게시판에 있는 글을 가져왔습니다.(조금 손봄) 의도에 맞지 않는 부분은 수정 바랍니다.


refactoring 의 전제 조건은, Refactoring 전 후의 결과가 같아야 한다. 라는 것이다.

Martin Folwer의 Refactoring p326(한서), 10장의 Parameterize Method 를 살펴보면 다음과 같은 내용이 나온다.


~cpp 
protected Dollars baseCharge() {
  double result = Math.min(lastUsage(),100) * 0.03;
  if (lastUsage() > 100) {
    result += (Math.min (lastUsage(),200) - 100) * 0.05;
  };
  if (lastUsage() > 200) {
    result += (lastUsage() - 200) * 0.07;
  };
  return new Dollars (result);
}


이것은 다음과 같이 대체될 수 있다.

protected Dollars baseCharge() {
  double result = usageInRange(0, 100) * 0.03;      //--(1)
  result += usageInRange (100,200) - 100) * 0.05;
  result += usageInRange (200, Integer.MAX_VALUE) * 0.07;
  return new Dollars (result);
}

protected int usageInRange(int start, int end) {
  if (lastUsage() > start) return Math.min(lastUsage(),end) - start;
  else return 0;
}


(1) 의 코드를 살펴보면 로직이 달라짐을 알 수 있다. 처음의 코드는 더 작은 값을 원할 뿐인데, 아래의 코드에서는 0 보다 작은 값은 가질 수 없게 되어있다. (예를 들어 lastUsage() 음수값을 지니면 결과가 달라진다)

"MatrinFowler의 추종자들은 lastUsage()가 0 이상인 값에 대해 동작하는것일테니 (코드를 보고 추정하면 그렇다) 당연한거 아니냐?" 라고 이의를 제할지는 모르지만, 이건 Refactoring 에서 한결같이 추구했던 "의도를 명확하게"라는 부분을 Refactoring이라는 도구에 끼워맞추다보니 의도를 불명확하게 한 결과를 낳은것 같다. (치의오류)

위의 (1)번 코드는 원래처럼 그대로 두거나, usageInRange(Integer.MIN_VALUE, 100)으로 호출하는게 맞을 듯 하다.

하지만 이것도 임시 방편일뿐, 위험은 존재한다. lastUsage()의 값이 Integer.MIN_VALUE 이거나, Integer.MAX_VALUE 라면? (이런일이 결코 일어날 수 없다고 장담할 수 있는가?)

-- 이선우


  • 코드의 의도가 틀렸느냐에 대한 검증 - 만일 프로그램 내에서의 의도한바가 맞는지에 대한 검증은 UnitTest Code 쪽으로 넘는게 나을 것 같다고 생각이 드네요. 글의 내용도 결국은 전체 Context 내에서 파악해야 하니까. 의도가 중시된다면 Test Code 는 필수겠죠. (여서의 '의도'는 각 모듈별 input 에 대한 output 정도로 바꿔서 생각하셔도 좋을듯)

로직이 달라졌을 경우에 대한 검증에 대해서는, Refactoring 전에 Test Code 를 만들것이고, 로직에 따른 수용 여부는 테스트 코드쪽에서 결론이 지어져야 될 것이라는 생각이 듭니다. (아마 의도에 벗어난 코드로 바뀌어져버렸다면 Test Code 에서 검증되겠죠.) 코드 자체만 보고 바로 잘못된 코드라고 단정짓 보단 전체 프로그램 내에서 의도에 따르는 코드일지를 생각해야 될 것 같다는 생각.

  • 예제 코드로 적절했느냐 - 좀 더 쉽게 의도에 맞게 Refactoring 되어진 것이 이 예제 바로 전인 Raise 이긴 하지만. 그리 좋은 예는 아닌듯 하다. usageInRange 로 빼내 위해 약간 일부러 일반화 공식을 만들었다고 해야 할까요. 그 덕에 코드 자체만으로 뜻을 이해하가 좀 모호해졌다는 부분에는 동감.

  • Refactoring의 Motivation - Pattern 이건 Refactoring 이건 'Motivation' 부분이 있죠. 즉, 무엇을 의도하여 이러이러하게 코드를 작성했는가입니다. Parameterize Method 의 의도는 'couple of methods that do similar things but vary depending on a few values'에 대한 처리이죠. 즉, 비슷한 일을 하는 메소드들이긴 한데 일부 값들에 영향받는 코드들에 대해서는, 그 영향받게 하는 값들을 parameter 로 넣어주게끔 하고, 같은 일을 하는 부분에 대해선 묶음으로서 중복을 줄이고, 추후 중복이 될 부분들이 적어지도록 하자는 것이겠죠. -- 석천


앞 글은 질문이라보다는 지적과 비판인 듯 합니다.

그 지적을 충분히 이해합니다.

리팩토링은 코드의 외부적 행동을 바꾸지 않으면서 내부적 구조를 변환하는 것을 말합니다. 여서 핵심은 "외부적 행동"에 있습니다. 저는 이 "외부적 행동"을 "의미있는/의도하는 외부적 행동"으로 봅니다 -- 어차피 우리에겐 코드 자체가 궁극이 아니고 그 코드가 현실에 드러내는 "시스템"이 궁극이 때문에.

그렇다면, 모든 상태 공간이 유지되어야 하는 것은 아닙니다. 어차피 원래 코드 자체가 인간의 아이디어를 "어설프게" 표현해 낸 것이고, 거서부터 이미 상태 공간은 좁혀지거나, 늘려져있습니다.

하지만 이런 논의를 떠나서 도대체 왜 리팩토링을 하는가 생각해볼 필요가 있겠습니다. 우리는 리팩토링을 "리팩토링이라는 것이 옳다 그르다"를 따지 위해 사용하는 것이 아니고, 우리의 프로그래밍에 도움이 되 위해 사용합니다.

리팩토링이라는 책을 읽을 때에는 논리적으로 옳거나 틀린 부분을 찾아내려고 노력하는 것보다, 나에게 도움이 되면 취하고 그렇지 않다면 나중을 약하는 것이 "프로그래머"에게 득이 되는 듯 합니다.

물론, 이론을 공부하는 전산학자에게는 좀 다르겠지요. 하지만, 누군가 말하듯이 "완벽한 이론"은 현실에서는 큰 가치가 없 마련입니다. 저는 리팩토링에서 "완벽한 이론"보다 "유용한 이론"을 찾습니다.

ps. 현실에서 정말 모든 상태 공간/계가 고대로 유지되는 리팩토링은 없습니다. 가장 대표적인 Extract a Method 조차도 모든 경우에 동일한 행동 유지를 보장할 수는 없습니다. 1+2가 2+1과 같지 않다고 말할 수 있습니다. 하지만 우리에게 의미있는 정도 내에서 충분히 서로 같다고 말할 수도 있습니다 -- 물론 필요에 따라 양자를 구분할 수도 있어야겠지만, 산수 답안 채점시에 1+2, 2+1 중 어느 것에 점수를 줄 지 고민할 필요는 없겠죠.

~cpp 
> { Refactoring(by Martin Fowler)의 잘못된 refactoring }
> { 선우(guest),  }

[snip]

> 
> 
> 위의 (1)번 코드는 원래처럼 그대로 두거나, usageInRange(Integer.MIN_VALUE, 100)으로
> 호출하는게 맞을 듯 하다.
> 
> 하지만 이것도 임시 방편일뿐, 위험은 존재한다.
> 
> lastUsage()의 값이 Integer.MIN_VALUE 이거나, Integer.MAX_VALUE 라면?
> (이런일이 결코 일어날 수 없다고 장담할 수 있는가?)
> 

우리에겐 프로그램의 옳음(correctness)이 일차적입니다. 이것은 UnitTest나 Eiffel 같은 DBC 언어로 상당한 정도까지 보장 가능 합니다.

그 다음에 비로소 리팩토링의 옳음을 따질 여유가 있습니다. 틀린/틀릴 수 있는 프로그램을 "옳게 리팩토링"하면 역시 틀린/틀릴 수 있는 프로그램이 나옵니다.

-- 김창준
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:27:52
Processing time 0.0168 sec