Santos의 개발블로그

Refactoring (6) 본문

Refactoring (6)

Santos 2020. 12. 30. 16:16

* 마틴 파울러님의 Refactoring, Chapter 별로 내용을 다룹니다. Chapter3 코드에서 나는 악취 의 관한 글입니다.  


* Refactoring(5)의 내용이 궁금하시다면 여기를 클릭해주세요.

리팩터링이 어떻게 작동하는지 감이 왔지만, "언제" 적용할 줄 아는 것은 다릅니다. 리팩터링을 언제 시작하고 언제 그만할지를 판단하는 일은 리팩터링의 작동 원리를 아는 것 못지 않게 중요합니다. 이번 챕터에서는 "리팩터링 할 시점"에 대해 설명합니다. 필자는 이를 "냄새"라는 표현을 사용하였습니다.

#1 기이한 이름 

코드는 단순하고 명료하게 작성해야합니다. 특히 가장 중요한 요소는 바로 "이름"입니다. 하지만 모든 개발자들이 알다시피 이름을 짓는 것은 프로그래밍에서 가장 어렵기로 손꼽힙니다. 이름을 잘 지을수록 나중에 문맥을 파악하느라 헤매는 시간을 크게 절약할 수 있기 때문에 이름을 잘 정리하는 것이 필요합니다. 

#2 중복 코드 

똑같은 코드 구조가 여러 곳에서 반복된다면 각각을 볼 때마다 서로 차이점은 없는지 주의 깊에 살펴봐야 하는 부담감과 하나를 변경하게 되었을 때 에러를 낼 수 있는 가능성이 높아진다는 점 등 살펴보아야 할 것 투성입니다. 그렇기에 중복코드는 하나의 코드로 리팩터링을 해야 합니다. 만약 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우 "함수 추출하기"를 써서 양쪽 모두 추출된 메서드를 호출하게 바꾸면 됩니다. 코드가 비슷하기는 한데 완전히 똑같지는 않다면, "문장 슬라이드"로 비슷한 부분을 한 곳에 모은 다음 "함수 추출하기"를 적용합니다. 

#3 긴 함수

코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 "짧은 함수"로 구성할 때 나옵니다. 함수가 길어지면 길어질수록 코드의 가독성을 망가뜨리고, 세분화되어 있지 않아 목적이 명확하지 않을 수 있습니다. 물론 짧은 함수로 구성하여 긴 함수를 목적에 맞게 세분화 시키게 되면 함수가 하는 일을 파악하기 위해 왔다 갔다 해야하는 부담감이 존재하지만, 요즘 개발 환경에서는 함수 호출부와 선언부 사이를 빠르게 이동할 수 있고, 호출과 선언을 동시에 보여주기 때문에 크게 문제가 되지 않습니다.

 짧은 함수로 구성할 때는 "좋은 함수 이름"을 짓는 것입니다. 주석을 달지 않을 정도로 "함수의 의도"가 담길만큼 좋은 이름을 짓게되면 코드가 한줄이든 여러줄이든 코드의 이해를 높일 수 있습니다. 

 함수를 짧게 만드는 작업의 99%는 "함수 추출하기"가 차지하게 됩니다. 함수 본문에서 따로 묶어 빼내면 좋은 코드 덩어리를 찾아 새로운 함수로 만드는 "함수 추출하기"를 할 때에는 코드에 씌여있는 "주석"을 참고하면 추출할 코드를 쉽게 찾아낼 수 있습니다. "주석은 보통 코드만으로 목적을 이해하기 어려운 부분에 달려 있는 경우"가 많이 때문에 주의깊게 볼 필요가 있습니다. 조건문이나 반목문도 추출 대상의 실마리를 제공합니다. Switch 문을 구성하는 case문마다 "함수 추출하기"를 적용해서 각 case의 본문을 함수 호출문 하나로 바꾸거나, 반복문 안의 코드와 함께 추출해서 독립된 함수로 만드는 것이 이에 해당합니다. 

#4 긴 매개변수 목록

매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많습니다. 다른 매개변수에서 값을 얻어올 수 있는 매개변수가 존재하면 "매개변수를 질의 함수로 바꾸기"로 제거하고, 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 "객체 통째로 넘기기"를 적용해서 원본 데이터 구조를 그대로 전달합니다. 항상 함께 전달되는 매개변수들은 "매개변수 객체 만들기"로 하나로 묶어버립니다. 

 클래스는 매개변수 목록을 줄이는 데 가장 효과적인 수단입니다. 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 "여러 함수를 클래스로 묶기"를 이용하여 공통 값들을 클래스의 필드로 정의하여 사용합니다. 

#5 전역 데이터 

