훌륭한 개발자는 언제 어떻게 Rails 코드를 리팩토링해야 하는지 알고 있습니다.

게시 됨: 2022-03-11

대규모 리팩터링: 왜 그런 일을 했을까요?

고장나지 않았다면 수리하지 마십시오.

그것은 잘 알려진 구절이지만 우리가 알고 있듯이 인간의 기술 발전의 대부분은 망가지지 않은 것을 고치기로 결정한 사람들에 의해 이루어졌습니다. 특히 소프트웨어 산업에서는 우리가 하는 대부분이 고장나지 않은 것을 고치는 것이라고 주장할 수 있습니다.

기능 수정, UI 개선, 속도 및 메모리 효율성 개선, 기능 추가: 모두 수행할 가치가 있는지 쉽게 확인할 수 있는 활동이며 경험 많은 Rails 개발자인 우리는 이에 대해 시간을 할애하는 것에 대해 찬성하거나 반대합니다. 그러나 대부분의 경우 회색 영역에 속하는 활동이 있습니다. 표준 리팩토링, 특히 대규모 코드 리팩토링입니다.

대규모 리팩토링이라는 용어는 설명할 가치가 있습니다. "대규모"로 간주될 수 있는 것은 용어가 약간 모호하기 때문에 경우에 따라 다를 수 있지만, 저는 몇 가지 클래스 이상 또는 하나 이상의 하위 시스템 이상에 크게 영향을 미치는 모든 것을 고려하고 해당 인터페이스는 "대형 .” 반면에 단일 클래스의 인터페이스 뒤에 숨겨진 상태로 유지되는 모든 Rails 리팩토링은 확실히 "작은" 것입니다. 물론 그 사이에 많은 회색 영역이 있습니다. 마지막으로, 당신의 직감을 믿으십시오. 당신이 그것을 하는 것이 두렵다면 그것은 아마도 "큰" 것입니다.

정의상 리팩토링은 가시적인 기능을 생성하지 않으며 클라이언트에게 보여줄 수 있는 것도 없고 결과물도 생성하지 않습니다. 기껏해야 속도와 메모리 사용량이 약간 향상될 수 있지만 이것이 주요 목표는 아닙니다. 일차적인 목표는 당신이 만족하는 코드라고 말할 수 있습니다. 그러나 코드베이스 전체에 걸쳐 광범위한 결과를 가져오는 방식으로 코드를 재배열하기 때문에 모든 지옥이 무너지고 문제가 발생할 가능성이 있습니다. 우리가 언급한 두려움은 물론 여기에서 비롯됩니다. 당신의 코드베이스에 새로운 사람을 소개한 적이 있습니까? 그들이 독특하게 구성된 코드에 대해 물었을 때 당신은 다음과 같이 대답했습니다.

예아아, 이것은 당시에는 말이 되었던 레거시 코드지만 사양이 변경되어 이제 고칠 수 없을 정도로 비용이 많이 듭니까?

어쩌면 당신은 그들에게 매우 진지한 표정을 짓고 만지지 말고 그대로 두라고 말했습니다.

Rails 코드를 리팩토링하는 방법을 계획할 때 시작하려면 복잡한 차트가 필요할 수 있습니다.

"왜 우리가 그것을 하고 싶어할까요?"라는 질문입니다. 그것은 자연스러운 일이며 그것을하는 것만큼 중요 할 수 있습니다 ...

"왜 우리가 그것을 하고 싶어할까요?"라는 질문입니다. 리팩토링에 많은 시간을 할애할 수 있도록 설득해야 하는 다른 사람들이 꽤 자주 있기 때문에 리팩토링 프로세스만큼 중요할 수 있습니다. 따라서 이를 수행하려는 경우와 얻을 수 있는 이점을 고려해 보겠습니다.

성능 개선

유지 관리 측면에서 현재 코드 구성에 만족하지만 여전히 성능 문제를 일으키고 있습니다. 현재 설정되어 있는 방식을 최적화하는 것은 너무 어렵고 변경 사항은 매우 취약할 것입니다.

