알고리즘, CS

[특강] 클린코드_조건과 탈출

차돌박이츄베릅 2023. 8. 4. 15:50

1. 긍정 조건 사용하기

//bad
const isNotBoy = false;

//good 
const isBoy = true;

// ! 표기가 잘보이지 않고 부정의 부정이라 조건문을 잘못 해석할 여지가 다분함
if(!isNotBoy){} 
if(isBoy){}

// 꼭 부정문을 사용해야할때는??
const isBoy = !isNotBoy // if 문 바로 상단에서 긍정 조건으로 변경해주기!
if(isBoy){}

 

2. 조건문에 이름 붙이기

중요한건 어떤 조건들이 있느냐의 나열 (X) -> 그래서 어떤 조건인데? (O)

//bad
if(name === 'js' 
	&& obj.someProperty.title === "sparta"
	&& height < 180) {...}

//good 
const isJsTutor = name === 'js' && obj.someProperty.title === "sparta"
	|| height < 180;
if(isJsTutor) {...}

 

3. 함수를 통해서 조건을 사용하자

만약 아주 복잡한 조건문에 맞닥드렸다면?

그리고 이 조건이 언제든 변경의 여지가 남아있다면?

//bad
if(name === 'js' 
	&& obj.someProperty.title === "sparta"
	&& height < 180
	|| lastName === 'lee') {...} // 근데 다음에 몸무게도 추가될지도 몰라요~

//good 
const checkThisDataValid = ({name, title, hegiht, lastName})=>{
	let result = true;
	... // 비지니스 로직

	return result;
}

if(checkThisDataValid()) {...}

 

4. 조건문의 성능은 생각하지 말자

  • 조건문을 압축하지 말기. 성능상 티도 나지 않는데, 압축된 조건문을 다시 해석하느라 많은 시간 허비하게 됨
  • 조건문을 최적화하려고 하기보다는 이름을 명확하게 지어줌으로써 보기 좋고 읽기 편하게 변경하자!

 

5. 조건, 반복 depth 지옥

if문을 작성할 때, 가장 주의할 점은 깊이(Deep)! 

이중(if 안에 if), 삼중, N 중 중첩문을 가장 기피해야 하고, 최대한 깊이가 1인 분기문을 작성하는 것이 좋다.

 

5-1) Guard Clause(=early return)

사전 조건이 판별하여 거짓이라면 예외 혹은 반환 처리하여 더 이상 다음 단계가 실행되지 않도록 사전에 차단하는 방식.

// bad
async function guardClause(userId) {
    const user = await getUser(userId);
    if (user) {
        const orders = await getOrders(user);        
        if (orders && orders.length > 0) {
            const order = orders.find(order => order.value > 100);
            if (order) {
                // 실제 처리할 로직
                console.log(`Found an order from user ${user.id} with value greater than 100: ${order.id}`);
            }
        }
    }
}

// good
async function guardClause(userId) {
    const user = await getUser(userId);
    if (!user) return;
    
    const orders = await getOrders(user);
    if (!orders || orders.length === 0) return;

    const order = orders.find(order => order.value > 100);
    if (!order) return;
    
    // 실제 처리할 로직
    console.log(`Found an order from user ${user.id} with value greater than 100: ${order.id}`);
}

 

5-2) object mapping

function loginWithKakao() {}
function loginWithGoogle() {}
function loginWithApple() {}

// switch
const socialLogin = (social) => {
  switch (social) {
    case "kakao":
      loginWithKakao();
    case "google":
      loginWithGoogle();
    case "apple":
      loginWithApple();
  }
};

//obj mapping
const socialMapper = {
  kakao: loginWithKakao,
  google: loginWithGoogle,
  apple: loginWithApple,
};

const socialLogin = (social) => {
  socialMapper[social]();
};

 

 


 

early return 주의할 점 => 정확하게 탈출하기!

  • Early return은 먼저 리턴을 시켜버림으로써 함수의 흐름을 분산시킬 수 있기 때문에, 함수의 일관성을 해칠 수 있습니다.
    즉, 함수를 읽는 사람이 어떤 조건에서 반환이 일어나는지를 정확히 이해하고 추적할 수 있어야 합니다.
  • 명확하게 종료되지 않는다는 의미를 내포한다.
    말 그대로 'early' return 이기때문에 뒤에 무언가 새로운 비지니스 로직이 있다는 암시가 될 수 있습니다.
    단순히 return; 만 작성할 경우 함수는 원하는 값이 아닌 undefined 을 반환하게 됩니다.
    반환값이 없는 void 함수일때는 문제가 없겠지만 필요한 반환값이 존재할 경우 원치 않는 undefined 을 반환하게 되고 만약 undefined check 를 외부에서 하지 않으면 오류가 발생하겠죠?

 

initial value 초기값을 할당해 주는 방법

이렇게 명확한 초기값을 할당해주게 되면 반환값이 꼭 필요한 함수의 경우 만약 내부 조건문이 잘못되어서 이상한 값을 받는다 하더라도 undefined 로 부터 초래되는 문제는 방지할 수 있겠죠?

function calculateTotalPrice(items) {
    // 초기값 설정
    let totalPrice = 0;

    if (!items || items.length === 0) {
        return totalPrice;
    }

    items.forEach(item => {
        totalPrice += item.price * item.quantity;
    });

    return totalPrice;
}