'전체 글'에 해당되는 글 78건

마틴은 특정 기술, 도구, 아키텍처를 사용하기 전에 문제점 부터 찾는다. 항상 손익을 제대로 이해하고 사용하는 것이다. 리팩토링도 다르지 않다. 엄현히 딸려오는 문제가 있다.

새 기능 개발 속도 저하

리팩토링의 긍극적인 목적은 개발 속도를 높이는 데 있다. 하지만 리팩터링으로 인해 진행이 느려진다고 생각하는 사람이 여전히 많다.

 

더보기

숙련도가 없는 일은 원래 느린 것이다.

리팩토링 숙련도를 쌓는 게 실전에서 리팩토링을 제대로 적용하는 데 가장 큰 걸림돌이다. 

 

리팩토링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다.

 

그렇다라도 상황에 맞게 조절해야 한다. 이는 개발자의 성향, 습관, 경험에 따라 조절되는 게 일반적이다. 계획 된 리팩토링을 지양하고 준비를 위한 리팩토링을 지향해야 하기 때문이다.

 

건강한 코드의 위력을 충분히 경험해보지 않고서는 코드베이스가 건강할 때와 허약할 때의 생상성의 차이를 체감하기 어렵다. 코드베이스가 건강하면 기존 코드를 새로운 방식으로 조합하기 쉬워서 복잡한 새 기능을 더 빨리 추가할 수 있다.

 

마틴 - 개발 속도 저하를 이우로 리팩터링을 금하는 비생산적인 문화를 관리자 탓으로 돌리는 사람이 많지만, 나는 오히려 개발자 스스로가 그렇게 생각하는 경우도 많이 봤다. 심지어 관리자가 리팩토링에 호의적임에도 리팩토링하면 안 되는 줄 아는 사람도 있다.

 

사람들이 빠지기 쉬운 가장 위험한 오류는 리팩토링을 '클린 코드'나 '바람직한 엔지니어링 습관'처럼 도덕적인 이유로 정당화하는 것이다. 리팩토링의 본질은 코드베이스를 예쁘게 꾸미는 데 있지 않다. 오로지 경제적인 이유로 하는 것이다.

 

리팩토링은 개발 기간을 단축하고자 하는 것이다. 기능 추가 시간을 줄이고, 버그 수정 시간을 줄여준다.

 

리팩토링하도록 이끄는 동력은 어디까지나 경제적인 효과에 있다. 이를 명확하게 이해하는 개발자, 관리자, 고객이 많아질수록 기능 개발 횟수와 개발 시간을 정비례 할 것이다.

 

코드 소유권

리팩토링하다 보면 모듈의 내부뿐만 아니라 시스템의 다른 부분과 연동하는 방식에도 영향을 주는 경우가 많다. 함수 이름을 바꾸고 싶어도 코드의 소유자가 다른 팀이라서 나에게는 쓰기 권한이 없거나 고객에게 API로 제공된 것이라면 이런 함수는 인터페이스를 누가 선언했는지와 관계없이 클라이언트가 사용하는 '공개된 인터페이스'에 속한다.

 

코드 소유권이 나뉘어 있으면 리팩토링에 방해가 된다. 클라이언트에 영향을 주지 않고서는 원하는 형태로 변경할 수 없기 때문이다. 그렇다고 리팩토링을 할 수 없는 건 아니다. 여전히 훌륭하게 개선할 수 있지만 제약이 따를 뿐이다. 함수 이름을 변경할 때는 기존 함수도 그대로 유지하되 함수 본문에서 새 함수를 호출하도록 수정한다. 그리고 폐기 대상으로 지정하고 시간이 흐른 뒤에 삭제할 수도 있지만, 때로는 영원히 남겨둬야 할 수도 있다.

 

마틴은 코드 소유권을 작은 단위로 나눠 엄격히 관리하는 데 반대하는 입장이다. 대신 코드의 소유권을 팀에 두는 것이다. 그래서 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 한다. 설사 다른 사람이 작성했더라도 말이다. 프로그래머마다 각자가 책임지는 영역이 있을 수는 있다. 이말은 자신이 맡은 영역의 변경 사항을 관리하라는 뜻이지, 다른 사람이 수정하지 못하게 막으라는 뜻이 아니다.

 

이렇게 코드 소유권을 느슨하게 정하는 방식은 여러 팀으로 구성된 조직에도 적용할 수 있다. 예컨대 어떤 팀은 다른 팀 사람이 자기 팀 코드의 브랜치를 따서 수정하고 커밋을 요청하는, 흡사 오픈소스 개발 모델을 권장하기도 한다. 이 방식은 코드 소유권을 엄격히 제한하는 방식과 완전히 풀어서 변경을 통제하기 어려운 방식을 절충한 것으로, 대규모 시스템 개발 시 잘 어울린다.

 

