2023. 10. 12. 20:21ㆍ카테고리 없음
6장
- 아키텍처를 고려할 때 변수의 가변성을 염려할까?
대답은 단순하다. 경합race 조건, 동시 업데이트concurrent update 문제가 모두 가변 변수로 인해 발생하기 때문이다. 다시 말해 우리가 동시성 애플리케이션에서 마주치는 모든 문제, 즉 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.
변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다. 이렇게 분리하려면 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침해야 한다. 현명한 아키텍트라면 가능한 한 많은 처리를 불변 컴포넌트로 옮겨야 하고, 가변 컴포넌트에서는 가능한 한 많은 코드를 빼내야 한다.
- 이벤트 소싱
계좌 잔고를 변경하는 대신 트랜잭션 자체를 저장한다고 생각해보면, 잔고 조회를 요청할 때마다 계좌 개설 시점부터 발생한 모든 트랜잭션을 단순히 더한다. 이 전략에서는 가변 변수가 하나도 필요 없다.
이벤트 소싱에 깔려 있는 기본 발상이 바로 상태가 아닌 트랜잭션을 저장하자는 것이다. 저장 공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 완전한 함수형으로 만들 수 있다.
- 결론
구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과되는 규율이다.
객체지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다.
함수형 프로그래밍은 변수 할당에 부과되는 규율이다.
각 패러다임은 우리가 코드를 작성하는 방식의 형태를 한정시킨다.
SOLID
- SOLID는 좋은 벽돌(clean code)로 좋은 아키텍처를 정의하는 원칙이다.
SOLID 원칙은 함수와 데이터 구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명해준다. 여기에서 클래스는 단순히 함수와 데이터를 결합한 집합을 가리킨다.
- SOLID 원칙의 목적은 중간 수준의 소프트웨어 구조가 아래와 같도록 만드는 데 있다.
변경에 유연하다.
이해하기 쉽다.
많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트의 기반이 된다.
중간 수준이라 함은 프로그래머가 이들 원칙을 모듈 수준에서 작업할 때 적용할 수 있다는 뜻이다. 즉 코드 수준보다는 조금 상위에서 적용되며 모듈과 컴포넌트 내부에서 사용되는 소프트웨어 구조를 정의하는 데 도움을 준다.
잘 만들어진 벽돌로도 빌딩의 아키텍처를 완전히 망쳐버릴 수 있듯이, 잘 설계된 중간 수준의 컴포넌트를 이요하더라도 시스템 전체를 엉망진창으로 만들 가능성 또한 존재한다.
- SRP 단일 책임 원칙single responsibility principle
콘웨이 법칙에 따른 따름정리: 소프트웨어 시스템이 가질 수 있는 최적의 구조는 시스템을 만드는 조직의 사회적 구조에 커다란 영향을 받는다. 따라서 각 소프트웨어 모듈은 변경의 이유가 하나, 단 하나여야만 한다.
- OCP 개방 폐쇄 원칙 open-closed principle
기존 코드를 수정하기보다는 반드시 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야만 소프트웨어 시스템을 쉽게 변경 할 수 있다는 것이 이 원칙의 요지다.
- LSP 리스코프 치환 원칙 liskov substitution principle
상호 대체 가능한 구성 요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 이들 구성요소는 반드시 서로 치환 가능해야 한다는 계약을 반드시 지켜야 한다.
- ISP 인터페이스 분리 원칙 Interface segregation principle
소프트웨어 설계자는 사용하지 않은 것에 의존하지 않아야 한다.
- DIP 의존성 역전 원칙
고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안 된다. 대신 세부사항이 정책에 의존해야 한다.
7장
- SRP라는 이름만 보면 모든 모듈이 단 하나의 일만 해야 한다는 의미로 받아들이기 쉽다.
하지만 단 하나의 일만 해야 한다는 원칙은 사실 따로 있다. 바로 함수는 반드시 하나의, 단 하나의 일만 해야 한다는 원칙이다. 이 원칙은 커다란 함수를 작은 함수들로 리팩터링하는 더 저수준에서 사용된다. 하지만 이 원칙은 SOLID 원칙이 아니며, SRP도 아니다.
- 단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다.
소프트웨어 시스템은 사용자와 이해관계자를 만족시키기 위해 변경된다. SRP가 말하는 변경의 이유란 바로 이들 사용자와 이해관계자를 가리킨다. 이 원칙은 다음과 같이 바꿔 말할 수도 있다.
- 하나의 모듈은 하나의, 오직 하나의 사용자 또는 이해관계자에 대해서만 책임져야 한다.
안타깝게도 사용자와 이해관계자라는 단어는 부적절한 표현이다. 시스템이 동일한 방식으로 변경되기를 원하는 사용자나 이해관계자가 두 명 이상일 수도 있기 때문이다. 이런 의미보다는 집단, 즉 해당 변경을 요청하는 한 명 이상의 살마들을 가리킨다. 이러한 집단을 액터actor라고 부르겠다.
- 하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다.
그럼 모듈이란 또 무슨 뜻일까? 가장 단순한 정의는 바로 소스 파일이다.
응집된cohesive이라는 단어가 SRP를 암시한다. 단일 액터를 책임지는 코드를 함께 묶어주는 힘이 바로 응집성cohesion이다.
- SRP를 위반하는 징후1: 우발적 중복
class Employee{
// CFO 보고를 위해 사용
calcuatePay(){}
// COO 보고를 위해 사용
reportHours(){}
// CTO 보고를 위해 사용
save(){}
}
개발자가 이 세 메서드를 Employee라는 단일 클래스에 배치하여 세 액터가 서로 결합되어 버렸다.
calcuatePay() 메서드와 reportHours() 메서드가 초과 근무를 제외한 업무 시간을 계산하는 알고리즘을 공유한다고 해보자. 개발자는 코드 중복을 피하기 위해 이 알고리즘을 regularHours()라는 메서드에 넣었다고 해보자.
CFO 팀에서 초과 근무를 제외한 업무 시간을 계산하는 방식을 약간 수정하기로 결정했다. COO팀에서는 이 같은 변경을 원하지 않는다고 해보자.
이 업무를 할당받은 개발자는 calculatePay() 메서드가 유틸 메서드인 regularHours()를 호출한다는 사실을 발견한다. 하지만 reportHours() 메서드에서 호출된다는 사실은 알지 못한다.
COO팀 직원은 reportHours() 메서드가 생성한 보고서를 여전히 이용한다. 하지만 수치들은 엉터리다.
이러한 문제는 서로 다른 액터가 의존하는 코드를 너무 가까이 배치했기 때문에 발생한다. SRP는 서로 다른 액터가 의존하는 코드를 서로 분리하라고 말한다.
- SRP를 위반하는 징후2: 병합
CTO 팀에서 데이터베이스의 Employee 테이블 스키마를 약간 수정하기로 했다. COO 팀에서는 reportHours() 메서드의 보고서 포맷을 변경하기로 했다. 이들 변경사항은 서로 충돌한다. 결과적으로 병합이 발생한다.
- 두 징후 모두 많은 사람이 서로 다른 목적으로 동일한 소스 파일을 변경하는 경우에 해당한다.
이 문제를 벗어나는 방법은 서로 다른 액터를 뒷받침하는 코드를 서로 분리하는 것이다.
- 해결책
아마 가장 확실한 해결책은 데이터와 메서드를 분리하는 방식일 것이다. 아무런 메서드가 없는 간단한 데이터 구조인 EmployeeData 클래스를 만들어, 세 개의 클래스가 공유하도록 한다. 각 클래스는 자신의 메서드에 반드시 필요한 소스 코드만을 포함한다. 세 클래스는 서로의 존재를 몰라야 한다. 따라서 우연한 중복을 피할 수 있다.
하지만 이 해결책은 개발자가 세 가지 클래스를 인스턴스화하고 추적해야 한다는 게 단점이다. 이러한 난관에서 빠져나올 때 흔히 쓰는 기법으로 퍼사드 패턴이 있다.