함수 추출하기는 마틴 파울러가 가장 많이 사용하는 리팩터링 중 하나이다.
코드 조각을 찾아 무슨일을 하는지 파악한 다음, 독립된 함수로 추출하고, 목적에 맞는 이름을 붙인다.
코드를 언제 독립된 함수로 묶어야 할지에 관한 의견은 수없이 많다.
먼저, 길이를 기준으로 삼을 수 있다.
가령 함수 하나가 한 화면을 넘어가면 안 된다는 규칙을 떠올릴 수 있다.
재사용성을 기준으로 할 수도 있다.
두번이상 사용될 코드는 함수로 만들고, 한 번만 쓰이는 코드는 인라인 상태로 놔두는 것이다.
하지만 저자는 "목적과 구현을 분리"하는 방식이 가장 합리적인 기준이라고 한다.
코드를 보고 무슨일을 하는지 파악하는데 한참이 걸린다면, 그 부분을 함수로 추출한 뒤, 무슨 일에 걸맞는 이름을 짓는다
이렇게 해두면 나중에 코드를 다시 읽을 때 함수의 목적인 눈에 확 들어오고, 본문 코드에 대해서는 더 이상 신경쓸 일이 거의 없다.
이 원칙을 적용한 뒤로는 함수를 아주 짧게, 대체로 단 몇줄만 담도록 작성하는 습관이 생겼다.
내 경험상 함수 안에 들어갈 코드가 5~6줄을 넘어갈 때마다 슬슬 냄새를 풍기기 시작했고, 단 한 줄 짜리 함수를 만드는 일도 적지 않았다.
길이가 그리 중요하지 않다는 사실을 깨닫게 된 계기는 켄트 벡이 보여준 오리지널 스몰토크 시스템이었다.
당시 스몰토크는 흑백 시스템에서 실행됐다.
그래서 화면에서 텍스트나 그래픽을 강조하려면 해당 부분의 색상을 반전시켜야 했다.
스몰토크의 그래픽스 클래스에는 이 목적으로 쓰이는 highlight 메서드가 있었는데,
구현 코드를 보니 단순히 reverse라는 메서드만 호출하고 있었다.
메서드 이름이 구현 코드보다 길었지만, 그건 문제가 되지 않았다.
코드의 목적과 구현 사이의 차이가 그만큼 컸기 때문이다.
함수를 짧게 만들면 함수 호출이 많아져서 성능이 느려질까 걱정하는 사람도 있다.
저자가 젊던 시절에는 간혹 문제가 되긴했지만, 요즘은 그럴일이 없다고 한다.
오히려 함수가 짧으면 캐싱하기가 더 쉽기 때문에, 컴파잉러가 최적화하는 데 유리할 때가 많다.
성능 최적화에 대해서는 항상 일반지침을 따르도록 하자.
이러한 짧은 함수의 이점은 이름을 잘 지어야만 발휘되므로, 이름 짓기에 특별히 신경 써야 한다.
이름을 잘 짓기까지는 어느 정도 훈련이 필요하다.
하지만 일단 요령을 터득한 후에는 별도 문서 없이 코드 자체만으로 내용을 충분히 설명되게 만들 수 있다.
긴 함수에는 각각의 코드 덩어리 첫머리에 그 목적을 설명하는 주석이 달려 있을 때가 많다.
해당 코드 덩어리를 추출한 함수의 이름을 지을 때, 이 주서을 참고하면 도움이 될 것이다.
절차
1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다. (어떻게 하는지가 아닌 무엇을 하는지 드러나야 한다.)
=> 대상 코드가 함수 호출문 하나처럼 매우 간단하더라도 함수로 뽑아서 목적이 더 잘드러나는 이름을 붙일 수 있다면 추출한다.
이런 이름이 떠오르지 않는다면 함수로 추출하면 안된다는 신호다.
하지만 추출하는 과정에서 좋은 이름이 떠오를 수도 있으니, 처음부터 최선의 이름부터 짓고 시작할 필요는 없다.
일단 함수로 추출해서 사용해보고 효과가 크지 않으면 다시 원래 상태로 인라인해도 된다.
그 과정에서 조금이라도 깨달은게 있다면 시간 낭비는 아니다.
중첩 함수를 지원하는 언어를 사용한다면, 추출한 함수를 원래 함수 안에 중첩시킨다.
그러면 다음 단계에서 수행할 유효범위를 벗어난 변수를 처리하는 작업을 줄일 수 있다.
최적화를 할 때에는 다음 2 규칙을 따르기 바란다. 첫번째, 하지마라. 두번째, 아직 하지마라. - M,A, 잭슨
원래 함수의 바깥으로 꺼내야할 때가 오면 어제든 함수 옮기기를 적용하면된다.
2. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여넣는다.
3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다.
=> 원본 함수의 중첩 함수로 추출할 때는 이런 문제가 생기지 않는다.
=> 일반적으로 함수에는 지역변수와 매개변수가 있기 마련이다.. 가장 일반적인 처리방법은 이런 변수를 모두 인수로 전달하는 것이다. 사용은 하지만 값이 바뀌지 않는 변수는 대체로 이렇게 처리할 수 있다.
=> 추출한 코드에서만 사용하는 변수가 추출한 함수 밖에 선언되어 있다면, 추출한 함수 안에서 선언하도록 수정한다.
=> 추출한 코드 안에서 값이 바뀌는 변수 중에서 값으로 전달되는 것들은 주의해서 처리한다.
=> 이런 변수가 하나뿐이라면 추출한 코드를 질의 함수로 취급해서 그 결과를 해당 변수에 대입한다.
=> 떄로는 추출한 코드에서 값을 수정하는 지역 변수가 너무 많을 수 있다. 이럴 때는 함수 추출을 멈추고, 변수 쪼개기나 임시 변수를 질의 함수로 바꾸기와 같은 다른 리팩터링을 적용해서 변수를 사용하는 코드를 단순하게 바꿔본다. 그런 다음 함수 추출을 다시 시도한다.
4. 변수를 다 처리했다면 컴파일한다.
=> 컴파일되는 언어로 개발 중이라면 변수를 모두 처리하고 나서 한번 컴파일해보자. 제대로 처리하지 못한 변수를 찾는 데 도움될 때가 많다.
5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다. (추출한 함수로 일을 위임한다.)
6. 테스트한다.
7. 다른 코드에 방금 추출한 것과 똑같거나 비슷한 코드가 없는지 살핀다. 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다. (인라인 코드를 함수 호출로 바꾸기)
여기서 Clock.today는 저자가 Clock Wrapper라고 부르는 것으로, 시스템 시계를 감싸는 객체다.
저자는 Date.now()처럼 시스템 시간을 알려주는 함수는 직접 호출하지 않는다.
직접 호출하면 테스트할 때마다 결과가 달라져서 오류 상황을 재현하기가 어렵기 때문이다.
banner를 출력하는 코드는 다음과 같이 간단히 추출할 수 있다.
그냥 해당 코드를 잘라내서, 새 함수에 붙이고, 원래 자리에 새 함수 호출문을 넣으면 된다.
마찬가지로 세부 사항을 출력하는 코드도 간단히 추출할 수 있다.
여기까지만 보면 함수 추출 리팩터링이 너무 간단하다고 여길 수 있다.
하지만 더 까다로울 때가 있다.
지금은 printDetails()가 printOwing()에 중첩되도록 정의되어있는데,
이렇게 하면 추출한 함수에서 printOwing()에 정의된 모든 변수에 접근할 수 있다.
하지만 중첩 함수를 지원하지 않는 언어에서는 불가능한 방법이다.
그럴 떄는 함수를 최상위 수준으로 추출하는 문제로 볼 수 있다.
따라서 원본 함수에서만 접근할 수 있는 변수들에 특별히 신경써야한다.
원본 함수의 인수나 그 함수 안에서 저의된 임시 변수가 여기 해당한다.
=> 예를 들어서, 지금처럼 지역변수를 사용하고 값을 변경하지는 않을 때.
지역변수와 관련하여 가장 간단한 경우는 변수를 사용하지만, 다른 값을 다시 대입하지는 않을 때다.
이 경우에는 지역 변수들을 그냥 매개변수로 넘기면 된다.
지역 변수가 배열/레코드/객체와 같은 데이터 구조라면, 똑같이 매개변수로 넘긴 후 필드 값을 수정할 수 있다.
가령 마감일을 설정하는 코드는 위와 같이추출된다.
=> 다른 예로, 지역 변수의 값을 변경할 때를 생각해보자.
만약 매개변수에 값을 대입하는 코드를 발견하면, 그 변수를 쪼개서 임시 변수를 새로 하나 만들어 그 변수에 대입하게 한다.
대입 대상이 되는 임시 변수는 크게 2가지로 나눌 수 있다.
먼저 간단한 경우는 변수가 추출된 코드 안에서만 사용될 때다.
즉, 이 변수는 추출된 코드 안에서만 존재한다. 만약 변수가 초기화되는 지점과 실제로 사용되는 지점이 떨어져 있다면, 문장 슬라이드하기를 활용하여 변수 조작을 모두 한곳에서 처리하도록 모아두면 편하다.
이보다 특이한 경우는 변수가 추출한 함수 밖에서 사용될 떄다. 이럴 떄는 변수에 대입된 새 값을 반환해야한다. 앞에서 본 코드를 다시 살펴보자.
일단 리팩터링하고자 하는 코드를 한곳으로 모은다.
그다음엔 모았던 추출한 부분을 새로운 함수로 복사한다.
=> outstanding의 선언문을 추출할 코드 앞으로 옮겼기 때문에, 매개변수로 전달하지 않아도 된다.
추출한 코드에서 값이 변경된 변수는 outstanding뿐이다. 따라서 이 값을 반환한다.
이제 추출한 함수에서 새 값을 반환하니, 이 값을 원래 변수에 저장하고, 자신의 코딩 스타일에 맞게 수정한다.
* 그러면 값을 반환할 변수가 여러개라면?
방법이 몇 가지 있다. 나는 주로 추출할 코드를 다르게 재구성하는 방향으로 처리한다.
개인적으로 함수가 값 하나만 반환하는 방식을 선호하기 때문에, 각각을 반환하는 함수 여러개로 만든다.
굳이 한 함수에서 여러 값을 반환해야 한다면 값들을 레코드로 묶어서 반환해도 되지만,
임시 변수 추출 작업을 다른 방식으로 처리하는 것이 나을 때가 많다.
'개발 > Web Programming' 카테고리의 다른 글
[리팩터링] 함수 인라인하기 필사 (0) | 2021.06.02 |
---|---|
[리팩터링] 함수 추출하기 회고 (0) | 2021.06.02 |
[리팩터링] 테스트 구축하기 (0) | 2021.05.30 |
js로된 React 코드를 ts로 마이그레이션 하면서 느낀점 (0) | 2021.05.16 |
[TS] Require stack: - /node_modules/tsc-watch/lib/tsc-watch.js /node_modules/tsc-watch/index.js 에러 (0) | 2021.05.14 |