-
클래스
-
클래스 체계
-
클래스는 작아야한다.
-
변경하기 쉬운 클래스
-
시스템
-
시스템 제작과 시스템 사용을 분리하라
-
확장
-
자바 프록시
-
순수 자바 AOP 프레임워크
-
AspectJ관점
-
창발성
-
단순한 설계규칙 1: 모든 테스트를 실행하라
-
단순한 설계규칙 2~4: 리팩터링
-
중복을 없애라
-
표현하라
-
클래스와 메서드 수를 최소로 줄여라
-
동시성
-
동시성이 필요한 이유?
-
동시성 방어 원칙
-
스레드 환경에 안전한 컬렉션
-
실행 모델을 이해하라
-
동기화 하는 메서드 사이에 존재하는 의존성을 이해하라
-
동기화하는 부분을 작게 만들어라
-
올바른 종료 코드는 구현하기 어렵다
-
스레드 코드 테스트하기
클래스
클래스 체계
static public 상수 > static private 변수 > private 변수 > 공개 함수 > 비공개 함수
추상화 단계가 순차적으로 내려간다.
- 캡슐화
변수와 유틸리티 함수는 가능한 공개하지 않는 편이 낫지만 반드시 숨겨야 한다는 법칙도 없다.
클래스는 작아야한다.
책임이 많아서는 안된다. 클래스 이름은 해당 클래스의 책임을 기술해야 한다. 클래스 이름이 모호하면 책임이 모호한 것이다.
- 단일 책임 원칙 SRP
클래스나 모듈을 변경하는 이유는 하나 뿐이어야 한다는 원칙이다.
큰 클래스 한두개가 아니라 작은 클래스 여러개로 이루어진 시스템이 바람직하다.
- 응집도
클래스는 인스턴스 변수가 적어야 한다. 일반적으로 메서드가 변수를 더 많이 사용할 수록 응집도가 높다.
'함수를 작게, 매개변수 목록을 짧게'라는 전략을 따르다보면 인스턴스 변수가 많아진다. 이는 클래스를 나눠야 한다는 신호다.
- 응집도를 유지하면 작은 클래스 여럿이 나온다.
변경하기 쉬운 클래스
변경하기 쉽게 설계된 클래스는 OCP또한 지원한다. 클래스는 확장에 개방적이고 수정에 폐쇄적이어야 한다는 원칙이다.
- 변경으로부터 격리
인터페이스와 추상 클래스를 이용하여 구현이 미치는 영향을 격리한다. 결합도를 낮추다보면 DIP를 따르는 클래스가 나온다.
시스템
시스템 제작과 시스템 사용을 분리하라
제작은 사용과 아주 다르다. 관심사 분리는 가장 오래되고 가장 중요한 설계 기법 중 하나이다.
- Main 분리
- 팩토리
객체를 생성하는 시점을 애플리케이션이 결정할 필요도 생긴다. ABSTRACT FACTORY 패턴을 사용한다.
- 의존성 주입
사용과 제작을 분리하는 강력한 메커니즘 하나가 의존성 주입(DI)이다.
확장
- 횡단 관심사
AOP는 횡단 관심사에 대해서 모듈성을 확보하는 일반적인 방법론이다. AOP에서 관점이라는 모듈 구성 개념은 "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다"라고 명시한다.
자바 프록시
JDK에서 제공하는 동적 프록시는 인터페이스만 가능하다. 클래스 프록시를 사용하려면 CGLIB, ASM, Javassist 등과 같은 바이트 코드 처리 라이브러리가 필요하다.
순수 자바 AOP 프레임워크
순수 자바 관점을 구현하는 스프링AOP, JBoss AOP 등과 같은 여러 자바 프레임워크는 내부적으로 프록시를 사용한다.
클라이언트는 모르게 기본 동작을 확장한 중첩 DECORATOR 객체 집합의 가장 외곽과 통신한다. 트랜잭션, 캐싱 등에도 DECORATOR를 추가할 수 있다.
AspectJ관점
AspectJ는 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장이다.
테스트 주도 시스템 아키텍처 구축
의사 결정을 최적화하라
명백한 가치가 있을 때 표준을 현명하게 사용하라
시스템은 도메인 특화 언어가 필요하다
창발성
남이 모르거나 하지 아니한 것을 처음으로 또는 새롭게 밝혀내거나 이루어 내는 성질
단순한 설계규칙 1: 모든 테스트를 실행하라
"테스트 케이스를 만들고 계속 돌려라"라는 간단하고 단순한 규칙을 계속해서 따르면 시스템은 낮은 결합도와 높은 응집력이라는, 객체 지향 방법론이 지향하는 목표를 저절로 달성한다.
단순한 설계규칙 2~4: 리팩터링
규칙 1 테스트 케이스가 모두 작성되었다면 리팩터링 하면서 걱정이 없다.
중복을 없애라
중복은 추가 작업, 추가 위험, 불필요한 복잡도를 뜻한다. Template Method 패턴 적용
표현하라
- 좋은 이름을 선택하라: 이름과 기능이 완전히 다른 클래스나 함수를 만들지 않는다.
- 함수와 클래스 크기를 가능한 줄인다.
- 표준 명칭을 사용한다.
- 단위 테스트 케이스를 꼼꼼히 작성한다.
클래스와 메서드 수를 최소로 줄여라
클래스와 함수를 줄이는 작업도 중요하지만, 더 중요한 것은 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이다.
동시성
동시성이 필요한 이유?
동시성은 결합을 없애는 전략이다. 즉, 무엇과 언제를 분리하는 전략이다. 무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다.
미신과 오해
- 동시성은 항상 성능을 높여준다.
- 동시성을 구현해도 설계는 변하지 않는다.
- 웹 또는 EJB컨테이너를 사용하면 동시성을 이해할 필요가 없다.
타당한 생각
- 동시성을 다소 부하를 유발한다. : 성능 측면에서 부하가 걸리며 코드도 더 짜야한다.
- 동시성은 복잡하다.
- 일반적으로 동시성 버그는 재현하기 어렵다. : 일회성 문제로 여겨 무시하기 쉽다.
- 동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.
동시성 방어 원칙
- 단일 책임 원칙 SRP
동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다. 즉, 동시성 관련 코드는 다른 코드와 분리해야 한다는 뜻이다.
- 따름 정리 corollary : 자료 범위를 제한하라
공유 객체를 사용하는 코드 내 임계영역(critical section)을 synchroized 키워드로 보호하라고 권장한다.
- 따름 정리: 자료 사본을 사용하라
객체를 복사해 읽기 전용으로 사용하는 방법이 가능하다.
- 따름 정리: 스레드는 가능한 독립적으로 구현하라
자신만의 세상에 존재하는 스레드를 구현한다. 즉, 다른 스레드와 자료를 공유하지 않는다. 각 스레드는 클라이언트 요청 하나를 처리한다.
스레드 환경에 안전한 컬렉션
java.util.concurrent 패키지에서 제공하는 클래스, 실제로 ConcurrentHashMap은 거의 모든 상황에서 HashMap보다 빠르다.
java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks
실행 모델을 이해하라
다중 스레드 프로그래밍에서 사용하는 실행 모델
생산자-소비자 Producer-Consumer
하나 이상 생산자 스레드가 정보를 생성해 버퍼나 대기열(queue)에 넣는다. 하나 이상 소비자 스레드가 대기열에서 정보를 가져와 사용한다. 대기열은 한정된 자원이므로, 문제가 생길 수 있다.
읽기-쓰기
읽기 스레드와 쓰기 스레드가 존재하는 경우 처리율이 가장 큰 문제이다. 처리율을 강조하면 기아현상이 생기거나 오래된 정보가 쌓인다. 양쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요하다.
식사하는 철학자들
각 철학자 왼쪽에는 포크가 있다. 왼쪽이나 오른쪽 철학자가 포크를 사용중이면 포크를 내려놓을 때까지 기다려야한다. 철학자가 스레드 포크가 자원이다. 여러 프로세스가 자원을 얻으려고 대기중이다. 주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하 등을 겪는다.
동기화 하는 메서드 사이에 존재하는 의존성을 이해하라
공유 클래스 하나에 동기화된 메서드가 여럿이라면 구현이 올바른지 다시 한번 확인해야 한다. 공유 객체 하나에는 메서드 하나만 사용하라.
동기화하는 부분을 작게 만들어라
자바에서 synchronized 키워드를 사용하면 락을 설정한다. 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행 가능하다. 락은 스레드를 지연시키고 부하를 증가시킨다. synchronized를 남발하는 코드는 좋지 않다. 또한 임계 영역 수를 최대한 줄여야 한다.
올바른 종료 코드는 구현하기 어렵다
깔끔하게 종료하는 코드는 구현하기 어렵다. 가장 흔히 발생하는 문제가 데드락이다.
스레드 코드 테스트하기
문제를 노출하는 테스크 코드 작성, 프로그램 설정과 시스템 설정을 바꿔가면서 자주 테스트.
말이 안되는 실패는 잠정적인 스레드 문제로 취급하라
다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자.
다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라.
프로세서 수보다 많은 스레드를 테스트해봐라. : 스레드를 스와핑 할때도 문제가 발생한다.
강제로 실패를 일으켜 봐라.
클래스
클래스 체계
static public 상수 > static private 변수 > private 변수 > 공개 함수 > 비공개 함수
추상화 단계가 순차적으로 내려간다.
- 캡슐화
변수와 유틸리티 함수는 가능한 공개하지 않는 편이 낫지만 반드시 숨겨야 한다는 법칙도 없다.
클래스는 작아야한다.
책임이 많아서는 안된다. 클래스 이름은 해당 클래스의 책임을 기술해야 한다. 클래스 이름이 모호하면 책임이 모호한 것이다.
- 단일 책임 원칙 SRP
클래스나 모듈을 변경하는 이유는 하나 뿐이어야 한다는 원칙이다.
큰 클래스 한두개가 아니라 작은 클래스 여러개로 이루어진 시스템이 바람직하다.
- 응집도
클래스는 인스턴스 변수가 적어야 한다. 일반적으로 메서드가 변수를 더 많이 사용할 수록 응집도가 높다.
'함수를 작게, 매개변수 목록을 짧게'라는 전략을 따르다보면 인스턴스 변수가 많아진다. 이는 클래스를 나눠야 한다는 신호다.
- 응집도를 유지하면 작은 클래스 여럿이 나온다.
변경하기 쉬운 클래스
변경하기 쉽게 설계된 클래스는 OCP또한 지원한다. 클래스는 확장에 개방적이고 수정에 폐쇄적이어야 한다는 원칙이다.
- 변경으로부터 격리
인터페이스와 추상 클래스를 이용하여 구현이 미치는 영향을 격리한다. 결합도를 낮추다보면 DIP를 따르는 클래스가 나온다.
시스템
시스템 제작과 시스템 사용을 분리하라
제작은 사용과 아주 다르다. 관심사 분리는 가장 오래되고 가장 중요한 설계 기법 중 하나이다.
- Main 분리
- 팩토리
객체를 생성하는 시점을 애플리케이션이 결정할 필요도 생긴다. ABSTRACT FACTORY 패턴을 사용한다.
- 의존성 주입
사용과 제작을 분리하는 강력한 메커니즘 하나가 의존성 주입(DI)이다.
확장
- 횡단 관심사
AOP는 횡단 관심사에 대해서 모듈성을 확보하는 일반적인 방법론이다. AOP에서 관점이라는 모듈 구성 개념은 "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다"라고 명시한다.
자바 프록시
JDK에서 제공하는 동적 프록시는 인터페이스만 가능하다. 클래스 프록시를 사용하려면 CGLIB, ASM, Javassist 등과 같은 바이트 코드 처리 라이브러리가 필요하다.
순수 자바 AOP 프레임워크
순수 자바 관점을 구현하는 스프링AOP, JBoss AOP 등과 같은 여러 자바 프레임워크는 내부적으로 프록시를 사용한다.
클라이언트는 모르게 기본 동작을 확장한 중첩 DECORATOR 객체 집합의 가장 외곽과 통신한다. 트랜잭션, 캐싱 등에도 DECORATOR를 추가할 수 있다.
AspectJ관점
AspectJ는 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장이다.
테스트 주도 시스템 아키텍처 구축
의사 결정을 최적화하라
명백한 가치가 있을 때 표준을 현명하게 사용하라
시스템은 도메인 특화 언어가 필요하다
창발성
남이 모르거나 하지 아니한 것을 처음으로 또는 새롭게 밝혀내거나 이루어 내는 성질
단순한 설계규칙 1: 모든 테스트를 실행하라
"테스트 케이스를 만들고 계속 돌려라"라는 간단하고 단순한 규칙을 계속해서 따르면 시스템은 낮은 결합도와 높은 응집력이라는, 객체 지향 방법론이 지향하는 목표를 저절로 달성한다.
단순한 설계규칙 2~4: 리팩터링
규칙 1 테스트 케이스가 모두 작성되었다면 리팩터링 하면서 걱정이 없다.
중복을 없애라
중복은 추가 작업, 추가 위험, 불필요한 복잡도를 뜻한다. Template Method 패턴 적용
표현하라
- 좋은 이름을 선택하라: 이름과 기능이 완전히 다른 클래스나 함수를 만들지 않는다.
- 함수와 클래스 크기를 가능한 줄인다.
- 표준 명칭을 사용한다.
- 단위 테스트 케이스를 꼼꼼히 작성한다.
클래스와 메서드 수를 최소로 줄여라
클래스와 함수를 줄이는 작업도 중요하지만, 더 중요한 것은 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이다.
동시성
동시성이 필요한 이유?
동시성은 결합을 없애는 전략이다. 즉, 무엇과 언제를 분리하는 전략이다. 무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다.
미신과 오해
- 동시성은 항상 성능을 높여준다.
- 동시성을 구현해도 설계는 변하지 않는다.
- 웹 또는 EJB컨테이너를 사용하면 동시성을 이해할 필요가 없다.
타당한 생각
- 동시성을 다소 부하를 유발한다. : 성능 측면에서 부하가 걸리며 코드도 더 짜야한다.
- 동시성은 복잡하다.
- 일반적으로 동시성 버그는 재현하기 어렵다. : 일회성 문제로 여겨 무시하기 쉽다.
- 동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.
동시성 방어 원칙
- 단일 책임 원칙 SRP
동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다. 즉, 동시성 관련 코드는 다른 코드와 분리해야 한다는 뜻이다.
- 따름 정리 corollary : 자료 범위를 제한하라
공유 객체를 사용하는 코드 내 임계영역(critical section)을 synchroized 키워드로 보호하라고 권장한다.
- 따름 정리: 자료 사본을 사용하라
객체를 복사해 읽기 전용으로 사용하는 방법이 가능하다.
- 따름 정리: 스레드는 가능한 독립적으로 구현하라
자신만의 세상에 존재하는 스레드를 구현한다. 즉, 다른 스레드와 자료를 공유하지 않는다. 각 스레드는 클라이언트 요청 하나를 처리한다.
스레드 환경에 안전한 컬렉션
java.util.concurrent 패키지에서 제공하는 클래스, 실제로 ConcurrentHashMap은 거의 모든 상황에서 HashMap보다 빠르다.
java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks
실행 모델을 이해하라
다중 스레드 프로그래밍에서 사용하는 실행 모델
생산자-소비자 Producer-Consumer
하나 이상 생산자 스레드가 정보를 생성해 버퍼나 대기열(queue)에 넣는다. 하나 이상 소비자 스레드가 대기열에서 정보를 가져와 사용한다. 대기열은 한정된 자원이므로, 문제가 생길 수 있다.
읽기-쓰기
읽기 스레드와 쓰기 스레드가 존재하는 경우 처리율이 가장 큰 문제이다. 처리율을 강조하면 기아현상이 생기거나 오래된 정보가 쌓인다. 양쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요하다.
식사하는 철학자들
각 철학자 왼쪽에는 포크가 있다. 왼쪽이나 오른쪽 철학자가 포크를 사용중이면 포크를 내려놓을 때까지 기다려야한다. 철학자가 스레드 포크가 자원이다. 여러 프로세스가 자원을 얻으려고 대기중이다. 주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하 등을 겪는다.
동기화 하는 메서드 사이에 존재하는 의존성을 이해하라
공유 클래스 하나에 동기화된 메서드가 여럿이라면 구현이 올바른지 다시 한번 확인해야 한다. 공유 객체 하나에는 메서드 하나만 사용하라.
동기화하는 부분을 작게 만들어라
자바에서 synchronized 키워드를 사용하면 락을 설정한다. 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행 가능하다. 락은 스레드를 지연시키고 부하를 증가시킨다. synchronized를 남발하는 코드는 좋지 않다. 또한 임계 영역 수를 최대한 줄여야 한다.
올바른 종료 코드는 구현하기 어렵다
깔끔하게 종료하는 코드는 구현하기 어렵다. 가장 흔히 발생하는 문제가 데드락이다.
스레드 코드 테스트하기
문제를 노출하는 테스크 코드 작성, 프로그램 설정과 시스템 설정을 바꿔가면서 자주 테스트.
말이 안되는 실패는 잠정적인 스레드 문제로 취급하라
다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자.
다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라.
프로세서 수보다 많은 스레드를 테스트해봐라. : 스레드를 스와핑 할때도 문제가 발생한다.
강제로 실패를 일으켜 봐라.