Search
📖

엘레강트 오브젝트를 읽고서 (2/3)

Tags
Book
Date
2023/12/26

개요

1편을 작성한지 어언 한달이 지났는데요, 못다한 마무리를 이번주 내로 마무리 지어볼 예정입니다.
클래스안에 1개이상 4개 이하의 객체를 캡슐화
클래스안에 5개 이하의 퍼블릭 메서드 사용.
정적 메서드 금지 (유틸리티 클래스, 싱글턴 금지)
인자의 값으로 null금지
불변 객체 사용
Getter setter 사용 금지
객체를 다른 객체와 상호작용할 수 있도록 준비시키기 위해 필요한 몇 가지 원칙

2.1 가능하면 적게 캡슐화하세요.

클래스안에 4개이하의 객체를 캡슐화 할 것을 권장한다.
상태는 객체의 식별자여야 한다. -> 4개 이상의 객체로 식별자를 구성할 경우 직관에 위배된다. -> 복잡도 증가.
상태는 객체의 식별자.
상태가 없는 객체는 악명 높은 정적 메서드와 유사하다.
애플리케이션 전체를 유지보수 가능하도록 만들기 위해서는 최선을 다해서 객체를 분리해야 한다.
기술적인 관점에서 객체 분리란 상호작용하는 다른 객체를 수정하지 않고도 해당 객체를 수정할 수 있도록 만든다는 것이다.
객체를 분리하는 데 사용하는 도구 = 인터페이스
의존성 역전의 법칙
의존관계를 갖는 모듈 인스턴스의 구성이 추상화에 의존하는 것
구체적인 의존 관계가 추상화에 의해 런타임에 결정되기 때문에 다형성을 적극적으로 활용할 수 있으며 모듈의 재사용성이 높아진다.
메서드의 목적이 무엇인지 먼저 생각하라.
빌더 = 명사 : 뭔가를 만들고 새로운 객체를 반환하는 메서드 -> 객체에게 무엇을 만들라고 요청한다.
조정자 = 동사 : 엔티티를 수정하는 메서드 -> 객체에게 무엇을 할지를 알려주어야 한다.
두 개의 개념이 섞여 있는 메서드가 존재해서는 안됨.
좋은 메서드 이름 : 객체를 설계한 목적, 객체가 수행해야 하는 임무, 객체의 존재 목적과 살아가는 의미를 더 잘 이해할 수 있도록 해준다.
boolean값을 반환하는 경우 : 가독성 측면에서 이름을 형용사로 지어야 한다.
객체들은 어떤 것도 공유해서는 안된다. 대신 독립적이어야 하고 닫혀 있어야 한다.
개방 -폐쇄 원칙 : 확장에 대해서 열려있고, 수정에 대해서 닫혀있다.
결합도 증가, 응집도 저하
전역 가시성 안에 방치됨 : 이 객체가 어떤 문맥 안에서 어떻게 사용되어야 하고, 이 객체의 변경으로 인해 사용자가 어떤 영향을 받지에 관해서도 알 수 없음.
객체가 자신의 문제를 해결하는데 덜 집중.
불변성 : 크기가 작고, 응집력이 높으며, 느슨하게 결합되고, 유지보수하기 쉬운 클래스를 만들 수 있도록 한다.
불변 객체를 수정해야 한다면 프로퍼티를 수정하는 대신 새로운 객체를 생성해야 한다.
불변 객체를 사용해야 하는 이유
식별자 가변성 : 불변 객체에는 식별자 가변성 문제가 없다.
실패 원자성을 보장할 수 있다. : 완전하고 견고한 상태의 객체를 가지거나 아니면 실패하거나 둘 중 하나만 가능한 특성. 중간은 없다.
시간적 결합을 제거할 수 있다. : 가변 객체를 처리하는 코드의 순서에 대해 신경 쓸 필요 없음.
부수효과 제거 : 수정할 수 없으니까ㅇㅇ
null참조 없애기 : 모든 객체가 불변이면 애포에 null을 포함 시키는게 불가능해진다.
스레드 안전성 :
작고 더 단순한 객체 : 객체가 더 단순해질 수록 응집도는 더 높아지고, 유지보수하기는 더 쉬워진다.
나쁜 설계는 문서화를 강제한다.
작은 객체 : 유지보수가 가능 + 높은 응집력 + 용이한 테스트
클래스의 크기를 정하는 기준 : 퍼블릭 메서드의 개수
왜 적은 수의 퍼블릭 메서드를 가져야 할까?
많은 수 보다 적은 수의 메서드 들을 가지고 조화를 이루도록 만드는 것이 쉽다.
단일 책임 원칙을 지키는데 도움이 된다.
클래스가 작으면 메서드와 프로퍼티가 더 '가까이' 있을 수 있기 때문에 응집도가 높아진다.
자바 jvm이랑 연결해서 설명하면 좋을 듯
정적 메서드 대신 객체를 사용해라.
정적 메서드는 소프트웨어를 유지보수하기 어렵게 만든다.
객체지향 프로그래밍과 절차적 프로그래밍의 차이는 'is a'이다.
객체지향적인 생각 : 객체가 무엇인지만 정의하고 객체들이 필요할 때 스스로 상호작용하도록 제어한다.
cpu에게 할 일을 지시하는 것이 아니라 정의한다. ( x는 5와 9의 최댓값이다.)
선언형 스타일 vs. 명령형 스타일
명령형 : 프로그램의 상태를 면경하는 문장을 사용해서 계산 방식을 서술
메서드를 호출한 시점에 cpu가 즉시 결과를 계산
선언형 : 제어흐름을 서술하지 않고 계산 로직을 표현
메서드가 무엇인지만 정의 -> 실제 사용 시점에 값을 계산한다
cpu에게 결과가 실제로 필요한 시점과 위치를 결정하도록 위임, cpu는 요청이 있을 경우에만 계산을 실행
왜 선언형 방식이 더 좋은가?
1.
직접 성능 최적화를 제어할 수 있기 때문에 더 빠르다.
2.
다형성 (코드 블록 사이의 의존성을 끊을 수 있는 능력 ) : 객체 사이의 결합도를 낮출 수 있을 뿐만 아니라, 이 작업을 우아하게 처리할 수도 있다.
정적 메서드는 분리가 불가능 하다. 정적 메서드를 전달하는 것을 불가능 하다. (???)
1.
표현력
선언형 방식은 결과를 이야기하는데 반해 명령형 방식은 수행 사능한 한 가지 방법을 이야기 한다.
때문에 명령형 방식에서 결과를 예상하기 위해서는 먼저 머릿속에서 코드를 '실행' 해야한다. -> 선언형보다 덜 직관적.
2.
코드의 응집도
불변 객체가 시간적인 결합 문제를 해결할 수 있다. (선언형 프로그래밍 역시 마찬가지)
정적 메서드가 이미 있을 경우 : 우리 코드가 객체를 직접 처리할 수 있도록 정적 메서드를 감싸는 클래스를 만들어 종양을 고립시키는 것이다.
유틸리티 클래스 , 싱글톤 패턴 = 안티패턴 \
유틸리티 클래스와 싱글톤의 차이 : 싱글톤은 분리 가능한 의존성으로 연결되어 있는데 반해, 유틸리티 클래스는 분리가 불사능한 하드코딩된 결합도를 가진다.
함수형 프로그래밍과 조합 가능한 데코레이터
함수합성이랑 비슷한 개념
정적 메서드는 조합이 불가능하다.
null을 사용하는 방식은 각각의 객체가 자신의 행동을 온전히 책임진다는 객체지향 패러다임과 상반되는 아이디어이다. 121p
null여부를 체크함으로써 객체가 맡아야 하는 상당량의 책임을 빼앗게 된다. 123p -> 객체를 멍청한 자료구조로 퇴화 시키고 있는 것
전달할 것이 없다면, 비어있는 것처럼 행동하는 객체를 전달하면 된다. 대신 항상 객체를 전달하되, 전달할 객체에게 무리한 요청을 한다면 응답을 거부하도록 객체를 구현해야 한다. 125p -> 모나드 같다!
실제적인 구현이 필요하다! 한 번 고민하고 만들어 보자!
null확인 로직으로 코드를 오염시켜서는 안된다.
불변 객체 : 객체가 살아있는 동안 상태가 변하지 않음 ( 객체가 대표하는 엔티티에 충성하기 때문 ). 객체의 행동이나 메소드의 반환값은 중요하지 않음.
상수처럼 동작하는 객체 = 불변성의 특별한 경우
객체 = 실제 엔티티(ex : 디스크에 있는 파일)의 대표자
불변 객체와 가변 객체의 차이
불변 객체에는 식별자가 존재하지 않으며, 절대로 상태를 변경할 수 없다.
불변 객체의 식별자는 객체의 상태와 완전히 동일하다.
객체지향의 사실과 오해와 충돌되는 내용인것 같다.
클래스는 어떤 식으로든 멤버에게 접근하는 것을 허용하지 않는다. -> 캡슐화
자료구조는 투명하지만 객체는 불투명하다.
모든 프로그래밍 스타일의 핵심 목표는 가시성의 범위를 축소해서 사물을 단순화 시키는 것
특정 시점에 이해해야 하는 범위가 작을수록, 소프트웨어의 유지보수성이 향상되고 이해하고 수정하기도 쉬워진다.
데이터를 객체 안에 감추고 절대로 외부에 노출해서는 안된다.
getter와 setter는 캡슐화 원칙에 위배된다.
객체를 데이터 저장소로 취급하지 않고 객체로서 존중해 줄 것.
메소드 안에서 new연산자를 사용하여 다른 객체 참조 = 하드 코딩된 의존성
하드 코딩된 의존성 : 클래스가 다른 클래스에 직접 연결되어 있음.
하드 코딩된 의존성이 소프트웨어를 테스트하고 유지보수하기 어렵게 만든다. -> 변경의 전파가 일어남.
객체가 필요한 의존성을 직접 생성하는 대신, 생성자를 통해 의존성을 주입해야 한다.
부 생성자에서만 new를 사용할 것.
코드가 런타임에 다른 코드에 의해 수정된다는 사실을 항상 기억해야 한다면, 코드를 읽기가 매우 어려울 것이다.
런타임에 객체의 타입을 조사하는 것은 클래스 사이의 결합도가 높아지기 때문에 기술적인 관점에서 좋지 않다.
반환된 값이 객체인지 확인해야 하기 때문에 객체를 신뢰할 수 없게 된다. 객체에 대한 존중 없음.
객체 = 신뢰( 객체가 자신의 행동을 전적으로 책임지고 외부는 어떤 식으로든 간섭하지 않는다. )하는 엔티티
반환값을 검사하는 방식은 애플리케이션에 대한 신뢰가 부족하다는 신호이다.
안전하게 실패하기 보다 빠르게 실패하기
안전하게 실패하기 : 문제가 발생한 상황에서도 소프트웨어가 계속 실행될 수 있도록 최대한 많은 노력을 기울일 것을 권장.
빠르게 실패하기 : 일단 문제가 발생하면 곧바로 실행을 중단하고 최대한 빨리 예외를 던지는 것.
상황을 구조하는 대신, 가능하면 실패를 분명하게 만든다.
문제를 빨리 발견 할 수록 문제를 수정하는 시간도 빨라진다.
null의 대안 : null 대신 어떤 것을 사용해야 할까?
null을 반환 하거나 예외를 던지는 대신 객체 컬렉션을 반환한다.
Null object 디자인 패턴 사용
겉으로 보기에는 원래의 객체처럼 보이지만 실제로는 다르게 행동하는 객체 반환.
체크 예외란 무엇인가?
자바에서 RuntimeException을 상속하지 않은 예외. 반드시 예외 처리를 하는 코드를 작성해야 한다,
언체크 예외란 무엇인가?
RuntimeException을 상속한 예외. 예외 처리가 강제 되지 않는다. 언체크 예외는 무시할 수 있으며 예외를 잡시 않아도 무방하다.
왜 체크 예외만 던져야 하는가?
어떤 예외가 던져질 지 예상할 수 없다. 비가시적
체크 예외는 가시적이기 때문에 예외에 대한 타입을 인지할 수 있음 (?)
예외 체이닝
상위로 던져
예외 체이닝은 의미론적으로 문제와 관련된 문맥을 풍부하게 만들기 위해 필요하다.
부모 클래스에서 자식 클래스에 대한 접근은 문제를 발생하게 할 수 있다.
클래스와 메서드를 final이나 abstract로 제한하면 문제 발생 가능성을 제한할 수 있다.