본문 바로가기
Study/Refactoring

리팩터링 2

by irerin07 2023. 8. 31.
728x90

2장

리팩터링 원칙

리팩터링 전반에 적용되는 원칙 몇 가지를 알아봅시다.

2.1 리팩터링 정의

동사 명사 둘 다 결국 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 특정한 방식에 따라 내부 구조를 변경하는 것이다.

리팩터링하는 동안에는 코드가 항상 정상 작동하기 때문에 전체 리팩터링이 끝나지 않았더라도 언제든 멈출 수 있다.

리팩터링 전 후로 코드가 똑같이 동작해야 하는데 이는 항상 똑같을 수는 없다.

가령 함수 추출하기 를 사용하면 콜스택이 달라져 성능이 변할 수 있다. 하지만 사용자 관점에서는 달려져서는 안된다.

함수 선언 바꾸기 혹은 함수 옮기기 같은 리팩터링을 하면 모듈의 인터페이스가 바뀔 수 있다.

심지어 리팩터링 과정에서 발견된 버그는 리팩터링 후에도 남아 있어야 한다. (아무도 발견못한 숨은 버그는 수정한다. 우리들만의 비밀)

성능 최적화는 오로지 속도 개선에만 신경 쓴다. 그러다보니 코드는 다루기에 더 어렵게 바뀔 수 있다.

2.2 두 개의 모자

기능 추가와 리팩터링 두 목적을 명확히 하자.

기능 추가시에는 기능 추가만 철저하게, 리팩터링 시에는 리팩터링만 철저하게 한다.

기능 추가할때는 기존 코드는 절대 건드리지 않고

리팩터링 할 때는 기능 추가는 절대 하지 않는다. 놓친 테스트 케이스가 있는게 아닌 이상 테스트 추가도 하지 않고 부득이 인터페이스를 변경해야 할 때만 기존 테스트를 수정한다.

개발을 하면서 두 모자를 자주 바꿔쓰는 일이 있을 수 있다.

항상 내가 하고 있는 작업이 무엇인지 인지하고 그에 따른 미묘한 작업 방식의 차이를 분명하게 인식해야 한다.

2.3 리팩터링 하는 이유

리팩터링은 건강한 상태로 유지하는 데 도움을 준다. 다만 모든 문제를 해결해주지는 않는다.

리팩터링하면 소프트웨어 설계가 좋아진다.

리팩터링을 하지 않으면 내부 설계가 썩기 쉽다.

내부 설계를 충분히 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 기반 구조가 무너지기 쉽고 코드만 봐서는 설계를 파악하기 어려워진다. 그렇게 되면 점점 설계를 유지하기 어려워지고, 설계의 부패 속도가 빨라진다.

같은 일을 하더라도 설계가 나쁘면 코드가 길어진다. 중복코드가 많아지기 때문이다. 중복코드를 제거하면 코드량이 줄고 이는 곧 수정시에 드는 노력을 많이 줄일 수 있다.

리팩터링하면 소프트웨어를 이해하기 쉬워진다.

컴퓨터에게 시키려는 일과 이를 표현한 코드의 차이를 최대한 줄여야 한다. 프로그래밍은 결국 내가 원하는 바를 정확히 표현하는 일이다.

다른 개발자들, 특히 나 자신을 위해서 이해하기 쉬운 코드를 유지해야 한다.

리팩터링하면 버그를 쉽게 찾을 수 있다.

이해하기 쉬운 코드는 버그를 찾기 쉽다는 뜻이기도 하다.

리팩토링을 진행하며 명확해진 구조의 프로그램은 버그를 지나치려야 지나칠 수 없을 정도이다.

리팩터링하면 프로그래밍 속도를 높일 수 있다.

결국 개발 속도가 올라가게 된다.

내부 설계가 잘 된 소프트웨어는 새로운 기능을 추가할 지점과 어떻게 고칠지를 쉽게 찾을 수 있다.

내부 설계에 심혈을 기울이면 소프트웨어의 지구력이 올라 빠르게 개발할 수 있는 상태를 더 오래 유지할 수 있다.