여기에서 할 일은 한 가지뿐이며 그것은 광범위하게 프로파일링하는 것입니다. 벤치마크를 실행하고 얻을 수 있는 금액을 추정한 다음 구체적인 수익으로 전환되는 방법을 추정해 보십시오. 때로는 제안된 코드 리팩토링이 가치가 없다는 것을 깨닫게 될 수도 있습니다. 다른 경우에는 케이스를 뒷받침할 콜드 하드 데이터가 있습니다.

아키텍처 개선

아키텍처는 괜찮지만 다소 구식일 수도 있고, 코드베이스의 해당 부분을 만질 때마다 움츠러드는 너무 나빠서일 수도 있습니다. 빠르고 잘 작동하지만 새로운 기능을 추가하는 것은 고통스럽습니다. 그 고통 속에 리팩토링의 비즈니스 가치가 있습니다. "고통"은 또한 리팩토링 프로세스가 새로운 기능을 추가하는 데 더 오래 걸리거나 훨씬 더 오래 걸린다는 것을 의미합니다.

그리고 얻을 수 있는 이점이 있습니다. 제안된 대규모 리팩토링이 있거나 없는 몇 가지 샘플 기능에 대한 비용/이익을 추정합니다. 유사한 차이점이 시스템이 개발되는 동안 현재와 미래에 영원히 시스템의 해당 부분에 영향을 미치는 대부분의 향후 기능에 적용될 것이라고 설명합니다. 귀하의 추정치는 종종 소프트웨어 개발에 있기 때문에 틀릴 수 있지만 그 비율은 아마도 야구장에 있을 것입니다.

최신 정보 제공

때로는 코드가 처음에 잘 작성되었습니다. 당신은 그것에 매우 만족합니다. 빠르고 메모리 효율적이며 유지 관리가 가능하며 사양과 잘 일치합니다. 처음에는. 그러나 사양이 바뀌거나 비즈니스 목표가 바뀌거나 최종 사용자에 대해 초기 가정을 무효화하는 새로운 사실을 알게 됩니다. 코드는 여전히 잘 작동하고 여전히 만족하지만 최종 제품의 맥락에서 보면 뭔가 어색합니다. 사물이 약간 잘못된 하위 시스템에 배치되었거나 속성이 잘못된 클래스에 있거나 일부 이름이 더 이상 의미가 없을 수 있습니다. 그들은 이제 완전히 다르게 명명된 비즈니스 용어의 역할을 수행하고 있습니다. 그러나 관련된 작업이 다른 모든 예와 마찬가지로 규모에 맞을 것이기 때문에 모든 종류의 대규모 Rails 리팩토링을 정당화하는 것은 여전히 ​​매우 어렵습니다. 그러나 이점은 훨씬 덜 가시적입니다. 생각해보면 유지관리도 그리 어렵지 않습니다. 어떤 것들은 실제로는 다른 것임을 기억해야 합니다. A는 실제로 B를 의미하고 A의 속성 Y는 실제로 C와 관련이 있음을 기억해야 합니다.

그리고 여기에 진정한 이점이 있습니다. 신경 심리학 분야에는 단기 또는 작업 기억이 7+/-2 요소만 보유할 수 있음을 시사하는 많은 실험이 있으며, 그 중 하나가 Sternberg 실험입니다. 우리가 주제를 공부할 때 우리는 기본 요소로 시작하고, 처음에는 더 높은 수준의 개념에 대해 생각할 때 그 정의에 대해 생각해야 합니다. 예를 들어 "salted SHA256 암호"라는 간단한 용어를 고려하십시오. 처음에는 "salted" 및 "SHA256"에 대한 작업 메모리 정의와 "해시 함수"의 정의를 유지해야 합니다. 그러나 일단 용어를 완전히 이해하면 직관적으로 이해하기 때문에 하나의 메모리 슬롯만 차지합니다. 이것이 우리가 더 높은 수준의 개념에 대해 추론할 수 있도록 낮은 수준의 개념을 완전히 이해해야 하는 이유 중 하나입니다. 우리 프로젝트에 특정한 용어와 정의에 대해서도 마찬가지입니다. 그러나 우리가 코드를 논의할 때마다 실제 의미로의 번역을 기억해야 한다면 그 번역은 소중한 작업 메모리 슬롯 중 다른 하나를 점유하고 있습니다. 그것은 인지 부하를 생성하고 우리 코드의 논리를 통해 추론하기 어렵게 만듭니다. 결과적으로 추론하기가 더 어렵다면 중요한 점을 간과하고 버그를 도입할 가능성이 더 크다는 것을 의미합니다.

