본문 바로가기
기타

리팩토링 스터디 #3.5 - 내용 미리보기

by 유세지 2020. 10. 12.

추석 연휴와 한글날로 연휴를 보내고 리팩토링 스터디 시간이 다시 돌아왔습니다. 이번 회차에는 기본적인 리팩토링 기법들을 알아봅니다. 기능에 따라 정-반 구조로 이루어진 기법이 대부분이기 때문에 하나를 이해하면 다른 하나도 자연스럽게 이해가 됩니다. 처음부터 하나씩 살펴보겠습니다.

 

함수 추출하기 Extract Function

함수 추출하기는 가장 빈번하게 사용되는 리팩토링 기법입니다. 코드 블럭을 찾아 어떤 기능을 하고 있는지 파악한 다음, 독립된 하나의 함수로 추출하고 적절한 이름을 붙여주면 됩니다.

특히 이름을 붙여주는 부분은 아래에 나오는 기법들에 필수적으로 들어간다고 봐도 무방하니 모든 작업마다 염두해 두면 좋을 것 같습니다.

 

그렇다면 함수 추출하기는 언제 하는 것이 좋을까요? 가장 먼저 눈에 들어오는 것은 코드의 길이 입니다. 길이가 너무 길어져서 한 화면만에 파악이 어려워진다면 추출하는 것이 좋습니다. 또는 같은 기능이 여러번 반복될 때, 즉 재사용성을 기준으로 해도 좋습니다. 이 밖에도 많은 방법과 기준이 있겠지만 저자가 추천하는 것은 바로 목적과 구현을 분리하는 방식입니다. 이렇게 해두면 나중에 그 코드를 다시 읽더라도 이 함수가 어떤 역할을 하기 위해 만들어졌는지 한 눈에 들어오기 때문에 그 함수의 코드에 대해서는 신경 쓸 필요가 없어집니다. 코드의 해석 효율이 의사 코드(pseudocode) 수준으로 높아지는 것이죠.

 

여기서 추출해야 할 부분이 얼마나 짧은지는 별 문제가 되지 않습니다. 저자의 경험으로 미루어 보면 한 줄 짜리 코드를 추출하는 일도 적지 않았다고 합니다. 이런 식으로 함수가 많아지면 코드의 동작 속도나 메모리쪽에서 문제가 생기지 않을까 걱정하는 사람들을 위해 일반 지침을 친절히 적어두었습니다.

 

"최적화를 할 때는 다음 두 규칙을 따르기 바란다. 첫 번째, 하지 마라. 두 번째(전문가 한정), 아직 하지 마라" - M. A. 잭슨

 

 

이 기법을 적용하면서 유의해야 할 점은 유효 범위를 벗어나는 변수가 있는 경우입니다. 지엽적으로 사용되는 변수가 있다면 함수를 통째로 추출했다가 낭패를 볼 수 있습니다. 이러한 경우 해당 변수를 매개변수를 통해 전달해서 함수가 다른 곳에 선언되어 있더라도 문제 없이 작동할 수 있도록 해주는 것이 중요합니다. 혹시나 발생할 문제들을 예방하기 위해 리팩토링 기법을 적용하고 난 뒤에는 항상 컴파일과 테스트를 해보는 것을 잊지 맙시다. 프로그래머가 실수로 놓치고 간 부분들을 발견하게 될지도 모릅니다.

 

 

 

함수 인라인하기 Inline Function

함수 인라인하기는 앞서 살펴보았던 함수 추출하기의 정반대의 리팩토링 기법입니다. 함수 추출하기가 원본 코드에서 해당 코드를 따로 빼주었다면, 이번에는 빠져나가있는 코드를 원래의 자리로 넣어주는 기법입니다.

 

함수는 목적이 분명하게 드러나면서도 가능한한 짧은 이름을 사용하는 것이 좋습니다. 저자가 이름 짓기를 계속해서 강조하는 이유이기도 합니다. 함수를 추출해서 좋은 이름을 지어주는 방법도 있지만, 때로는 그런 이름만큼 본문의 코드 자체를 깔끔하게 리팩토링 할 때도 있습니다. 특히 간접 호출의 경우 유용할 때도 있지만 필요하지 않은 간접 호출은 코드 이해를 방해할 뿐입니다.

 

