사실 이번엔 삽질 기록은 아니고, 가장 기본적이면서도 내가 제일 좋아하는 리팩터링 기법인 Extract Function에 대해 다루고자 한다. 사실상 IDE가 대부분의 일을 해주고 몇가지만 수동으로 해주면 되기 때문에 가장 손쉬운 리팩터링 기법이다.
오늘 리팩터링할 친구는 토스에 최종 결제 요청을 보내는 PaymentRequestService라는 클래스의 requestPayment()다.
현재 requestPayment()의 흐름은 다음과 같다.
1. Base64 인코더로 SecretKey를 인코딩하여 authKey를 생성한다.
2. RestTemplate 인스턴스를 생성한다.
3. HttpHeaers 인스턴스를 생성한다.
4. HttpHeaers의 인스턴스에 필요한 값들을 세팅한다.
5. Request시 Param으로 사용할 JSONObject 인스턴스를 생성한다.
6. param에 필요한 값들을 세팅하고, 예외처리한다.
7. 위에서 만든 param, headers를 이용해 HttpEntity를 완성한다.
8. RestTemplate으로 요청을 보낸다.
9. 요청을 보내고 받은 값을 리턴한다.
고작 한 페이지에 모두 들어오는 내용인데도 정말 다양한 일을 수행하고 있다.
이 코드는 클린 코드의 기본 원칙인 "함수는 한 가지 일만 수행하라"를 무시하고 있기 때문에, 코드를 작성한 나에게나 한 눈에 보이는 코드지 남에게는 읽기 괴롭다.
3자가 이 코드를 읽을 때는 세부 구현을 아는 것이 중요한 게 아니라, 전체적인 맥락을 먼저 파악하고 필요한 부분만 찾아서 읽으면 되기 때문에 지금의 코드는 너무 투머치한 상태다.
그나마 지금은 코드가 굉장히 짧은 예시를 가져왔으니 망정이지, 여기서 딱 두 배만 길어진다면 생각만 해도 화가 난다.
제 3자의 코드를 읽을 때 대부분의 경우 "코드 하나하나를 음미하며 모든 구현을 알기 위해서" 읽는 것이 아니라 전체적인 흐름을 읽고, 이후 필요한 부분만 세부 구현을 확인한다는 것을 생각하면 왜 지금 코드가 왜 나쁜 코드인지 이해될 것이다.
코드 사이사이마다 주절주절 "이런 일을 하는 코드입니다", "저런 일을 하는 코드입니다"라고 주석을 남길 수도 있겠지만, 그보다 좋은 방법은 주석으로 남겨야할 부분들을 모두 함수 단위로 추출하고 함수명으로 그것을 나타내는 것이다.
우선 코드를 관심사별로 나눠보자.
1. AuthKey를 생성한다.
2. HttpHeaders 세팅한다.
3. Param을 세팅한다. (예외처리까지)
4. HttpEntity를 세팅한다.
5. 요청을 보낸다.
6. 값을 반환한다.
지금 나눈 관심사대로 함수를 추출할 것이다.
첫번째 함수 추출 시도 시에는 IDE에서 제공해주는 단축키로 바로 함수화할 수 있는 것들을 먼저 해놓고, 그 이후에 나머지를 추출하는 것이 편하다.
IDE에서 제공해주는 단축키만 사용했을 때는 이 정도까지 나눌 수 있었다.
아마 내가 Base64로 secretKey를 인코드하는 부분을 굳이 함수로 추출한 것이 의아하게 느껴지는 사람도 있을 것이다.
내가 굳이 한 줄짜리 코드를 함수화한 근거는 다음과 같다.
1. 한 줄짜리 코드더라도 이 변수가 무엇인지 이해하는데 시간이 걸리는 코드보다, 명확하게 드러나는 코드가 낫다.
2. secretKey에서 Bytes를 받아오고 인코딩한 뒤 String으로 반환하는 것은 짧은 코드지만 그 자체만으로 하나의 역할을 가지고 있다고 판단했다.
3. 이 코드를 읽는 사람 입장에서 중요한 것은 "AuthKey를 어떻게 만들었냐"가 아니라 "이게 AuthKey다"라는 것이라고 생각했고, 실제 구현을 알아야 한다면 그 때 함수를 확인하면 될 일이라고 판단했다.
이제 이 코드를 읽는 제 3자는 기존보다 코드의 흐름을 파악하기 쉬워졌을 것이라고 생각한다.
그냥 읽어봐도 authKey를 얻고, 헤더를 세팅하고, 파라미터를 세팅한 뒤, 요청을 보내고 반환값을 리턴한다는 게 눈에 보이니 말이다.
하지만 아직 부족하다.
읽기 좋은 코드를 작성한다는 것은 읽기 좋은 글을 쓰는 것과 같다.
지금의 코드는 마치 내가 블로그에 작성하는 글처럼(ㅎㅎ) 불필요한 정보들이 많이 보인다.
코드를 읽는 제 3자가 굳이 객체 인스턴스를 생성하는 과정들을 지켜보고 있어야할까?
코드의 흐름을 파악하는데 아무런 도움도 되지 않는 정보인데 말이다.
그러나 이제부터는 수동으로 직접 코드를 옮겨가며 리팩터링해야하는 영역이다.
읽기 좋은 코드를 위해 조금만 수고스러운 일을 해보자.
제 3자가 전혀 알 필요가 없는 인스턴스 생성 과정을 모두 함수 내부로 같이 빼내버렸다.
그리고 이에 맞춰 기존에는 set ~ 이였던 함수명을 generate ~로 변경했다.
보통 generate보다는 create를 많이 사용하는 것 같다.
이건 개인의 취향 혹은 팀의 컨벤션 룰을 따르면 될 것이다.
갑자기 옆길로 새서 딴짓을 하나 했다.
httpEntity나 response라는 변수명으로도 흐름을 파악하는데 큰 어려움은 없겠지만 코드는 읽기 쉬우면 읽기 쉬울수록 좋은 법이다.
보다 명확한 표현을 위해 "httpEntity" -> "httpRequestEntity", "response" -> "httpResponseEntity"로 변수명을 변경하였다. 마찬가지로 함수명도 postRequest에서 postHttpRequest로 변경했다.
나한테는 "이 정도면 충분히 알아볼만한 함수명이겠지?, 알아볼만한 변수명이겠지?"라고 생각한 것들이 남의 눈에 그렇지 한다는 것을 여러번의 프로젝트 경험을 통해 깨달았기 때문에, 조금이라도 명확하게 명칭을 작성하려고 노력하고 있다.
(물론 변수명이 쓸데없이 길어져서 더 눈에 안 들어온다는 의견이 있을수도 있고, 충분히 존중한다. 클린 코드나 리팩터링에는 늘 호불호가 존재하니까..)
님들도 그렇게 하삼.
읽기 힘들어요.
이젠 정말 처음 코드에 비하면 너무나도 흐름을 읽기 편한 코드가 되었다.
"AuthKey를 생성하고, 그 다음에 그걸로 httpHeader를 생성하고, orderId랑 amount를 바탕으로 param을 생성하고, 이걸로 request Entity를 만들어서 결제 요청을 보내는구나"라는 게 마치 하나의 글처럼 읽히는 것이다.
만약 세부 구현이 궁금하다면 해당 메서드를 클릭만 한다면 우리의 IDE가 해당 함수에게 우리를 안내해줄 것이다.
그리고 지금까지의 리팩터링은 너무나도 정석적인 것들이였기 때문에 대부분의 사람들은 납득하겠지만 마지막은 약간 호불호의 영역이라고 생각된다.
"Header를 생성한다", "Parameter를 생성한다", "HttpEntity를 생성한다"의 세 가지가 모두 문맥상 따로 나눠질 필요가 있는 것일까?
나는 그렇지 않다고 생각한다.
따라서 처음 얘기했던 것과 다르게 조금 더 다듬어보고자 한다.
1. AuthKey를 생성한다.
2. AuthKey와 인자로 받은 값들을 이용해 httpRequestEntity를 생성한다.
3. httpRequestEntity로 post요청을 보내고 httpResponse를 반환 받는다.
4. 해당 값을 반환한다.
이제 제 3자가 내 코드를 읽을 때, 아마 이런 순서로 코드를 이해하게 될 것이다.
1. 우선 들어와서 코드를 읽는다.
2. authKey 생성, 요청 생성, 요청 보내기, 이렇게 크게 3부분으로 나뉘어져 있다는 것을 파악한다.
3. 필요한 부분의 세부 구현사항은 메서드에서 직접 확인한다.
즉, http 요청을 보내는 부분을 확인하기 위해 header 세팅하는 과정을 읽을 필요가 없으며, header 세팅하는 부분을 확인하기 위해 authKey가 어떻게 생성되는지 보고 있을 필요가 없다는 것이다.
마지막으로 리팩터링 전과 리팩터링 후 코드 흐름을 알기 위해 읽어야하는 코드 양을 비교해보며 마무리하겠다.
전
후
언젠가 한 번 이런 글을 적어보고 싶었는데 귀찮아서 미루고 미루다가 드디어 작성하게 되었다. 끝.
'Language & Framework > 삽질기록' 카테고리의 다른 글
삽질기록(23) Redis SortedSet으로 랭킹 기능 구현하기 (0) | 2023.02.02 |
---|---|
삽질기록 (21) 비동기 이벤트 테스트하기 (0) | 2023.01.25 |
삽질 기록(19) DTO에서 멤버 객체 유효성 검사하기 (0) | 2023.01.10 |
삽질 기록 (18) 또 너냐 Jackson.. JsonMappingException 해결하기 (2) | 2022.12.28 |
삽질 기록(17) 레디스로 데이터를 캐싱해보자 (0) | 2022.12.19 |