모듈을 분리하는 가장 중요한 기준은 아마도 시스템에서 각 모듈이 자신을 제외한 다른 모든 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기느냐에 있을 것이다.
클래스는 내부 정보뿐 아니라 클래스 사이의 연결 관계를 숨기는 데도 유용하다.
1. 레코드 캡슐화 하기
- 단순한 레코드에는 단점이 있다. 특히, 계산해서 얻을 수 있는 값과 그렇지 않은 값을 명확히 구분해 저장해야 하는 점이 번거롭다.
- 객체를 사용하면 어떻게 저장햇는지를 숨긴 채 세 가지 값을 각각 메서드로 제공할 수 있다.
- 사용자는 무엇이 저장된 값이고 무엇이 계산된 값인지 알 필요가 없다.
- 캡슐화하면 이름을 바꿀 때도 좋다.
- 값이 불변이면 단순히 시작과 끝과 길이를 모두 구해서 레코드에 저장한다.
- 레코드 구조는 두 가지로 구분할 수 있다. 하나는 필드 이름을 노출하는 형태고, 다른 하나는 내가 원하는 이름을 쓸 수 있는 형태다.
절차
- 레코드를 담은 변수를 캡슐화한다.
- 레코드를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다. 이 클래스에 원본 레코드를 반환하는 접근자도 정의하고, 변수를 캡슐화하는 함수들이 이 접근자를 사용하도록 수정한다.
- 테스트한다.
- 원본 레코드 대신 새로 정의한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
- 레코드를 반환하는 예전 함수를 사용하는 코드를 4에서 만든 새 함수를 사용하도록 바꾼다. 필드에 접근할 때는 객체의 접근자를 사용한다. 적절한 접근자가 없다면 추가한다. 한 부분을 바꿀 때마다 테스트한다.
- 클래스에서 원본 데이터를 반환하는 접근자와 원본 레코드를 반환하는 함수들을 제거한다.
- 테스트한다.
- 레코드의 피륻도 데이터 구조인 중첩 구조라면 레코드 캡슐화하기와 컬렉션 캡슐화하기를 재귀적으로 적용한다.
2. 컬렉션 캡슐화 하기
- 가변 데이터를 캡슐화 하면 데이터 구조가 언제 어떻게 수정되는지 파악하기 쉬워서 필요한 시점에 데이터 구조를 변경하기도 쉬워진다.
- 특히 객체 지향 개발자들은 캡슐활르 적극 권장하는데 컬렉션을 다룰 때는 곧잘 실수를 저지르곤 한다.
- 컬렉션 변수로의 접근을 캡슐화하면서 게터가 컬렉션 자체를 반환하도록 한다면, 그 컬렉션을 감싼 클래스가 눈치채지 못하는 상태에서 컬력선의 원소들이 바뀌어버릴 수 있다.
- 이런 문제를 방제하기 위해 컬렉션을 감싼 클래스에 흔히 add()와 remove()라는 이름의 컬렉션 변경자 메서드를 만든다.
- 원본 모듈 밖에서는 컬렉션을 수정하지 않는 습관을 갖고 있다면 이런 메서드를 제공하는 것만으로도 충분할 수 있다.
- 컬렉션 게터가 원본 컬렉션을 반환하지 않게 만들어서 클라이언트가 실수로 컬렉션을 바꿀 가능성을 차단하는 게 낫다.
- 내부 컬렉션을 직접 수정하지 못하게 막는 방법 중 하나로, 절대로 컬렉션 값을 반환하지 않게 할 수 있다.
- 컬렉션에 접근하려면 컬렉션이 소속된 클래스의 적절한 메서드를 반드시 거치게 하는 것이다.
- 가장 흔히 사용하는 방식은 아마도 컬렉션 게터를 제공하되 내부 컬렉션의 복제본을 반환하는 것이다.
- 가장 중요한 점은 코드베이스에서 일관성을 주는 것이다.
절차
- 아직 컬렉션을 캡슐화하지 않았다면 변수 캡슐화하기부터 한다.
- 컬렉션에 원소를 추가/제거하는 함수를 추가한다.
- 정적 검사를 수행한다.
- 컬렉션을 참조하는 부분을 모두 찾는다. 컬렉션의 변경자를 호출하는 코드가 모두 앞에서 추가한 추가/제거 함수를 호출하도록 수정한다. 하나씩 수정할 때마다 테스트한다.
- 컬렉션 게터를 수정해서 원본 내용을 수정할 수 없는 읽기전용 프락시나 복제본을 반환하게 한다.
- 테스트한다.
- 세터를 제공해야 할 특별한 이유가 있다면 인수로 받은 컬렉션의 복제본을 필드에 저장하게 한다.
- 어느 정도 강박증을 갖고 불필요한 복제본을 만드는 편이, 예상치 못한 수정이 촉발한 오류를 디버깅하는 것보다 낫다.
- 컬렉션 관리를 책임지는 클래스라면 항상 복제본을 제공해야 한다.
3. 기본형을 객체로 바꾸기
- 개발 초기에는 단순한 정보를 숫자나 문자열 같은 간단한 데이터 항목으로 표현할 때가 많다.
그러다 개발이 진행되면서 간단했던 이 정보들이 더 이상 간단하지 않게 변한다.
- 단순한 출력 이상의 기능이 필요해지는 순간 그 데이터를 표현하는 전용 클래스를 정의하는 편이다.
- 나중에 특별한 동작이 필요해지면 이 클래스에 추가하면 되니 프로그램이 커질수록 점점 유용한 도구가 된다.
절차
- 아직 변수를 캡슐화 하지 않았따면 캡슐화한다.
- 단순한 값 클래스를 만든다. 생성자는 기존 값을 인수르 받아서 저장하고, 이 값을 반환하는 게터를 추가한다.
- 정적 검사를 수행한다.
- 값 클래스의 인스턴스를 새로 만들어서 필드에 저장하도록 세터를 수정한다. 이미 있다면 필드의 타입을 적절히 변경한다.
- 새로 만든 클래스의 게터를 호출한 결과를 반환하도록 게터를 수정한다.
- 테스트한다.
- 함수 이름을 바꾸면 원본 접근자의 동작을 더 잘 드러낼 수 있는지 검토한다.
4. 임시 변수를 질의 함수로 바꾸기
- 임시 변수를 사용하면 값을 계산하는 코드가 반복되는 걸 줄이고 값의 의미를 설명할 수도 있어서 유용하다.
- 긴 함수의 한 부분을 별도 함수로 추출하고자 할 때 먼저 변수들을 가각의 함수로 만들면 일이 수월해진다.
- 변수 대신 함수로 만들어두면 비슷한 계산을 수행하는 다른 함수에서도 사용할 수 있어 코드 중복이 줄어든다. 그래서 나는 여러 곳에서 똑같은 방식으로 계산되는 변수를 발견할 때마다 함수로 바꿀 수 있는지 살펴본다.
- 이번 리팩터링은 클래스 안에서 적용할 때 효과가 가장크다. 클래스는 추출할 메서드들에 공유 컨텍스트를 제공하기 때문이다.
- 변수에 값을 한 번 대입한 뒤 더 복잡한 코드 덩어리에서 여러 차례 다시 대입하는 경우는 모두 질의 함수로 추출해야 한다.
절차
- 변수가 사용되기 전에 값이 확실히 결정되는지, 변수를 사용할 때마다 계산 로직이 매번 다른 결과를 내지 않는지 확인한다.
- 읽기전용으로 만들 수 있는 변수는 읽기전용으로 만든다.
- 테스트한다.
- 변수 대입문을 함수로 추출한다.
- 테스트한다.
- 변수 인라인하기로 임시 변수를 제거한다.
출처
리팩터링 2판