본문 바로가기

컴퓨터공학/기타

CleanCode> Control Structure

nested structure를 피하자 

 

User guards & Fail fast

조건문을 첫 줄부터 걸어서 해당 안되면 미리 빠지게 하는 장치

조건문이 Guard고 return값이 Fail fast이다. 

 

예시

 

이렇게 하면 Guard & Fail Fast가 적용된 코드이다. 입구컷을 일찍 시키는 것

근데 보면 Abstraction level 차이가 나는 걸 볼 수있다.

 

 

isEmpty 와 showErrorMessage function을 만들어서 level을 맞춰줬다.

 

 

processTransactions 메소드 아래에 isEmpty, showErrorMessage, processTransaction으로 구성하여 간단하게 만들었다.

결과값은 Positive하게 나오도록 함수를 설계하자.

processTransaction이 이름이 비슷하여 혼동할 수 있지만 여기서는 테스트이니 넘어간다.

processTransaction 내용이 아직 너저분한데 이걸 바꾸자.

 

 

 

processTransaction 함수에서 조건문을 함수화했고 payment와 refund도 함수화해서 가독성을 올림

Refund 함수는 Payment와 유사해서 생략함

Payment 처리 함수인 processPayment함수도 여전히 abastraction level 의 조화가 이루어지지 않았다.

물질을 더 이상 쪼개지지 않을 때까지 쪼개는 것처럼 함수도 마찬가지이다. 

 

함수로 만들어서 조건문에 넣는다. 하지만 useCreditCard, usePayPal, usePlan 자주 쓰이는 것 같다. 

 

useTransaction 하나로 통합한다.

각 결제 수단 함수 안에다가 payment인지 refund인지 판별하는 조건문을 넣는 방식이다.

이전보다 좀 더 간결하다. 

 

 

 

이것은 전체 코드 내용

 

Error code handling 

 

메인함수를 작성하고 dictionary 배열을 변수에 담아서 processTransactions 함수에 주입한다.

만약 값이 비어있으면 비어있다는 dict를 만들고 반환한다.

메인함수에서는 코드를 읽어서 에러메시지를 표현한다.

이런 프로세스로 진행한다.

 

Error객체를 throw하는 방법

main 함수> processTransactions함수

이니 main에서 try catch를 감싸고 processTransactions에서 throw한다.

비어 있으면 작동 안한다.

 

main > processTransactions > processTransaction  순서

processTransaction에서 throw한다. 

 

 

error guard를 따로 함수로 만들어서 수정.

validateTransaction에서 throw하면

processTransaction에서 던진 것이고 processTransactions에서 받는다.

 

validateTransactions에서 throw하면

processTransactions에서 던진 것이고 main함수에서 받는다.

 

function main(){
	const transactions = [
		{
			id : 't1',
			type : 'OPEN',
			method : 'CREDIT_CARD',
			amount : '23.99'
		},
        {
			id : 't2',
			type : 'OPEN',
			method : 'PAYPAL',
			amount : '100.43'
		}
	]
    try{ 
    	processTransactions(transactions); 
    } catch(error){
    	showErrorMessage(error.message);
    }
}
function processTransactions(transactions) {
	validateTransactions(transactions);   
    for (const transaction of transactions){
    	try{
    		processTransaction(transaction);
    	} catch(error){
    		showErrorMessage(error.message, error.item);
    	}
    }
}
function validateTransactions(transactions){
	if(isEmpty(transactions)){
		const error = new Error('No transactions provided!');
        error.code = 1;
        throw error;
	}
}
function isEmpty(transactions){
	if(!transctions || transactions.length === 0){
    	return True
	}
}
function showErrorMessage(message, item){
	console.log(message);
    if (item) {
    console.log(item);
    }
}
function processTransaction(transaction){
	validateTransaction(transaction);
   	if(useTransactionMethod(transaction, 'CREDIT_CARD')){
    	processCreditCardTransaction(transaction);
    } else if(useTransactionMethod(transaction, 'PAYPAL')){
    	processPayPalTransaction(transaction);
    } else if(useTransactionMethod(transaction, 'PLAN')){
    	processPlanTransaction(transaction);
    }
}
function validateTransaction(transaction){
	if(!isOpen(transaction)) {
    	const error = new Error('Invalid transaction type1');
        throw error;
    }
    if(!isPayment(transaction)) {
      	const error = new Error('Invalid transaction type!');
        error.item = transaction;
        throw error;
    }	
}
function isOpen(transaction){
	return transaction.status ==='OPEN';
}
function isPayment(transaction){
	return transaction.type === 'PAYMENT';
}
function isRefund(transaction){
	return transaction.type === 'REFUND';
}
function useTransactionMethod(transaction, method){
	return transaction.method === method;
}
function processCreditCardTransaction(transaction){
	if (isPayment(transaction)){
    	processCreditCardPayment();
    } else if (isRefund(transaction)){
    	processCreditCardRefund();
    }
   
}
function processPayPalTransaction(transaction){
	if (isPayment(transaction)){
    	processPayPalPayment();
    } else if (isRefund(transaction)){
    	processPayPalRefund();
    } 
}
function processPlanTransaction(transaction){
	if (isPayment(transaction)){
    	processPlanPayment();
    } else if (isRefund(transaction)){
    	processPlanRefund();
    }
}

