inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

워밍업 클럽 4기 BE - 1주차 발자국

민수킴
1

1주차 미션 회고

강의에서 배운 내용을 내 머릿속에 있는 단어들로 재해석하니 개념들이 확실히 잡히는 느낌이었다.

AS-IS 코드에 챕터 별 내용을 하나씩 적용해가면서 리팩토링을 하니 어느새 클린 코드가 되어 있어서 재밌는 경험이었다.

 

1주차 강의 회고

생각보다 긴 강의 시간에 조금 지치기도 했지만 실습 위주의 커리큘럼으로 집중해서 진행할 수 있었다.

요즘 업무가 바쁘다는 핑계로 코드를 작성할 때 이런 개념들을 잠시 등한시 했던 나를 반성하게 된다.

클린 코드 핸드북을 목표로 핵심만 정리해서 추후 코드 작성 시 매번 참고하는 자료가 되도록 만들어야겠다.

 

1주차 학습 내용 요약

추상화


추상(抽象) : 중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버린다.

이름 짓기


메서드 추상화


void 서점에서_책을_샀다() {
		우빈이는 산책을 하다 은행에 가서 얼마 인출했다.
		서점 가는 길에 아이스크림을 하나 사먹었다.
		남은 돈으로 서점에 가서 보고싶은 책을 고르고, 책을 구매했다.
}
void 산책하면서_돈쓰기() {
		Money 은행에서_현금_인출();
		Balance 아이스크림_사먹기(Money);
		Book 서점에서_책_구입하기(Balance);
}

잘 쓰여진 코드라면, 한 메서드의 주제는 반드시 하나다.

추상화 레벨


public static void main(String[] args) {
		showGameStartComments(); **// 10 (추상화 레벨)**
		initializeGame(); **// 10**
		showBoard(); **// 10**
		
		if(gameStatus == 1) { **// 5 (갑자기 너무 구체적인 내용)**
				...
		}
}

매직 넘버, 매직 스트링


private static String[][] board = new String[8][10];
public static final int BOARD_ROW_SIZE = 8;
public static final int BOARD_COL_SIZE = 10;

private static String[][] board = new String[BOARD_ROW_SIZE][BOARD_COL_SIZE];

메서드 선언부


// bad
String createDailyShopKey(String shopId, String localDateString);

// good
String createDailyShopKey(String shopId, LocalDate sellingDate);

Early return


if(a > 3) {
		doSomething1();
} else if(a <= 3 && b > 1) {
		doSomething2();
} else {
		doSomething3();
}
void extracted() {
		if(a > 3) {
				doSomething1();
				return;
		}
		if(a <= 3 && b > 1) {
				doSomething2();
				return;
		}
		doSomething3();
}

중첩문 줄이기


for(int i=0; i<20; i++) {
		for(int j=20; j<30; j++) {
				if(i>=10 && j<25) {
						doSomething();
				}
		}
}

사용할 변수는 최대한 가깝게 선언하기


int i = 10;
// 코드 20줄
...

int j = i + 30; // 해당 코드에서 i의 값을 다시 기억해내야 한다.
int i = 10;
int j = i + 30;

부정어를 대하는 자세


Null을 대하는 자세


Optional

public T orElse(T other) {
		return value != null ? value : other;
}

public T orElseGet(Supplier<? extends T> supplier) {
		return value != null ? value : supplier.get();
}

// ================================================================================ //
Optional<String> value = Optional.of("Hello");

// ⚠️ expensiveOperation() 항상 호출됨
String result = value.orElse(expensiveOperation()); 

// ✅ 값이 비어있을 때만 expensiveOperation() 호출됨
String result = value.orElseGet(() -> expensiveOperation()); 

상속 대신 조합 사용하기


public abstract class Cell {
		protected boolean isOpened;
		protected boolean isFlagged;
		
		public abstract String getSign();
		...
}