리팩토링을 진행하며 잘못 추출된 함수들도 다시 인라인합니다. 필요할때 다시 추출하더라도 인라인을 아끼지 맙시다.

 

단, 인라인하는 함수가 다형 메서드라면, 인라인해서는 안됩니다. 서브클래스에서 오버라이드 하고 있는 메서드가 사라져버리면 정상적으로 동작하지 않게됩니다. 이는 전과 후의 동작이 동일해야 한다는 리팩토링 원칙에 어긋나며, 멀리 갈 것 없이 프로그래머가 의도한대로 프로그램이 동작하지 않을 수 있습니다.

 

 

변수 추출하기 Extract Variable

처음에 살펴보았던 함수 추출하기와 비슷합니다. 표현식이 너무 길어지거나, 복잡하게 이루어져있다면 이를 지역 변수를 활용하여 관리하게 쉽게 만들 수 있습니다. 이때 추가하는 변수는 후에 중단점을 지정하거나 상태를 출력하도록 할 수 있기 때문에 디버깅에도 도움이 됩니다.

 

변수 추출하기의 과정은 함수 추출하기보다 간단합니다. 적절한 이름으로 불변 변수(const)를 선언하고, 원본 표현식을 대입하여 그 대신으로 사용해주면 됩니다. 추출이 끝났다면 테스트를 돌리는 것도 놓치지 않도록 합니다.

 

 

변수 인라인하기 Inline Variable

변수 추출하기의 반대 리팩터링 기법인 변수 인라인하기입니다. 앞서 나왔던 인라인 기법과 유사합니다. 추출한 변수가 원래 표현식과 큰 차이가 없다면, 경우에 따라 오히려 주변 코드를 리팩토링하는데 방해 요소가 되기도 합니다. 이러한 변수들은 변수 인라인하기를 통해 교체해주는 것이 바람직합니다.

 

 

함수 선언 바꾸기 Change Function Declaration

함수는 프로그램을 작은 부분으로 나누는 가장 보편적인 방법입니다. 그중에서도 함수 선언은 각 부분들을 이어주는 교두보 역할을 하게 되는데, 이러한 선언이 잘 정의된다면 시스템을 관리하기가 상당히 쉬워집니다. 따라서 잘 정의된 함수는 선택이 아닌 필수입니다.

 

만약 현재 코드가 다음과 같다면, 함수 선언 바꾸기를 적용하는 것을 생각해보아야 합니다. 함수나 매개변수의 이름이 적절하지 않은 경우, 적절하지 못한 매개변수를 받고 있는 경우, 더 받아야 할 매개변수가 없는 경우들처럼 선언이 완벽하지 않을때는 함수를 제대로 정의해 줄 필요가 있습니다.

 

함수 선언 바꾸기를 적용하며 IDE의 자동 리팩터링 도구를 사용하는 것도 좋은 방법입니다. 이런 바꾸기 작업은 일일이 손으로 하다보면 실수하는 부분이 생기기 마련이지만 안전하고 빠르게 적용시켜줍니다. 다만 함수 이름들을 하나하나 확인하면서 어긋난 부분들을 고칠 생각을 할 틈을 주지 않기 때문에 위의 마이그레이션 절차를 활용할 일이 적어진다는 아쉬운 점(?)도 있습니다.

 

 

 

변수 캡슐화하기 Encapsulate Variable

변수 캡슐화하기는 전역변수처럼 관리하기 어려운 데이터들의 문제점을 보완해주는 작업입니다. 접근을 독점하는 함수를 만들어줌으로써 데이터를 직접적으로 조작하는 대신 함수를 조작하게 되어 결과적으로 훨씬 단순한 작업을 할 수 있하도록 만들어줍니다.

 