전체 코드

 

위 코드에서는

validateTransaction에서 error를 던지면 processTransaction을 넘어 processTransactions에서 try catch문으로 받았는데

validateTransaction에서 던지면 processTransaction에서 받도록 변경하자.

 

 

 

 

전체코드

function main(){
	const transactions = [
		{
			id : 't1',
			type : 'OPEN',
			method : 'CREDIT_CARD',
			amount : '23.99'
		},
        {
			id : 't2',
			type : 'OPEN',
			method : 'PAYPAL',
			amount : '100.43'
		}
	]
    try{ 
    	processTransactions(transactions); 
    } catch(error){
    	showErrorMessage(error.message);
    }
}
function processTransactions(transactions) {
	validateTransactions(transactions);   
    for (const transaction of transactions){
    	processTransaction(transaction);
    }
}
function validateTransactions(transactions){
	if(isEmpty(transactions)){
		const error = new Error('No transactions provided!');
        error.code = 1;
        throw error;
	}
}
function isEmpty(transactions){
	if(!transctions || transactions.length === 0){
    	return True
	}
}
function showErrorMessage(message, item){
	console.log(message);
    if (item) {
    console.log(item);
    }
}
function processTransaction(transaction){
	try{
		validateTransaction(transaction);
        processByMethod(transaction);
   	} catch(error){
    	showErrorMessage(error.message, error.item);
    }
}
function validateTransaction(transaction){
	if(!isOpen(transaction)) {
    	const error = new Error('Invalid transaction type1');
        throw error;
    }
    if(!isPayment(transaction)) {
      	const error = new Error('Invalid transaction type!');
        error.item = transaction;
        throw error;
    }	
}
function processByMethod(transaction){
		if(useTransactionMethod(transaction, 'CREDIT_CARD')){
    		processCreditCardTransaction(transaction);
    	} else if(useTransactionMethod(transaction, 'PAYPAL')){
    		processPayPalTransaction(transaction);
    	} else if(useTransactionMethod(transaction, 'PLAN')){
    		processPlanTransaction(transaction);
    	}				
}
function isOpen(transaction){
	return transaction.status ==='OPEN';
}
function isPayment(transaction){
	return transaction.type === 'PAYMENT';
}
function isRefund(transaction){
	return transaction.type === 'REFUND';
}
function useTransactionMethod(transaction, method){
	return transaction.method === method;
}
function processCreditCardTransaction(transaction){
	if (isPayment(transaction)){
    	processCreditCardPayment();
    } else if (isRefund(transaction)){
    	processCreditCardRefund();
    }
   
}
function processPayPalTransaction(transaction){
	if (isPayment(transaction)){
    	processPayPalPayment();
    } else if (isRefund(transaction)){
    	processPayPalRefund();
    } 
}
function processPlanTransaction(transaction){
	if (isPayment(transaction)){
    	processPlanPayment();
    } else if (isRefund(transaction)){
    	processPlanRefund();
    }
}

 

 

