본문 바로가기
Architecture

[Clean Code 정리] 객체와 자료구조

by 테리는당근을좋아해 2022. 5. 29.

Clean Code - 객체와 자료구조

 

1. 개요 

- 변수를 private으로 정의하는 이유는 외부 클래스가 클래스 내부 변수에 의존하지 않고 변경의 유연함을 얻기 위함이다.

- 멤버 변수의 사용 용도에 따라

    a. read only -> getter() 제공

    b. write only -> setter() 제공

    c. read/write -> getter(), setter() 제공

 

 

 

2. 자료 추상화

class Wafer():
    def __init__(self):
        self.id = None
        self.exposure_start_time = None
        self.exposure_end_time = None
        self.exposure_time = None

- 클래스 내부 데이터를 public으로 선언하고 외부에서 직접 접근하는 것은 올바른 방식이 아니다.

 

 

 

class Wafer():
    def __init__(self):
        self.__id = None
        self.__exposure_start_time = None
        self.__exposure_end_time = None
        self.__exposure_time = None
    
    @property
    def id(self):
        return self.__id
    
    @id.setter
    def id(self, id):
        self.__id = id

    @property
    def exposure_start_time(self):
        return self.__expoure_start_time

    @exposure_start_time.setter
    def exposure_start_time(self, time):
        self.__exposure_start_time = time

    @property
    def exposure_end_time(self):
        return self.__exposure_end_time
    
    @exposure_end_time.setter
    def exposure_end_time(self, time):
        self.__exposure_end_time = time

    @property
    def exposure_time(self):
        return self.__exposure_time

    @exposure_time.setter
    def exposure_time(self, time):
        self.__exposure_time = time

- 멤버 변수를 private으로 선언하고 getter, setter를 정의한다고 해서 완벽한 캡슐화가 아니다.

- 결과적으로 getter, setter를 통해 클래스 내부 구조를 알 수 있고, 직접 접근할 수 있기 때문이다.

- 변수 사이에 함수라는 게층을 넣는다고 구현이 저절로 감춰지는 것은 아니다.

 

 

 

class Wafer():
    @abstractmethod
    def get_exposure_start_time(self) -> pd.Timestamp:
        pass

    @abstractmethod
    def get_exposure_start_time(self) -> pd.Timestamp:
        pass

    @abstractmethod
    def get_exposure_time(self) -> pd.Timestamp:
        pass

    @abstractmethod
    def set_exposure_time(self, start_time, end_time):
        pass

- 추상 클래스를 제공해 사용자가 구현을 모른 채, 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스라고 할 수 있다.

 

 

 

3. 데이터/객체 비대칭

- 객체는 추상화 뒤로 데이터를 숨긴 채 데이터를 다루는 함수만 공개한다.

- 자료 구조는 데이터를 그대로 공개하며 별다른 함수는 제공하지 않는다.

- 두 정의는 본질적으로 상반된다.

 

 

 

1) 절차지향적 코드

class CleanFactory():
    def __init__(self, total_wafer, produced_wafer, execution_time):
        self.total_wafer = total_wafer
        self.produced_wafer = produced_wafer
        self.execution_time = execution_time

class CMPFactory():
    def __init__(self, total_wafer, produced_wafer, execution_time):
        self.total_wafer = total_wafer
        self.produced_wafer = produced_wafer
        self.execution_time = execution_time

class EtchFactory():
    def __init__(self, total_wafer, produced_wafer, execution_time):
        self.total_wafer = total_wafer
        self.produced_wafer = produced_wafer
        self.execution_time = execution_time

class LossClassification():
    @classmethod
    def calculate_wafer_per_day(cls, Factory):
        if type(Factory) == CleanFactory:
            # do something
            return wafer_per_day
        if type(Factory) == CMPFactory:
            # do something
            return wafer_per_day
        if type(Factory) == EtchFactory:
            # do something
            return wafer_per_day
        raise NoneFactory("[%s]", type(Factory))
    
    # 새로운 기능 추가
    @classmethod
    def analysis_loss(cls, Factory):
        if type(Factory) == CleanFactory:
            # do something
            return analysis_result
        if type(Factory) == CMPFactory:
            # do something
            return analysis_result
        if type(Factory) == EtchFactory:
            # do something
            return analysis_result
        raise NoneFactory("[%s]", type(Factory))

- 절차지향적 코드에서 각 공정을 나타내는 클래스는 단순히 공정정보를 담고있는 자료구조의 역할로써 존재한다.

