python clean code chapter4
Python Clean Code
4. SOLID 원칙
- S: 단일 책임 원칙
- O: 개방/폐쇄의 원칙
- L: 리스코프(Liskov) 치환 원칙
- I: 인터페이스 분리 원칙
- D: 의존성 역전 원칙
4-1. 단일 책임 원칙(Single Responsibility Principle - SRP)
소프트웨어 컴포넌트(일반적으로 클래스)가 단 하나의 책임을 져야한다는 원칙.
클래스가 유일한 책임이 있다는 것은 하나의 구체적인 일을 담당하는것, 따라서 변화해야 할 이유는 단 하나뿐이다.
만약, 클래스가 여러 책임을 가지고 있다면, 유지보수할 때 해당 클래스를 수시로 변경해줘야한다. 즉, 클래스가 손상되기 쉽고 유지보수가 어렵다.
그러나 각 클래스가 딱 하나의 메서드를 가져야 한다는 것은 아님에 주의하자. 처리해야할 로직이 같은 경우 하나의 클래스에 여러 메서드를 추가할 수 있다.
4-2. 개방/폐쇄 원칙(Open/Close Principle - OCP)
클래스를 디자인할 때는 유지보수가 쉽도록 로직을 캡슐화하여 확장에는 개방되고, 수정에는 폐쇄되도록 해야 한다.
간단히 말해서 확장가능하고, 새로운 요구사항이나 도메인 변화에 잘 적응하는 코드를 작성해야 한다는 뜻이다.
다시 말하자면, 새로운 문제가 발생할 경우 새로운 것을 추가만 할 뿐 기존 코드는 그대로 유지해야 한다는 뜻이다.
이 원칙은 다형성의 효과적인 사용과 밀접하게 관련되어 있다.
코드를 변경하지 않고 기능을 확장하기 위해서는 보호하려는 추상화에 대해서는 적절한 폐쇄를 해야한다.
4-3. 리스코프 치환 원칙(Liskov Substitution Priciple -LSP)
설계 시 안전성을 유지하기 위해 객체 타입이 유지해야하는 일련의 특성.
어떤 클래스에서든 클라이언트는 특별한 주의를 기울이지 않고도 하위 타입을 사용할 수 있어야 한다. 어떤 하위 타입을 사용해도 실행에 따른 결과를 염려하지 않아야 한다.
즉, 클라이언트는 완전히 분리되어 있으며 클래스 변경 사항과 독립되어야 한다.
만약, S가 T의 하위 타입이라면 프로그램을 변경하지 않고 T타입의 객체를 S타입의 객체로 치환 가능해야 한다.
결론적으로 이 원칙은 계약을 통한 설계와도 관련이 있다.
주어진 타입과 클라이언트 사이에는 계약이 필요하다.
LSP의 규칙에 따르면 하위 클래스는 상위 클래서에서 정의한 계약을 따르도록 디자인해야 한다.
class Event:
~~~
def meet(self, event_data:dict) -> bool:
return False
class LoginEvent:
def meet(self, event_data:list) -> bool:
return bool(event_data)
LSP 위반 기준은 명확하다. 파생클래스가 부모클래스가 정의한 파라미터와 다른 타입을 사용했기 때문에 다르게 동작한다.
이 원칙에 따르면 호출자는 아무런 차이를 느끼지 않고 투명하게 Event 또는 LoginEvent를 사용할 수 있어야 한다. 이 두 가지 타입의 객체를 치환해도 애플리케이션 실행에 실패해서는 안 된다. 그렇지 않다면 계층 구조의 다형성이 손상된 것이다.
반환값을 부울 값이 아닌 다른 값으로 변경해도 동일한 오류가 발생한다. 파생 클래스 중 하나가 이 반환 타입을 변경하면 계약을 위반하게 되며 정상적으로 동작할 것이라고 기대할 수 없다.
부모 클래스는 클라이언트와의 계약을 정의한다. 이 하위 클래스는 그러한 계약을 따라야 한다.
하위 클래스는 부모 클래스에 정의된 것보다 사전조건을 엄격하게 만들면 안된다.
하위클래스는 부모 클래스에 정의된 것보다 약한 사후조건을 만들면 안된다.
LSP는 다형성을 강조하기 때문에 좋은 디자인의 기초가 된다. 인터페이스의 메서드가 올바른 계층구조를 갖도록하여 상속된 클래스가 부모 클래스와 다형성을 유지하도록 하는 것이다.
LSP가 OCP에 기여한다고 말할 수 있다.
4-4. 인터페이스 분리 원칙(Interface Segregation Principle - ISP)
객체 지향적인 용어로 인터페이스는 객체가 노출하는 메서드 집합.
객체가 수신하거나 해석할 수 있는 모든 메시지가 인터페이스.
덕 타이핑(duck typing): ‘어떤 새가 오리처럼 걷고 오리처럼 꽥꽥 소리를 낸다면 오리여야만 한다.’
즉, 클래스의 메서드는 실제로 그 객체가 무엇인지 결정한다.
추상 기본 클래스(Abstract Base Class): 파생 클래스가 구현해야 할 일부분을 기본 동작 또는 인터페이스로 정의하는 것.
오랫동안 덕 타이핑은 파이썬에서 인터페이스를 정의하는 유일한 방법이었지만, 추상 기본 클래스를 이용해 확장된 방식으로 인터페이스를 정의할 수 있게 되었다.
다중 메서드를 가진 인터페이그 있다면, 매우 정확하고 구체적인 구분에 따라 더 적은 수의 메서드를 가진 여러개의 매서드로 분할하는 것이 좋다.
SRP와 유사하지만 주요 차이점은 ISP는 인터페이스에 대해 이야기하고 있다. 따라서 이것은 행동의 추상화이다.
4-5. 의존성 역전 원칙(Denpendency Inversion Principle - DIP)
의존성을 역전시킨다는 것은 코드가 세부사항이나 구체적인 구현에 적응하지 않도록, API같은 것에 적응하도록 하는 것이다.
추상화를 통해 세부사항에 의존하지 않도록 해야 하지만, 반대로 세부사항(구체적인 구현)은 추상화에 의존해야 한다.
추상화는 인터페이스 형태로 제공된다.
A와 B 두 객체가 상호작용한다고 생각해보자. 이때 A가 B에 의존한다. 그렇다면 B의 코드가 변경되면 두 객체의 상호작용은 실패할 것이며, 자연스럽게 A의 코드도 변경되어야 한다.
이를 뒤집어 B가 A에 적응해야한다.
이렇게 하려면 인터페이스를 개발하고 코드가 B의 구체적인 구현에 의존하지 않도록 해야 한다. 대신에 정의한 인터페이스에 의존적이도록 해야 한다.
이를 의존성 역전이라 한다.
런타임 중에도 객체의 프로퍼티를 수정해도 인터페이스를 통해 여전히 잘 작동한다. 이렇게 의존성을 동적으로 제공한다고 하여 의존성 주입(dependency injection) 이라 한다.
파이썬은 충분히 융통성 있으며, 동적타입 언어이다. 인터페이스를 사용하지 않고, 특정 데이터 대상 객체를 제공할 수 있다. 그렇다면 객체를 통째로 넘기면 되는데, 추상 기본 클래스(인터페이스)를 정의 해야할 필요가 있을까?
이 또한 사실이다. 실제로 파이썬에서는 이렇게 할 필요가 없으며, 똑같이 동작한다.
그러나 추상 기본 클래스를 통해 덕타이핑의 장점을 쉽게 누릴 수 있어 클린디자인을 위해 바람직하다.