Santos의 개발블로그

Refactoring (5) 본문

Refactoring (5)

Santos 2020. 12. 19. 14:46

* 마틴 파울러님의 Refactoring, Chapter 별로 내용을 다룹니다. Chapter2 리팩터링 원칙 의 관한 글입니다.  


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

#1 리팩터링 정의 

리팩터링: 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법

이 책에서 필자는 리팩터링은 "동작을 보존하는 작은 단계들을 거쳐 코드를 수정하고, 이러한 단계들을 순차적으로 연결하여 큰 변화를 만들어 내는 일" 이라고 설명합니다. 상황에 따라 리팩터링은 아주 작은 부분을 또는 큰 부분을 차지할 수도 있습니다. 크건 작건 간에 작업을 수많은 단계로 잘게 나눔으로써 추후에는 더 작업을 더 빨리 처리할 수 있습니다. 그 이유는 단계들이 체계적으로 구성되어 있고, 무엇보다 디버깅하는 데 시간을 뺏기지 않기 때문입니다.

 

"리팩터링의 궁극적인 목적은 코드를 이해하고 수정하기 쉽게 만드는 것입니다."

#2 두 개의 모자

소프트웨어 개발을 할 때 한가지 목적을 명확하게 구분하여 작업하는 것을 추천합니다. "기능추가"를 할 것인가? 아니면 "리팩터링"을 할 것인가? 필자는 이를 두개의 모자에 비유합니다. 기능을 추가할 때는 "기능추가" 모자를 쓴 다음 기존 코드는 절대 건드리지 않고 새 기능만을 추가합니다. 반면 "리팩터링"을 할때는 오로지 코드 재구성에만 전념합니다.

두가지를 한꺼번에 진행하는 것만큼 어리석은 것이 없다고 책에서는 말합니다. 

#3 리팩터링하는 이유

리팩터링을 하는 이유는 4가지로 정리할 수 있습니다. 

 

1) 소프트웨어 설계가 좋아집니다. 

2) 소프트웨어를 이해하기 쉬워집니다. 

3) 버그를 쉽게 찾을 수 있습니다. 

4) 프로그래밍 속도를 높일 수 있습니다. 

 

1) 소프트웨어 설계가 좋아집니다.

튼튼한 건물은 "촘촘한 기반"을 핵심으로 세워지는 것은 누구나 아는 사실입니다. 아키텍처를 충분히 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 프로그램의 "기반"구조는 쉽게 무너지기 십상입니다. 한번 무너진 구조는 악효과가 누적이되고, 구조설계가 부패되는 속도는 더욱 빨라지게 됩니다. 시간이 조금 더 걸리더라도 코드량이 조금 더 늘어나더라도 "나중"을 생각한다면 "지금"살펴 보는 것이 나은 선택이 될 것입니다. 

  

2) 소프트웨어를 이해하기 쉬워집니다. 

프로그래밍은 컴퓨터에게 시키려는 일과 이를 표현한 코드의 차이를 줄여나갈때 다른 사람들과 더 많은 대화를 나눌 수 있습니다. 프로그래밍을 혼자하는 시간보다 다른 사람과 함께 작업하는 시간이 대부분입니다. 내가 작성한 코드를 남이 수정할 수도 있고, 남이 작성한 기능을 내가 추가할 수도 있습니다. 프로그래머들이 컴퓨터를 통해 더 쉬운 대화를 하기 위해서는 "코드의 목적이 더 잘 드러나게", "의도를 더 명확하게 전달하는 것"이 중요합니다. 

 

3) 버그를 쉽게 찾을 수 있습니다. 

버그를 잘 찾아내는 사람은 걱정할 필요가 없습니다. 하지만 암만봐도 버그가 보이지 않는 사람은 리팩터링을 통해 견고한 코드를 작성하는 것이 매우 중요합니다.

 

4) 프로그래밍 속도를 높일 수 있습니다. 

