책 스터디 모임에서 마틴 파울러의 엔터프라이즈 애플리케이션 아키텍처 패턴
(항상 제목이 헷갈린다...ㅎㅎ)를 마무리 짓고 왕정의 디자인 패턴의 아름다움
이라는 책으로 스터디를 시작했다. 구글 출신 개발자라고 하여 작가에 대해서 찾아보려고 했는데 Linkedin도 없는 것 같고, 중국어로된 자료만 많이 나와서 찾을 수가 없었다.
제목에서는 마치 디자인 패턴에 대해서 이야기할 것 같지만 전반적인 목차를 보면 다양한 소재를 다룬다. 전반부에는 고품질 코드와 설계에 대한 이야기를 주로 다루고, 후반부에서야 디자인 패턴 내용을 다룬다. 아직 다 읽지는 않았지만 개발을 하면서 자연스럽게 마주할 수 있는 내용들에 대해서 정리를 정말 잘했다는 느낌을 많이 받았다. 코드 리뷰를 하면서 이게 왜 개선되어야 하는지 설명할 때 용어들을 잘 사용하는 것이 도움이 되는 것 같다. 책의 초반부에서는 용어에 대한 설명과 가벼운 사례들을 들어서 소개해주기 때문에 개념을 한번 정리하기에 너무 좋은 책이라는 생각이 든다.
취모구자: 터럭을 불어 헤쳐 잘 드러나지 않는 남의 허물까지 들춰낸다는 뜻
구글에서 근무할 때 코드 품질을 매우 중시했기 때문에 취모구자
수준 까지 달했다고 한다. 엄격한 품질 관리 덕분에 프로젝트 유지 관리 비용은 매우 낮아졌다고 합니다.
코드 설계에 대한 지식은 확장성과 가독성이 높아 유지 보수가 용이한 고품질의 코드를 작성할 때 필요합니다. 그런데 업무가 너무 과도하게 들어오다 보면 생각할 시간이 줄어듭니다. 명확한 가이드 라인이 없어서 일 수도 있지만, 어떤 코드가 고품질의 코드인지 알지 못해서 작성을 못할 수도 있습니다. 단순히 기능을 구현하고 사용 가능한 코드를 만드는 것은 복잡하지 않을 수 있지만, 사용하기 쉬운 코드를 작성하는 것은 쉽지 않습니다.
코드를 작성하는 시간보다 읽는 시간이 더 많습니다. 디자인 패턴과 설계 원칙에 대해서 확실히 이해하면서 코드가 설계된 이유를 한눈에 파악할 수 있다면 코드는 쉽게 읽힙니다.
코드 품질의 좋음과 나쁨을 판단하는 척도는 여러가지가 있습니다.
유연성, 확장성, 유지보수성, 가독성, 이해 용이성, 가변성, 재사용성,
테스트 용이성, 모듈성, 높은 응집도와 낮은 결합도, 높은 효율성, 고성능,
보안성, 호환성, 사용성, 명확성, 간결성 등
어떤 용어를 선택해야 품질을 잘 설명할 수 있을까요? 더 나아가 흑과 백으로 나누어 평가할 수 있는 척도인가요? 일반적으로 자주 사용되는 유지 보수성, 가독성, 확장성, 유연성, 간결성, 재사용성, 테스트 용이성에 대해서 살펴봅니다.
객체지향 프로그래밍
은 복잡한 설계 사상을 실현할 수 있으므로 다양한 설계 원칙과 디자인 패턴 코딩 구현의 기초가 됩니다.
설계 원칙
은 정신적인 방법이고 디자인 패턴은 움직임입니다. 설계 원칙에는 SOLID, KISS, YAGNI, DRY 등이 있습니다.
디자인 패턴
은 일부 설계 문제에 대해 요약된 솔루션 또는 설계 사상을 모아둔 것입니다.
코딩 규칙
을 참조하면 가독성이 높은 코드를 작성할 수 있습니다. 일관성을 유지하기 위해서 기본적인 기술이지만 능숙해야 합니다.
리팩터링
은 소프트웨어가 계속해서 업데이트 되기 때문에 만능의 설계는 존재하지 않습니다. 문제를 해결하기 위해서 리팩터링을 해야합니다. 해당 내용들은 코드의 품질을 유지하거나 개선하기 위한 방법론이고, 본질적으로 고품질 코드 작성에 기여합니다.
과도한 설계는 나중에 요구 사항이 변하지 않을 수도 있기 때문에 복잡성만 높이는 작업입니다. 코드 설계의 원래 의도는 코드 품질을 향상시키는 것입니다. 어떤 문제를 개선할 수 있는지를 생각하지 않으면 과도한 설계가 될 수 있습니다. 응집도를 높이고, 결합도를 낮추기 위해서 디자인 패턴을 적용하는 시도를 해볼 수 있습니다. 리팩터링을 하면서 과도한 설계를 방지할 수 있습니다. 특정 시나리오 외의 코드 설계에 대해서는 이야기하지 않습니다.
개인적으로 제가 잘 못하는 영역이 특정 시나리오에 한정 짓는 것 같습니다. 미래에 발생할 수 있을 상황들을 고려하여 가끔 시나리오에 벗어나는 경우에 대해서 논제거리를 던지는 경우가 있습니다. MVP를 만드는데 있어서 방해가 되는 요소라고 생각하여 읽으면서 반성하게 됐습니다.
객체지향 프로그래밍은 엄격하게 정의되어 있지 않습니다.
객체지향 프로그래밍 언어, 객체지향 분석, 객체지향 설계의 3단계를 진행합니다.
각 특성들이 존재하는 의미와 특성들을 통해 해결할 수 있는 문제를 함께 생각해야 합니다.
캡슐화
는 정보 은닉 또는 데이터 액세스 보호라고도 합니다. 외부 접근을 제한하도록 합니다. 클래스는 외부에 필요한 메서드만 노출을 시킵니다.
추상화
는 메서드의 내부 구현을 숨기는 것을 의미합니다. 메서드를 사용하는 순간 많이 숨겨지기 때문에 특이성이 그리 높지 않습니다. 추상화와 캡슐화는 인간이 복잡한 시스템을 체계적으로 관리하기 위한 수단으로 사용됩니다.
상속
의 가장 큰 역할은 코드의 재사용입니다.
다형성
은 코드의 확장성을 향상시킬 수 있습니다. 인터페이스 기반의 프로그래밍, DIP 등 장황한 if-else 구문을 제거하기 등 테크닉 코드의 기초가 됩니다.
객체 지향 분석은 요구사항을 분석하는 것입니다.
설계를 통해서 책임과 기능을 나누고 어떤 클래스가 있는지 확인합니다.
클래스 간의 상호 작용을 정의합니다.
작성된 명세를 기반으로 코딩을 시작합니다.
절차적 프로그래밍 스타일은 순서대로 실행되는 메서드들을 나열하여 데이터를 조작하고, 이를 통해 기능을 구현하는 프로그래밍 스타일
객체 지향은 대규모의 복잡한 프로그램 개발에 더 적합하다. 재사용, 확장, 유지관리가 쉽기 때문이다.캡슐화, 추상화, 상속, 다형성이 도움을 준다.
객체 지향이 사용자에 더 친화적이고 고급언어고 지능적이다.
함수형 프로그래밍 또한 엄격한 정의는 없다. 독특한 철학을 갖고 있다 일련의 수학적 함수, 또는 표현식의 조합으로 표현될 수 있다고 생각한다.
stateless function 임에 절차형과 다르다. 항상 같은 값을 반환한다.
예를 들어
//STATELESS_FUNCTION
int b;
int increase(int a){
return a + b;
}
//STATEFUL_FUNCTION
int increase(int a, int b){
return a + b;
}
꼭 함수형 프로그래밍언어야 함수형 프로그래밍을 할 수 있는 것은 아니다. Java에서도 가능하다. Strema, 람다 표현식, FunctionalInterface를 사용하면 된다.
객체지향 프로그래밍의 단위는 클래스 또는 객체,
절차적 프로그래밍의 단위는 함수,
함수형 프로그래밍의 단위는 Stateless Function 이다.
객체 지향 코드여도 본질적으로는 절차적 프로그래밍 스타일인 경우가 많다.
setter, getter를 남용하면 그렇게 보일 수 있다.
전역 변수와 전역 메서드를 남용하면 그렇게 된다.
Constants로 상수를 관리하면 코드 충돌 가능성이 높아지고, 컴파일 시간을 증가 시키고, 재사용성이 떨어진다. (컴파일 시간을 증가시키는 부분은 살짝 의문이다)
사용할 클래스가 본인의 상수를 알고 있는 것이 응집도와 코드의 재사용성이 높아진다.
Utils 클래스도 마찬가지다. 꼭 필요한지 질문을 던져보고 클래스에 책임을 줄 수 있다면 클래스 단위에서 풀어보자. 그렇다고 사용하지 말라는 것은 아니다.
객체 지향으로 구성을 하더라도 클래스의 메서드에 초점을 맞추면 절차적 프로그래밍 스타일을 가져간다. 어떤 방식을 채택하던 궁극적인 목표는 유지하기 쉽고, 읽기 쉽고, 재사용이 쉬우며, 확장하기 쉬운 고품질 코드를 작성하는 것이다.
DDD의 인기 이후 빈약한 도메인 모델을 기반으로 한 개발 방식이 비판받기 시작했다.
빈약한 모델에 기반한 전통적인 개발방식
해당 방식에서는 도메인 모델에 비즈니스 로직을 일절 넣지 않고 데이터만 관리한다. 비즈니스 로직은 전부 service에 존재한다.
풍성한 도메인 모델에 기반한 DDD 개발 방식
풍성한 도메인 모델은 데이터와 비즈니스 로직이 하나의 클래스에 포함된다. DDD를 잘 하기 위해서는 비즈니스에 친숙해져야한다.복잡한 로직들을 도메인 안에 녹여서 풀어낸다. 상대적으로 Service가 가볍게 구성된다.
그럼 풍부한 도메인 모델 방식에서 Service의 역할은 무엇인가?
1. 저장소 계층과의 통신을 담당한다.
2. 여러 도메인 모델의 비즈니스 논리를 결합한다.
이런 장점에도 불구하고 왜 빈약한 모델의 개발 방식을 많이 채택할까?
1. 단순하다.
2. 도메인에 로직을 넣을 경우 설계를 하기 까다롭다.
3. 관성적으로 기존 방식을 채택한다.
시스템이 복잡할수록 코드 재사용성과 유지 관리 용이성에 대한 요구사항이 높아지기 때문에 초기 설계에 시간과 에너지를 많이 투자해야한다.
인터페이스를 사용하여 객체지향 추상화, 다형석, 설계 원칙을 구현하고 추상 클래스를 사용하여 객체지향 상속과 템플릿 디자인 패턴을 구현하는 등의 작업을 수행할 수 있다.
추상 클래스는 보통 코드 재사용을 위해서 사용된다.
인터페이스는 디커플링에 초점을 맞추고 있다.
is-a 관계를 나타내고자 하고 코드 재사용 문제를 해결하려면 추상 클래스를,
has-a 관계를 나타내고 추상화 문제를 해결하려면 인터페이스를 사용하면 된다.