2.4 언제 리팩터링해야 할까?

3의 법칙

  1. 처음에는 그냥 개발을 한다.
  2. 비슷한 일을 두 번째로 하게 되면, 일단 계속 개발을 한다. (중복이 생겨 당황스럽겠지만 일단 진행한다)
  3. 비슷한 일을 세 번째 하게 되면 리팩터링 한다.

준비를 위한 리팩터리: 기능을 쉽게 추가하게 만들기

리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전이다.

  • 현재 코드 구조를 살펴보고 구조를 바꾸면 다른 작업을 하기 수월해질 부분을 찾는다.

준비를 위한 리팩터링을 사용해 버그가 수정된 상태를 오래 지속시키고, 같은곳에 다른 버그가 발생할 확률을 줄인다.

이해를 위한 리팩터링: 코드를 이해하기 쉽게 만들기

코드를 수정하기 위해서는 코드가 하는 일을 파악해야 한다.

미래의 나 자신, 혹은 다른 사람을 위해 그 코드가 하는 일을 더 명확하게 드러나도록 리팩터링 해야한다.

변수를 적절한 이름으로 변경해주고, 긴 함수를 잘게 나누는 등의 작업을 하여 코드를 깔끔하게 정리한다.

코드를 분석하며 리팩터링을 하면 더 깊은 수준까지 이해가 가능해진다.

쓰레기 줍기 리팩터링

코드를 파악하던 중 비효율적인 로직을 발견할 때가 있다.

간단한 수정이라면 즉시 고치고, 시간이 좀 걸린다면 짧은 메모를 남겨두고 하던 일을 마무리 한 뒤 처리한다.

수정하는데 오래 걸리고 우선 급하게 처리해야 할 일이 있을 수도 있다. 그래도 처음 봤을 때보다 조금이라도 더 깔끔하게 정리할 수 있도록 하자.

리팩터링은 각각의 작은 단계가 코드를 깨뜨리지 않는다는 사실이다. 작업을 잘게 나누면 몇 달에 걸쳐 진행해도 그 사이 한 순간도 코드가 깨지지 않기도 한다.

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

앞에서 본 리팩터링은 모두 기회가 있을때마다 진행한다. 기능을 추가하거나 버그를 수정하는 동안에 리팩터링도 함께 진행하는 것이다.

리팩터링은 실수를 바로잡거나 보기 싫은 코드에만 적용하는 것이 아닌 잘 작성된 코드에도 적용되어야 한다.

그동안 리팩터링에 소홀했다면, 따로 시간을 내서 새 기능을 추가하기 쉽도록 코드베이스를 개선할 필요가 있다.

그러나 리팩터링 작업 대부분은 드러나지 않게, 기회게 될 때마다 하도록 해야한다는 것을 잊지 말자.

오래 걸리는 리팩터링

아무리 규모가 큰 대규모 리팩터링이라도 팀 전체가 리팩터링에 매달리는것은 그다지 좋은 생각이 아닌것 같다.

차라리 몇주에 걸쳐 조금씩 단계별로 문제를 해결하는 편이 효과적일것이다.

이보다는 누구든 리팩터링해야 할 코드와 관련한 작업을 하게 될 때마다 조금씩 원하는 방향으로 개선하는 것이다.

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

코드리뷰를 진행하며 어떤 아이디어가 떠오르면 이들을 리팩터링을 활용해 쉽게 적용할 수 있는지 살펴본다. 쉽다면, 실제로 적용한다.

이렇게 진행하면 떠오른 아이디어들을 실제로 적용했을 때의 모습을 더 명확하게 볼 수 있으며 리팩터링 하지 않았을때는 보지 못하거나 떠올리지 못한 아이디어들이 떠오르기도 한다.

다만 풀 요청 모델에서를 그리 효과적이지 않다.

작성자와 함께 참석하여 코드를 훑는 방식이 더 효과적이다.

관리자에게는 뭐라고 말해야 할까?

