자바에는 anyMatch()처럼 조건을 만족하는 요소가 컬렉션 내부에 하나라도 포함되는 경우 true를 리턴하는 메서드가 존재한다고 함 하지만, 이를 모르고
for ( ~ ) {
if ( ~ )
}
이렇게 메서드 하나면 될 것을 반복문과 조건문을 사용하게 된다면 코드가 복잡해 지고 실수로인한 버그가 발생할 가능성도 있음
동일한 기능으로는 자바스크립트에서는 anyMatch가 아닌 some이 존재!
표준 컬렉션 라이브러리에는 다양하고 편리한 메서드가 많기 때문에 표준 라이브러리를 확인하여 불필요한 작업을 한 게 아닌지 확인해야한다!
바퀴의 재발명
이미 널리 사용되고 있는 기술과 해결법이 존재하는데도 이를 전혀 모르거나 의도적으로 무시하고 비슷한 것을 만들어내는 것을 의미함
하지만 모든 상황에서 '바퀴의 재발명'이 절대 안 된다고는 할 수 없고 학습이 목적이라면 굳이 바퀴를 재발명 해보는 것도 좋은 방법
조기 리턴을 응용하여 조기 컨티뉴를 활용하여 반복문 내부의 조건 분기 중첩을 해소할 수 있음
continue(실행하고 있는 처리를 건너뛰고 다음 반복문으로 넘어가는 제어문)
for () {
if ( ~ ) {
if ( ~ ) {
~
if ( ~ ) {
~
}
}
}
}
만약 위와 같은 반복문 속 3중의 if문이 존재한다면 위를 다음과 같이 바꿀 수 있음
for () {
// 살아 있지 않다면 continue로 다시 한번 반복을 진행
// 조건 continue로 변경하려면 조건을 반전해야함
if (if의 조건 반전) continue
if ( ~ ) continue
원하던 로직
if ( ~ ) continue
원하던 조건을 만족할 때 행할 로직 (반복문을 종료할 때 쓸)
}
이러한 변경을 통해 코드의 가독성을 높이고 어디까지 실행되는지 continue로 쉽게 확인할 수 있어 이해하기도 쉬워짐
이와 마찬가지로 break을 활용하여 조기 브레이크를 구현할 수 있음
조기 리턴, 조기 컨티뉴처럼 조건을 반전하고 브레이크 하면 됨
--> 난 개인적으로 조기 브레이크가 더 명확한 뜻을 가지고 있다고 생각함
컬렉션에서도 응집도가 낮아지는 경우가 생길 수 있고 코드가 중복되는 경우가 생길 수도 있음
이렇게 컬렉션과 관련된 처리를 하는 코드가 여기저기 구현되어서 응집도가 낮아질 땐 컬렉션 처리를 캡슐화하여 해결할 수 있음
7.3.1 컬렉션 처리를 캡슐화 하기
컬렉션과 관련된 응집도가 낮아지는 문제는 '일급 컬렉션 패턴'을 사용해 해결할 수 있음
일급 컬렉션 (First Class Collection)은 컬렉션과 관련된 로직을 캡슐화하는 디자인 패턴
클래스에는 다음 두 가지가 존재해야함
그렇다면 일급 컬렉션은 다음과 같은 요소로 구성된다고 할 수 있음
게임의 멤버 집합인 '파티'가 존재한다면
class Party {
void add (~) {
members.add(새로운동료!)
}
}
파티의 인원을 추가하는 add라는 메서드가 존재할 때 기존에 추가를 한다면
기존 members의 요소가 변화하는 부수 효과가 발생
부수 효과를 막기 위해 새로운 리스트를 생성하고 그곳에 요소를 추가하는 형태로 구현하여 새로운 Part 클래스의 인스턴스를 리턴하면 됨
class Party {
List<Member> adding = new ArrayList<>(member);
adding.add(새로운동료!)
return new Part(adding)
}
이외로도 리스트 조작에 필요한 로직을 모두 한 곳에 모아 클래스를 정의하면 됨
외부로 전달할 땐 컬렉션의 변경을 막는 것이 좋음 자바에서는 unmodifiableList메서드를 활용하곤 함
결합도
결합도란 '모듈 사이의 의존도를 나타내는 지표'라고 할 수 있음
응집도와 마찬가지로 여기에서도 '모듈'은 클래스를 나타낸다고 생각한다면, '클래스 사이의 의존도를 나타내는 지표'를 결합도라고도 할 수 있음
어떤 클래스가 다른 클래스에 많이 의존하는 구조를 강한 결합이라고 하며 이해하기도, 변경하기도 굉장히 힘듦 그래서 느슨한결합 구조를 가져야 코드 변경이 쉬움
온라인 쇼핑몰에
class DiscountManager {
// 일반 할인
static int getDiscountPrice(int price) {
- 3천원이 할인됨
}
// 할인 가격 계산 로직
// 할인 적용 여부 판단 로직
// 총액 상한 확인 로직
}
다음과 같은 여름 할인 서비스가 추가 되었음
class SummerDiscountManager {
// 여름 할인
DiscountManager discountManager;
DisCount.getDiscountPrice(product.price)
}
여름 할인
이런 상황속에서 언제부턴가 일반 할인이 4천원 할인으로 변경이 됨
그럼 당연하게도 여름 할인은 일반 할인의 getDiscountPrice를 사용하기 때문에 여름 할인도 4천원이 되어버림
이 외에도 일반 할인이 담긴 DiscountManager에는 여러가지 로직이 포함됨
이럴 땐 로직의 위치 자체에 문제가 있음
한 클래스에 처리해야할 작업이 집중되어 있기도 하지만 요름 할인처럼 별 할일이 없는 애도 존재하고 편의를 위해 다른 클래스의 메서드를 무리하게 활용함
이런 클래스 설계가 바로 책무를 고려하지 않은 설계라고 할 수 있음
8.1.3 단일 책임 원칙
단일 책임 원칙은 '클래스가 담당하는 책임은 하나로 제한해야 한다.'는 원칙인데
위 코드의 DiscountManager.getDiscountPrice는 일반 할인을 책임지기 위한 것이지 여름 할인을 책임지기 위해 만들어진 것이 아님
따라서 둘 모두는 단일 책임 원칙을 위반하는 것이라 볼 수 있음
상품명과 가격이 타당한지 판단하는 책임은 Product가 가져야 하지만 DiscountManager는 다른 클래스에게 책임을 지우지 않고 무엇이든 대신 해주는 과보호하는 부모라고 할 수 있음
이처럼 책임을 대신 지는 클래스가 만들어지면 다른 클래스가 제대로 성장할 수 없음
그렇다면 위에 코드를 단일 책임을 지키도록 설계하려면 다음과 같이 하면 됨
class RegularPrice {
// 가격을 나타내는 클래스
// 가격과 관련된 책임을 지는 클래스 구조
}
class RegularDiscountedPrice {
// 일반 할인 가격
RegularDiscountedPrice (final RegularPrice price)
~ int DISCOUNT_AMOUND = 4000
}
class SummerDiscountedPrice {
// 일반 할인 가격
~ int DISCOUNT_AMOUND = 3000
}
클래스가 일반 할인 가격, 여름 할인 가격으로 구분되어 있고 할인과 관련된 사양이 변경되어도 서로 영향을 주지 않음
이렇듯 관심사에 따라 분리해서 독립되어 있는 구조를 느슨한 결합이라고 부름
8.1.6 DRY 원칙의 잘못된 적용
DRY원칙은 Don`t Repeat yourself란 뜻으로 '반복을 피해라'라는 의미이지만 무조건 '코드 중복을 절대 허용하지 말라'가 아닌 '모든 지식은 시스템 내에서 단 한 번만, 애매하지 않고, 권이 있게 표현되어야 한다'라는 뜻을 가지고 있기도 함
이 이유는 위에 두 개의 거의 동일한 로직이 존재하기에 왜 중복된 코드가 있을까? 라고 생각할 수 있기 때문 결론적으로는 DRY는 각각의 개념 단위 내에서 반복을 하지 말라는 의미로, 같은 로직 비슷한 로직이라도 개념이 다르면 중복을 허용해야 함
개념적으로 다른 것까지도 무리하게 중복을 제거하려하면, 강한 결합 상태가 되어 단일 책임 원칙이 깨지게 됨
8.2.1 상속과 관련된 강한 결합
상속은 주의해서 다루지 않으면 곧바로 강한 결합 구조를 유발함
슈퍼 클래스 의존
상속 관계에서 서브 클래스는 슈퍼 클래스에 굉장히 크게 의존하기 때문에, 슈퍼 클래스의 변화를 놓치는 순간 서브 클래스에는 문제가 생길 수 있음
다음과 같은 클래스가 있다고 하면
class PhysicalAttack {
int singleAttck() {
~
}
int doubleAttack() {
~
}
}
class FighterPhysicalAttack extends PhysicalAttack {
@Override
int singleAttck() {
~
}
@Override
int doubleAttack() {
~
}
}
추후 이 클래스를 extends하여 오버라이드한 클래스가 존재할 때 초기에는 문제가 없지만 나중엔 서브 클래스가 슈퍼클래스에 의존하기 때문에 의도치 않은 동작으로 동작할 수 있음
그렇기 때문에 슈퍼 클래스 의존으로 인한 강한 결합을 피하려면, 상속보다 컴포지션을 사용하는 것이 좋음
class FigtherPhysicalAttack {
// 추가된 인스턴스 변수 (컴포지션)
private final PhysicalAttack physicalAttack;
int singleAttack() {
한 대 때림
}
int double() {
두 대 때림 혹은 세게 때림
}
}
컴포지션을 활용한다면 PhyisicalAttack의 로직을 변경하더라도 FigtherPhysicalAttack이 받는 영향을 줄일 수 있음
8.2.2 인스턴스 변수 별로 클래스 분할이 가능한 로직
util이라는 클래스에 예약취소, 다크모드, 메일 보내기 등 여러 메서드가 모두 들어있다면
class util {
예약취소 인스턴스 변수
다크모드 인스턴스 변수
메일 보내기 인스턴스 변수
void 예약취소()
void 다크모드()
void 메일보내기()
}
각각의 인슽턴스 변수가 일대일임을 확인하여 3개의 클래스로 분리해, 강한 결합 문제를 해결할 수 있음
class 예약취소 {
}
class 다크모드 {
}
class 예약취소 {
}
하지만 실제 제품 코드에서는 클래스 간 의존 관계가 훨씬 복잡하기 때문에 클래스를 잘 분리하려면 각각의 인스턴스 변수와 메서드가 무엇과 관련있는지 잘 파악하는 것이 중요
8.2.5 높은 응집도를 오해해서 생기는 강한 결합
관련이 깊은 데이터와 논리를 한 곳에 모은 구조를 응집도가 높은 구조라고 하는데 이런 점을 잘못 이해하여 강한 결합이 발생하는 경우도 존재함
sellingPrice라는 클래스 속에 판매 수수료 계산, 배송비 계산, 쇼핑 포인트 계산을 하는 메서드 들이 들어있고 일부 사람들은 판매가격과 비슷한 개념들로 여러 개념들을 이해하여 넣었을 테지만, '판매가격'이란 클래스에 판매 가격과 관련없는 개념들이 섞여있으므로 강한 결합에 해당함
결론적으로, 응집도가 높다는 개념을 인지하고 있는 상태에서 관련이 깊다고 생각되는 로직을 한 곳에 모으려고 했지만 결과적으론 강한 결합 구조를 만드는 상황이 자주 발생
8.2.6 스마트 UI
화면 표시를 담당하는 클래스 중에서 화면 표시와 직접적인 관련이 없는 책무가 구현되어 있는 클래스를 스마트 UI라고 함
기능은 같게 유지하며 디자인을 완전히 바꾸려고 한다면 복잡한 금액 계산 로직이 프론트 코드에 섞여 있으므로, 디자인을 변경하다 보면 기존의 것이 동작하지 않거나 버그가 발생하기 쉬움
그렇기 때문에 이들은 서로 다른 클래스로 분할하는 것이 좋음
MVVM UI패턴
모델, 뷰, 뷰모델
Model : 데이터의 구조를 표현하고, 데이터의 전달과 처리를 담당하는 영역
View : UI를 표현, UI를 통한 인터랙션, ViewModel과의 느슨한 결합을 통한 데이터 표현
View Model : View와 더불어 View Model과 데이터 바인딩을 통해 상호 상태 변경을 인식하고 자동 갱신
8.2.7 거대 데이터 클래스
데이터 클래스가 수 많은 인스턴스 변수를 가지면, '데이터를 편리하게 운반하는 역할'로 인식하고 데이터를 계속 추가하기 쉽기 때문에 데이터 클래스가 거대해지고 데이터 클래스보다 훨씬 더 많은 악마를 불러들일 수 있음
특정 유스케이스와 관련 없는 데이터에 접근하고 그 데이터를 사용하게 되면, 부주의한 변경으로 인해서 버그가 발생할 가능성이 높아짐
그러다가 결국 전역 변수와 같은 성질을 띄게 되므로 주의하는 것이 좋음
8.2.9 갓 클래스
트랜잭션 스크립트 패턴(메서드 내부에 일련의 처리가 하나하나 길게 작성되어 있는 구조)엑서 한 단계 더 나아가면 갓 클래스가 됨
갓 클래스
하나의 클래스 내부에 수천에서 수만 줄의 로직을 담고, 수많은 책임을 담당하는 로직이 난잡하게 섞여있는 클래스
어떤 로직과 관련 있는지 책무를 파악하기 힘들고 수천 수만 줄의 로직을 읽어야 하기 때문에 '신'이라는 이름과 다르게 온갖 악마들의 소굴이자 강한 결합의 화신
이러다보면 개발이라는 작업이 버그를 수정하고, 또 누락된 부분을 다시 수정하는 과정을 반복하는 단순 노동으로 변질되어 버림
더군다나 운 좋게 발견되지 않은 버그가 있다면 릴리스 후에 문제를 일으켜 실질적인 손실을 줌
8.2.10 강한 결합 클래스 대처 방법
거대 데이터 클래스, 트랜잭션 스크립트 패턴, 갓 클래스에 대처하는 방법은 모두 같음
객체 지향 설게와 단일 책임 원칙에 따라 제대로 설계하는 것
단일 책임 원칙에 따라 설계된 클래스는 아무리 많아도 200줄, 100줄이기 때문에 조기 리턴, 전략 패턴, 일급 컬렉션 같은 방법이 도움이 됨