그리고 더 분명한 부작용을 잊지 말자. 고객이나 올바른 비즈니스 용어에 대해 잘 알고 있는 사람과 변경 사항에 대해 논의할 때 혼동을 일으킬 수 있는 좋은 기회가 있습니다. 팀에 합류하는 새로운 사람들은 비즈니스 용어와 코드의 해당 용어에 익숙해져야 합니다.

나는 이러한 이유들이 매우 설득력 있고 많은 경우에 리팩토링 비용을 정당화한다고 생각합니다. 하지만 리팩토링 시기와 방법을 결정하기 위해 최선의 판단을 내려야 하는 극단적인 경우가 많이 있을 수 있으므로 주의하십시오.

궁극적으로 우리 중 많은 사람들이 새로운 프로젝트를 시작하는 것을 즐기는 것과 같은 이유로 대규모 리팩토링이 좋습니다. 당신은 그 빈 소스 파일을 보고 용감한 새로운 세계가 당신의 마음을 소용돌이 치기 시작합니다. 이번에는 올바르게 수행하고 코드가 우아하고 아름답게 배치될 뿐만 아니라 빠르고 강력하며 쉽게 확장할 수 있으며 가장 중요한 것은 매일 작업하는 것이 즐겁다는 것입니다. 소규모 및 대규모 리팩토링을 통해 그 느낌을 되찾고 오래된 코드베이스에 새로운 생명을 불어넣고 기술적인 부채를 상환할 수 있습니다.

마지막으로 특정 새 기능을 더 쉽게 구현할 수 있도록 리팩토링을 추진하는 것이 가장 좋습니다. 이 경우 리팩토링에 더 집중할 수 있고 기능 자체의 더 빠른 구현을 통해 리팩토링에 소요된 많은 시간을 즉시 회수할 수 있습니다.

준비

테스트 범위가 만질 가능성이 있는 코드베이스의 모든 영역에서 매우 좋은지 확인하십시오. 잘 가려지지 않은 특정 부분이 보이면 먼저 테스트 범위를 높이는 데 시간을 할애하십시오. 테스트가 전혀 없다면 먼저 테스트를 만드는 데 시간을 투자해야 합니다. 적절한 테스트 스위트를 만들 수 없다면 수락 테스트에 집중하고 가능한 한 많이 작성하고 리팩토링하는 동안 단위 테스트를 작성하십시오. 이론적으로 좋은 테스트 커버리지 없이 코드 리팩토링을 수행할 수 있지만 수동 테스트를 많이 수행하고 자주 수행해야 합니다. 훨씬 더 오래 걸리고 오류가 발생하기 쉽습니다. 궁극적으로 테스트 범위가 충분하지 않은 경우 대규모 Rails 리팩토링을 수행하는 비용이 너무 높아 유감스럽게도 전혀 수행하지 않는 것을 고려해야 합니다. 제 생각에는 이것이 충분히 자주 강조되지 않는 자동화된 테스트의 이점입니다. 자동화된 테스트를 사용하면 더 자주 그리고 더 중요하게는 더 과감하게 리팩토링할 수 있습니다.

테스트 적용 범위가 양호한지 확인했으면 변경 사항 매핑을 시작할 때입니다. 처음에는 코딩을 하지 말아야 합니다. 관련된 모든 변경 사항을 대략적으로 매핑하고 코드베이스를 통해 모든 결과를 추적하고 그에 대한 지식을 마음에 로드해야 합니다. 당신의 목표는 왜 당신이 무언가를 변경하고 그것이 코드베이스에서 수행하는 역할을 정확히 이해하는 것입니다. 변경해야 할 것처럼 보이거나 고장난 것이 해결되는 것처럼 보이기 때문에 변경하는 것을 우연히 발견하면 결국 막다른 골목에 놓이게 될 것입니다. 새 코드가 작동하는 것처럼 보이지만 잘못되어 이제 변경 사항을 모두 기억할 수조차 없습니다. 이 시점에서 대규모 코드 리팩토링에서 수행한 작업을 포기해야 할 수 있으며 본질적으로 시간을 낭비했습니다. 따라서 시간을 갖고 코드를 탐색하여 수행하려는 각 변경의 결과를 이해하십시오. 그것은 결국 멋지게 지불할 것입니다.