public class EmptyCell extends Cell {
		...
    @Override
    public String getSign() {
        if (isOpened) {
            return EMPTY_SIGN;
        }
        if (isFlagged) {
            return FLAG_SIGN;
        }
        return UNCHECKED_SIGN;
    }
}
public class CellState {
    private boolean isFlagged;
    private boolean isOpened;
    
    public static CellState initialize() {
        return new CellState(false, false);
    }
    
    public boolean isOpened() {
        return isOpened;
    }

    public boolean isFlagged() {
        return isFlagged;
    }
}

public class EmptyCell {
		private final CellState cellState = CellState.initialize();
}

Value Object


class Address {
		private String 시도;
		private String 시군구;
		private String 도로명;
		
		// 생성자 외에 상태 변경 메서드 없음 (불변성)
		public Address(String 시도, String 시군구, String 도로명) {
		
				// 유효성 검증
			  if (시도== null || 시도.trim().isEmpty()) {
            throw new IllegalArgumentException(fieldName + "는 비어 있을 수 없습니다.");
        }
        if(...)
        
        this.시도 = 시도;
        this.시군구 = 시군구;
        this.도로명 = 도로명;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Address)) return false;
        
        // 동등성 검증
        Address that = (Address) o;
        return Objects.equals(시도, that.시도) &&
               Objects.equals(시군구, that.시군구) &&
               Objects.equals(도로명, that.도로명);
    }

    @Override
    public int hashCode() {
        return Objects.hash(시도, 시군구, 도로명);
    }
}

vs Entity

class UserAccount {
		private String userId; // 식별자
		private String 이름;
		private String 생년월일;
}

일급 컬렉션


class CreditCards {
		private final List<CreditCard> cards;
		
		public List<CreditCard> findValidCards() {
				return this.cards.stream()
						.filter(CreditCard::isValid)
						.toList();
		}
}

Enum의 특성과 활용


public enum UserAction {
		OPEN("셀 열기"),
		FLAG("깃발 꽂기"),
		UNKNOWN("알 수 없음");
		
		private final String description;
		
		UserAction(String description) {
				this.description = description;
		}
}

다형성 활용하기


private String decideCellSignFrom(CellSnapshot snapshot) {
    CellSnapshotStatus status = snapshot.getStatus();
    if (status == CellSnapshotStatus.EMPTY) {
        return EMPTY_SIGN;
    }
    if (status == CellSnapshotStatus.FLAG) {
        return FLAG_SIGN;
    }
    if (status == CellSnapshotStatus.LAND_MINE) {
        return LAND_MINE_SIGN;
    }
    if (status == CellSnapshotStatus.NUMBER) {
        return String.valueOf(snapshot.getNearbyLandMineCount());
    }
    if (status == CellSnapshotStatus.UNCHECKED) {
        return UNCHECKED_SIGN;
    }
    throw new IllegalArgumentException("확인할 수 없는 셀입니다.");
}
private String decideCellSignFrom(CellSnapshot snapshot) {
		List<CellSignProvidable> cellSignProviders = List.of(
				new EmptyCellSignProvicer(),
				new FlagCellSignProvider(),
				...
		);
		
		return cellSignProviders.stream()
				.filter(provider -> provider.supports(snapshot))
				.findFirst()
				.map(provider -> provider.provide(snapshot))
				.orElseThrow(() -> new IllegalArgumentException("확인할 수 없는 셀입니다."));
}
public interface CellSignProvidable {
		boolean supports(CellSnapshot cellSnapshot);
		String provide(CellSnapshot cellSnapshot);
}
public class EmptyCellSignProvicer implements CellSignProvidable {
		private static final String EMPTY_SIGN = "■";
		
		@Override 
		public boolean supports(CellSnapshot cellSnapshot) {
				return cellSnapshot.getStatus() == CellSnapshotStatus.EMPTY;
		}

		@Override
		public String provice(CellSnapshot cellSnapshot) {
				return EMPTY_SIGN;		
		}
} 

답변 0