: 조건부 로직은 필연적인 요소이지만, 쉽게 프로그램을 복잡해보이게 만든다. 이 장의 리팩터링을 적용하면 이러한 로직들을 보다 이해하기 쉬운 코드로 바꿀 수 있다.
1. 조건문 분해하기
: 복잡한 조건절은 해당 코드의 목적이 무엇인지 파악하기 어렵게 만든다. 조건절과 작업들을 함수로 추출해주면 코드 의도가 확실히 전달된다.
2. 조건식 통합하기
: 같은 결과를 수행하는 조건절을 통합한다. 관련 코드를 모으면서 하려는 일이 명확해지고, 보통의 경우 함수 추출까지 이어지면서 의도를 파악하기 쉬운 코드가 된다.
- 같은 결과를 수행하면
||
로 묶일 가능성이 크고, if문 중첩이라면&&
로 묶일 가능성이 크다. - 논리연산자로 묶여 더 복잡해보인다면 이 때 함수추출을 고민한다.
3. 중첩 조건문을 보호 구문으로 바꾸기
: early-return의 형태로 바꾸기
- 정상 조건:
if
-else
가 모두 정상인 형태.if
,else
에 같은 무게를 두어 둘다 중요함을 나타낸다.- 만약 이러한 경우인데 early return로 바꾸었을 때 중복 코드가 나오게 되는 경우가 있다. 그러한 경우 오히려 if-else코드가 명확할 것 같다.
- 비정상 조건: (보호 구문) 비정상인 경우 바로 함수에서 빠져나온다.
- 이 케이스에선 조건을 반대로 만들어 더 간결해지는 경우도 있다.
함수 진입점과 반환점이 하나여야한다?
: 하나일 때 더욱 명확하다면 하나로, 그렇지 않다면 (e.g. 보호 구문의 경우) 더욱 명확하도록 early return 형태로 변경하는 것이 맞아보인다.
4. 조건부 로직을 다형성으로 바꾸기
: 클래스와 다형성 활용하기. 팩터리 함수를 정의해 적절한 서브클래스의 인스턴스를 반환해준다.
- 무조건적인 다형성 활용은 당연히 오버엔지니어링 일 수 있고, 핵심은 ‘공통 switch 로직이 중복으로 있는가?’ 인 것 같다.
- 특히, 해당 부분이 ‘다형성이 맞는가?’ 를 생각해야한다. 데이터만 다르고, 실제 동작 자체는 각각이 다를바가 없는 경우도 있다.
- 기본 동작을 슈퍼 클래스로, 변형 동작들을 서브클래스로 만드는 방법도 있다.
5. 특이 케이스 추가하기
: 특정 값을 확인한 후 같은 동작을 수행하는 코드를 객체로 위임하기. 특히, null check에서 같은 특이 케이스를 반환해야 하는 경우가 많다. 이 때 특이 케이스 객체를 통해 반환하도록 한다. (aka. 특이 케이스 패턴, 널 객체 패턴, ..)
6. 어서션 추가하기
: 항상 해당 조건이 참이라고 가정하는 조건부 문장. 만약 어서션에 걸린다면 개발자가 로직을 짰음을 알려준다. 다른 개발자에게 어떤 상태여야함을 알려주는 소통 도구가 되기도 한다.
- 테스트 코드: 어서션과 같은 역할을 한다. 따라서 테스트 코드가 존재한다면 어서션은 불필요할 수 있다.
어서션(error)과 catch
- 어서션은
Error
를 유발하기 때문에 제대로되지 않은 어서션은 위험할 수 있다. 자바에서는exception
을 확장한Throwable
만 catch가 가능하다. 따라서 어서션은 “어떠한 외부에 입력에 따라서도 유발되는 것이 아닌, 개발자의 실수로만 유발될 수 있는” 조건에 다는 것이 좋다.
7. 제어 플래그를 탈출문으로 바꾸기
: flag
역할을 하는 boolean
변수를 없애고 break
, continue
를 통한 제어를 적극 활용한다.
Reference)
리팩터링 2판