01. 리팩터링: 첫 번째 예시
들어가면서
일반적인 서적과 달리 이 책은 가장 먼저 예시를 보여준다. 리팩터링과 관련된 일반화된 설명이나 정의를 제쳐두고 예시를 통해 리팩터링이 어떤 절차로 진행되는지 전체적인 그림을 설명한다. 실무에서 리팩터링을 시도할 때마다, 그냥 프로세스 없이 코드와 정면으로 부딪혔다. 좋은 코드에 대한 명확한 기준도 없었다. 그래서 항상 시작이 막막했던 것 같고, 작업을 마친 뒤에도 후련함이 덜 했다. 이 책의 첫 장에서는 리팩터링의 전체적인 진행과정을 보여주고 각각의 단계에서 사용되는 다양한 리팩터링 기법을 소개한다. 사용되는 다양한 리팩터링 기법은 뒷장에서 구체적으로 다루기 때문에 리팩터링이 무엇이고 어떻게 진행되는지 여기에 더 초점을 맞추고 읽었다.
리팩터링이란?
리팩터링은 겉으로 드러나는 코드의 기능은 바꾸지 않으면서 내부 구조를 개선하는 방식으로 소프트웨어 시스템을 수정하는 과정이다.
리팩터링 절차
- 코드가 하는 일을 찾기
- 코드를 읽고 개선점을 찾기
- 리팩터링 작업을 통해 개선점을 코드에 반영하기
리팩터링과 테스트
- 테스트 코드 작성
모든 리팩터링 작업의 첫 시작은 테스트 코드를 마련하는 것이다. - 컴파일 - 테스트 - 커밋
리팩터링은 작은 단계로 나눠서 진행해야한다. 그래야 수정 중 오류가 생기더라도 살펴볼 범위가 좁기 때문에 디버깅하는게 쉬워진다.
리팩터링 작업
공연료 청구서를 출력하는 예시 코드가 주어졌다. 리팩터링할 코드는 하나의 함수안에 모든 로직이 구현되어 있는 코드였다.
예시 코드는 아래의 단계를 거쳐 리팩터링이 진행되었다.
- 함수 추출하기
- 단계 쪼개기 (기능 단위 코드 분리)
- 조건부 로직을 다형성으로 표현하기
함수 추출하기
함수를 추출하는 것은 구조를 바로 잡는 과정이다. 일단 구조가 바로잡혀야 코드를 파악하기도 수정하기도 쉽다. 하나로 뭉쳐진 로직들을 나누려면 부분 동작을 파악해 별도의 함수로 추출한다. 여기서 중요한 점은 함수의 네이밍과 매개변수 처리를 잘 해주는 것이다. 네이밍이 명확하면 함수 본문을 읽지 않아도 함수가 무슨일을 하는지 단 번에 파악할 수 있다. 그리고 함수를 쪼개다 보면 유효 범위를 벗어나는 변수들이 생긴다. 즉 추출한 함수에서 바로 사용할 수 없어 매개 변수로 전달해야하는 변수들이다. 이 때 매개변수 중 새로 추출된 함수에서 값이 변하는 변수가 있다면, 매개변수로 전달하는게 아니라 새로 추출된 함수에서 지역변수로 관리해야한다.
함수를 분리한 후 더 명확한 표현을 위해 변수명이나 제거해야할 변수들을 한번 더 검토해야한다. 이 부분에서 저자는 분리된 함수의 반환값은 전부 result라는 네이밍으로 변경했다. 처음에는 result라는 변수명이 왜 더 명확한지 공감되지 않았다. 실제 반환되는 데이터가 무엇인지 설명하는 변수 이름이 더 명확한게 아닌가 의문을 품었다. 하지만 반환값을 result라는 변수명으로 했을 때 이점은 그 변수의 역할을 바로 알 수 있어 코드의 가독성이 좋아져 설득력있는 방법이라고 생각했다. 한편 값을 임의로 담아두고 사용하는 임시 변수를 사용하는 것도 지양해야한다고 말한다. 이유는 임시변수가 생긴다는 것은 지역에서 관리할 변수들이 늘어난다는 의미인데 이는 나중에 함수를 추출하기 복잡해지기 때문이다. 쉽게 말해 함수를 추출할 때 처리해야될 변수들이 많아질수록 함수를 분리하기 어려워진다는 것이다. 만약 필요하다면 질의 함수를 사용하는 것을 권장한다. 질의 함수란 부수효과 없이 연산을 통해 값을 계산하여 반환하는 함수이다. 결론은 변수를 정의하는 것보다 값을 반환하는 함수를 만들어 사용하는 구조가 좋다는 것이다. 이렇게 따로 함수로 만들어두면 필요한 경우 함수를 호출해서 원하는 값을 얻을 수 있다. 따로 매개변수로 값을 전달하지 않아도 되고 이 후 다른 함수를 추출할 때도 정의해둔 질의 함수를 통해 원하는 값을 얻을 수 있다.
단계 쪼개기
단계 쪼개기는 기능 단위로 코드 구조를 개선하는 단계이다. 조금 더 큰 범위에서 코드 구조를 개선하면 코드의 중복을 줄일 수도 있고 전체적인 코드 구조가 더 명료해진다. 공연료 청구서를 출력하는 예시 코드는 공연료를 '계산하는 단계'와 청구서를 '출력하는 단계' 두 단계로 로직을 나눌 수 있다. 그런데 한 가지 요구 사항이 있다. 출력하는 단계에서 HTML형식과 PlainText형식 두 방법으로 출력해야한다. 이 요구 사항을 위해 상위함수 1개와, 하위함수 2개로 구조를 변경한다. 이렇게 구조를 변경하면 상위함수에서 계산을 수행하고 하위함수로 계산된 값을 내려줄 수 있다. 즉 계산하는 로직을 상위함수에서 1번만 작성하면된다. 이 단계에서 중요한 점은 기능별로 단계를 나눌 포인트를 찾아 구조를 변경하는 것이다. 추가로 인상 깊었던 부분하나 있었는데, 데이터 불변성을 유지하기 위해 중간 데이터 구조를 사용하는 것이었다. 상위 함수에서 외부에서 전달 받은 원본데이터를 그대로 사용하는게 아니라 사본 데이터를 만들어서 연산할 때 사용하고 하위 함수로 뿌려주었다. 이처럼 잠재적인 리스크까지 고려하는 습관을 가져야겠다. 아니 가져야 한다.
조건부 로직을 다형성으로 표현하기
연극 장르에 따라 공연료를 계산하는 코드가 처음에 switch문 안에 전부 작성 되어있었다. 이러한 형태의 조건부 로직은 수정이 필요하면 switch문 안에 있는 계산 로직들을 직접 수정해야하는데, 코드가 추가되고 수정 횟수가 많아지면 나중엔 관리하기 어려운 코드가 된다. 이런 경우 프로그래밍의 구조적인 요소로 보완해야 한다고 말한다. 예시에서는 다양한 구조적인 보완 방법 중 객체지향의 다형성을 사용했다. 다형성을 사용하니까 코드가 명확하게 분리되고 더 구조적인 모습으로 변화했다. 우선 상속 계층을 정의해야한다. 상속 계층을 정의할 때는 2가지 경우에 따라 설계가 달라질 수 있다. 첫 째는 조건마다 로직이 대부분 비슷한 경우가 있고, 두 번째는 조건마다 로직이 제각기 다른 경우가 있다. 전자는 로직이 대부분 비슷하기 때문에 슈퍼클래스에 메서드로 정의하고 필요하면 서브클래스에서 오버라이드 하는게 구조가 명확해지고, 후자는 슈퍼클래스에는 아래 처럼 안내 메시지를 남겨두고 서브클래스에서 메서드를 오버라이드해서 사용하는 방식이 더 명확한 구조이다.
// 슈퍼클래스...
get amount() {
throw new Error('서브클래스에서 처리하도록 설계되었습니다.')
}
마무리
첫 장에서는 리팩터링의 기본 절차들을 경험했다. 좋은 코드의 기준은 '얼마나 수정하기 쉬운가' 라고 책에서는 정의하고 있다. 이를 위해 리팩터링의 작업의 핵심은 코드를 끊임없이 구조적으로 개선하는 과정이고 그 결과, 다른 사람이 수정하기 좋은 코드가 되는 것이다. 건강한 코드 베이스를 가진 프로그램은 오류가 발생했을 때도 문제를 해결하기 수월하다. 개인적인 경험으로는 정형화된 절차 없이 리팩토링을 하는 것은 매번 시작도, 과정도 어려운 일이다. 제시된 리팩터링의 기본 절차를 이용하면서 '수정하기 쉬운 코드'를 지향하면 효율적으로 리팩터링을 진행할 수 있고, 더 명확한 결과를 얻을 수 있을 것이다.