브랜치

현재 흔히 볼 수 있는 팀 단위 작업 방식은 버전 관리 시스템을 사용하여 팀원마다 코드베이스의 브랜치를 하나씩 맡아서 작업하다가, 결과물이 어느 정도 쌓이면 마스터 브랜치에 통합해서 다른 팀원과 공유하는 것이다. 그런데 이렇게 하면 어떤 기능 전체를 한 브랜치에만 구현해놓고, 프로덕션 버전으로 릴리스할 때가 돼서야 마스터에 통합하는 경우가 많다. 이 방식을 선호하는 이들은 작업이 끝나지 않은 코드가 마스터에 섞이지 않고, 기능이 추가될 때마다 버전을 명확히 나눌 수 있고, 기능에 문제가 생기면 이전 상태로 쉽게 되돌릴 수 있어서 좋다고 한다.

 

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

 

마틴은 Merge통합을 명확히 구분한다. 마스터를 브랜치로 'Merge'하는 작업은 단방향이다. 브랜치만 바뀌고 마스터는 그대로다. 반면, '통합'은 마스터를 개인 브랜치로 가져와서 작업한 결과를 다시 마스터에 올리는 양방향 처리를 뜻 한다. 그래서 마스터와 브랜치가 모두 변경된다.

 

누군가 개인 브랜치에서 작업한 내용을 마스터에 통합하기 전까지는 다른 사람이 그 내용을 볼 수 없다. 통합한 뒤에는 마스터에서 달라진 내용을 내 브랜치에 머지해야 하는데, 그러려면 상당한 노력이 들 수 있다. 특히 의미가 변한 부분을 처리하기가 만만치 않다. 최신 버전 관리 시스템은 복잡한 변경 사항을 텍스트 수준에서 머지하는 데는 매우 뛰어나지만, 코드의 의미는 전혀 이해하지 못한다.

 

이 처럼 머지가 복잡해지는 문제는 기능별 브랜치들이 독립적으로 개발되는 기간이 길어질수록 기하급수적으로 늘어난다. 4주간 작업한 브랜치들을 통합하는 노력은 2주간 작업한 브랜치들을 통합할 때 보다 두 배 이상 든다. 이 때문에 기능별 브랜치의 통합 주기를 2~3일 단위로 짧게 관리해야 한다고 주장하는 사람이 많다.

 

마틴은 더 짧아야 한다고 주장한다. 이 방식을 지속적 통합(CI) 또는 트렁크 기반 개발(TBD)이라 한다. CI에 따르면 모든 팀원이 하루에 최소 한 번은 마스터와 통합한다. 이렇게 하면 다른 브랜치들과의 차이가 크게 벌어지는 브랜치가 없어져서 머지의 복잡도를 상당히 낮출 수 있다. 하지만 CI를 적용하기 위해서는 치러야 할 대가가 있다. 마스터를 건강하게 유지하고, 거대한 기능을 잘게 쪼개는 법을 배우고, 각 기능을 끌 수 있는 기능 토클을 적용하여 완료되지 않은 기능이 시스템 전체를 망치지 않도록 해야한다.

 

머지의 복잡도를 줄일 수 있어서 CI를 선호하기도 하지만 가장 큰 이유는 리팩토링과 궁합이 아주 좋기 때문이다. 리팩터링을 하다 보면 코드베이스 전반에 걸쳐 자잘하게 수정하는 부분이 많을 때가 있다.

 

기능별 브랜치 방식에서는 리팩토링을 도저히 진행할 수 없을 정도로 심각한 머지 문제가 발생하기가 쉽다. 켄트 백이 CI와 리팩터링을 합쳐서 익스트림 프로그래밍을 만든 이유도 두 기법의 궁합이 잘 맞기 때문이다.

 

기능별 브랜치가 가져오는 리팩토링 부담은 너무나 크다. 그래서 CI를 완벽히 적용하지는 못하더라도 통합 주기만큼은 최대한 짧게 잡아야 한다.

 

참고로 CI를 적용하는 편이 소프트웨어를 배포하는 데 훨씬 효과적이라는 객관적인 증거가 있다.

 

테스팅

