일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 스프링MVC
- QueryDSL
- 스프링 데이터 JPA
- JPA 활용2
- 타임리프 문법
- JPQL
- JPA
- 스프링
- 실무활용
- jpa 활용
- 타임리프
- 스프링 mvc
- 일론머스크
- 트위터
- 임베디드 타입
- 예제 도메인 모델
- 불변 객체
- 값 타입 컬렉션
- 벌크 연산
- 검증 애노테이션
- 페이징
- 김영한
- 로그인
- Spring Data JPA
- JPA 활용 2
- 프로젝트 환경설정
- Bean Validation
- 컬렉션 조회 최적화
- API 개발 고급
- 기본문법
- Today
- Total
RE-Heat 개발자 일지
내 코드가 그렇게 이상한가요? - 7장 중첩을 제거하는 구조화 테크닉 본문
https://product.kyobobook.co.kr/detail/S000202521361
내 코드가 그렇게 이상한가요? | 센바 다이야 - 교보문고
내 코드가 그렇게 이상한가요? | 예약 판매부터 1만 부 돌파! 일본 아마존 IT 분야 베스트셀러 개발자가 직접 선정한 〈IT 엔지니어 도서 대상 2023〉 기술서 부문 대상 공감 100% 나쁜 코드 사례로
product.kyobobook.co.kr
제목 : 내코드가 그렇게 이상 한가요
저자 : 센바 다이야
옮긴이/역자 : 윤인성
이 책의 내용을 토대로 정리한 글입니다.

7장 컬렉션 : 중첩을 제거하는 구조화 테크닉
7.1 이미 존재하는 기능을 다시 구현하기
이미 널리 사용되는 기술과 해결법이 존재하면 기존 라이브러리를 쓰는 게 효율적이다.
=> 바퀴의 재발명을 피하자
코드 7.1 '감옥 열쇠 소지 여부 확인 코드'
boolean hasPrisonKey = false;
// items는 List<Item> 자료형
for (Item each : items) {
if (each.name.equals("감옥 열쇠")){
hasPrisonKey = true;
break;
}
}
코드 7.2 개선 코드
boolean hasPrisonKey = items.stream().anyMatch(
item -> item.name.equals("감옥 열쇠")
);
또 다른 예시 - 문자열 뒤집는 기능
public static String reverseString(String input) {
String reversed = "";
for (int i = input.length() - 1; i >= 0; i--) {
reversed += input.charAt(i);
}
return reversed;
}
문자열 뒤집는 기능 개선
public static String reverseString(String input) {
StringBuilder reversed = new StringBuilder(input);
return reversed.reverse().toString();
}
=> 반복문으로 바꾸는 대신 StringBuilder의 reverse 사용
7.2 반복 처리 내부의 조건 분기 중첩
① 조기 컨티뉴로 조건 분기 중첩 제거하기
반복문 내부의 조건 분기 중첩은 조기 리턴을 응요한 조기 컨티뉴로 해결할 수 있다.
■ 코드 개선과정
RPG에서 독 데미지를 받는 사양
for (Member member : members){
// HP가 0보다 큰지 확인
if ( 0 < member.hitPoint) {
//중독 상태인지 확이
if (member.containsState(StateType.poison)){
member.hitpoint -=10;
//HP -10 감소 후 음수거나 0이면 상태 변경
if (member.hitPoint<=0){
member.hitPoint = 0;
member.addState(StateType.dead);
member.removeState(StateType.poison);
}
}
}
}
for (Member member : members){
// HP가 0이면 죽은 상태이므로 다음 멤버로 넘어간다.
if (member.hitPoint == 0) continue;
// 독 상태가 아니면 다음 member로 넘어간다.
if (!member.containsState(StateType.poison)) continue;
// 독 데미지 -10
member.hitpoint -= 10;
// 독 데미지를 받은 후 살아있으면 다음 멤버로 넘어간다.
if (member.hitpoint > 0) continue;
// 음수가 나올 때를 대비해 HP를 0으로 맞춰주고 상태를 독 -> 죽음으로 변경
member.hitPoint = 0;
member.addState(StateType.dead);
member.removeState(StateType.poison);
}
② 조기 브레이크로 중첩 제거하기
break는 처리를 중단하고 반복문 전체를 벗어나는 제어구문.
▶ 구현할 코드 - 연계공격
- 첫 멤버부터 연계 공격 성공 여부 평가
- 연계 공격 성공 시 해당 멤버 공격력에 1.1배 추가
- 한 명이라도 연계 공격에 실패하면 후속 멤버 연계 평가 X
- 한 멤버의 추가 데미지(1.1배 적용된)가 30 이상이면 추가되는 데미지 총 데미지에 합산
- 한 멤버의 추가 데미지가 30 이하면 연계 실패
■ 코드 개선 과정
int totalDamage = 0;
for (Member member : members) {
// 연계 공격 성공 여부 확인
if (member.hasTeamAttackSucceeded()) {
// 1.1배 추가
int damage = (int)(member.attack() * 1.1);
// 연계공격 성공 데미지가 30 이상인지 여부 확인
if (damage >= 30) {
totalDamage += damage;
} else {
break;
}
} else {
break;
}
}
int totalDamage = 0;
for (Member member : members){
if (!member.hasTeamAttackSucceeded()) break;
int damage = (int)(member.attack() * 1.1);
if (damage < 30) break;
totalDamage += damage;
}
7.3 응집도가 낮은 컬렉션 처리