리팩토링 프로세스에 도움이 필요합니다. 당신은 다른 것을 선호할지 모르지만 나는 단순한 백지와 펜을 좋아합니다. 나는 종이의 왼쪽 상단에 만들고 싶은 초기 변경 사항을 쓰는 것으로 시작합니다. 그런 다음 변경의 영향을 받는 모든 위치를 찾기 시작하고 초기 변경 아래에 기록합니다. 여기서 판단력을 사용하는 것이 중요합니다. 궁극적으로 종이의 메모와 도표는 자신을 위한 것이므로 기억에 가장 잘 맞는 스타일을 선택하십시오. 나는 그 아래에 글머리 기호 표시가 있는 짧은 코드 조각과 직접(전체 화살표) 또는 간접적으로(파선 화살표) 의존하는 것을 나타내는 다른 메모로 이어지는 많은 화살표를 작성합니다. 또한 코드베이스에서 발견한 특정 사항을 상기시키기 위해 화살표에 속기 표시로 주석을 달았습니다. 메모에 계획된 변경 사항을 수행하는 동안에만 앞으로 며칠 동안 해당 메모를 다시 볼 수 있으며 공간을 덜 사용하고 종이에 더 쉽게 배치할 수 있도록 매우 짧고 비밀스러운 알림을 사용하는 것이 좋습니다. . Rails 리팩토링 후 몇 개월 동안 책상을 청소하다가 그 문서 중 하나를 발견했습니다. 그것은 완전히 횡설수설이었습니다. 나는 그것이 미친 사람에 의해 쓰여졌을 수 있다는 점을 제외하고는 그 종이에 있는 어떤 것이 의미하는지 전혀 몰랐습니다. 하지만 그 문제를 푸는 동안 종이 한 장이 없어서는 안 된다는 것을 알고 있습니다. 또한 모든 변경 사항을 기록할 필요가 있다고 생각하지 마십시오. 다른 방식으로 그룹화하고 세부 정보를 추적할 수 있습니다. 예를 들어, 주요 논문에서 "Ab의 모든 발생 이름을 Cd로 변경"해야 한다는 점을 언급한 다음 여러 가지 방법으로 세부 사항을 추적할 수 있습니다. 별도의 종이에 모두 작성하거나, 다시 한 번 전체 검색을 수행하도록 계획하거나, 선택한 편집기에서 변경 사항을 열어야 하는 모든 소스 파일을 그대로 둘 수 있습니다. 변경 사항에 대한 매핑을 마치면 다시 검토할 수 있는 메모를 작성합니다.

초기 변경의 결과를 계획할 때 대규모 특성으로 인해 추가 결과를 가져오는 추가 변경을 식별하게 될 가능성이 큽니다. 모든 종속 변경 사항을 확인하면서 분석을 반복합니다. 변경 사항의 크기에 따라 동일한 용지에 작성하거나 새 빈 용지를 선택할 수 있습니다. 변경 사항을 매핑하는 동안 시도하고 수행해야 할 매우 중요한 것은 분기 변경 사항을 실제로 중지할 수 있는 경계를 식별하고 확인하는 것입니다. 리팩토링을 가장 합리적이고 반올림된 변경 집합으로 제한하려고 합니다. 그냥 멈추고 나머지는 그대로 둘 수 있는 지점이 보이면 다른 변경 사항과 개념적으로 관련되어 있더라도 리팩토링해야 한다고 보더라도 그렇게 하세요. 이 코드 리팩토링 라운드를 끝내고 철저히 테스트하고 배포하고 더 많은 것을 위해 돌아오십시오. 관리 가능한 변경 사항의 크기를 유지하려면 이러한 지점을 적극적으로 찾아야 합니다. 물론 언제나처럼 판단을 내려야 합니다. 꽤 자주 나는 약간의 인터페이스 변환을 수행하기 위해 일부 프록시 클래스를 추가하여 리팩토링 프로세스를 중단할 수 있는 지점에 도달했습니다. 심지어 리팩토링이 "자연스러운 중지"(즉, 프록시 코드가 거의 필요하지 않음) 지점이 되는 지점까지 리팩토링을 추진하는 것만큼 많은 작업이 필요하다는 것을 깨달았을 때 구현하기 시작했습니다. 그런 다음 역추적하여 마지막 변경 사항을 되돌리고 리팩토링했습니다. 이 모든 것이 미지의 영역을 매핑하는 것처럼 들린다면 영역 지도가 2차원일 뿐이라는 점을 제외하고는 그렇게 느끼기 때문입니다.