Factory function & Polymorphism

아래는 이전에 작성했던 코드 중 일부이다. 

각 transaction을 처리할 때 이전에는 processByMethod 함수를 통해 transaction의 method 종류를

조건문에 따라 분류한다. 그리고 method 종류 별로  transaction을 만들어서 각 종류 별 transaction에는

payment인지 refund인지 조건문을 분리하여 실행했다. 하지만 이 방법은 중복되는 코드가 많다. 

따라서 factory method를 이용하여 중복을 최소화할 수 있다.

 

 getTransactionProcessor가 transaction 종류에 따라 dictionary에 이름을 담고

processWithProcessor에 dictionary에 있는 이름을 읽고 실행한다. 

 

 

 

전체코드

function main(){
	const transactions = [
		{
			id : 't1',
			type : 'OPEN',
			method : 'CREDIT_CARD',
			amount : '23.99'
		},
        {
			id : 't2',
			type : 'OPEN',
			method : 'PAYPAL',
			amount : '100.43'
		}
	]
    try{ 
    	processTransactions(transactions); 
    } catch(error){
    	showErrorMessage(error.message);
    }
}
function processTransactions(transactions) {
	validateTransactions(transactions);   
    for (const transaction of transactions){
    	processTransaction(transaction);
    }
}
function validateTransactions(transactions){
	if(isEmpty(transactions)){
		const error = new Error('No transactions provided!');
        error.code = 1;
        throw error;
	}
}
function isEmpty(transactions){
	if(!transctions || transactions.length === 0){
    	return True
	}
}
function showErrorMessage(message, item = {} ){
	console.log(message);
    console.log(item);
    
}
function processTransaction(transaction){
	try{
		validateTransaction(transaction);
        processWithProcessor(transaction);
    } catch(error){
    	showErrorMessage(error.message, error.item);
    }
}
function validateTransaction(transaction){
	if(!isOpen(transaction)) {
    	const error = new Error('Invalid transaction type1');
        throw error;
    }
    if(!isPayment(transaction) && isRefund(transaction)) {
      	const error = new Error('Invalid transaction type!');
        error.item = transaction;
        throw error;
    }	
}
function processWithProcessor(transaction){
	const processors = getTransactionProcessor(transaction);       
   	if(isPayment(transaction)){
    	processors.processPayment(transaction); 
    }else{
       	processors.processRefund(transaction);
    }
}
function getTransactionProcessor(transaction){
	let processors = {
    	processPayment : null,
        processRefund : null
    };   
    if(useTransactionMethod(transaction, 'CREDIT_CARD')){
    	processors.processPayment = processCreditCardPayment;
        processors.processRefund = processCredditCardRefund;     
    } else if(useTransactionMethod(transaction, 'PAYPAL')){
		processors.processPayment = processPayPalPayment;
        processors.processRefund = processPayPalRefund;
    } else if(useTransactionMethod(transaction, 'PLAN')){
    	processors.processPayment = processPlanPayment;
        processors.processRefund = processPlanRefund;
    }
    return processors;
}

function isOpen(transaction){
	return transaction.status ==='OPEN';
}
function isPayment(transaction){
	return transaction.type === 'PAYMENT';
}
function isRefund(transaction){
	return transaction.type === 'REFUND';
}
function useTransactionMethod(transaction, method){
	return transaction.method === method;
}

 

 

 


요약

1. nesting을 피하기 위해 guard & fail fast 사용

2. 조건문에서 boolean은 긍정값이 나오도록

3. error handling. 에러를 던지고 받을 때 상위에서 받는 경우 두 단계 이상 넘어가는 곳에서 받는 건 비추

4. factory pattern을 사용하여 중복을 피하자. 

5. abstraction level을 맞추자.

쪼갤 수 없을 때까지 계속 쪼갤 것.

코드 구현은 low level에서. 상위 level에서는 조건문이나 내용은 메서드로 간결화