2.5 리팩터링 시 고려할 문제
손익을 제대로 이해하고 있어야 한다.
새 기능 개발 속도 저하
리팩터링의 궁극적인 목표는 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다.
새 기능을 구현하기 편해지겠다 싶은 리팩터링이라면 주저하지 않고 리팩터링부터 한다.
직접 건드릴 일이 거의 없거나, 불편한 정도가 심하지 않으면 하지 않는 편
개선할 방향이 마땅히 떠오르지 않으면 일단 둔다.
보통은 리팩터링을 너무 안해서 문제
코드베이스가 건강하면 기존 코드를 새로운 방식으로 조합하기 쉬워서 복잡한 새 기능을 더 빨리 추가할 수 있다.
리팩터링의 본질은 코드베이스를 예쁘게 꾸미는데 있지 않다. 오로지 경제적인 이유로 하는 것이다.
코드 소유권
리팩터링 대상 코드가 다른 곳에서 호출하는 경우, 또는 API인 경우 누가 얼마나 쓰고있는지 모른다.
기존 코드를 두고 기존 코드가 다른 함수를 호출하는 식으로 리팩터링해도 되지만, 그러면 인터페이스가 지져분해진다.
이를 방지하기 위해선 조직 시스템을 손봐야한다.
팀 단위로 코드 소유권을 공유하여, 내 코드를 호출하는 팀원 코드(클라이언트)를 수정할 수 있게 하는것, 그리고 수정한 팀원 코드를 커밋 요청을 그 코드 주인인 팀원에게 하는 것이다.
브랜치
가장 흔한 팀 단위 작업 방식 기능 브랜치 방식
팀원 마다 브랜치를 가진다.
어느 정도 작업을 하면, 마스터 브랜치로 통합한다.
이 방식은 프로덕션 버전으로 릴리스할 때 마스터에 통합하는 경우가 많다.
이렇게 되면 독립 브랜치로 작업하는 기간이 길어져 마스터로 통합하기 어려워지는 경우가 발생할 수 있다.
이를 방지하려면 수시로 개인 브랜치를 마스터로 리베이스 또는 머지를 해야한다.
여기서 말하는 머지와 통합 차이
머지는 개인 브랜치로 마스터를 MERGE 하는 것 (마스터 =단방향=> 브랜치)
통합은 머지 전에 마스터로부터 PULL 후 마스터로 PUSH 하는 것(마스터 <=양방향=> 브랜치)
통합 인터벌이 길면 발생할 수 있는 흔한 문제는 다음과 같다.
누군가 개인 브랜치에서 작업한 내용을 마스터에 통합하기 전까지 다름 팀원은 그 내용을 모른다.
내가 함수 이름을 변경했고, 마스터로 통합을 늦게 했다.
그 사이 팀원은 이름이 바뀔지 모르기 때문에 그 함수를 호출하는 코드를 개발했다.
그리고 내가 마스터로 통합을 하면, 팀원 코드에서 버그가 발생한다.
위 처럼 통합주기가 길면 길수록 머지 복잡도가 급격히 높아진다.
이를 방지하기 위해 주기(최소 하루 한번)를 짧게 가져가는 방식을 지속적 통합(Continuous Integration:CI) 또는 트렁크 기반 개발(Trunk-Based Development:TBD)이라 한다.
CI를 위해선 마스터를 건강하게 유지하도록 거대한 기능을 잘게 쪼개는 방법과 각 기능을 키고 끌 수 있는 기능 토글을 적용할 줄 알아야 한다.
CI는 리팩터링과 궁합이 좋다. 이 두 기법을 합친 것이 익스트림 프로그래밍(eXtreme Programming:XP)이다.
기능별 브랜치 방식도 통합 주기를 짧게 가져가면 문제를 최소화 할 수 있다.
각 상황에 맞게 정책을 적용해 사용하면 된다.
테스팅
리팩터링 특징은 겉보기 동작은 똑같이 유지된다는 것이다.
리팩터링은 단계별 변경 폭이 작아 도중에 발생한 버그를 쉽게 잡을 수 있다.
만일 원인을 못 찾더라도 버전 관리 시스템을 이용하여 가장 최근에 정상 작동하던 상태로 돌리면 그만이다.
핵심은 오류를 재빨리 잡는 것이다.
이렇게 하려면 코드의 다양한 측면을 검사하는 테스트 스위트(자가 테스트 코드)가 필요하다.
자가 테스트 코드는 새 기능 추가를 안전하게 해준다.
조금에 변경에도 계속 테스트를 한다면, 문제를 일으킨 부분이 순식간에 들어난다.
또한 리팩터링 과정에서 버그가 생길 수 있다는 불안을 해소할 수 있다
테스트 코드 없이 리팩터링하는 방법도 있다.
테스트 커버리지가 좁은 범위에선 안전하다고 검증된 몇 가지 리팩터링 기법만을 사용해도 효과적으로 리팩터링 할 수 있다.
레거시 코드
레거시 코드의 문제, 대체로 복잡하고 테스트도 제대로 갖춰지지 않은 것이 많다.
무엇보다 다른 사람이 작성한 것이라 거부감이 든다.
레거시 시스템을 파악할 때 리팩터링이 도움된다.
레거시 시스템은 테스트 코드가 없어 리팩터링하기 까다롭다.
이를 위해 테스트를 보강해야하는 데 이것도 상당히 까다롭다.
이 문제는 마땅히 해결할 방법이 없다. 그래서 처음부터 자가 테스트 코드를 만들어야 한다.
캠핑 규칙을 상기하며, 각 부분 별로 조금씩 나누어 정복하는 것이 최선이다.
2.6 리팩터링, 아키텍처, 애그니(YAGNI)
기존 방식, 코딩 전에 설계와 아키텍처를 완성하고 코딩 시작
코딩 전에 아키텍처를 확정지으려 할 때 문제는 소프트웨어 요구사항을 사전에 모두 파악해야 한다는 것이다. 사실상 실현 불가능한 목표다.
리팩터링은 이런 관점을 바꿔 코딩 시작 후에도 요구사항 변화에 따른 대응하도록 코드 베이스를 잘 설계해준다.
향후 변경에 유연하도록 유연성 메커니즘을 소프트웨어에 심지도 한다.
예를 들어, 나중에 사용할 것 같은 매개변수를 미리 적용해 두는 것
이것도 결국 예측에 불과하기에 매개변수가 더 늘어난다던가 변수가 생기면 오히려 변화에 대응하는 능력을 떨어뜨를 때가 대부분이다.
유연성 매커니즘이란 예상되는 결과를 미리 예측하고 대비해두는 것
리팩터링은 앞으로 어느 부분에 유연성이 필요하고 어떻게 해야 그 변화에 가장 잘 대응할지 추측하지 않고 현재 요구사항에 최선에 소프트웨어를 만든다.
진행하면서 사용자 요구사항을 더 잘 이해하게 되면 아키텍처도 그에 맞게 리팩터링한다.
꼭 필요하다고 확신이 들 때만 유연성 메커니즘을 미리 추가한다.
이런 식 설계 방식을 간결한 설계, 점진적 설계, YAGNI(you aren't going to need it)등으로 부른다.
YAGNI는 아키텍처를 고려하지 말라는 뜻이 아니라 아키텍처와 설계를 개발 프로세스에 녹이는 방식이다.
YAGNI 적용이 선제적인 아키텍처에 소홀하라는 뜻이 아니다.
확실한 부분은 미리 설계해두고, 나중에 이해도가 더 높아졌을 때 나머지를 리팩터링으로 설계한다.(진화형 아키렉처 원칙)
2.7 리팩터링과 소프트웨어 개발 프로세스
익스트림 프로그래밍은 지속적 통합, 자가 테스트 코드, 리팩터링, 등을 하나로 묶은 프로세스다.
자가 테스트 코드 + 리팩터링 = 테스트 주도 개발(Test-Driven Development:TDD)
익스트림 프로그래밍은 최초의 애자일 소프트웨어 방법론 중 하나다.
애자일을 제대로 적용하려면 리팩터링에 대한 팀의 역량과 열정이 뒷받침되어 프로세스 전반에 리팩터링이 자연스럽게 스며들도록 해야 한다.
리팩터링의 첫 번째 토대는 자가 테스트 코드다.
팀으로 개발하면서 리팩터링 하려면 각 팀원이 다른 팀원의 작업을 방해하지 않으면서 리팩터링을 할 수 있어야 한다.
이를 위해선 지속적 통합이 필요하다. 그래야 리팩터링으로 인한 코드 변화를 빠르게 알아내지 못해 생기는 문제를 방지할 수 있다.
함수 이름 변경, 함수 제거 등을 한다면, 잦은 통합 말고 호출부에서 감지할 방법이 마땅치 않다.
이런 문제를 빠르게 발견할 수 있게 돕는 자가 테스크 코드는 지속적 통합의 핵심 요소다.
지속적 통합, 자가 테스트 코드, 리팩터링 세 기법을 적용하면 YAGNI 설계 방식으로 개발을 진행 할 수 있다.
추측에 근거한 유연성 메커니즘을 갖춘 시스템보다 단순한 시스템이 변경하기 훨씬 쉽다.
위와 같은 토대를 마련했다면, 애자일이 주는 이점인 지속적인 배포를 수행할 수 있다.
이로 인해 고객의 비즈니스 요구를 프로덕션 코드로 빠르게 반영해 빠르게 배포할 수 있다.
2.8 리팩터링과 성능
직관적인 설계 vs 성능
리팩터링이 성능을 저하시킬까봐 걱정하는 사람이 많다.
저자는 리팩터링이 성능이 느려진다고 해도 진행한다. 이유는 소프트웨어를 이해하기 쉽게 만들기 때문이다.
성능을 무시하기 때문이아니라 경험상 성능이 낮아지는 경우가 드물기 때문이다.
만약 느려진다고 해도 리팩터링된 코드는 되려 튜닝하기 좋은 상태이다.
빠른 소프트웨어 작성 방법 세 가지
예산 분배 방식
가장 엄격한 방식, 설계를 여러 컴포넌트로 나눠 각각 시스템 자원(시간,공간) 예산을 할당
각 컴포넌트는 할당된 예산만 사용 가능, 컴포넌트간 예산 교환 가능
지속적인 관심 기울이기
가장 흔한 방식이지만 효과는 좋지 않다.
성능 개선을 위해 수정하다보니 코드만 다루기 어렵게 변한다.
이 방식은 최적화가 프로그램 전체에 퍼지게 된다.
일부분만 최적화
대부분 프로그램은 전체 코드 중 극히 일부에서 시간을 소비한다. 그래서 코드 전체를 고르게 최적화 한다면, 대부분이 낭비인 것이다.
이 최적화는 '90%의 시간은 낭비'라는 통계에서 착안한 것이다.
성능 최적화는 신경 안쓰고, 코드를 다루기 쉽게 만드는 데만 집중하며 개발한다.
성능 최적화 단계에선 프로파일러 프로그램으로 시간과 공간을 잡아먹는 지점을 탐색한다.
그 특정된 부분들만 개선한다.
성능이 개선되지 않았다면, 이전 개선 내용을 되돌리고, 다른 최적화 대상을 개선한다.
위 과정을 목표 성능이 도달할때까지 반복한다.
이렇게 되면 필요한 부분만 개선하게 된다.
잘 리팩터링해두면, 튜닝에 소비할 시간을 벌 수 있다. 또한 코드 범위가 줄어, 프로그램으로 성능을 더 세밀하게 측정할 수 있다. 때문에 튜닝이 더 쉬워진다.
결과적으로 리팩터링은 단기적으로는 성능이 떨어질 수도 있으나 최적화 단계에선 튜닝하기가 더 쉬워져 더 빠른 소프트웨어를 얻게 된다.
2.9 리팩터링의 유래
명확한 유래는 없다.
현재 리팩터링이란 말 자체는 중요성이 충분히 검증됐고, 그 증거로 수 많은 IDE에서 자동 리팩터링 기능까지 지원한다.
2.10 리팩터링 자동화
과거 리팩터링과 현재 리팩터링에서 가장 큰 변화는 자동 리팩터링 지원 도구 등장이다.
IDE에서 지원하는 자동 리팩터링
리팩터링을 자동화하는 가장 수준 낮은 방식은 텍스트로 조작하는 것이다.
이 불완전하기에 테스트해보기 전까지 신뢰해선 안된다.
텍스트 에디터가 해당한다.
코드를 텍스트가 아닌 구문 트리로 인식해 자동 리팩터링하는 것이 제대로된 방식이다.
IDE가 해당한다.
언어마다 자동 리팩터링을 지원하는 정도가 달라진다.
자바와 같은 정적 타입 언어는 안전하게 리팩터링을 지원할 수 있다.
자바는 클래스 이름이 조금만 달라도 다른 타입이다.
메서드 이름 변경 시 같은 메서드 이름이라도 타입에 맞게 정확히 걸러낸다.
자동 리팩터링 기능은 IDE가 강력하므로 리팩터링은 IDE로 진행하는 것이 좋다.
2.11 더 알고 싶다면
리팩터링이 궁금하다, 월리엄 웨이크의 "리팩터링 워크북"
패턴과 접목된 리팩터링, 조슈아 케리에프스키의 "패턴을 활용한 리팩터링"
레거시 코드를 리팩터링하는 방법, 마이클 페더스의 "레거시 코드 활용 전략"
'IT책, 강의 > 리팩터링' 카테고리의 다른 글
04 - 테스트 구축하기 (1) | 2023.07.12 |
---|---|
03 - 코드에서 나는 악취 (0) | 2023.07.10 |
02 - 리팩터링 원칙 - 01 (0) | 2023.07.06 |
01 - 리팩터링: 첫 번째 예시 - 02 (0) | 2023.07.04 |
01 - 리팩터링: 첫 번째 예시 - 01 (0) | 2023.07.02 |