본문 바로가기
기타

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

by 유세지 2021. 2. 8.

계절학기 일정 때문에 포스팅이 많이 미뤄졌습니다. 이것저것 하기만 하고 정작 포스팅은 안 한 덕분에 글 쓸 거리들이 가득 쌓여있으니 당분간은 신나게 타이핑 해야겠네요.

 

이번 스터디는 지난 시간에 진행했던 API 리팩토링의 후반부와 상속 다루기에 관한 내용입니다. 바로 시작해보겠습니다.

 

 

 

 

세터 제거하기

우리가 setter 메서드를 선언하는 목적은 특정 필드의 값을 변경하기 위해서입니다. 따라서 setter를 선언하는 행위는 곧 이 필드는 변경될 수 있는 값임을 나타내는 것과 같습니다.

 

그러나 코드들을 보면 우리는 어떤 필드를 새로 만들때 습관적으로 Getter와 Setter를 선언해주는 경우가 꽤 많습니다. 실제로는 한 번 선언된 필드의 값을 후에 변경해 줄 일이 없을때 조차도요.

 

이런 경우 세터 제거하기를 통해 필요하지 않은 세터를 지워줍니다. 

 

사용되지 않는 세터를 그냥 둘 경우 코드를 봤을때 이 필드의 변경 유무를 확실히 전달할 수 있는 이점을 포기하게 되고, 생산성 있는 코드에서 한 걸음 멀어지게 됩니다. 불필요한 세터가 보인다면 제때 제거해줍시다.

 

세터 제거하기는 다음의 과정을 통해 진행합니다.

 

1. 생성자에 설정해야 할 값을 받을 매개변수를 추가합니다.
2. 생성자 바깥에서 세터를 호출하는 곳을 찾아 제거하고, 새로 바뀐 생성자를 사용하도록 바꿔줍니다.
3. 정상적으로 작동하는지 테스트합니다.

 

 

 

생성자를 팩터리 함수로 바꾸기

생성자는 많은 객체 지향 언어에서 제공하는 객체 초기화 함수입니다. 새로운 객체를 생성할때 new className 의 형식으로 사용하는 모습을 많이 보았을텐데 이 함수가 바로 생성자입니다.

 

이러한 생성자는 객체를 생성하는 목적을 가진 특별한 함수로 일반 함수와는 다른 점이 있는데, (자바의 경우) 대표적으로 반환하는 값이 그 생성자를 정의한 클래스의 인스턴스로 정해져있다는 점입니다.

 

생성자의 이름도 따로 변경할 수 없다는 점도 흠이라면 흠입니다. 사람에게 더 친숙한 다른 이름이 있더라도 반드시 new className 과 같이 사용할 수 밖에 없다는 제약도 가지고 있습니다.

 

따라서 이러한 제약이 있는 생성자 대신 팩터리 함수를 사용하는 것도 꽤 매력적인 선택지일 수 있겠네요.

생성자를 팩터리 함수로 바꾸기는 다음 과정을 통해 진행됩니다.

 

1. 팩터리 함수를 만듭니다.
2. 생성자를 호출하던 코드를 팩터리 함수 호출로 변경합니다.
3. 생성자의 가시 범위가 최소가 되도록 제한한다.

 

 

함수를 명령으로 바꾸기

명령(command)은 일반적인 함수의 매커니즘보다 더 유연하게 함수를 제어할 수 있습니다. 명령은 명령 취소(undo) 같은 보조 연산을 제공하거나 생명 주기를 정밀하게 제어하기 위한 매개 변수를 만들기 위해서도 사용되고, 상속과 훅(hook)을 이용해 사용자 맞춤형 기능을 제공하기도 합니다.

 

객체까지는 지원하지만 일급 함수를 지원하지 않는 프로그래밍 언어에서도 명령은 유용하게 사용됩니다. 명령을 이용해 일급 함수의 기능들을 비슷하게 구현해낼 수 있기 때문입니다.

 

