-
[Refactoring] 11. 기본형 집착클린코드 2022. 9. 14. 21:33
| 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.
기본형 집착
- 도메인에 필요한 기본 타입을 만들지 않고 프로그래밍 언어에서 제공하는 기본 타입을 사용하는 경우가 많다.
- 기본형으로는 단위 또는 표기법을 표현하기 어렵다.
- 관련 리팩토링 기술
- 기본형을 객체로 바꾸기
- 타입 코드를 서브 클래스로 바꾸기
- 조건부 로직을 다형성으로 바꾸기
- 클래스 추출하기
- 매개변수 객체 만들기
Refactoring 1. 필드 옮기기
- 개발 초기에는 기본형(숫자, 문자열)으로 표현한 데이터가 나중에는 해당 데이터와 관련있는 다양한 기능을 필요로 하는 경우가 발생한다.
- ex) 문자열로 표현하던 전화번호의 지역 코드 추가 + 다양한 포맷 지원
- ex) 문자열로 표현하던 전화번호의 지역 코드 추가 + 다양한 포맷 지원
- 기본형을 사용한 데이터를 감싸 줄 클래스를 만들면, 필요한 기능을 추가할 수 있다.
Before
public class Order { private String priority; public Order(String priority) { this.priority = priority; } public String getPriority() { return priority; } } public class OrderProcessor { public long numberOfHighPriorityOrders(List<Order> orders) { return orders.stream() .filter(o -> o.getPriority() == "high" || o.getPriority() == "rush") .count(); } }
After
public class Order { private Priority priority; public Order(Priority priority) { this.priority = priority; } public Priority getPriority() { return priority; } } public class Priority { private String value; private List<String> legalValues = List.of("low", "normal", "high", "rush"); public Priority(String value) { if (legalValues.contains(value)) { this.value = value; } else { throw new IllegalArgumentException("illegal value for priority " + value); } } @Override public String toString() { return this.value; } private int index() { return legalValues.indexOf(this.value); } public boolean higherThan(Priority other) { return this.index() > other.index(); } } public class OrderProcessor { public long numberOfHighPriorityOrders(List<Order> orders) { return orders.stream() .filter(o -> o.getPriority().higherThan(new Priority("normal"))) .count(); } }
Refactoring 2. 타입 코드를 서브 클래스로 바꾸기
- 비슷하지만 다른 것들을 표현해야 하는 경우, 문자열, 열거형, 숫자 등으로 표현하기도 한다.
- ex) 주문 타입 -> 일반 주문, 빠른 주문...
- 조건부 로직을 다형성으로 바꾸기 -> 조건문을 다형성으로 표현할 수 있을 경우
- 필드 내리기 -> 특정 타입에만 유효한 필드가 있을 경우
- 전체 상속
Before
public class Employee { private String name; private TelephoneNumber personalPhoneNumber; public Employee(String name, TelephoneNumber personalPhoneNumber) { this.name = name; this.personalPhoneNumber = personalPhoneNumber; } }
After
public abstract class Employee { private String name; protected Employee(String name) { this.name = name; } public static Employee createEmployee(String name, String type) { switch (type) { case "engineer": return new Engineer(name); case "manager": return new Manager(name); case "salesman": return new Salesman(name); default: throw new IllegalArgumentException("type error"); } } public abstract String getType(); @Override public String toString() { return "Employee{" + "name='" + name + '\'' + '}'; } } public class Engineer extends Employee { public Engineer(String name) { super(name); } @Override public String getType() { return "engineer"; } } public class Manager extends Employee { public Manager(String name) { super(name); } @Override public String getType() { return "manager"; } } public class Salesman extends Employee { public Salesman(String name) { super(name); } @Override public String getType() { return "salesman"; } }
- 서브 클래스 상속
Before
public class Employee { private String name; private String type; public Employee(String name, String type) { this.validate(type); this.name = name; this.type = type; } private void validate(String type) { List<String> legalTypes = List.of("engineer", "manager", "salesman"); if (!legalTypes.contains(type)) { throw new IllegalArgumentException(type); } } public String capitalizedType() { return this.type.substring(0, 1).toUpperCase() + this.type.substring(1).toLowerCase(); } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", type='" + type + '\'' + '}'; } } public class FullTimeEmployee extends Employee { public FullTimeEmployee(String name, String type) { super(name, type); } } public class PartTimeEmployee extends Employee { public PartTimeEmployee(String name, String type) { super(name, type); } }
After
public class Employee { private String name; private EmployeeType employeeType; public Employee(String name, String type) { this.name = name; this.employeeType = employeeType(type); } private EmployeeType employeeType(String typeValue) { return switch (typeValue) { case "engineer" -> new Engineer(); case "manager" -> new Manager(); case "salesman" -> new Salesman(); default -> throw new IllegalArgumentException("type error"); }; } public String capitalizedType() { return this.employeeType.capitalizedType(); } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", type='" + employeeType.toString() + '\'' + '}'; } } public class EmployeeType { public String capitalizedType() { return this.toString().substring(0, 1).toUpperCase() + this.toString().substring(1).toLowerCase(); } } public class Engineer extends EmployeeType { @Override public String toString() { return "engineer"; } } public class Manager extends EmployeeType { @Override public String toString() { return "manager"; } } public class Salesman extends EmployeeType { @Override public String toString() { return "salesman"; } } public class FullTimeEmployee extends Employee { public FullTimeEmployee(String name, String type) { super(name, type); } } public class PartTimeEmployee extends Employee { public PartTimeEmployee(String name, String type) { super(name, type); } }
Refactoring 3. 조건부 로직을 다형성으로 바꾸기
- 복잡한 조건식을 상속과 다형성을 사용해 코드를 보다 명확하게 분리할 수있다.
- switch 문을 사용해서 타입에 따라 각기 다른 로직을 사용할 수 있다.
- 기본 동작과 특수한 기능이 섞여 있는 경우에 상속 구조를 만들어서 기본 동작을 상위 클래스에 두고 특수한 기능을 하위 클래스로 옮겨서 각 타입에 따른 차이점을 강조할 수 있다.
- 모든 조건문을 다형성으로 옮기지 않고 단순한 조건문은 그대로 두는 것도 좋다.
- 오직 복잡한 조건문을 다형성을 활용해 좀 더 나은 코드로 만들 수 있는 경우에만 적용 -> 과용 X
Before
public class Employee { private String type; private List<String> availableProjects; public Employee(String type, List<String> availableProjects) { this.type = type; this.availableProjects = availableProjects; } public int vacationHours() { return switch (type) { case "full-time" -> 120; case "part-time" -> 80; case "temporal" -> 32; default -> 0; }; } public boolean canAccessTo(String project) { return switch (type) { case "full-time" -> true; case "part-time", "temporal" -> this.availableProjects.contains(project); default -> false; }; } }
After
public abstract class Employee { protected List<String> availableProjects; public Employee() { } public Employee(List<String> availableProjects) { this.availableProjects = availableProjects; } public abstract int vacationHours(); public boolean canAccessTo(String project) { return this.availableProjects.contains(project); } } public class FullTimeEmployee extends Employee { @Override public int vacationHours() { return 120; } } public class PartTimeEmployee extends Employee { public PartTimeEmployee(List<String> availableProjects) { super(availableProjects); } @Override public int vacationHours() { return 80; } @Override public boolean canAccessTo(String project) { return this.availableProjects.contains(project); } } public class TemporalEmployee extends Employee { public TemporalEmployee(List<String> availableProjects) { super(availableProjects); } @Override public int vacationHours() { return 32; } }
'클린코드' 카테고리의 다른 글
[Refactoring] 13. 반복문 (0) 2022.09.16 [Refactoring] 12. 반복되는 switch 문 (0) 2022.09.15 [Refactoring] 10. 데이터 뭉치 (0) 2022.09.13 [Refactoring] 9. 기능 편애 (0) 2022.09.12 [Refactoring] 8. 산탄총 수술 (0) 2022.09.11