많은 사람들이 걱정하는 것이 "리팩터링을 하는데 시간이 더 드니 전체 개발 속도가 떨어질 것 같다" 입니다. 하지만 막상 현업에서는 이와 다른 결과를 도출합니다. 초기에는 개발 속도가 빨랐지만 현재 새 기능을 하나 추가하는데 훨씬 오래걸린다는 말은 누군가에게는 매우 익숙한 상황일 것입니다. 즉, 새로운 기능을 추가할수록 기존 코드베이스에 잘 녹여낼 방법을 찾는 데 드는 시간이 늘어난다는 것입니다. 

 

"내부 설계가 잘 된 소프트웨어일수록 새로운 기능을 추가할 지점과 어떻게 고칠지에 대한 생각할 시간을 줄일 수 있습니다."

"코드가 명확하면 버그를 만들 가능성도 줄고, 디버깅하기가 훨씬 쉬워집니다. "

#4 언제 리팩터링해야 할까?

필자는 "3의 법칙"을 이야기합니다. 

 

1. 처음에는 그냥한다. 

2. 비슷한 일을 두 번째로 하게 되면, 일단 계속 진행한다.

3. 비슷한 일을 세 번째로 하게 되면 리팩터링한다.

 

위에 언급한 3가지를 실전에서 반영할 때는 총 7가지로 나누어 이야기 할 수 있습니다.

 

1) 준비: 기능을 쉽게 추가하게 만들기

2) 이해: 코드를 이해하기 쉽게 만들기 

3) 쓰레기 줍기 리팩터링

4) 계획된 리팩터링, 수시로 하는 리팩터링

5) 오래 걸리는 리팩터링

6) 코드 리뷰에 리팩터링 활용하기 

7) 리팩터링하지 말아야 할 때 

 

1) 준비:기능을 쉽게 추가하게 만들기 

리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전입니다. 구조를 살짝 바꾸면 다른 작업을 하기가 훨씬 쉬워질 만한 부분을 찾는 것이 쉽기 떄문입니다.

예를 들어 요구사항을 거의 만족하지만 값 몇개가 방해되는 함수가 있다고 가정을 해봅시다. 물론 함수를 복제해서 해당 값만 수정해도 되지만, 나중에 이 부분을 변경할 일이 생기면 원래 코드와 복제한 코드를 모두 수정해야하는 굉장히 번거러운 상황이 발생합니다.

 이런 상황을 대비하여 새로운 기능을 추가하기 직전 리팩터링을 통해 상황을 개선해놓으면 코드의 지속성을 높이는 동시에 수정에 대한 오류 방지를 할 수 있습니다. 

 

2) 이해: 코드를 이해하기 쉽게 만들기 

 코드를 수정하기 위해 전제되어야 할 조건은 그 코드가 하는 일을 파악하는 것입니다. 내가 아닌 다른 사람이 볼 수 있는 코드는 의도가 더 명확하게 드러나도록 함수 이름을 잘못 정해서 실제로 하는 일을 파악하는 데 시간이 오래 걸리지 않을 수 있도록 하는 것이 중요합니다. 더 좋은 설계를 위해서는 자잘한 것부터 명확하게 만드는 것이 중요합니다. 

 

3) 쓰레기 줍기 리팩토링

 코드를 파악하던 중에 일을 비효율적으로 처리하는 로직을 발견한 적이 누구나 있을 것입니다. 로직이 쓸데없이 복잡하거나, 매개변수화한 함수 하나면 될 일을 거의 똑같은 함수 여러 개로 작성해놨을 수도 있습니다. 이때 그냥 보고 지나치는 것이 아닌 간단히 수정할 수 있는 것은 즉시 고치로, 시간이 조금 걸리는 일은 짧은 메모를 남기고, 하던 일을 끝내고 나서 처리하는 융통성이 필요합니다. 물론 자기의 시간을 빼앗기는 그런 아찔함은 존재하지만, 한 번 문제가 되던 코드는 더 커다란 복잡한 문제를 만드는 것이 당연한 이치이기 떄문에 사전에 개선해두는 것이 좋습니다. 

 

4) 계획된 리팩터링과 수시로 하는 리팩터링 

 리팩터링은 프로그래밍과 구분되는 별개의 활동이 아닙니다. 보기 싫은 코드를 발견하면 즉시 리팩터링하는 것이 필요합니다. 리팩터링 일정을 따로 잡아두지 않고, 기능을 추가하거나 버그를 잡는 동안 리팩터링도 함께하는 것이 필요합니다. 리팩터링 작업 대부분은 드러나지 않게, 기회가 될 때마다 해야 합니다. 

