리팩터링 전반에 적용되는 원칙 몇 가지를 알아보자.
소프트웨어 개발 용어에 대해 이야기할 때 개발자마다 약간의 차이점이 존재한다. 이와 마찬가지로 리팩터링이라는 용어도 다소 두리뭉실한 의미로 통용된다.
하지만 저자는 좀 더 엄격하게 정의해야 유용하다고 생각한다.
저자는 리팩터링의 명사적 의미와 동사적 의미를 세분화한다.
명사적으로 소프트웨어의 겉보기 동작은 유지한 채, 코드를 이해하고 수정하기 쉽게 내부 구조를 변경하는 기법이라고 한다. 저자가 생각하는 좋은 코드는 수정하기 쉬워야 한다라는 저자의 생각이 잘 드러나는 정의라 볼 수 있다.
동사적으로는 겉은 유지한 채, 여러 가지 리팩터링 기법을 적용해 소프트웨어를 재구성한다
예시를 들은 문장은 다음과 같다.
" 앞으로 몇 시간은 리팩터링 할 것 같은데 그 사이 적용하는 리팩터링은 수십 가지나 될 것 같다."
만약 리팩터링을 하는데, 코드의 구조가 변경됐다면 이는 리팩터링이라 정의하지 않는다. 저자는 코드 베이스를 정리하고
나 구조를 바꾸는 모든 작업을 restructuring이라고 하고 리팩터링인 restructuring의 특수한 형태로 본다.
앞에서 겉보기 동작 (observable behavior)이라는 표현의 뜻은 리팩터링 하기 전과 후의 코드가 똑같이 동작해야 함을 뜻한다. 다만 성능적으로 변할 수는 있는데 그렇다 해도 사용자 관점에서는 달라지는 점이 없어야 한다.
켄트 백의 비유 중 "two hats" , 두 개의 모자가 있다.
기능을 추가할 때는 기능 추가 모자를 쓴 다음 기존 코드는 절대 건드리지 않고 새 기능을 추가하기만 한다.
진척도는 테스트를 추가해서 통과하는지 확인하는 방식으로 한다.
반면 리팩터링을 할 때는 리팩터링 모자를 쓴 다음 기능 추가는 절대 하지 않기로 다짐하고 오로지 코드 재구성에만 전념한다.
테스트 또한 새로 만들지 않는다. 부득이 인터페이스를 변경해야 할 때만 기존 테스트를 수정한다.
이는 모자에 따른 미묘한 작업 방식의 차이를 분명하게 인지하고 개발해야 함을 말한다.
리팩터링 하는 이유
경험을 토대로 상대방을 이해시키는 것과 이론을 토대로 상대방을 이해시키는 것에는 많은 차이가 존재한다고 생각한다.
내가 공부를 통해 리팩터링의 장점을 알기에 리팩터링을 하고 싶은데, 만약 그에 대해 반대표를 던지는 사람이 있다면 나는 어떻게 할 것인가? 아마 현재로선 말로만 떠들 수밖에 없지 않을까 싶다. 그래도 말로라도 정확하게 떠들 수 있게 지식을 습득해보자.
리팩터링이 모든 문제점을 해결해주는 것은 아니다. 하지만 좋은 코드로 유지하는 데 도와주는 것은 분명한 사실이다.
1. 리팩터링 하면 소프트웨어 설계가 좋아진다.
- 아키텍처를 충분히 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 기반 구조가 무너지기 쉽다. 그러면 코드만을 봐서 설계를 파악하기 어려워진다.
코드만으로 설계를 파악하기 어려워지면 설계를 유지하기 어려워지고, 설계가 부패되는 속도는 더욱 빨라진다.
반면 규칙적인 리팩터링은 코드의 구조를 지탱해 줄 수 있다.
예를 들어 반복적인 코드 제거는 설계 개선 작업에 중요한 한 축을 차지한다. 코드량을 줄인다고 성능이 개선되지 않는다. 프로그램 용량이 속도에 영향을 주는 경우는 별로 없다. 다만 코드량이 줄면 수정하는 데 드는 노력은 크게 달라진다.
코드가 길수록 수정하는데 어려워지며, 이해해야 할 코드량이 늘어난다. 반면 중복 코드를 제거하면 모든 코드가 언제나 고유한 일을 수행함을 보장할 수 있기 때문이다.
2. 리팩터링을 통해 소프트웨어 이해가 쉬워진다.
- 프로그래밍을 통해 우리는 컴퓨터가 해야 할 일을 코드로 표현한다. 따라서 시키려는 일과 이를 표현하려는 코드의 차이를 최대한 줄여야 한다.
문제만을 해결하기 위해 작성된 코드를 보면 미래의 나, 혹은 유지보수 개발자는 해당 코드를 이해하는데 분명 오랜 시간이 걸릴 것이다.
코드를 이해하기 쉽게 만들려면 일하는 리듬에 변화를 줘야 한다. 이상적인 구조가 아닌 코드가 보인다면 시간을 투자해 리팩터링 하자. 이를 통해 코드의 목적이 더 잘 드러나게, 즉 내 의도를 더 명확하게 전달하도록 개선할 수 있다.
3. 리팩터링 하면 버그를 쉽게 찾을 수 있다.
- 코드를 이해가 쉽다는 말은 버그를 찾기도 쉽다는 말이기도 하다. 리팩터링을 하면 코드가 하는 일을 깊게 파악하게 되면서 새로 깨달은 것을 곧바로 코드에 반영하게 된다.
프로그램의 구조를 명확하게 다듬으면 그냥 "이럴 것이다"라고 가정하던 점들이 분명히 드러나기 때문이다.
4. 리팩터링 하면 프로그래밍 속도를 향상할 수 있다.
- 가장 많이 부딪치는 벽이 아닐까 싶다. 리팩터링이 설계의 품질을 높인다는 점은 누구나 알 수 있다. 다만 리팩터링을 하는데 시간이 드니 전체 개발 속도에 지장을 준다고 생각하는 사람이 많다.
경험 상으로 느낀 건데, 많은 리팩터링을 진행하지 않고 초기 진척이 매우 빠르게 진행된 프로젝트가 후반에 요청되는 기능을 추가하기 위해 초반보다 훨씬 많은 시간을 소비하는 것을 많이 보았다.
그 이유는 새로운 기능을 기존의 코드 베이스에 잘 녹여낼 방법을 찾는 데 드는 시간이 늘어났기 때문이다.
어찌어찌 기능을 추가했다고 해도 버그가 발생하는 경우가 잦고 해결하기 위해 드는 시간도 길다. 결국 새로 개발하고 싶다는 욕구가 들 정도로 말이다.
리팩터링을 하면 기존 코드의 설계를 얼마든지 개선할 수 있기 때문에 빠른 속도를 유지할 수 있다. 설계를 처음부터 잘한다는 것은 매우 어렵다고 생각한다. 리팩터링을 통해 유연하게 움직일 수 있는 발판을 마련하는 것은 매우 중요하다고 생각한다.
만약 내가 설계에 대해 이해를 했고 좋은 리팩터링 기법도 숙지했다.
그럼 드는 의문은 내가 언제 리팩터링을 해야 하는 가에 대해서 고민할 수 있다.
언제 리팩터링 해야 하는가?
1. 준비를 위한 리팩터링
리팩터링 하기 가장 좋은 시점은 코드 베이스에 기능을 새로 추가하기 직전이다. 해당 시점에 현재 코드를 보면서, 구조를 살짝 바꾸면 다른 작업을 하기가 훨씬 쉬워질 만한 부분을 찾는다.
즉시 기능을 추가하였을 때 발생하는 문제는 바로 보이지 않을 것이다. 아마도, 그럼 내 머릿속에 있는 기능 추가가 최선인지 한번 더 생각해보는 시간을 갖고 전체적인 시점으로 바라보는 습관을 가져보자.
2. 이해를 위한 리팩터링
리팩터링은 결국 코드의 구조나 코드를 수정하는 일이다. 그의 전제 조건은 코드가 하는 일을 정확히 파악해야 한다는 것이다. 저자는 코드를 파악할 때마다 그 코드의 의도가 더 명확하게 드러나도록 리팩터링 할 여지가 없는지 찾는다고 한다.
어떤 역할을 하는지 이해된 변수는 적절한 이름으로 변경해주고, 긴 함수는 잘게 나눈다.
자잘 자잘한 일이라 생각할 수도 있지만 이를 통해 복잡한 코드 아래 숨어 있는 리팩터링 할 수 있는 기회를 발견할 수 있을 것이다.
여기서 드는 의문점은 다른 사람을 이해시키는 일은 매우 어려운 일이라는 것을 안다. 내가 리팩터링 한 코드가 다른 사람에게는 가독성이 떨어져 보일 수 있을 것이다. 어떻게 해야 할까? 사실 코드에서 가독성이라는 것은 개발자마다의 지식의 수준에 따라 달라지는 감이 없잖아 있는 것 같다..
3. 쓰레기 줍기 리팩터링
이 부분은 절충안이라고 생각된다. 분명 목표를 달성하기 위해 작은 부분에서 리팩터링을 시작했지만 원래 하려던 작업과 관련 없는 부분에서 시간을 많이 소모할 수 있다. 그렇다고 쓰레기가 나뒹굴게 방치하는 것도 좋지 않다.
이때 저자는 간단히 수정할 수 있는 것은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음, 하던 일을 끝내고 처리한다고 한다.
4. 계획된 리팩터링과 수시로 하는 리팩터링
본론만 말하면 계획된 리팩터링을 하게 되는 일은 최소한으로 줄여야 한다. 과거 백기선 님의 라이브 방송 중 리팩터링에 대해 충고하는 이야기를 들은 적 있는데, 리팩터링은 당연히, 중간중간해야 하는 거라고 하셨다.
저자도 동일한 말을 한다. 기능을 추가하거나 버그를 잡는 동안 리팩터링도 함께 진행한다. 프로그래밍 과정에서 자연스레 녹인 것이다.
우리는 알고 있다. 새 기능을 추가하는 가장 쉬운 방법은 새 기능을 추가하기 쉽도록 코드를 수정하는 것이 그 기능을 가장 빠르게 추가할 수 있는 길임을 안다.
5. 오래 걸리는 리팩터링
오래 걸리는 리팩터링 예시로 라이브러리를 교체하는 일, 일부 코드를 다른 팀과 공유하기 위해 컴포넌트로 빼내는 작업, 골치 아픈 의존성을 정리하는 작업 등은 몇 주가 걸리는 리팩터링 작업이다. 그럼 팀 전체가 리팩터링에만 몰두해야 하는 것이 좋을까?
저자는 이 생각에 회의적이다. 주어진 문제를 몇 주에 걸쳐 조금씩 해결하는 편이 효과적일 때가 많다고 한다.
누구든지 리팩터링을 해야 할 코드와 관련한 작업을 하게 될 때마다 팀이 원하는 방향으로 조금씩 개선하는 식이다.
리팩터링이 코드를 깨트리지 않는다는 장점을 활용하는 것이다.
6. 코드 리뷰에 리팩터링 활용
단순히 코드 리뷰에서 코드를 읽고 이해한 뒤 몇 가지 개선 사항을 제시하는 것보다, 리팩터링 하여 새로운 아이디어를 쉽게 구현해 넣을 수 있는지 살펴보는 것도 좋다. 이는 코드 리뷰의 결과를 더 구체적으로 도출하는데 도움이 된다. 개선점을 제시하는 데서 그치지 않고, 실제로 구현할 수 있기 때문이다.
또한 리팩터링을 적용하면서 코드 리뷰를 진행한다면 리팩터링을 해보지 않고는 절대 떠올릴 수 없는 한 차원 높은 아이디어가 떠오르기도 한다.
7. 관리자에게 뭐라고 말해야 할까?
리팩터링은 기능을 추가하는 일이 아니다. 따라서 보고할 때 리팩터링에 대해 설명할 필요가 있다. 관리자가 개발자가 아니라면 말이다.
관리자에게 적절한 설명 없이 개발 일정에 리팩터링을 잡아버린다면 개발팀과의 오해가 커질 수 있다. 설상가상으로 어설픈 재구성을 통해 코드 베이스가 망가진다면 불신은 증폭될 것이다.
만약 관리자가 기술에 정통하다면 관리자가 스스로 리팩터링을 검사할 것이다.
그렇지 않은 관리자는 건강한 코드 베이스가 유지력과 생산성에 미치는 영향을 모른다. 이 경우 이들에게는 리팩터링 한다고 말하지 말라고 저자는 조언한다.
프로 개발자의 역할은 효과적인 소프트웨어를 빠르게 만드는 것이라고 하면, 경험상 리팩터링을 하면 소프트웨어를 빠르게 만드는데 효과적이기 때문이다.
8. 리팩터링 하지 말아야 할 때
실제로 리팩터링을 하면 안 되는 상황이 존재한다고 한다.
굳이 수정할 필요가 없다면 리팩터링 하지 않는다. 외부 API 다루듯 호출하는 코드라면 지저분해도 그냥 둔다고 한다.
내부 동작을 이해해야 할 시점에 리팩터링 해야 효과를 볼 수 있기 때문이다.
또 리팩터링 하는 것보다 처음부터 새로 작성하는 게 쉬울 때도 리팩터링 하지 않는다만 이 방법은 결정하는 것이 쉽지 않다. 직접 리팩터링 하기 전에는 어느 쪽이 쉬운지 확실히 알 수 없기 때문이다.
리팩터링의 시작점 정리
- 시작하기 직전의 구조적인 검토
- 자신 혹은 타인을 위한 이해력을 높이기 위한 리팩터링
- 첫 목표를 잃어버리지 말고 작은 쓰레기는 즉시, 큰 쓰레기는 메모 후 나중에 치우자.
- 리팩터링은 특별한 계획을 통해 하는 것보단 수시로 진행하는 것이 좋다.
- 장기적인 리팩터링을 다룰 때 점진적으로 해결하는 것
- 코드 리뷰에 리팩터링을 활용해보는 것
- 관리자와의 의사소통 방법
- 리팩터링을 반드시 꼭 해야 하는 것은 아니다.
리팩터링 시 고려해야 하는 문제
리팩터링의 궁극적인 목적은 개발 속도를 높여서 더 적은 노력으로 더 많은 가치를 창출하는 것이다.
사람들이 빠지기 쉬운 가장 위험한 오류는 리팩터링을 "클린 코드"나 "바람직한 엔지니어링 습관"처럼 도덕적인 이유로 정당화하는 것이다.
리팩터링의 본질은 코드 베이스를 이쁘게 꾸미는 것이 아닌 오로지 경제적인 이유로 하는 것이다.
기능 추가 시간을 줄이고, 버그 수정 시간을 줄여준다.
스스로 인식하는 데 그치지 말고 다른 사람과 대화할 때도 이 점을 명심해야 한다.
리팩터링 하도록 이끄는 동력은 어디까지나 경제적인 효과에 있다.
리팩터링을 하다 보면 코드의 소유권이 자신에게 없을 때가 있다. 예를 들어 딱 한 사람이 모든 코드의 수정권을 가지고 있거나, 소유권이 작게 분할되어 나누어져 있으면 리팩터링에 방해가 된다.
저자가 선호하는 방식은 코드의 소유권을 팀에 두는 것이다. 그래서 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 한다.
프로그래머마다 각자가 책임지는 영역이 있을 수 있다. 이 말은 자신이 맡은 영역의 변경 사항을 관리하라는 뜻이지, 다른 사람이 수정하지 못하게 막으라는 뜻은 아니다.
앞서 1장에서도 테스트 코드에 대한 중요함을 말했다. 리팩터링의 큰 장점은 코드의 변경이 동작에 영향을 미치지 않는다는 것이다. 하지만 실수를 통해 동작이 깨진다면 어떻게 할까?
여기서 핵심은 오류를 빠르게 발견하고 처리하는 것이다. 이를 위해서는 코드의 다양한 측면을 검사하는 test suite가 필요하다.
즉 대부분의 리팩터링을 위해서는 자가 테스트 코드를 마련해야 한다는 뜻이다.
레거시 코드는 내부가 복잡하고 테스트도 제대도 갖춰지지 않은 것이 많다.
무엇보다도 다른 사람이 작성한 것이다.
리팩터링은 레거시 시스템을 파악할 때 도움이 된다.
제 기능과 맞지 않는 함수 이름을 바로 잡고 프로그램 구문을 매끄럽게 다듬어 탈바꿈시킬 수 있다. 그런데 테스트가 없다는 사실은 매우 절망적이다.
이런 경우 프로그램에서 테스트를 추가할 틈새를 찾아서 시스템을 테스 해야 한다. 이러한 틈새를 만들 때 리팩터링이 활용된다. (레거시 코드 활용 전략이라는 책을 추천한다.)
그래도 리팩터링을 적용해 틈새를 찾았다 해도 너무 위험한 것 아닌가 생각이 든다. 그리고 복잡하게 얽힌 코드를 깔끔하게 정리하기 위해 드는 노력도 만만치 않을 것 같다.
이럴 땐 역시 순위를 정하여 차근차근 리팩터링 하는 것이 좋지 않을까 생각하는 도중 저자도 비슷한 맥락의 말을 한다.
서로 관련된 부분끼리 나눠서 하나씩, 예전보다 조금이라도 개선하려고 노력하고, 자주 보는 부분을 더 많이 리팩터링 하도록 조언한다.
이번 장에서는 리팩터링의 정의와 소개, 장점과 단점, 유래만을 다루었는데 벌써부터 다음 챕터가 궁금하다.
사실 전문적인 엔지니어들은 리팩터링이라는 단어가 없을 때부터 꾸준히 자신의 코드를 다듬었을 것이다.
리팩터링으로부터 나온 아키텍처나, 방법론을 보면 그저 감탄한다.
생산성과 효율, 건강한 지속성이 리팩터링으로 부터 파생될 수 있다는 것을 연구하고 입증한 분들께 감사드린다.
'독서에서 한걸음' 카테고리의 다른 글
Refactoring_2 이해하기 힘든 이름 (0) | 2022.11.27 |
---|---|
Clean Code .Part14 (1) (0) | 2022.08.15 |
Refactoring .Chapter 01 (0) | 2022.08.06 |
Clean Code .Part13 (0) | 2022.08.05 |
Clean Code .Part12 (1) | 2022.08.04 |
댓글