컬렉션 처리도 응집도가 낮아지기 쉽다.
예시
1. 멤버를 추가하는 기능(addMember)이 필드 맵 관련 클래스, 특별 이벤트 제어 클래스에 중복
2. 파티 멤버가 1명이라도 존재하는지 확인하는 기능 중복
class FieldManager {
// 멤버를 추가합니다.
void addMember(List<Member> members, Member newMember) {
if (members.stream().anyMatch(member -> member.id == newMember.id)) {
throw new RuntimeException("이미 존재하는 멤버입니다.");
}
if (members.size() == MAX MEMBER_COUNT){
throw new RuntimeException("이 이상 멤버를 추가할 수 없습니다.");
}
members.add(newMember);
}
// 파티 멤버가 1명이라도 존재하면 true를 리턴
boolean partyIsAlive(List<Member> members) {
return members.stream().anyMatch(member -> member.isAlive());
}
}
// 게임 중에 발생하는 특별 이벤트를 제어하는 클래스
class SpecialEventManager {
// 멤버를 추가합니다.
void addMember(List<Member> members, Member member) {
members.add(member)
}
}
// 전투를 제어하는 클래스
class BattleManager {
// 파티 멤버가 1명이라도 존재하는 경우 true를 리턴
boolean membersAreAlive(List<Member> members) {
boolean result = false;
for (Member each : members) {
if (each.isAlive()) {
result = true;
break;
}
return result;
}
}
}
① 컬렉션 처리를 캡슐화 하기
컬렉션과 관련된 응집도가 낮아지는 문제는 일급 컬렉션 패턴을 사용해 해결할 수 있다.
일급컬렉션(First Class Collection)이란 컬렉션과 관련된 로직을 캡슐화하는 디자인 패턴을 말한다.
클래스는 인스턴스 변수, 인스턴변수에 잘못된 값이 할당되지 않게 막고 정상적으로 조작하는 메서드가 필요하다.
따라서 일급 컬렉션은 다음과 같은 요소로 구성된다.
- 컬렉션 자료형의 인스턴스 변수
- 컬렉션 자료형의 인스턴스 변수에 잘못된 값이 할당되지 않게 막고, 정상적으로 조작하는 메서드
멤버 컬렉션 List<member>를 인스턴스 변수로 가지는 클래스 Party 작성
class Party {
static final int MAX_MEMBER_COUNT = 4;
private final List<Member> members;
Party() {
members = new ArrayList<Member>();
}
//Party의 List<Member>주입은 Party 클래스에만 하도록 제한
private Party(List<Member> members) {
this.members = members;
}
//멤버 추가하기
Party add(final Member newMember) { //부수효과 막기 위해 Party로 return
if (exists(newMember)) {
throw new RuntimeException("이미 파티에 참가돼 있습니다");
}
if (isFull()) {
throw new RuntimeException("이 이상 멤버를 추가할 수 없습니다");
}
// 새 인스턴스를 만들고 return해 기존 members 변수에 영향을 주지 않는다.(부수효과 방지)
final List<Member> adding = new Arraylist<>(members);
adding.add(newMember);
return new Party(adding);
}
// 파티 멤버 1명이라도 살아있는지 확인
boolean isAlive() {
return members.stream().anyMatch(each -> each.isAlive());
}
// 이미 멤버가 파티에소속돼 있는지 확인
boolean exists(Member member) {
return members.stream().anymatch(each -> each.id == member.id);
}
// 파티 인원이 최대인지 확인
boolean isFull() {
return members.size() == MAX_MEMBER_COUNT;
}
}
멤버 추가하기, 파티가 한 명이라도 살아있으면 true를 리턴하는 기능 등 로직을 모두 한 곳에 모아 클래스를 정의하면 응집도가 높은 설계가 가능하다.
② 외부로 전달할 때 컬렉션의 변경 막기
파티 멤버 전원의 상태 화면을 표시하는 기능을 만드려면 List<Member>에 접근해 전체 데이터를 참조할 수 있어야 한다. 하지만 인스턴스 변수를 외부로 전달하면 Party 클래스 외부에서 마음대로 멤버를 추가하거나 제거할 수 있다.
따라서 외부로 전달할 땐 컬렉션 요소를 변경하지 못하게 막아야 한다.
Java엔 unmodifiableList() 즉, 읽기 전용으로 리턴해 클래스 외부에서 마음대로 컬렉션을 조작하는 상황을 방지할 수 있다.
class Party {
List<Member> members() {
return members.unmodifiableList();
}
}