실행

리팩토링 준비를 마쳤으면 계획을 실행할 차례입니다. 집중력을 높이고 산만하지 않은 환경을 확보하십시오. 이 시점에서 인터넷 연결을 완전히 끊는 경우가 있습니다. 문제는 준비를 잘했다면 옆에 있는 종이에 메모를 잘 하면 집중력이 올라간다는 것입니다! 이러한 방식으로 변경 사항을 매우 빠르게 이동할 수 있습니다. 이론적으로 대부분의 작업은 사전에 준비하는 동안 수행되었습니다.

실제로 코드를 리팩토링하면 매우 구체적이고 나쁜 코드처럼 보일 수 있는 이상한 코드 비트에 주의를 기울이십시오. 나쁜 코드일 수도 있지만 실제로는 프로덕션에서 버그를 조사하는 동안 발견된 이상한 코너 케이스를 처리하는 경우가 많습니다. 시간이 지나면서 대부분의 Rails 코드는 이상한 코너 케이스 버그를 처리하는 "머리카락" 또는 "사마귀"가 커집니다. 예를 들어 여기에는 IE6에 필요할 수 있는 이상한 응답 코드 또는 이상한 타이밍 버그를 처리하는 조건이 있습니다. 큰 그림에서는 중요하지 않지만 여전히 중요한 세부 사항입니다. 이상적으로는 먼저 다루려고 하지 않는다면 단위 테스트로 명시적으로 다루어집니다. 한 번은 중간 크기의 애플리케이션을 Rails 2에서 Rails 3으로 이식하는 임무를 받은 적이 있습니다. 코드에 매우 익숙했지만 약간 지저분하고 고려해야 할 변경 사항이 많이 있었기 때문에 재구현을 선택했습니다. 실제로 재구현은 현명한 방법이 아니기 때문에 빈 Rails 3 앱으로 시작했고 대략 설명된 프로세스를 사용하여 이전 앱의 세로 조각을 새 앱으로 리팩토링했습니다. 수직 슬라이스를 완료할 때마다 이전 Rails 코드를 살펴보고 각 라인을 살펴보고 새 코드에 해당 코드가 있는지 다시 확인했습니다. 나는 본질적으로 모든 이전 코드 "머리카락"을 선택하고 새 코드베이스에서 복제했습니다. 결국 새 코드베이스는 모든 경우를 해결했습니다.

수동 테스트를 충분히 자주 수행해야 합니다. 리팩토링 프로세스에서 자연스러운 "중단"을 찾도록 강제할 뿐만 아니라 시스템의 일부를 테스트할 수 있을 뿐만 아니라 프로세스에서 중단할 것으로 예상하지 않은 어떤 것도 중단하지 않았다는 확신을 줍니다. .

포장해

Rails 코드 리팩토링을 완료했으면 마지막으로 모든 변경 사항을 검토하십시오. 전체 diff를 보고 넘어가십시오. 리팩토링을 시작할 때 지금 알고 있는 지식이 없었기 때문에 놓친 미묘한 부분을 종종 발견할 수 있습니다. 대규모 리팩토링의 좋은 이점입니다. 특히 원래 작성하지 않은 경우 코드 구성에 대한 더 명확한 정신적 이미지를 얻을 수 있습니다.

가능하면 동료에게도 검토를 요청하십시오. 그는 코드베이스의 정확한 부분에 특별히 익숙할 필요도 없지만 프로젝트와 해당 코드에 대해서는 일반적으로 익숙해야 합니다. 변화에 대한 새로운 시각을 갖는 것은 많은 도움이 될 수 있습니다. 다른 개발자가 볼 수 없는 경우에는 자신이 직접 개발자인 척해야 합니다. 푹 주무시고 상쾌한 마음으로 복습하세요.

