파이썬 클린코드를 읽으며 정리한 내용입니다.
load_activity()
메서드는 데이터 구조가 변경되면 수정되어야 한다. 이 경우 데이터 구조의 변경으로 인해 시스템 모니터링하는 객체를 수정하게 되기 때문에 바람직하지 않다. 다른 메서드들도 마찬가지이다. 따라서 이 클래스를 변경해야 하는 이유가 너무 많아진다.class SystemMonitor:
def load_activity(self):
"""소스에서 처리할 이벤트를 가져온다."""
def identify_events(self):
"""가져온 데이터를 파싱하여 도메인 객체 이벤트로 변환"""
def stream_events(self):
"""파싱한 이벤트를 외부 에이전트로 전송"""
ActivityReader
클래스만 상속받아 사용하면 된다. 수정을 하지 않았다면 로그를 읽는데에 필요없는 다른 메소드도 상속받게 되었을 것이다.class Event:
def __init__(self, raw_data):
self.raw_data = raw_data
class UnknownEvent(Event):
"""데이터만으로 식별할 수 없는 이벤트"""
class LoginEvent(Event):
"""로그인 사용자에 의한 이벤트"""
class LogoutEvent(Event):
"""로그아웃 사용자에 의한 이벤트"""
class SystemMonitor:
"""시스템에서 발생한 이벤트 분류"""
def __init__(self, event_data):
self.event_data = event_data
def identify_event(self):
if (
self.event_data["before"]["session"] == 0
and self.event_data["after"]["session"] == 1
):
return LoginEvent(self.event_data)
elif (
self.event_data["before"]["session"] == 1
and self.event_data["after"]["session"] == 0
):
return LogoutEvent(self.event_data)
return UnknownEvent(self.event_data)
class Event:
def __init__(self, raw_data):
self.raw_data = raw_data
# 객체를 만들지 않고 클래스의 메소드 사용
# 인스턴스 속성을 변화시키지 않는 경우
@staticmethod
def meets_condition(event_data: dict):
return False
class UnknownEvent(Event):
"""데이터만으로 식별할 수 없는 이벤트"""
class LoginEvent(Event):
"""로그인 사용자에 의한 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return(
event_data["before"]["session"] == 0
and event_data["after"]["session"] == 1
)
class LogoutEvent(Event):
"""로그아웃 사용자에 의한 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return(
event_data["before"]["session"] == 1
and event_data["after"]["session"] == 0
)
class SystemMonitor:
"""시스템에서 발생한 이벤트 분류"""
def __init__(self, event_data):
self.event_data = event_data
def identify_event(self):
for event_cls in Event.__subclasses__():
try:
if event_cls.meets_condition(self.event_data):
return event_cls(self.event_data)
except KeyError:
continue
return UnknownEvent(self.event_data)
Event.__subclasses__()
>>> [__main__.UnknownEvent, __main__.LoginEvent, __main__.LogoutEvent]
__subclasses__()
메서드를 사용해 이벤트 유형을 찾기 때문에, 이제 새로운 유형의 이벤트를 지원하려면 Event 클래스를 상속받아 비즈니스 로직에 따라 meets_condition()
메서드를 구현하기만 하면 된다.class TransactionEvent(Event):
"""시스템에서 발생한 트랜잭션 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return event_data["after"].get("transaction") is not None
l1 = SystemMonitor({"before": {"session": 0}, "after": {"session": 1}})
l1.identify_event().__class__.__name__
>>> 'LoginEvent'
l2 = SystemMonitor({"before": {"session": 1}, "after": {"session": 0}})
l2.identify_event().__class__.__name__
>>> 'LogoutEvent'
l3 = SystemMonitor({"before": {"session": 1}, "after": {"session": 1}})
l3.identify_event().__class__.__name__
>>> 'UnknownEvent'
l4 = SystemMonitor({"after": {"transaction": "Tx001"}})
l4.identify_event().__class__.__name__
>>> 'TransactionEvent'
SystemMonitor.identify_event()
메서드는 전혀 수정되지 않았으므로 이 메서드는 새로운 유형의 이벤트에 대해 폐쇄되어 있다.메서드 서명의 잘못된 데이터타입 검사
Pylint로 호환되지 않는 서명 검사
# 사전조건을 확인하도록 수정
class Event:
def __init__(self, raw_data):
self.raw_data = raw_data
@staticmethod
def meets_condition(event_data: dict):
return False
@staticmethod
def meets_condition_pre(event_data: dict):
"""
인터페이스 계약의 사전조건
event_data 파라미터가 적절한 형태인지 유효성 검사
"""
assert isinstance(event_data, dict), f"{event_data!r} is not a dict"
for moment in {"before", "after"}:
assert moment in event_data, f"{moment} not in {event_data}"
assert isinstance(event_data[moment], dict)
# 올바른 이벤트 유형 탐지 전 사전조건 검사
class SystemMonitor:
"""시스템에서 발생한 이벤트 분류"""
def __init__(self, event_data):
self.event_data = event_data
def identify_event(self):
Event.meets_condition_pre(self.event_data)
for event_cls in Event.__subclasses__():
try:
if event_cls.meets_condition(self.event_data):
return event_cls(self.event_data)
except KeyError:
continue
return UnknownEvent(self.event_data)
LogoutEvent
는 before와 after의 session 키를 사용하기 때문에 그대로 사용할 수 없다. 이를 그대로 사용하면 하위 클래스에서 상위 클래스의 계약을 깨게 되고, KeyError가 발생한다. 이를 해결하기 위해서는 TransactionEvent
와 마찬가지로 .get()
메서드를 이용하면 된다.class LoginEvent(Event):
"""로그인 사용자에 의한 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return(
event_data["before"].get("session") == 0
and event_data["after"].get("session") == 1
)
class LogoutEvent(Event):
"""로그아웃 사용자에 의한 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return(
event_data["before"].get("session") == 1
and event_data["after"].get("session") == 0
)
l1 = SystemMonitor({"before": {"session": 0}, "after": {"session": 1}})
l1.identify_event().__class__.__name__
>>> 'LoginEvent'
l2 = SystemMonitor({"before": {"session": 1}, "after": {"session": 0}})
l2.identify_event().__class__.__name__
>>> 'LogoutEvent'
l3 = SystemMonitor({"before": {"session": 1}, "after": {"session": 1}})
l3.identify_event().__class__.__name__
>>> 'UnknownEvent'
l4 = SystemMonitor({"before": {}, "after": {"transaction": "Tx001"}})
l4.identify_event().__class__.__name__
>>> 'UnknownEvent'
__len__()
이라는 매직메서드를 정의from_xml()
from_json()
from_xml()
메서드와 from_json()
메서드를 모두 구현해야 한다. 하지만 둘 중 하나만 필요하다면 나머지 하나는 필요없는 메서드가 된다.from_xml()
from_json()
__enter__
와 __exit__
두 메서드가 있어야 컨텍스트 관리자의 역할을 할 수 있다.send()
send()
send()
메서드를 가진 객체를 넘길 수 있다.