소프트웨어 개발이란 기능만을 추가 하는 것이 아닌 새 기능을 추가하기 쉽도록 코드를 수정하는 것이 그 기능을 가장 빠르게 추가하는 길임을 명심해야 합니다. 

 

5) 오래 걸리는 리팩터링 

 보통은 짧은 리팩터링이 대다수지만, 라이브러리를 새것으로 교체하거나, 일부 코드를 다른 팀과 공유하기 위해 컴포넌트로 빼내는 작업 등 몇 주가 걸리는 대규모 리팩터링이 존재합니다. 이런 상황에서는 팀 전체가 리팩터링에 매달리는 것 보다는 누구든지 리팩터링해야 할 코드와 관련한 작업을 하게 될 때마다 원하는 방향으로 조금씩 개선하는 방향을 추구합니다. 리팩터링이 코드를 깨트리지 않는 장점을 활용하여 일부를 조금씩 변경해나가는 것입니다. 물론 테스트는 무조건이겠지요. 

 

6) 코드 리뷰에 리팩터링 활용하기 

 자신이 작성한 코드를 개발팀 전체에 보여주고 피드백을 받으면서 리팩터링을 해나가는 것도 좋은 방법입니다. 내 눈에는 명확한 코드가 다른 팀원에는 그렇지 않을 수 있고, 다른 사람의 아이디어를 얻을 수도 있기 때문입니다. 또한 다른 사람의 코드를 리뷰하면서 절대 떠올릴 수 없었던 한 차원 높은 아이디어가 떠오르기도 하고, 이를 내가 짠 코드에 바로 적용해 볼 수도 있습니다. 개선안에서 그치는 것이 아닌 상당수를 즉시 구현할 수 있다는 점에서 코드 리뷰 내에서 리팩터링을 활용하는 방법은 팀에 많은 도움을 안겨다줍니다. 

 

7) 리팩터링을 하지 말아야 할 때 

 지금까지의 이야기는 무조건 리팩터링을 권하는 상황이었다면, 리팩터링을 하면 안되는 상황도 존재하는 것을 잊어서는 안됩니다. 이는 외부 API를 다룰 때, 또는 처음부터 코드를 새로 작성하는 게 쉬울 때가 속합니다. 물론 이런 상황에서 리팩터링을 하는 것이 옳은 판단인지는 어렵지만, 경험상으로 내부 동작을 이해하기 너무나 어려울 때에는 확실한 결정을 내려야 합니다. 

#5 리팩터링 시 고려할 문제 

마음먹은데로 생각한데로 짜여진 코드를 통해 돌아가는 프로그램이 있다면 얼마나 좋을까? 라는 궁금증을 가져본 개발자는 상당수라고 생각됩니다. 이전 토픽에서 "WHEN"이라는 주제에 대해서 이야기를 했다면, 이번 토픽에서는 "WHAT"에 대해서 이야기해보려 합니다. 

 

리팩터링을 할 시 고려할 문제를 상황에 따라 총 6가지로 나누어 보았습니다.

 

1) 새 기능 개발 속도 저하 

2) 코드 소유권 

3) 브랜치 

4) 테스팅 

5) 레거시 코드 

6) 데이터베이스 

 

1) 새 기능 개발 속도 저하 

 리팩터링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것입니다. 이러한 목적을 가지고 있는 리팩터링이라도 상황에 맞게 조율해야함을 잊어서는 안됩니다. 새 기능을 구현해 넣었을 때 편해진다는 판단을 내릴 수 있는 리팩터링이라면 주저하지 않고 리팩터링부터 합니다. 반면에 직접 건드릴 일이 거의 없거나, 불편한 정도가 그리 심하지 않다고 판단되면 리팩터링을 하지 않습니다. 

 그러나 아무런 판단도 없이 개발 속도 저하를 이유로 리팩터링을 꺼려한다면 아직 건강한 코드의 위력을 충분히 경험해보지 못한 프로그래머라 생각합니다. 기능 추가의 시간을 줄이고, 버그 수정시간을 줄여주는 오로지 경제적인 효과를 만드는 리팩터링은 결국은 개발 진행 그래프에서 "좋은 설계" 곡선을 더 많이 불러 올 수 있습니다. 

 