관리자가 기술에 대해 무지하고 코드베이스의 건강 상태가 생산성에 미치는 영향을 모른다면 굳이 말하지 말자.

리팩터링하지 말아야 할 때

지저분한 코드지만 굳이 수정할 필요가 없는 코드 → 외부 API 다루듯 호출해서 쓰는 코드등..이런 코드들은 내부 동작을 이해해야 할 시점에 리팩터링해야 효과를 볼 수 있다.

리팩터링보다 처음부터 다시 작성하는게 쉬운 경우 → 다만 이 기준을 구분하는것은 쉽지 않다.

2.5 리팩터링 시 고려할 문제

리팩터링에 딸려 오는 문제도 있다. 그렇기에 언제 발생하고 어떻게 대처하는지 알고 있어야 한다.

새 기능 개발 속도 저하

리팩터링의 궁극적인 목적은 개발 속도를 높이는 데 있다.

상황에 맞게 조율 할 필요가 있는데, 리팩터링이 필요해 보이지만, 추가하려는 새 기능이 아주 작아 기능 추가부터 하고 싶은 상황이라면?

건강한 코드베이스는 기존 코드를 새로운 방식으로 조합하기 쉬워지기 때문에 복잡한 새 기능도 빨리 추가할 수 있다.

리팩터링을 할지 말지 판단하는 능력은 오랜 경험을 걸쳐 서서히 형성된다.

리팩터링은 도덕적인 이유로 하는것이 아니다. 경제적인 이유로 행하는 것이다.

개발기간을 단축하고 버그 수정 시간을 줄이기 위함이라는 것을 잊지 말아야 한다.

코드 소유권

코드 소유권이 나뉘어 있으면 리팩터링에 방해가 된다. 클라이언트에 영향을 주지 않고서는 원하는 형태로 변경할 수 없기 때문.

그렇다고 리팩터링을 할 수 없는건 아니지만 다른 제약사항이 따를 뿐이다.

느슨한 코드 소유권을 적용하여 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 한다.

브랜치

보통 팀원마다 브랜치를 나누어 맡아 작업하다가 결과물이 어느정도 쌓이면 마스터에 브랜치를 통합하여 다른 팀원과 공유하는 방법을 사용한다.

이 방법의 단점은 독립 브랜치로 작업하는 기간이 길어질수록 작업 결과를 마스터로 통합하기가 어려워진다는 것이다.

개인 브랜치에서 작업한 내용은 마스터에 통합하기 전까지는 다른 사람이 그 내용을 알 수 없다. 통합후에는 코드의 의미를 파악하는데 시간을 더 들여야 한다.

지속적 통합에서는 모든 팀원이 하루에 최소 한 번은 마스터와 통합한다. 이렇게 하면 다른 브랜치들과 차이가 크게 벌어지지 않기 때문에 머지의 복잡도를 낮출 수 있다.

하지만 이 CI를 적용하기 위해서는 마스터를 건강하게 유지하고, 거대한 기능을 잘게 쪼개는 법을 배우고, 각 기능을 끌 수 있는 토글 기능을 적용하여 완료되지 않은 기능이 시스템 전체를 망치지 않도록 해야 한다.

CI는 자주 통합하는 만큼 리팩터링과 궁합이 좋다.

기능별 브랜치를 사용하되 브랜치를 자주 통합할 수 있다면 문제가 발생할 가능성이 줄어든다.

테스팅

핵심은 오류를 재빨리 잡는 데 있다. 그런 환경을 만들기 위해선 테스트 스위트가 필요하다.

리팩터링을 위해서 자가 테스트 코드를 마련해야 한다.

자가 테스트를 이용하여 테스트가 실패시 가장 최근 통과한 버전에서 무엇이 달라졌는지 살펴볼 수 있다. 테스트 주기가 짧다면 단 몇 줄만 비교하면 된다.

리팩터링 과정에서 버그가 생길 위험이 아주 크다는 불안감도 해소할 수 있다.

