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 라면? (이런일이 결코 일어날 수 없다고 장담할 수 있는가?)

-- 이선우


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


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

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

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

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

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

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

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

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 언어로 상당한 정도까지 보장 가능 합니다.

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

-- 김창준
Retrieved from http://wiki.zeropage.org/wiki.php/RefactoringDiscussion
last modified 2021-02-07 05:27:52