소프트웨어는 부드러움을 지니도록
만들어졌다. 소프트웨어를 만든 이유는 기계의 행위를 쉽게 변경할 수 있도록 하기 위함이다. 소프트웨어가 가진 본연의 목적을 추구하려면 소프트웨어는 반드시 부드러워야 한다. 다시 말해 변경하기 쉬워야 한다. 이해관계자가 기능에 대한 생각을 바꾸면, 이러한 변경사항을 간단하고 쉽게 적용할 수 있어야 한다. 이러한 변경사항을 적용하는 데 드는 어려움은 변경되는 범위에 비례해야 하며 변경사항의 형태와는 관련이 없어야 한다. 소프트웨어 개발의 비용 증가로 결정짓는 주된 요인은 바로 이 변경사항의 범위와 형태의 차이에 있다. 바로 이 때문에 개발 비용은 요청된 변경사항의 크기에 비례한다. 또한 개발 첫 해가 다음 해보다 비용이 덜 들고, 다음에는 그 다음 해보다 비용이 적게 드는 이유도 이 때문이다.
이해관계자는 범위가 비슷한 일련의 변경사항을 제시할 뿐이지만, 개발자 입장에서는 복잡도가 지속적으로 증가하는 퍼즐판 위에서 이해관계자가 계속해서 퍼즐 조각을 맞추라는 지시를 하는 것처럼 느껴진다. 새로운 요청사항이 발생할 때마다 바로 이전의 변경사항을 적용하는 것보다 조금 더 힘들어지는데, 시스템의 형태와 요구사항의 형태가 아다리가 맞지 않기 때문이다. 문제는 당연히 시스템의 아키텍처다. 아키텍처가 특정 형태를 다른 형태보다 선호하면 할수록, 새로운 기능이 이 구조에 맞추는 것이 더 힘들어진다. 따라서 아키텍처는 형태에 독립적이어야 하고, 그럴수록 더 실용적이다.
::더 높은 가치
기능인가 아키텍처인가? 둘 중 어느 것의 가치가 더 높은가?
소프트웨어 시스템이 동작하도록 만드는 것이 더 중요한가? 아니면 소프트웨어 시스템을 더 쉽게 변경할 수 있도록 하는 것이 더 중요한가?
라고 업무 관리자에게 묻는다면 대다수가 소프트웨어 시스템이 동작하는 것이 더 중요하다고 대답할 것이다. 이어서 개발자에게 묻는다면 업무 관리자의 의견에 대체로 동조하는 태도를 취하게 된다. 하지만 이는 잘못된 태도이다. 완벽하게 동작하지만 수정이 아예 불가능한 프로그램이 있다면 이 프로그램은 요구사항이 변경될 때 동작하지 않게되고, 결국 프로그램이 돌아가도록 만들 수 없게 된다. 따라서 이런 프로그램은 거의 쓸모가 없다. 동작은 하지 않지만 변경이 쉬운 프로그램이 있다면 프로그램이 돌아가도록 만들 수 있고, 변경사항이 발생하더라도 여전히 동작하도록 유지보수 할 수 있다. 따라서 이런 프로그램은 앞으로도 계속 유용한 채로 남을 것이다. 유연한
소프트웨어다. 그리고 유연한 소프트웨어란 고객의 요구사항에 따라, 쉽게 그 기능을 변경하거나 추가할 수 있는 제품을 말한다. 따라서 고객의 요구사항이 변화함에 따라, 개발자가 제품의 기능을 쉽게 변화시킬 수 있는 구조를 설계하도록 돕는 아키텍처가 좋은 아키텍처라는 말이된다.하나의 모듈은 오직 하나의 엑터에 대해서만 책임져야 한다
라는 원칙이다. 고객
이란 접점에서 겹치는 것과 같은 경우이다. 책임 범위가 애매하면 기능도 모호해질 수밖에 없다. 책임 범위를 설정할 때는, 실패 상황을 가정해보는 방법이 유용하다. 어떤 메서드에 문제가 생겼을 때 큰 피해를 입는 속성들을 생각해보면 도움이 된다.객체를 다룸에 있어서 객체의 확장은 개방적으로, 객체의 수정은 폐쇄적으로 대하는 원칙이다. 쉽게말해 기능이 변하거나 확장은 가능하지만, 해당 기능의 코드는 수정하면 안된다는 뜻이다.
에어비앤비, 힐튼 호텔 모두 숙박업에 속한다. 하지만 이들의 사업방식은 극과 극이다. 에어비앤비는 자체적인 숙박시설을 갖추지 않고, 개인 소유의 주거공간을 아웃소싱한다. 반면, 힐튼 호텔은 토지를 확보하고, 관광 및 숙박시설 허가를 받아서 숙박시설을 건립한다.
사업의 성장 속도를 비교해보면, 에어비앤비 쪽이 압도적으로 확장에 유리하다는 사실을 알 수 있다. 에어비앤비는 땅을 구입해서 대규모 시공 및 운영권을 확보할 필요가 없다. 그들은 주거 공간을 가지고 있는 개인과 계약서만을도 사업을 확장할 수 있다. 이 때문에 에어비앤비는 엄청난 속도로 전통적인 숙박업의 성장 속도를 따라잡았다.
왼쪽의 힐튼부터 살펴보면 고객 서비스부터 건물 인테리어 및 토지 활용 방안까지 모두 Hilton 클래스의 책임 범위에 있기 때문에 추가되는 기능이 증가할 때마다 메서드가 늘어나고 있다. 위의 방식을 사용하면, 기능이 추가될 때마다 Hilton 클래스가 무거워진다. 만일 Hilton 클래스를 상속받는 클래스가 있다고 한다면 해당 클래스는 시작부터 무거운 짐을 떠안아야 할 것이다.
반면 오른쪽 에어비앤비는 힐튼에 비해 간단하다. contract() 메서드의 인자값에 Host 클래스만 계속 추가하기 때문이다. 해당 코드에서 방의 구조 변경과 같은 구체적인 사항은 Host 클래스가 자체적으로 수행한다. Host 클래스는 권한과 자유 그리고 책임을 가지고 있으며 상황은 유동적이다. 자체 service() 내용은 Host가 알아서 관리하면 되기 때문이다.
그 결과 airbnb 클래스는 Host 클래스를 추가하는 것만으로도 확장이 가능해진다. airbnb 클래스는 Host 클래스를 받기만 할 뿐 자신이 뭔가 기능을 구현하지는 않는다. 인자로 받은 하위 클래스의 기능을 오직 이용만 한다. airbnb 클래스는 똑같은 명령(service())을 지시할 뿐, 하위 Host 클래스는 각자 다른 기능을 구현한다.
여기서 리스코프 원칙은 복제
라는 사실을 기억해야 한다.
문제를 해결하는 방법은 여러가지가 있다. 그 중,
1. 인터페이스를 상속하는 인터페이스를 통한 해결
2. 여러 개의 인터페이스를 상속하여 해결
::DIP
고/저수준 모듈의 정의는 다음과 같다.
고수준 : 인터페이스와 같은 객체의 형태나 추상적 개념
저수준 : 구현된 객체
❗️ 위 DIP의 예시가 맞지 않는 부분이 있는 것 같아서 추후에 수정하겠다. 좀 더 괜찮은 예시를 찾아보거나 내가 직접 코드로 만들어봐야겠다.