뛰어난 자동 리팩터링 기능이 있다면 테스트 코드를 건너뛸 수 있겠지만 테스트 코드는 갖춰두면 유용하다.

자가 테스트 코드는 통합 과정에서의 충돌을 잡는 메커니즘으로 활용할 수 있다.

래거시 코드

레거시 코드는 대체로 복잡하고, 테스트도 제대로 갖춰지지 않았으며 무엇보다 다른 개발자가 작성한 코드일 가능성이 높다.

레코시 시스템 파악에 리팩터링이 큰 도움이 된다. 하지만 대규모 레거시 시스템을 테스트 없이 리팩터링하기는 어렵다.

결국 테스트 보강을 해야한다.

하지만 테스트를 염두에 두고 설계한 시스템이 아니라면 굉장히 까다로운 작업이 된다.

레거시 코드 활용 전략 책의 지침을 따라보도록 하자. 주요 내용은 ‘프로그램에서 테스트를 추가할 틈새를 찾아서 시스템을 테스트 해야 한다는 것이며 이러한 틈새를 만들기 위해 리팩터링이 사용 된다.

캠핑 규칙에 따라 처음 왔을 때보다 예전보다 조금이라도 개선하려고 노력해야 한다.

자주 들여다 보게 되는 부분을 더 많이 리팩토링 하면 그 부분을 쉽게 개선했을 때 얻는 효과도 더 크다.

데이터베이스

원래 데이터베이스 리팩터링은 어려운 영역이었지만 진화형 데이터베이스 설계와 데이터베이스 리팩터링 기법의 등장으로 상황이 바뀌었다.

커다란 변경들을 쉽게 조합하고 다룰 수 있는 데이터 마이그레이션 스크립트를 작성하고, 접근 코드와 데이터베이스 스키마에 대한 구조적 변경을 이 스크립트로 처리하게끔 통합하는 것

중요한 것은 작고 독립된 단계들로 쪼개는 것이다.

데이터베이스 리팩터링은 프로덕션 환경에 여러 단계로 나눠러 릴리스 하는 것이 대체로 좋다는 점에서 리팩터링과 다르다.

이렇게 하면 프로덕션 환경에 문제가 있어도 변경을 되돌리기 쉽다.

2.6 리팩터링, 아키텍처, 애그니(YAGNI)

리팩터링이 아키텍처에 미치는 효과는 요구사항 변화에 자연스럽게 대응하도록 코드베이스를 잘 설계해준다는 데 있다.

유연성 메커니즘을 심어두는 방식도 있지만 요구사항이 당초 예상과 다르게 변경되거나 설계에 결함이 있을수 있다는 것을 생각하면 유연성 메커니즘이 오히려 변화 대응 능력이 떨어진다는 것을 알 수 있다.

리팩터링을 사용하면 어느 부분에 유연성이 필요하고, 어떻게 해야 대응할 수 있을지 추측하지 않고 현재까지 파악한 요구사항만을 해결하는 소프트웨어를 구축할 수 있다.

단, 이 요구사항을 멋지게 해결하도록 설계한다.

요구사항을 더 잘 이해하게 되면 그에 맞게 리팩터링 한다.

소프트웨어의 복잡도에 영향을 주지 않는 메커니즘은 마음껏 추가한다.

이런 식의 설계 방식을 간결한 설계, 점진적 설계 YAGNI라 부른다.

YAGNI는 아키텍쳐를 전혀 고려하지 말라는 뜻이 아니다. YAGNI는 아키텍처와 설계를 개발 프로세스에 녹이는 방식이며 리팩터링의 뒷받침이 필수이다.

YAGNI는 선제적인 아키텍쳐 설계에 소홀해도 된다는 뜻이 아니다. 다만, 문제를 더 깊이 이해하게 되었을 떄 처리하는 쪽이 훨씬 낫다고 보는 것이다.

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

애자일을 적용하려면 리팩터링에 대한 팀의 역량과 열정이 뒷받침 되어 프로세스 전반에 리팩터링이 자연스럽게 스며들어야 한다.

