ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Refactoring] 11. 기본형 집착
    클린코드 2022. 9. 14. 21:33

     

     

    코딩으로 학습하는 리팩토링 - 인프런 | 강의

    리팩토링은 소프트웨어 엔지니어가 갖춰야 할 기본적인 소양 중 하나입니다. 이 강의는 인텔리J와 자바를 사용하여 보다 실용적인 방법으로 다양한 코드의 냄새와 리팩토링 기술을 설명하고 직

    www.inflearn.com

    | 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.


    기본형 집착
    • 도메인에 필요한 기본 타입을 만들지 않고 프로그래밍 언어에서 제공하는 기본 타입을 사용하는 경우가 많다.
    • 기본형으로는 단위 또는 표기법을 표현하기 어렵다.
    • 관련 리팩토링 기술
      • 기본형을 객체로 바꾸기
      • 타입 코드를 서브 클래스로 바꾸기
      • 조건부 로직을 다형성으로 바꾸기
      • 클래스 추출하기
      • 매개변수 객체 만들기


    Refactoring 1. 필드 옮기기

    • 개발 초기에는 기본형(숫자, 문자열)으로 표현한 데이터가 나중에는 해당 데이터와 관련있는 다양한 기능을 필요로 하는 경우가 발생한다.
      • 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

    댓글

Designed by Tistory.