다만 일급 함수를 지원하지 않는 프로그래밍 언어는 이제 사용할 일이 거의 없을겁니다. 객체 지향 프로그래밍을 넘어 함수형 프로그래밍까지 발전하면서 대부분의 프로그래밍 언어가 일급 함수를 지원하도록 바뀌었기 때문에 현재까지 꾸준히 사용되는 언어 중에선 일급 함수를 지원하지 않는 케이스를 찾아보기가 어렵습니다. 대표적으로는 C가 있습니다. 이 부분은 나중에 함수형 프로그래밍에 대한 이야기를 할때 다시 언급하도록 하겠습니다.

 

명령을 사용해서 얻을 수 있는 이점이 많을때 함수를 명령으로 바꾸기를 사용합니다. 다만 유연성에는 언제나 복잡성이 뒤따라오기 때문에, 이 기법을 사용하고 얻는 이점이 복잡성을 감수하고서 얻을만한 정도인지 충분히 고민해보고 적용해야 합니다.

 

다음과 같은 방법으로 적용합니다.

 

1. 대상 함수의 기능을 옮길 클래스를 만듭니다. 이때 클래스의 이름은 함수에 기초해서 짓습니다.
2. 함수를 클래스로 옮깁니다.

 

명령을 함수로 바꾸기

함수를 명령으로 바꾸기의 반대 리팩토링입니다. 위에서 설명했던 유연성이라는 명령의 이점을 얻는 대신 증가하는 복잡성을 저울질해보고, 장단점이 큰 쪽으로 바꿔주시면 되겠습니다. 보통 코드가 하는 일이 적고 간단할수록 함수를 사용하는 편이 낫습니다.

 

명령을 함수로 바꾸기는 다음과 같은 방법으로 적용합니다.

 

1. 명령을 생성하는 코드와 실행 메서드를 호출하는 코드를 함께 함수로 추출합니다.
2. 실행 함수가 호출하는 보조 메서드들을 인라인합니다.
3. 함수 선언 바꾸기를 적용하여 생성자의 매개변수들을 명령의 실행 메서드로 옮깁니다.
4. 실행 메서드에서 참조하는 필드들을 매개변수를 사용하도록 옮깁니다.
5. 생성자 호출과 명령의 실행 메서드 호출을 대체 함수 안으로 인라인합니다.

 

 

수정된 값 반환하기

여러 코드들을 거쳐가는 데이터들이 어떻게 수정되는지 추적하는 건 쉽지 않은 일입니다. 특히 같은 데이터를 읽고 수정하는 코드가 여러 곳에 있다면 흐름을 파악하기 더욱 까다로워집니다. 이러한 이유로 데이터가 수정된다면 그 데이터가 어떻게 수정되는지, 어디에서 수정되는지 쉽게 알 수 있도록 알려주는 것이 중요합니다.

 

만약 변수를 갱신하는 함수라면, 함수가 수정된 값을 반환하도록 코드를 작성하고 그 값을 변수에 넣어주는 방법을 통해 이 코드가 호출되었을때 변수가 갱신될 것임을 명확히 알려줄 수 있습니다.

 

수정된 값 반환하기는 값 하나를 변경하는 함수에 가장 효과적인 리팩토링입니다. 여러 개를 갱신하는데는 효과적이지 않습니다. 또한 함수 옮기기의 준비 작업으로 적용하기에도 좋습니다.

 

수정된 값 반환하기는 다음과 같은 방법으로 적용합니다.

 

1. 함수가 수정된 값을 반환하도록 변경하고, 호출자가 그 값을 자신의 변수에 저장하게 합니다.
2. 호출되는 함수에 반환할 값을 가질 새로운 변수를 선언합니다.
3. 계산이 선언과 동시에 이루어지도록 통합합니다.
4. 변수 이름을 변경하고 테스트합니다.

 

오류 코드를 예외로 바꾸기

API를 사용할때면 가끔 무엇인지 모를 오류 코드들을 맞닥뜨려 문서를 찾아보았던 경험이 있었습니다. 이처럼 오류가 발생하면 누군가 오류를 식별하고 직접 처리를 해주어야 했습니다. 그러나 이러한 오류 코드를 반환하는 방법은 다른 이에게 책임을 전가하기 때문에 이지 못하며, 프로그램의 전체적인 흐름에 혼란을 줄 수 있습니다.

 

