만혁
Data-Oriented Programming
1장 객체지향 프로그래밍의 복잡성
1.1 복잡성에 대해서
OOP 의 복잡성은 언어의 구문(syntax)이나 의미(semantics)와 관련이 없다.
복잡성은 프로그램은 객체로 구성되어야 하고, 객체는 상태를 갖고, 상태에 접근하거나 조작하는 메서드와 함께 한다는 점에 있다.
수년에 걸쳐 OOP 생태계는 언어에 새로운 특징(익명 클래스, 무명 함수 등)을 추가하고 개발자에게
더 간단한 인터페이스를 제공해 일부 복잡도를 숨겨 이 복잡도를 경감시켰다.
프레임워크는 내부에서 리플렉션과 사용자 정의 어노테이션과 같은 고급 언어적 특징에 의존한다.
1.1.1 설계 단계
설계시 UML 클래스도를 그린다. 객체지향 프로그래밍에선 모든 비즈니스 개체는 객체로 표현되고, 모든 객체는 클래스로부터 만들어진다.
클래스 도출은 쉬운 작업이지만 클래스 간 관계를 정의하는 순간 어려워진다.
포함(composition), 연관(association), 상속(inheritance), 사용(usage)
포함과 연관은 객체가 서로가 없어도 살 수 있는지 여부에 따라 다르다.
포함 관계에서는 한 객체가 죽으면 다른 객체도 죽는다. 연관 관계는 각자의 삶을 산다. 즉, 포함 관계는 한 객체가 없어지면 다른 객체도 없어지고, 연관 관계는 각 개체의 생애주기가 독립적이다.
UML 도면을 봐도 뭔말인지 모르겠다!!!!!!!!!!!
1.2 복잡성의 근원
- 시스템 구현에서 그가 선택한 프로그래밍 프로그래밍 패러다임에 관한 것
- 객체지향 패러다임에 대한 것
- OOP가 시스템의 복잡도를 증가시키는 경향을 보이는 점에 대한 것
책에서 언급되는 유형의 복잡도는 시스템을 이해하기 어렵게 만드는 정도를 말한다.
즉, 프로그램이 소비하는 자원의 양을 다루는 유형의 복잡도와는 관련이 없다.
마찬가지로, 단순성은 복잡하지 않다(즉, 이해하기 쉽다)는 의미로 사용된다.
복잡성과 단순성(어려움과 쉬움처럼)은 절대적인 개념이 아니고 상대적인 것임을 명심해야한다.
책에서 복잡도에 대해서 언급된 "타르 웅덩이 밖으로(Out of the Tar Pit)" 논문을 살짝 봤는데 재미있는 내용인듯 Out of the Tar Pit (opens in a new tab)
OOP 에 내재된 복잡성의 주요 근원은 다음과 같다.
- 코드와 데이터가 섞여있다.
- 객체가 변경 가능하다.
- 데이터가 멤버로 객체에 고정된다.
- 코드는 메서드로 클래스에 고정된다.
이 분석 내용은 함수형 프로그래밍이 기존 OOP 에 대해 갖는 생각과 비슷하다.
그러나 이 책에선 DOP 가 시스템 복잡도를 줄이려고 취하는 데이터 접근법은 FP와 다르다.
특징 | 복잡도에 미치는 영향 |
---|---|
코드와 데이터가 섞여 있다. | 클래스가 많은 관계에 연루되는 편이다. |
객체가 변경 가능하다. | 코드를 읽을 때 더 많은 생각이 필요하다. |
다중 스레드 환경에서 명시적 동기화가 필요하다 | |
데이터가 객체에 묶여 있다. | 데이터 직렬화가 쉽지 않다. |
코드는 클래스에 묶여 있다. | 클래스 계층 구조가 복잡하다. |
1.2.1 다량의 클래스 간 관계
클래스 복잡도를 산정하는 한 가지 방법은 멤버와 메서드를 생략하고
클래스와 클래스 간의 관계만 보는 것이다.
시스템 분석의 관점에서, 코드와 데이터가 함께 혼합되면
각 소프트웨어의 구성 요소가 많은 관계를 맺을 가능성이 높아져 시스템이 복잡해진다.
- 데이터 관계 :
- Library 는 여러 개의 Member 를 갖는다.
- Member 는 여러 개의 BookLending 을 갖는다.
- 코드 관계 :
- Member 는 User 를 확장한다.
- Librarian 은 Member 를 사용한다.
- Member 는 BookItem 을 사용한다.
코드 요소는 MemberCode 로 분리
데이터 요소는 MemberData 로 분리
1.2.2 예상치 못한 코드 동작
// ex 1.1 super simple code
class Member {
isBlocked;
displayBlockedStatusTwice() {
const isBlocked = this.isBlocked;
console.log(isBlocked);
console.log(isBlocked);
}
}
member.displayBlockedStatusTwice();
위 코드를 동작시켰을때 첫 번째 로그에선 true를 출력했다면 두 번째는 true 를 출력한다고 말할것이다. (맞음)
// ex 1.2 simple code
class Member {
isBlocked;
displayBlockedStatusTwice() {
console.log(isBlocked);
console.log(isBlocked);
}
}
member.displayBlockedStatusTwice();
하지만 약간 다르게 변수를 할당하지 않는다면 어떻게 될까?
위와 같이 첫 번째는 true를 반환한다면 두번째는??
단일 스레드인 경우에선 true 겠지만 다중 스레드 환경에서는 예측할 수 없다.
두 코드의 차이는 다음과 같다.
- 예제 1.1(위) 는 원시 자료형 값인 불리언 값에 두번 접근했다.
- 예제 1.2(아래) 는 객체 멤버에 두 번 접근 했다.
데이터가 변경될 수 있다면 코드는 불확실해진다.
두 번째 코드의 불확실한 동작은 OOP 의 불편한 결과중 하나다.
보통은 값이 바끼지 않는 원시 자료형과 달리 객체의 멤버는 값이 바뀔 수 있다.
이를 방지하기 위해 mutex 와 같은 동시성 안전 장치로 코드를 보호하지만,
이런 장치는 성능에 심각한 영향을 미치거나 데드락에 빠질 수 있다.