똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다.
코드가 중복되면 각각 코드를 볼 때마다 차이점이 존재하는지 주의 깊게 봐야 한다.
만약 중복 코드 중 하나를 변경하려고 할 때에는 다른 비슷한 코드들도 모두 살펴보고 적절히 수정해야 한다.
- 함수 추출하기
- 클래스 내에서의 메서드가 똑같은 표현식을 사용하는 경우
- 문장 슬라이드하기
- 클래스 내에서의 메서드가 비슷한데 완전히 똑같지 않은 경우
- 메서드 올리기
- 같은 부모로부터 파생된 서브 클래스들에 코드의 중복이 있는 경우
함수 추출하기
여기서 함수란 객체지향 언어의 method 혹은 절차형 언어의 procedure / subroutine 에도 동일하게 적용된다.
함수 추출하기란 코드 조각을 찾아 무슨 일을 하는지 파악한 다음, 독립된 함수로 추출하고 목적에 맞는 이름을 붙이는 것이다.
이 책에서는 목적과 구현을 기준으로 독립된 함수를 추출할 것을 추천한다.
코드를 보고 무슨 일을 하는 지 파악하는데 시간이 오래 걸린다면 그 부분을 함수로 추출한 뒤 "무슨 일"에 해당하는
이름을 짓는다.
이렇게 한다면 나중에 코드를 읽을 때 함수의 목적이 눈에 확 들어온다.
[예시]
{리팩터링 전}
public class Printer{
private void print(){
System.out.println("***");
System.out.println("this is Printer");
System.out.println("***");
...
}
...
}
{리팩터링 후}
public class Printer{
private void print(){
printInformation();
...
}
private void printInformation(){
System.out.println("***");
System.out.println("this is Printer");
System.out.println("***");
}
}
[절차]
- 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.
- "어떻게" 가 아닌 "무엇을" 하는지가 드러나야 한다.
- 추출할 코드를 원본 함수에서 복사해 새 함수에 복사한다.
- 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다.
- 있다면 매개변수로 전달한다.
- 변수를 다 처리했다면 컴파일한다.
- 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다.
- 테스트
- 다른 코드에 방금 추출한 것과 똑같거나 유사한 코드가 있는지 살핀다.
- 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.
함수를 짧게 만드는 것에 대해 성능이 느려질까 큰 고민을 하지 말자, 요즘에는 그런 고민을 안 해도 된다.
문장 슬라이드 하기
관련된 코드들이 가까이 모여있다면 이해하기가 더욱 수월하다.
하나의 데이터 구조를 사용하는 문장들은 다른 곳에 흩어져 있기보다는 한데 모여있어야 좋다.
관련 코드끼리 모으는 작업은 함수 추출하기
와 같은 리팩터링 방법의 준비단계로 행해진다.
애초에 관련된 코드들이 모여 있어야 함수를 추출할 수 있다.
[예시]
{리팩터링 전}
private class memberService{
private void join(MemberDto memberDto){
String memberName = memberDto.getName();
int age = memberDto.getAge();
... {다른 비즈니스 로직 수행 코드}
if (memberName.startsWith("kim")){
...
}
if (age < 10){
...
}
}
}
{리팩터링 후}
private class memberService{
private void join(MemberDto memberDto){
... {다른 비즈니스 로직 수행 코드}
String memberName = memberDto.getName();
if (memberName.startsWith("kim")){
...
}
int age = memberDto.getAge();
if (age < 10){
...
}
}
}
[절차]
- 코드를 이동할 목표 위치를 찾는다.
- 코드 조각의 원래 위치와 목표 위치 사이의 코드들을 훑어보면서 조각을 모으고 나면 동작이 달라지는 코드가 있는지 살핀다.
- 다음과 같은 간섭이 있다면 리팩터링 포기
- 코드 조각에서 참조하는 요소를 선언하는 문장 앞으로는 이동을 할 수 없다.
- 코드 조각을 참조하는 요소의 뒤로는 이동할 수 없다.
- 코드 조각에서 참조하는 요소를 수정하는 문장을 건너뛰어 이동할 수 없다.
- 코드 조각이 수정하는 요소를 참조하는 요소를 건너뛰어 이동할 수 없다,
- 다음과 같은 간섭이 있다면 리팩터링 포기
- 코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣는다.
- 테스트
메서드 올리기
중복된 두 메서드는 당장은 문제가 없을지 몰라도 미래에는 문제가 생길 수 있는 여지가 있다.
무언가 중복되었다는 것은 한쪽의 변경이 다른 한쪽에는 변경되지 않을 수 있다는 위험을 수반한다.
메서드들의 본문 코드가 같을 때에는 메서드를 올리기가 쉽다.
만약 서로 다른 클래스에 있는 두 메서드를 매개변수화했을 때 같은 메서드가 된다고 한다면, 매개변수화를 진행하여 상속 계층의 위로 올리면 된다.
만약 매당 메서드의 본문에서 참조하는 필드들이 서브클래스에만 있는 경우 필드들을 먼저 슈퍼클래스로 올린 후 메서드를 올린다.
[예시]
{리팩터링 전}
class Toy{
...
}
class Lego extends Toy{
getSize(){
...
}
}
class ToyCar extends Toy{
getSize(){
...
}
}
{리팩터링 후}
class Toy{
getSize(){...}
}
class Lego extends Toy{
...
}
class ToyCar extends Toy{
...
}
[절차]
- 똑같이 동작하는 메서드인지 확인
- 메서드 안에서 호출하는 다른 메서드와 참조하는 필드들을 슈퍼클래스에서도 호출하고 참조할 수 있는지 확인
- 메서드 시그니처가 다르다면 함수 선언 바꾸기로 슈퍼클래스에서 사용하고 싶은 형태로 통일
- 슈퍼클래스에 새로운 메서드를 생성하고, 대상 메서드의 코드를 복사해 넣는다.
- 정적 검사를 수행
- 서브 클래스 중 하나의 메서드를 제거
- 테스트
- 모든 서브클래스의 메서드가 없어질 때까지 다른 서브클래스의 메서드를 하나씩 제거
'Refactoring & CleanCode' 카테고리의 다른 글
전역 데이터 (Global Data) (0) | 2023.04.10 |
---|---|
기이한 이름 (Mysterious Name) (0) | 2023.04.05 |