이러한 오류 코드 대신, 예외를 사용하는 방법이 있습니다. 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘입니다. 예외를 사용하게되면 일일히 오류를 검사하는 로직을 짤 필요없이, 스스로 적절한 핸들러를 찾을때까지 콜스택을 거슬러 올라갑니다. 예외는 독자적인 흐름을 가지고 있기 때문에 프로그래머가 다른 부분에서는 신경을 쓰거나 특별한 상황을 위한 코드를 작성할 필요가 없게 됩니다.

 

그러나 예외는 단순한 오류보다는 절대 일어날 수 없는, 비정상적인 동작을 보일때만 쓰여야합니다. 그 외의 문제 상황에 대해선 직접 처리하여 프로그램이 의도한대로 돌아갈 수 있도록 하는 것이 좋습니다.

 

어디에 적용시킬지 잘 모르겠다면 이렇게 생각하면 간단합니다. "예외가 발생했을때, 프로그램을 종료시키는 코드로 연결해도 지장이 없는가?" 만약 문제가 생긴다면 그곳엔 예외를 사용해선 안된다는 신호입니다.

 

오류 코드를 예외로 바꾸기는 다음과 같은 방법으로 적용합니다.

 

1. 예외를 처리할 예외 핸들러를 콜스택 상위에 작성합니다.
2. 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 방법을 찾습니다.
3. 직접 처리할 수 있는 예외는 적절히 대처하고 그 밖의 예외는 다시 던집니다. (catch ~ throw 이용)
4. 오류 코드를 반환하던 곳에서 예외를 던지도록 수정합니다.
5. 오류 코드를 콜스택 위로 전달하는 코드를 제거합니다.

 

 

예외를 사전확인으로 바꾸기

예외는 분명 훌륭한 도구이지만 그렇다고 만능은 아닙니다. 위에서 언급했듯이 예외는 절대 일어날리 없는 부분에만 사용되어야 합니다. 모든 곳에 예외를 사용하기보다는 필요한 곳에만 예외 처리를 해주고 함수 수행시에 문제가 되는 조건을 검사할 수 있다면 그곳에서 먼저 조건을 확인해야합니다.

 

다음과 같은 방법을 통해 리팩토링을 적용합니다.

 

1. 예외를 유발하는 상황을 검사하는 조건문을 추가합니다.
2. try ~ catch 문을 제거합니다.

 

메서드 올리기

중복되는 코드를 제거하는 것은 리팩토링에서 빼놓을 수 없이 중요한 부분입니다. 중복은 단순히 불필요한 코드들이 쌓이는 것 뿐 아니라 한 쪽의 변경이 다른 쪽에 반영되지 않을 수 있다는 잠재적인 위험을 동반한다는 것을 의미합니다.

 

각각의 클래스에서 같은 메서드들을 사용하고 있다면 그 클래스들이 상속받고 있는 부모에게 메서드를 올려주면 되는데 단순히 메서드들의 코드가 똑같다면 별다른 수정 없이 그대로 복사 후 붙여넣기를 하면 되니 너무 간단합니다. 그러나 인생이 그렇듯 만사가 쉽게 풀리지만은 않죠. 리팩토링을 제대로 하려면 테스트가 잘 동작하는지 확인하고, 혹여나 테스트에서 놓쳤던 부분까지 찾아 확인을 해야합니다.

 

메서드끼리의 전체적인 흐름은 비슷하지만 세부적인 내용이 다를땐 템플릿 메서드 만들기도 고려해보는 것이 좋습니다.

 

다음과 같은 방법으로 진행해주시면 되겠습니다.

 

1. 똑같은 동작을 하는 메서드인지 확인하고, 아닐 경우엔 같은 메서드가 되도록 리팩토링 해줍니다.
2. 메서드 안에서 호출하는 다른 메서드와 참조하는 필드들을 슈퍼클래스에서도 사용할 수 있는지 확인합니다.
3. 슈퍼클래스에 새로운 메서드를 생성하고 대상 메서드의 코드를 복사해서 넣습니다.
4. 서브클래스의 메서드를 지우면서 테스트합니다.

 