리팩토링의 두드러진 특성은 프로그램의 겉보디 동작은 똑같이 유지된다는 것이다. 절차를 지켜 제대로 리팩토링하면 동작이 깨지지 않아야 한다. 하지만 실수를 저지르더라도 재빨리 해결하면 문제가 되지 않는다. 리팩토링은 단계별 변경 폭이 작아서 도중에 발생한 오류의 원인이 될만한 코드 범위가 넓지 않다. 원인을 못 찾더라도 버전 관리 시스템을 이용하여 가장 최근에 정상 동작하던 상태로 되돌리면 된다.

 

핵심은 오류를 재빨리 잡는 데 있다. 실제로 이렇게 하려면 코드의 다양한 측면을 검사하는 테스트 스위트가 필요하다. 그리고 이를 빠르게 실행할 수 있어야 수시로 테스트하는 데 부담이 없다.

 

자가 테스트 코드를 갖추기란 무리한 요구라고 생각하는 사람도 있을 것이다. 하지만 수십 년 동안 자가 테스트를 이용하여 소프트웨어를 빌드하는 팀을 봤다. 물론 테스트에 어느 정도 노력을 기울여야 하는 것은 사살이지만, 효과는 상당하다. 자가 테스트 코드는 리팩토링을 할 수 있게 해줄 뿐만 아니라, 새 기능 추가도 훨신 안전하게 진행할 수 있게 도와준다. 실수로 만든 버그를 빠르게 찾아서 제거 할 수 있게 때문이다. 이때 핵심은 테스트가 실패한다면 가장 최근에 통과한 버전에서 무엇이 달라졌는지 살펴볼 수 있다는 데 있다. 테스트 주기가 짧다면 단 몇 줄만 비교하면 되며, 문제를 일으킨 부분이 그 몇 줄 안에 있기 때문에 버그를 훨씬 쉽게 찾을 수 있다.

 

레거시 코드

유산(Money Money)은 많이 받을 수록 좋지만 프로그래밍할 때는 물려받을수록 좋지 않다. 물려받은 레거시 코드는 대체로 복잡하고 테스트도 제대로 갖춰지지 않은 것이 많다.

 

레거지 시스템을 파악할 때 리팩터링이 굉장히 도움된다. 제 기능과 맞지 않은 함수 이름을 바로 잡고 어설픈 프로그램 구문을 매끄럽게 다듬어서 거친 원석 같던 프로그램을 반짝이는 보석으로 만들 수 있다.

 

하지만 테스트 없이는 대규모 레거시 시스템을 명료하게 리팩토링하기는 어렵다. 이 문제의 정답은 당연히 테스트 보강이다.

 

테스트는 단순한 노동에 가까울 수 있다는 점을 제외하면 간단히 할 수 있어보이지만, 막상 해보면 생각보다 훨씬 까다롭다. 보통은 테스트를 염두에 두고 설계한 시스템만 쉽게 테스트할 수 있다.

 

마틴 - 쉽게 해결할 방법은 없다. 그나마 해줄 수 있는 조언은 "레거지 코드 활용 전략"에 나온 지침을 충실히 따르는 것이다.  주요 내용을 한 마디로 표현하면 '프로그램에서 테스트를 추가할 틈새를 찾아서 시스템을 테스트해야 한다.' 이다.

 

레거시 코드 리팩토링은 상당히 어려운 작업이다. 이 문제를 해결하는 쉬운 방법은 아쉽게도 없다. 그래서 처음부터 자가 테스트 코드를 작성해야 한다고 그토록 강조하는 이유이다.

 

마틴도 테스트를 갖추고 있더라도 복잡하게 얽힌 레거시 코드를 아름다운 코드로 단번에 리팩토링하는 데는 낙관적이지 않다. 선호하는 방식은 서로 관련된 부분끼리 나눠서 하나씩 공략하는 것이다. 코드의 한 부분을 훑고 캠핑 규칙에 다라 처음 왔을 때 보다 깨끗하게 치우는 것이다. 레거시 시스템의 규모가 크다면 자주 보는 부분을 더 많이 리팩터링한다.

 

데이터베이스

이 책의 초판에서 데이터베이스는 리팩토링하기 어려운 영역이라고 말했지만, 일 년도 지나지 않아서 프라모드 사달게가 개발한 진화형 데이터베이스 설계와 데이터베이스 리팩토링 기법은 현재 널리 적용되고 있다. 이 기법의 핵심은 커다란 변경들을 쉽게 조합하고 다룰 수 있는 데이터 마이그레이션 스크립트를 작성하고, 접근 코드와 데이터베이스 스키마에 대한 구조적 변경을 스크립트로 처리하게끔 통합하는 데 있다.

블로그 이미지

_김은찬

두번 다시는 꺾이지 않으리

,