이 방법을 사용하면 데이터 변경시 검증이나, 추가적인 로직을 넣는 경우에도 간단하게 작업하도록 만들어줍니다. 다른 기법들도 그렇지만 변수 캡슐화하기의 경우 장점이 압도적으로 많기 때문에 최대한 우선순위를 높게 두고 작업하는 것을 추천합니다. 보통은 아래와 같은 절차를 따릅니다.

 

- 변수로의 접근과 갱신을 전담하는 캡슐화 함수를 만들고, 정적 검사를 수행하여 확인합니다.

- 변수를 직접적으로 참조하던 부분을 모두 캡슐화 함수 호출로 바꿔줍니다. 작업을 완료할때마다 테스트합니다.

- 변수의 접근 범위를 제한합니다. (public에서 private으로)

 

 

변수 이름 바꾸기 Rename Variable

변수 이름 바꾸기는 적절하지 않은 이름을 가진 변수들을 적절한 이름으로 고쳐주는 작업입니다. 이 변수가 넓은 범위에서 사용된다면 바로 위의 변수 캡슐화하기를 적용하면 어떨지 고려해 보는 것이 좋습니다.

 

저자가 처음부터 끝까지 꾸준하게 적절한 이름 짓기를 강조해주고 있네요.

 

 

매개변수 객체 만들기 Introduce Parameter Object

함수에 매개변수로 전달하는 요소들 중에는 같은 카테고리에 있음에도 불구하고 값만 따로 떨어져나와 넘어가는 경우가 종종 있습니다. 매개변수 객체 만들기는 이러한 과정을 수정하는 기법입니다.

 

이렇게 같은 출처, 특징, 카테고리 등을 가진 요소들끼리 따로 넘어가는 것은 직관적으로 코드를 파악하는데 걸림돌이 됩니다. 여러 값을 동시에 넘겨주는 대신, 이 값들을 객체로 묶어 하나의 객체를 넘겨준다면 근본적으로 코드의 직관성을 높여줍니다. 또한 매개변수 숫자도 줄여주는 효과가 있으며 같은 데이터 항목을 참조할때 항상 동일한 이름을 사용하기에 일관성도 높여줍니다.

 

여기서 적당한 데이터 구조가 만들어져있지 않다면 새로 만들어야 하는데, 저자는 이때 클래스로 만드는 것을 선호합니다. 이유는 이후에 동작까지 함께 묶기 좋기 때문이라고 하네요.

 

 

여러 함수를 클래스로 묶기 Combine Functions into Class

이번 시간에 살펴 볼 마지막 기법입니다. 클래스는 객체 지향 언어의 기본이자 다른 언어에도 유용한 개념입니다. 데이터와 함수를 하나의 블록으로 묶고, 외부에 제공하는 클래스는 리팩토링에도 빼놓을 수 없습니다. 이 기법을 적용하는 과정을 살펴보면,

 

- 함수들이 공유하는 공통 데이터 레코드를 캡슐화하고,

- 공통 레코드를 사용하는 함수 각각을 새 클래스로 옮기고,

- 데이터를 조작하는 로직들은 함수로 추출해서 새 클래스로 옮겨줍니다.

 

위 과정을 여러 함수를 클래스로 묶기라고 명명합니다.

 

이렇게 하면 각 함수들이 공유하는 환경을 명확하게 표현할 수 있고, 알아보기에도 쉬워집니다. 각각의 함수에 전달되는 인수를 줄여서 객체 내에서의 함수 호출을 간결하게 만들 수도 있고, 이런 객체를 외부에 제공할 경우 참조를 제공할 수도 있습니다.

 

 

 

 

마치며

앞으로 나오게 될 중요한 리팩토링 기법들을 미리 알아보았습니다. 이번 포스팅에서 소개한 개념들과 앞으로 더 소개될 개념들을 이용해서 능수능란하게 리팩토링을 다룰 수 있게되면 좋겠습니다. 그럼 스터디 본편 리팩토링 스터디 #4 - 기본적인 리팩토링에서 뵙겠습니다.

반응형

댓글