2) 코드 소유권 

 리팩터링을 하다 보면 모듈의 내부뿐 아니라 시스템의 다른 부분과 연동하는 방식에도 영향을 주는 경우가 대개 있습니다. 함수를 호출하는 코드의 소유자가 다른 팀이라서 나에게는 쓰기 권한이 없을 때, 또한 바꾸려는 함수가 실제로 쓰이는지 모를 수 있는 상황이 존재 할 수도 있습니다.

 코드 소유권을 한 사람에게 맡기고 그 사람만 코드를 수정할 수 있는 방식보다는 코드 소유권을 팀에 두는 방식을 선호합니다. 그래서 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있도록 만드는 것이 중요합니다. 이러한 방법은 팀으로 국한되는 것이 아닌 여러 팀으로 구성된 조직에서도 적용할 수 있습니다. 코드 소유권을 느슨하게 정하는 방식은 팀 개발에 효율성을 극대화 시킬 것입니다. 

 

3) 브랜치 

 보통 팀 단위 작업방식은 버전 관리 시스템을 사용하여 팀원마다 코드베이스의 브랜치를 하나씩 맡아서 작업하다가, 결과물이 어느 정도 쌓이면 마스터 브랜치에 통합해서 다른 팀원과 공유하는 것입니다. 이 방식을 선호하는 팀은 작업이 끝나지 않은 코드가 마스터에 섞이지 않고, 기능이 추가될 때마다 버전을 명확히 나눌 수 있고, 기능에 문제가 생기면 이전 상태로 쉽게 되돌릴 수 있어서 좋다는 것이 대부분의 의견입니다. 

 하지만 "독립 브랜치로 작업하는 기간이 길어질수록" 작업 결과를 마스터로 통합하기가 어려워지는 단점이 존재합니다. 이러한 고통을 줄이고자 마스터를 개인 브랜치로 머지하거나 리베이스를 하지만 여러 기능 브랜치에서 동시에 개발이 진행될 때는 이런식으로 해결이 불가능합니다. 

머지: 마스터를 브랜치로 머지하는 작업은 단방향, 마스터만 바뀌고, 브랜치는 그대로임
통합: 마스터를 개인브랜치로 가져옴 > 작업한 결과를 다시 마스터 올리는 행위로 작업은 양방향, 마스터와 브랜치 모두 변경 

지속적인 머지를 통해서는 누군가 개인 브런치에서 작업한 내용을 마스터에 통합하기 전까지는 다른 사람이 그 내용을 볼 수 없습니다. 통합을 하였다하더라도 마스터에서 달라진 내용을 개인 브랜치로 가져와야하는데, 의미가 변한 부분(함수 이름, 함수 호출하는 코드 추가 등) 이 있다면 흐름을 잡기 위해서 상당한 노력이 필요로 합니다. 

 

그렇기에 지속적 통합(CI)이 필요합니다. 모든 팀원이 하루에 최소 한 번은 마스터와 통합하는 것을 의미합니다. 이렇게 하면 다른 브랜치들과의 차이가 크게 벌어지는 브랜치가 없어져서 머지의 복잡도를 상당히 낮출 수 있습니다. 이는 리팩터링과도 관련이 있는데 코드베이스 전반에 걸쳐 자잘하게 수정하는 부분이 많은 리팩터링은 머지 과정에서 쉬운 충돌을 만듭니다. CI를 통해 브랜치를 자주 통합할 수 있다면 충돌 문제를 줄일 수 있을 것입니다.

 

4) 테스팅 

 리팩터링을 할 때 프로그램의 내부가 명확한 코드로 수정해 나가더라도 겉보기 동작은 똑같이 유지되어야 합니다. 단계별 변경 폭이 작아서 도중에 발생한 오류의 원이이 될만한 코드 범위가 넓진 않지만, 오류가 있다면 이를 재빨리 잡는 것이 필요합니다.

 이를 위해선 코드의 다양한 측면을 검사하는 테스트가 필요합니다. 리팩터링을 할 때마다 테스트 코드를 마련하여 지속적인 테스트를 하는 것이 훨씬 안전한 결과물을 가져올 수 있습니다. 테스트 주기가 짧으면 짧을수록 버그를 쉽게 찾을 수 있고, 빠르게 제거할 수 있습니다. 이러한 주기 반복을 통해서 견고한 구조물을 만들 수 있습니다. 

 