필드 올리기

메서드 올리기를 진행할때는 메서드 안에서 호출하는 메서드와 참조하는 필드가 슈퍼클래스에서도 사용할 수 있는지 확인해 주는 작업이 필요합니다. 만약 사용하려는 필드가 하위 클래스에서 선언된 경우, 슈퍼클래스에선 이 메서드를 그대로 사용할 수가 없겠죠?

 

이럴때 사용하는 것이 필드 올리기입니다. 메서드 올리기를 사용할 수 있도록 먼저 요건을 만들어주고, 필드 올리기 자체로도 데이터 중복 선언을 없애는 효과를 갖습니다.

 

다음 과정을 통해 필드 올리기를 수행합니다.

 

1. 후보 필드들을 사용하는 곳에서 똑같은 방식으로 사용하고 있는지 확인합니다.
2. 필드들의 이름이 다르면 하나의 이름으로 통일해줍니다.
3. 슈퍼클래스에 새로운 필드를 생성합니다.
4. 서브클래스의 필드들을 제거합니다.

 

생성자 본문 올리기

생성자 또한 함수이므로 슈퍼클래스로 올리는 과정을 적용할 수 있습니다. 저자 또한 마찬가지인데, 생성자는 일반 함수와는 조금 다른 기능을 갖기 때문에 그 기능에 어느정도 제약을 두는 편이라고 합니다.

 

일반 함수들을 올리듯 생성자를 올리면 의도치 않은 상황을 맞닥뜨릴 수 있습니다. 생성자는 할 수 있는 일과 호출 순서에 제약이 있기 때문에 접근 방법을 조금 다른식으로 가져가야합니다. 전체적인 순서는 같지만, 생성자가 미치는 영향을 잘 고려하여 리팩토링을 진행해주시면 되겠습니다.

 

다음과 같은 과정에 따라 리팩토링을 진행합니다.

 

1. 후보 필드들을 사용하는 곳에서 똑같은 방식으로 사용하고 있는지 확인합니다.
2. 필드들의 이름이 다르면 하나의 이름으로 통일해줍니다.
3. 슈퍼클래스에 새로운 필드를 생성합니다.
4. 서브클래스의 필드들을 제거합니다.

 

메서드 내리기

메서드 올리기와 반대되는 개념입니다. 소수의 특정한 서브클래스에서만 사용되는 메서드의 경우 슈퍼클래스보단 서브클래스에서 선언하고 사용하는 것이 깔끔합니다. 이 리팩토링을 적용하려면 코드에 대한 깊은 이해가 필수적입니다. 해당 기능을 어떤 서브클래스에서 적용하는지, 정확히 무엇을 하는지 등 확실하게 알고 있을때 진행하는 것이 좋습니다.

 

서브클래스에 따라 다르게 동작해야한다면 다형성을 이용하는 방법도 있습니다. 다형성에 대한 리팩토링은 이전의 포스팅을 참고해주세요.

 

메서드 내리기는 다음 과정을 통해 수행합니다.

 

1. 대상 메서드를 모든 서브클래스에 복사합니다.
2. 슈퍼클래스에서 메서드를 제거합니다.
3. 이 메서드가 사용되지 않는 서브클래스에서는 메서드를 삭제해줍니다.

 

필드 내리기

필드 올리기와 반대인 리팩토링입니다. 굳이 슈퍼클래스에 선언할 필요 없는 필드들은 특정한 서브클래스들에 선언해주어 깔끔한 상태를 만들어주시면 됩니다.

 

필드 내리기는 다음과 같은 과정을 통해 수행합니다.

 

1. 대상 필드를 모든 서브클래스에 정의합니다.
2. 슈퍼클래스에서 필드를 제거합니다.
3. 이 필드가 사용되지 않는 서브클래스에서 필드를 제거합니다.

 

여기까지 이번 시간에 진행될 내용이었습니다. 리팩토링 스터디 #9로 이어집니다.

 

반응형

댓글