전역 데이터는 코드베이스 어디에서든 건드릴 수 있고, 값을 누가 바꾸었는지 찾아낼 메커니즘이 없습니다. 버그를 만들어내는 가장 커다란 원인이 될 수도 있고, 버그를 찾아내기도 굉장히 어렵습니다. 이를 방지하기 위해 보통 "변수 캡슐화하기"로 리팩터링을 합니다. 접근자 함수들을 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이거나, 함수로 감싸는 것만으로도 코드가 많아질수록 예측할 수 없는 변화에 대처할 수 있습니다. 

#6 가변 데이터

데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있습니다. 이런 이유로 함수형 프로그래밍에서는 데이터는 절대 변하지 않고, 데이터를 변경하려면 반드시 원래 데이터는 그대로 둔채 변경하려는 값에 해당하는 복사본을 만들어서 반환한다는 개념을 기본으로 삼고 있습니다.

 하지만 함수형 언어가 프로그래밍에서 차지하는 비중은 여전히 적고 변수 값을 바꿀 수 있는 언어를 사용하는 프로그래머가 더 많기 때문에 "변수 캡슐화하기"를 사용하거나 하나의 변수가 용도가 다른 값들을 저장하느라 값을 갱신하는 경우라면 "변수 쪼개기"를 이용하여 용도별로 독립 변수에 저장하는 것도 하나의 방법입니다. 또는 "세터 제거하기"를 통해 변수의 유효범위를 줄일 수 있습니다. 

#7 뒤엉킨 변경

뒤엉킨 변경이 일어나는 상황은 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생하게 됩니다. 예를 들면 하나의 DB가 추가될 때마다 함수 세개를 바꿔야하고, 화장품이 추가 될 때마다 또 다른 함수 네개를 바꿔야 하는 모듈이 있을 때가 이에 해당합니다.

 맥락 사이의 경계를 명확하게 나누고 소프트웨어 시스템의 기능이 변경될때마다 "의존성"을 줄이는 것이 필요합니다. DB에서 데이터를 가져와서 화장품 관련 로직에서 처리해야 하는 일처럼 순차적으로 실행 되야한다면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하는 식으로 "단계 쪼개기"를 이용하여 단계를 분리합니다. 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모으는 것이 필요합니다. 관련 함수들을 모아 클래스로 만드는 것도 하나의 방법입니다. 

#8 기능 편애 

프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤 영역 안에서 이뤄지는 상호작용은 최대한 늘리고, 영역 사이에서 이뤄지는 상호작용은 최소로 줄이는 데 주력합니다. 기능 편애는 "흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용 할일이 더 많을 때 생기는 것"입니다. 함수가 사용하는 모듈이 다양하다면 가장 많은 데이터를 포함한 모듈로 옮기고, "함수 추출하기"를 통해 함수를 여러 조각으로 나눈 후 각각을 적합한 모듈로 옮기는 가장 베스트한 방법입니다. 

#9 데이터 뭉치 

데이터 항목이 여러 곳에서 함께 뭉쳐 다니는 모습을 흔히 목격할 수 있습니다. 이렇게 몰려다닌 데이터들은 "매개변수 객체 만들기"를 이용해 하나의 객체로 묶고, "객체 통째로 넘기기"를 적용해서 매개변수 수를 줄일 수 있습니다. 아니면 "클래스로 추출하기"를 이용하여 간단한 레코드 구조가 아닌 "클래스"로 만드는 방법이 좋습니다. 이러한 연계 과정은 상당히 많은 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시킵니다. 

#10 반복되는 Switch문

Switch문이 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 Switch문들도 모두 찾아서 함께 수정해야 하는 비효율적인 상황이 발생하기 때문입니다. 이럴 때 "다형성"을 이용하면 반복된 Switch문의 코드베이스를 최신 스타일로 바꿔주면서, 한 군데에서 처리할 수 있게 됩니다.

#11 반복문

반복문은 프로그래밍 언어가 등장할 때부터 함께 한 핵심 프로그래밍 요소입니다. 하지만 리팩터링을 할 때에는 솜털 무늬 벽지보다도 못한 존재가 되어버렸습니다. 이런 부분은 "반복문을 파이프라인으로 바꾸기"를 적용해서 코드에서 각 원소들이 어떻게 처리되는지 쉽게 파악할 수 있도록 보안할 수 있습니다.

#12 성의 없는 요소 

보통 코드의 구조를 잡을 때 프로그래밍 언어가 제공하는 함수, 클래스, 인터페이스 등 코드 구조를 잡는 데 활용하는 프로그램 요소를 이용합니다. 하지만 "실질적으로 메서드가 하나뿐인 클래스, 나중에 본문을 더 채우려고 했는데 그렇지 못한 클래스, 원래는 풍성했던 클래스가 리팩터링을 거치면서 역할이 줄어든 클래스" 등 꼭 필요하지 않은 프로그램 요소로 바뀌는 경우도 존재합니다. 이런 경우에는 "함수 인라인하기", "클래스 인라인하기", "계층 합치기" 등을 적용하여 삭제하는 것이 좋습니다. 