QA가 부족하면 그 모자도 착용해야 합니다. 다시 한 번 휴식을 취하고 코드에서 거리를 두었다가 다시 돌아와 수동 테스트를 수행합니다. 당신은 많은 도구를 가지고 어수선한 전기 배선 캐비닛에 들어가 모든 것을 분류하고, 아마도 물건을 자르고 다시 배선하는 것과 같은 일을 겪었으므로 평소보다 조금 더 주의를 기울여야 합니다.

마지막으로, 이제 훨씬 더 명확하고 구현하기 쉽게 계획된 모든 변경 사항을 고려하여 노력의 결실을 즐기십시오.

언제 하지 않겠습니까?

프로젝트 코드를 최신 상태로 고품질로 유지하기 위해 대규모 리팩토링을 정기적으로 수행하면 많은 이점이 있지만 여전히 비용이 많이 드는 작업입니다. 바람직하지 않은 경우도 있습니다.

테스트 범위가 열악합니다.

언급했듯이 테스트 커버리지가 매우 열악하면 큰 문제가 될 수 있습니다. 자신의 판단에 따르되, 새로운 기능을 작업하고 가능한 한 많은 현지화된 소규모 리팩토링을 수행하면서 적용 범위를 높이는 데 집중하는 것이 단기적으로 더 나을 수 있습니다. 코드베이스의 더 큰 부분을 급락하고 정렬하기로 결정하면 많은 도움이 될 것입니다.

리팩토링은 새로운 기능에 의해 구동되지 않으며 오랫동안 코드베이스가 변경되지 않았습니다.

일부러 '코드베이스는 바뀌지 않는다'는 말 대신 과거형을 사용했다. 경험에 비추어 볼 때(그리고 경험에 따르면 여러 번 틀렸음을 의미합니다) 코드베이스의 특정 부분을 변경해야 할 시기에 대한 예측에 거의 의존할 수 없습니다. 따라서 차선책을 수행하십시오. 과거를 살펴보고 과거가 반복될 것이라고 가정하십시오. 오랫동안 변경되지 않은 항목이 있으면 지금 변경할 필요가 없습니다. 그 변화가 올 때까지 기다렸다가 다른 일을 하십시오.

시간이 촉박하다

유지 관리는 프로젝트 수명 주기에서 가장 비용이 많이 드는 부분이며 리팩토링을 통해 비용을 절감할 수 있습니다. 미래의 유지 보수를 더 저렴하게 만들기 위해 기술 부채를 줄이기 위해 모든 비즈니스가 리팩토링을 사용하는 것이 절대적으로 필요합니다. 그렇지 않으면 새로운 기능을 추가하는 데 점점 더 많은 비용이 드는 악순환에 빠질 위험이 있습니다. 그것이 왜 나쁜지 자명하기를 바랍니다.

즉, 대규모 리팩토링은 시간이 얼마나 걸릴지 예측할 수 없으며 중간에 수행해서는 안 됩니다. 내부 또는 외부 이유가 무엇이든 시간이 촉박하고 그 기간 내에 완료할 수 있을지 확신이 서지 않는다면 리팩토링을 포기해야 할 수도 있습니다. 압력과 스트레스, 특히 시간 유발형은 집중도를 낮추는데, 이는 대규모 리팩토링에 절대적으로 필요합니다. 팀에서 더 많은 "구매"를 받아 시간을 따로 확보하고 시간을 가질 수 있는 기간 동안 달력을 살펴보십시오. 연속적인 시간 연장일 필요는 없습니다. 물론 해결해야 할 다른 문제가 있지만 이러한 휴식 시간은 하루나 이틀을 넘지 않아야 합니다. 그렇다면, 코드베이스에 대해 배운 것과 정확히 어디서 멈췄는지 잊어버리기 시작하므로 자신의 계획을 상기시켜야 합니다.

결론

제가 몇 가지 유용한 지침을 제공하고 특정 경우에 대규모 리팩토링을 수행하는 것의 이점과 필요성을 확신했기를 바랍니다. 주제는 매우 모호하며 여기에 언급된 내용은 확실한 사실이 아니며 세부 사항은 프로젝트마다 다를 수 있습니다. 제 생각에는 일반적으로 적용할 수 있는 조언을 제공하려고 노력했지만 항상 그렇듯이 귀하의 특정 사례를 고려하고 자신의 경험을 사용하여 특정 문제에 적응하십시오. 행운을 빌어요 리팩토링!