11. API 리팩터링
- 좋은 API는 데이터를 갱신하는 함수와 그저 조회만 하는 함수를 명확히 해야합니다. -> 질의 함수와 변경 함수 분리하기
- 값 하나 때문에 여러개로 나뉜 함수들을 하나로 모아야합니다. -> 함수 매개변수화하기
- 매개변수는 그저 함수의 동작 모드를 전환하는 용도로 써야합니다. -> 플래그 인수 제거하기
- 데이터 구조가 필요 이상으로 분해하는 경우에 깔끔해져야합니다. -> 객체 통째로 넘기기
- 매개변수를 피호출 함수가 결정할 지, 호출 함수가 적절할지 정리해야합니다. -> 매개변수를 질의 함수로 바꾸기, 질의 함수를 매개변수로 바꾸기
- 객체가 불변이 되게 하려면 -> 세터 제거하기
- 호출자에 새로운 객체를 만들어 반환하려 할 때, 일반적인 생상자 능력으로 부족한 경우 -> 생성자를 팩터리 함수로 바꾸기
- 수많은 데이터를 받는 복잡한 함수를 쪼개는 문제를 다뤄야 하는 경우 -> 함수를 명령으로 바꾸기, 명령을 함수로 바꾸기
#
11.1 질의 함수와 변경 함수 분리하기#
배경- 겉보기 부수효과가 있는 함수와 없는 함수는 명확히 구분하는 것이 좋습니다.
- 질의 함수는 모두 부수효과가 없어야 한다는 규칙을 준수하는 것이 좋습니다.
- 상태를 변경하는 부분과 질의하는 부분이 같으면 이를 분리하는 것이 좋습니다.
#
절차- 대상 함수를 복제하고 질의 목적에 충실한 이름을 짓습니다.
- 새 질의 함수에서 부수효과를 모두 제거합니다.
- 정적 검사를 수행합니다.
- 원래 함수를 호출하는 곳을 모두 찾아냅니다. 호출하는 곳에서 반환 값을 사용한다면 질의 함수를 호출하도록 바꾸고, 원래 함수를 호출하는 코드를 바로 아래 줄에 새로 추가합니다. 하나를 수정할 때마다 테스트합니다.
- 원래 함수에서 질의 관련 코드를 제거합니다.
- 테스트합니다.
#
예시#
악당을 찾는 함수- 리팩터링 이후 코드 변경 (질의 함수 빼기, 알고리즘 교체)
#
11.2 함수 매개변수화하기#
배경- 함수 들의 로직이 아주 비슷하고 단지 리터럴 값만 다르다면, 다른 값만 매개변수로 받아 처리하는 함수 하나로 합쳐서 중복을 없앨 수 있습니다.
- 이렇게 하면 여러 곳에서 쓸 수 있으니 함수의 유용성이 커집니다.
#
절차- 비슷한 함수 중 하나를 선택합니다.
- 함수 선언 바꾸기로 리터럴들을 매개변수로 추가합니다.
- 이 함수를 호출하는 곳 모두에 적절한 리터럴 값을 추가합니다.
- 테스트합니다.
- 매개변수로 받은 값을 사용하도록 함수 본문을 수정합니다. 하나 수정할 때마다 테스트합니다.
- 비슷한 다른 함수를 호출하는 코드를 찾아 매개변수화된 함수를 호출하도록 하나씩 수정합니다. 하나 수정할 때마다 테스트합니다.
#
예시- 이를 요챡하면 다음과 같습니다.
#
11.3 플래그 인수 제거하기#
배경- 플래그 인수란 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수입니다.
- 플래그 인수를 제거하면 코드가 깔끔해짐은 물론 프로그래밍 도구에도 도움을 줍니다.
- 다만 플래그 인수를 두 개 이상 사용해야하는 경우에는 플래그 인수를 써야할 수도 있습니다.
#
절차- 매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성합니다.
- 원래 함수를 호출하는 코드들을 모두 찾아서 각 리터럴 값에 대응되는 명시적 함수를 호출하다록 수정합니다.
#
예시- 이를 분해하면 다음과 같습니다.
#
11.4 객체 통째로 넘기기#
배경- 하나의 레코드에서 값 두어 개를 가져와 인수로 넘기는 코드를 수정하면 레코드를 통째로 넘기고 함수 본문에서 필요한 값을 꺼내 쓰도록 수정합니다.
- 레코드를 통째로 넘기면 변화에 대응하기 쉽습니다.
- 함수가 레코드 자체에 의존하기를 원치 않을 때는 이 리팩터링을 수행하지 않습니다.
#
절차- 매개변수들을 원하는 형태로 받는 빈 함수를 만듭니다.
- 새 함수의 본문에서는 원래 함수를 호출하도록 하며, 새 매개변수와 원래 함수의 매개변수를 매핑합니다.
- 정적 검사를 수행합니다.
- 모든 호출자가 새 함수를 사용하게 수정합니다. 하나씩 수정하며 테스트합니다.
- 호출자를 모두 수정했다면 원래 함수를 인라인합니다.
- 새 함수의 이름을 적절히 수정하고 모든 호출자에 반영합니다.
#
예시- 리팩터링 후 코드
#
11.5 매개변수를 질의 함수로 바꾸기#
배경- 매개변수 목록은 함수의 변동 요인을 모아놓은 곳입니다.
- 다른 코드와 마찬가지로 이 목록에서도 중복은 피하는 게 좋으며 짧을수록 이해하기 쉽습니다.
- 제거하려는 매개변수의 값을 다른 매개변수에 질의해서 얻을 수 있다면 안심하고 질의 함수로 바꿀 수 있습니다.
- 다만 매개변수를 없애고 가변 전역 변수를 이용하면 안됩니다.
#
절차- 필요하다면 대상 매개변수의 값을 계산하는 코드를 별도 함수로 추출해놓습니다.
- 함수 본문에서 대상 매개변수로의 참조를 모두 찾아서 그 매개변수의 값을 만들어주는 표현식을 참조하도록 바꿉니다. 하나 수정하 때마다 테스트합니다.
- 함수 선언 바꾸기로 대상 매개변수를 없앱니다.
#
예시- 리팩터링 이후 코드
#
11.6 질의 함수를 매개변수로 바꾸기#
배경- 전역 함수를 참조하거나 제거하길 원하는 원소를 참조하는 경우, 해당 참조를 매개변수로 바꿔 해결해야합니다.
- 일반적으로 코드의 의존 관계를 바꾸려할 때 벌어집니다.
- 이러한 경우에는 호출자가 복잡해지는 문제가 있습니다.
#
절차- 변수 추출하기로 질의 코드를 함수 본문의 나머지 코드와 분리합니다.
- 함수 본문 중 해당 질의를 호출하지 않는 코드들을 별도 함수로 추출합니다.
- 방금 만든 변수를 인라인하여 제거합니다.
- 원래 함수도 인라인합니다.
- 새 함수의 이름을 원래 함수의 이름으로 고쳐줍니다.
#
예시- 리팩터링 후 코드
자바 스크립트와 불변 클래스
- 자바 스크립트의 클래스 모델에서는 객체 안의 데이터를 직접 얻어낼 방법이 항상 존재하므로 불변 클래스임을 보장하는 수단이 없다는 문제가 있습니다.
- 클래스에 불변 성격을 부여하는 건 좋은 전략이며, 질의 함수를 매개변수로 바꾸기 리팩터링은 이 전략을 실행하는 것은 도움이 됩니다.
#
11.7 세터 제거하기#
배경- 세터 메서드가 있음은 필드가 수정될 수 있다는 뜻입니다.
- 세터 제거하기 리팩터링이 필요한 상황은 주로 두가지입니다.
- 사람들이 무조건 접근자 메서드를 통해서만 필드를 다루려합니다.
- 클라이언트에서 생성 스크립트를 사용해 객체를 생성해야합니다.
#
절차- 설정해야 할 값을 생성자에서 받지 않는다면 그 값을 받을 매개변수를 생성자에 추가합니다.
- 생성자 밖에서 세터를 호출하는 곳을 찾아 제거하고, 대신 새로운 생성자를 사용하도록 합니다. 하나를 수정할 때마다 테스트합니다.
- 세터 메서드를 인라인합니다. 가능하다면 해당 필드를 불변으로 만듭니다.
- 테스트합니다.
#
예시- 세터 제거한 이후는 다음입니다.
#
11.8 생성자를 팩터리 함수로 바꾸기#
배경- 많은 객체 지향 언어에서 제공하는 생성자는 객체를 초기화하는 특별한 용도의 함수입니다. 다만 이는 이상한 제약이 붙어 있는 경우가 많습니다.
- 팩터리 함수에는 이러한 제약이 없습니다.
#
절차- 팩터리 함수를 만듭니다. 팩터리 함수의 본문에서는 원래의 생성자를 호출합니다.
- 생성자를 호출하던 코드를 팩터리 함수 호출로 바꿉니다.
- 하나씩 수정할 때마다 테스트합니다.
- 생성자의 가시 범위가 최소가 되도록 제한합니다.
#
예시- 리팩터링 이후 코드
#
11.9 함수를 명령으로 바꾸기#
배경- 함수를 그 함수만을 위한 객체 안으로 캡슐화하면 더 유용해지는데 이러한 객체를 가리켜 명령 객체 혹은 단순히 명령이라고 합니다.
- 명령은 평범한 함수 메커니즘보다 훨씬 유연하게 함수를 제어하고 표현할 수 있습니다.
- 다만 유연성을 높이는 것은 복잡성을 키울 수 있다는 것을 알고 있어야합니다.
#
절차- 대상 함수의 기능을 옮길 빈 클래스를 만듭니다. 클래스 이름은 함수 이름에 기초해 짓습니다.
- 방금 생성한 빈 클래스로 함수를 옮깁니다.
- 함수의 인수들 각각은 명령의 필드로 만들어 생성자를 통해 설정할지 고민해봅니다.
#
예시건강보험 애플리케이션에서 점수를 계산하는 함수입니다.
- 리팩터링 이후의 코드
#
11.10 명령을 함수로 바꾸기#
배경- 명령 객체는 복잡한 연산을 다룰 수 있는 강력한 메커니즘을 제공합니다.
- 명령은 그저 함수를 하나 호출해 정해진 일을 수행하는 용도로 쓰이는데 이러한 상황이고 로직이 복잡하지 않다면 명령 객체는 장점보다 단점이 크므로 평범함 함수로 바꾸는 것이 좋습니다.
#
절차- 명령을 생성하는 코드와 명령의 실행 메서드를 호출하는 코드를 함께 함수로 추출합니다.
- 명령의 실행 함수가 호출하는 보조 메서드들 각각을 인라인합니다.
- 함수 선언 바꾸기를 적용하여 생성자의 매개변수 모두를 명령의 실행 메서드로 옮깁니다.
- 명령의 실행 메서드에서 참조하는 필드들 대신 대응하는 매개변수를 사용하게끔 바꿉니다. 하나씩 수정할 때마다 테스트합니다.
- 생성자 호출과 명령의 실행 메서드 호출을 호출자 안으로 인라인합니다.
- 테스트합니다.
- 죽은 코드 제거하기로 명령 클래스로 없앱니다.
#
예시- 함수로 바꾸면 다음과 같습니다.
#
11.11 수정된 값 반환하기#
배경- 변수를 갱신하는 함수라면 수정된 값을 반환하여 호출자가 그 값을 변수에 담아두도록 합니다.
- 값 하나를 계산한다는 분명한 목적이 있는 함수들에 가장 효과적이고 반대로 값 여러 개를 갱신하는 함수에는 효과적이지 않습니다.
#
절차- 함수가 수정된 값을 반환하게 하여 호출자가 그 값을 자신의 변수에 저장합니다.
- 테스트합니다.
- 피호출 함수 안에 반환할 값을 가리키는 새로운 변수를 선언합니다.
- 테스트합니다.
- 계산이 선언과 동시에 이뤄지도록 통합합니다.
- 테스트합니다.
- 피호출 함수의 변수 이름을 새 역할에 어울리도록 바꿔줍니다.
- 테스트합니다.
#
예시GPS 위치 목록으로 다양한 계산을 수행하는 코드가 있습니다.
- 리팩터링 이후 코드
#
11.12 오류 코드를 예외로 바꾸기#
배경- 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘
- 예외는 정교한 메커니즘이지만, 다른 정교한 메커니즘과 같이 정확하게 사용할 때 최고의 효과를 냅니다.
#
절차- 콜스택 상위에 해당 예외를 처리할 예외 핸들러를 작성합니다.
- 테스트합니다.
- 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾습니다.
- 정적 검사를 수행합니다.
- catch절을 수정하여 직접 처리할 수있는 예외는 적절히 대처하고 그렇지 않은 예외는 다시 던집니다.
- 테스트합니다.
- 오류 코드를 반환하는 곳 모두에서 예외를 던지도록 수정합니다. 하니씩 수정할 때마다 테스트합니다.
- 모두 수정했다면 그 오류 코드를 콜 스택 위로 전달하는 코드를 모두 제거합니다. 하나씩 수정할 때마다 테스트합니다.
#
예시전역 테이블에서 배송지의 배송 규칙을 알아내는 코드
- 리팩터링 이후의 코드
#
11.13 예외를 사전확인으로 바꾸기#
배경- 함수 수행 시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면 예외를 던지는 대신 호출하는 곳에서 조건을 검사해야합니다.
#
절차- 예외를 유발하는 상황을 검사할 수 있는 조건문을 추가합니다. catch 블록의 코드를 조건문의 조건절 중 하나로 옮기고남은 try 블록의 코드를 다른 조건절로 옮깁니다.
- catch 블록에 어서션을 추가하고 테스트합니다.
- try문과 catch 블록을 제거합니다.
- 테스트합니다.
#
예시데이터베이스 연결 같은 자원들을 관리하는 자원 풀 클래스
- 리팩터링 이후의 코드