#13 추측성 일반화 

추측성 일반화는 "나중에 필요할 거야"라는 생각으로 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드를 말합니다. 물론 미래에 사용하면 다행이지만, 그렇지 않다면 쓸데없는 낭비일 뿐입니다. 당장 걸리적거리는 코드는 지워버리는게 좋습니다. 

#14 임시 필드 

간혹 특정 상황에서만 값이 설정되는 필드를 가진 클래스가 존재하기도 합니다. 하지만 객체를 가져올 때는 당연히 모든 필드가 채워져 있으리라 기대하는 게 보통이라, 임시필드를 갖도록 작성하면 사용자는 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유를 파악하느라 독해가 어려워집니다. 

 이런 상황에서는 "클래스 추출하기"로 추출을 하고, "함수 옮기기"로 임시 필드들과 관련된 코드를 모조리 새 클래스에 몰아 넣는 것이 필요합니다. 

#15 메시지 체인

메시지 체인은 클라이언트가 "한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드"를 말합니다. 예를 들언 getSometing1() 같은 게터내에서 또 다른 함수 getSometing2()를 호출, getSomething2() 게터내에서 getSomething3()을 호출하면서 객체 내베게이션 구조에 종속됐음을 의미합니다. 이런 문제는 "위임 숨기기"로 해결합니다. 

#16 내부자 거래

개발자는 모듈 사이에 벽을 두껍게 세우기를 좋아합니다. 모듈 사이의 데이터 거래가 많으면 "결합도"가 높아진다며 투덜됩니다. "결합도"를 최소화하고 "응집도"를 높이는 것이 중요합니다. 만약 여러 모듈들이 같은 관심사를 공유한아면 공통 부분을 정식으로 처리하는 "제3의 모듈을 새로 만들거나 위임 숨기기"를 이용하여 다른 모듈이 중간자 역할을 하게 만드는 것이 중요합니다. 

#17 거대한 클래스 

한 클래스가 너무 많은 일을 하려다 보면 필드 수가 늘어나고, 이로 인해 중복 코드가 생기가 쉽습니다. 이럴 때는 "클래스 추출하기"로 필드의 일부를 따로 묶고, 관련이 있는 것끼리 같은 컴포넌트로 추출합니다. 만약 분리할 컴포넌트를 원래 클래스와 상속 관계로 만드는 게 좋다면 "슈퍼 클래스 추출하기""타입코드를 서브클래스로 바꾸기"를 적용하는 것이 좋습니다. 중복 코드를 간단하게 줄이려면 각각의 공통 부분을 추출하여 새로운 메서드들로 뽑아내는 것이 필요합니다. 

#18 데이터 클래스 

데이터 클래스란 "데이터 필드와 게터/세터 메서드로만 구성된 클래스"를 말합니다. 데이터 저장 용도로만 쓰이는 클래스에서는 public 필드를 쓰기보다는 "레코드 캡슐화하기"를 사용하여 private하게 필드를 숨기는 것이 좋습니다. 또한 변경을 할 수 없는 필드는 "세터 제거하기"로 접근을 원천 봉쇄하는 것이 필요합니다. 

#19 주석 

 주석이 장황하게 달린 원인은 코드를 잘못 작성했기 때문에 나타나는 경우가 많습니다. 특정 코드 블록이 하는 일에 주석을 남기고 싶다면 "함수 추출하기"를 적용해보고, 이미 추출되어 있는 함수임에도 여전히 설명이 필요하다면 "함수 선언 바꾸기"로 함수 이름을 바꿔보는 것도 필요합니다. 주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링해보는 것이 필요합니다. 물론 장황한 주석이 아닌 목적이 있는 주석은 나중에 코드를 수정해야 할 프로그래머에게 많은 도움을 줄 수 있습니다. 

 

다음 장에서는 "테스트 구축하기"에 대해 다뤄볼 생각입니다. 긴 글 읽어 주셔서 감사합니다.  


< 참고자료 >

 

[책] Refactoring - 마틴 파울러 지음 

www.yes24.com/Product/Goods/89649360

 

<기타> Refactoring (6) end

'' 카테고리의 다른 글

코어자바스크립트  (24) 2021.08.30
Refactoring (5)  (0) 2020.12.19
Refactoring (4)  (0) 2020.12.15
Refactoring (3)  (0) 2020.12.12
Refactoring (2)  (0) 2020.12.12
Comments