테스트는 리팩터링에 굉장히 중요한 토대이다. 프로그래밍 도중 발생한 오류를 확실히 걸러내는 테스트를 자동으로 수행할 수 있어야 한다.

팀으로 작업을 한다면 지속적 통합을 통해 다른 사람의 작업을 방해하지 않으면서 언제든지 리팩터링 할 수 있어야 한다.

자가 테스트 코드, 지속적 통합, 리팩터링이라는 세 기법은 서로 상승효과를 발휘한다.

다만 이는 절대 간단하지 않다. 충분한 연습과 실력이 필요하다.

2.8 리팩터링과 성능

리팩터링하면 소프트웨어가 느려질 수 있다는 것은 사실이지만 추후에 성능 튜닝하기가 더 수월해진다. 즉, 튜닝하기 쉽게 만들고, 튜닝하는 것.

대부분 프로그램은 전체 코드 중 극히 일부분에서 대부분의 시간을 소비한다. 그렇기에 코드 전체를 최적화하면 그 중 약 90%는 효과가 없을 것이고 이는 곧 시간 낭비이다.

이것에 착안하여 의도적으로 성능 최적화에 돌입하기 전까지는 성능에 신경을 끄고, 다루기 쉬운 코드를 만드는데 집중한다.

프로파일러로 프로그램을 분석하여 시간과 공간을 많이 잡아먹는(성능에 큰 영향을 주는 작은 부분들) 지점을 알아낸 다음 그 부분들을 개선한다.

최적화를 위한 작업도 작은 단계로 나누어 진행한다.

리팩터링이 잘 된 프로그램은

  1. 성능 튜닝에 투입할 시간을 벌 수 있다.
  2. 성능을 더 세밀하게 분석할 수 있다.
    1. 프로파일러가 지적해주는 코드의 범위가 줄어들고 튜닝하기 쉬워진다. 개선안이 쉽게 떠오르며 효과적인 튜닝을 적용할 수 있다.

단기적으로 보면 리팩터링 단계에서는 성능이 느려질 수 있지만 결국 코드 튜닝에 들이는 시간이 줄어들기 때문에 더 빠른 소프트웨어를 얻을 수 있다.

2.9 리팩터링의 유래

실력있는 프로그래머는 항상 자신의 코드를 정리하는데 시간을 할애했다. 처음부터 깔끔한 코드는 없다는 것을 경험으로 알고 있었기 때문.

스몰토크의 컴파일-링크-실행 주기가 짧고 객체 지향 언어라는 점에서 이런 환경에 특화된 소프트웨어 개발 방법을 만들어 내었다.

생산성을 높이는데 리팩터링의 역할이 크다는 사실을 깨닫고 실전에 활용하기 시작

이후 리팩터링의 잠재 가치를 간파하고 다른 언어들에도 적용할 수 있도록 발전 시켰다.

2.10 리팩터링 자동화

리팩터링과 관련하여 발생한 가장 큰 변화는 자동 리팩터링의 등장

IDE에서 개발 도구가 처리해주며 따로 테스트할 필요가 없을 정도로 안정적이다.

다만 이러한 리팩터링 도구들이 간혹 문제를 일으킨다.

그렇기에 자동 리팩터링 도구를 쓴다 하더라도 테스트를 충분히 거치는것이 중요하다.

2.11 더 알고 싶다면

리팩터링 워크북 - 윌리엄 웨이크

패턴을 활용한 리팩터링 - 조슈아 케리에프스키

리팩토링 데이터베이스 - 스캇 엠블러, 프라모드 사달게

리팩토링 HTML - 엘리엇 러스티 해롤드

레거시 코드 활용 전략 - 마이클 페더스

리팩터링 웹사이드 - https://refactoring.com

728x90

'Study > Refactoring' 카테고리의 다른 글

리팩터링 4  (0) 2023.08.31
리팩터링 3  (0) 2023.08.31
리팩터링 1  (0) 2023.08.31
리팩터링 12  (0) 2023.08.30
리팩터링 11  (0) 2023.08.29