5) 레거시 코드 

  보통의 레거시 코드는 대체로 복잡하고 테스트도 제대로 갖춰지지 않은 것이 대부분입니다. 무엇보다도 내가 아닌 다른사람이 작성한 것이기 때문에 코드 이해를 하는데 시간을 소요할 수 밖에 없습니다. 

 물론 레거시 시스템을 파악할 때 리팩터링이 많은 도움이 되지만, 테스트 코드 없이 명료하게 리팩터링하는 것은 어렵습니다. 이 문제의 정답은 당연히 테스트 보강입니다. 하지만 쉽게 모든 테스트 코드를 만들어 낼 수는 없습니다. 그렇기에 코드가 서로 관련된 부분끼리 나눠서 하나씩 공략하는 방법이 필요합니다. 그럼에도 불구하고 가장 좋은 방법은 레거시 코드에서 멀어지는 것이 가장 중요합니다. 

#6 리팩터링과 소프트웨어 개발 프로세스 

리팩터링을 할 때에는 테스트를 빼먹을 수 없습니다. 테스트 주도 개발(TDD)는 자가 테스트 코드와 리팩터링을 묶은 개념입니다. 최조의 애자일 소프트웨어 방법론 중 하나인 TDD는 수년에 걸쳐 애자일의 부흥을 이끌었습니다. 애자일을 제대로 적용하려면 리팩터링이 프로세스 전반에 자연스럽게 스며들도록 해야합니다. 

 리팩터링의 첫 번째 토대는 자가 테스트 코드입니다. 프로그래밍 도중 발생한 오류를 확실히 걸러내는 테스트를 자동으로 수행할 수 있어야 합니다.

 리팩터링의 두 번째 토대는 지속적 통합(CI)입니다. 각 팀원이 다른사람의 작업을 방해하지 않으면서 언제든지 리팩터링 할 수 있어야 합니다.

 리팩터링의 세 번째 토대는 지속적 배포(CD)입니다. 수정사항의 위험요소 뿐만 아니라, 비즈니스 요구에 맞춰 릴리스 일정을 계획할 수 있습니다.  

 이러한 핵심 실천법을 통해서 요구사항 변화에 재빠르게 대응하고 안정적인 선순환 구조를 코드베이스에 심을 수 있습니다. 또한 견고한 기술적 토대는 프로덕션 코드로 반영하는 시간을 단축할 수 있어 고객에게 더 나은 서비스를 제공할 수 있고, 버그의 수를 줄여줘서 소프트웨어의 신뢰성도 높일 수 있습니다. 

#7 리팩터링 자동화 

리팩터링고 관련화여 지난 수십 년 사이에 일어난 가장 큰 변화는 자동 리팩터링을 지원하는 도구가 등장한 것입니다. 

JetBrains에서 인텔리제이 IDEA를 출시할 때 내세운 대표 기능 중 하나가 "자동 리팩터링"이었고, IBM도 뒤따라 비주얼 에이지 포 자바를 출시할 때 리팩터링 기능을 추가하였습니다. C#용 리팩티링 도구인 Visual Studio용 플러그인인 "리샤퍼"가 처음으로 지원을 하였고, 나중에는 Visual Studio팀에서 자체적으로 리팩터링 기능을 추가하였습니다. 

 아직은 완벽하고 다양한 지원을 하고 있지 않지만, 간단한 코드들은 이 도구를 이용해 리팩터링을 손 쉽게 해보는 것도 유용할 것입니다. 

 

다음 장에서는 "잘못된 코드(코드에서 나는 악취)에 조건"에 대해 다뤄볼 생각입니다. 긴 글 읽어 주셔서 감사합니다.  


< 참고자료 >

 

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

www.yes24.com/Product/Goods/89649360

 

<기타> Refactoring (5) end

 

  

'' 카테고리의 다른 글

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