- 절차지향적 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.

- 절차지향적 코드는 새로운 자료 구조를 추가하기 어렵다. 사용하는 클라이언트 코드에서 모든 함수를 수정해야 한다.

 

 

 

 

2) 객체 지향적 코드

class Factory():
    @abstractmethod
    def calculate_wafer_per_day(self):
        pass

    @abstractmethod
    def analysis_loss(self):
        pass

class CleanFactory(Factory):
    def __init__(self, total_wafer, produced_wafer, execution_time):
        self.__total_wafer = total_wafer
        self.__produced_wafer = produced_wafer
        self.__execution_time = execution_time
    
    def calculate_wafer_per_day(self):
        # do something
        return wafer_per_day

    def analysis_loss(self):
        # do something
        return analysis_result

class CMPFactory():
    def __init__(self, total_wafer, produced_wafer, execution_time):
        self.__total_wafer = total_wafer
        self.__produced_wafer = produced_wafer
        self.__execution_time = execution_time

    def calculate_wafer_per_day(self):
        # do something
        return wafer_per_day

    def analysis_loss(self):
        # do something
        return analysis_result

class EtchFactory():
    def __init__(self, total_wafer, produced_wafer, execution_time):
        self.__total_wafer = total_wafer
        self.__produced_wafer = produced_wafer
        self.__execution_time = execution_time

    def calculate_wafer_per_day(self):
        # do something
        return wafer_per_day

    def analysis_loss(self):
        # do something
        return analysis_result

class LossClassification():
    @classmethod
    def calculate_wafer_per_day(cls, Factory):
        return Factory.calcaute_wafer_per_day()
    
    # 새로운 기능 추가
    @classmethod
    def analysis_loss(cls, Factory):
        return Factory.analysis_loss()

- 객체 지향적 코드에서는 데이터를 클래스 내부로 감추고 인터페이스만을 제공해서 클라이언트 코드와 메시지를 주고 받는다.

- 객체 지향적 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.

- 객체 지향적 코드는 새로운 함수를 추가하기 위해서 모든 클래스를 고쳐야 하기 때문에 어렵다.

 

 

 

4. 디미터의 법칙

- 모듈은 자신이 조작하는 객체의 내부를 몰라야 한다.

- 클래스 (A)의 메서드 (b)는 다음과 같은 객체의 메서드만을 호출해야 한다.

    a. 클래스 (A)

    b. (b)가 생성한 객체

    c. (b) 인수로 넘어온 객체

    d. (A) 인스턴스 변수에 저장된 객체

- 구조체 구조 + 객체 구조 형태는 피하도록 하자

 

 

 

5. 자료 전달 객체 (DTO, Data Transfer Object)

- DTO는 public 변수가 있거나 private 변수에 getter/setter가 있는 객체를 말한다.

- save(), findById()와 같은 검색 함수도 제공할 수 있다.

- 활성 레코드는 데이터베이스 테이블이나 다른 소스에서 자료를 직접 변환한 결과이다.

- 활성 레코드는 비즈니스 로직 메서드를 추가해 자료구조 + 객체 구조의 형식을 띄게 된다.

- 활성 레코드는 자료구조로 취급하고 비즈니스 로직을 담으면서 내부 자료를 숨기는 객체를 따로 생성하도록 한다. 

 

 

 

6. 정리

- 객체는 동작을 공개하고 내부 데이터는 숨겨 새 객체 타입을 추가하기는 쉽지만, 기존 객체에 새 동작을 추가하기는 어렵다.

- 자료 구조는 별다른 동작없이 내부 데이터를 노출하므로 새 동작을 추가하기는 쉽지만, 기존 함수에 새 자료 구조를 추가하는 것은 어렵다.

- 소프트웨어를 개발할 때, 새로운 자료 타입을 추가하는 유연성이 필요하다면 객체가 더 적합하다.

- 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.

- 두 패러다임 사이에 편견 없이 문제 해결에 적합한 패러다임을 선택하도록 하자.

'Architecture' 카테고리의 다른 글

[Clean Code 정리] 경계  (0) 2022.05.29
[Clean Code 정리] 오류 처리  (0) 2022.05.29
[Clean Code 정리] 형식 맞추기  (0) 2022.05.29
[Clean Code 정리] 주석  (0) 2022.05.28
[Clean Code 정리] 함수  (0) 2022.05.28

댓글