통계에 따르면 프로젝트에 존재하는 코드 중 90% 정도는 예외를 처리하는 부분이다. 그렇기 때문에 애러 처리는 어플리케이션 개발에 있어 굉장히 중요한 부분을 차지하다. 내 생각에 코드는 성공하는 로직을 위주로 작성되어야 가장 깔끔해 보인다. 일관성 있어야 하며 일반적인 제어의 흐름과 예외 처리는 분리되어야 한다. 그렇기 위해선 각 함수들의 애러는 우아하게 처리되어야 한다. 

 

 애러의 가능성을 피할 수 있으면, 해당 설계대로 작성하는게 가장 좋다. 그렇지 않다면 애러는 명시적으로 처리 되어야 한다. 아래는 rawData를 Movie list로 파싱해서 description이 있는 경우 해당 text의 길이를 더해서 반환하는 함수이다. 함수가 실패하면 의도적으로 nil(Null)을 리턴하여 해당 함수의 실패를 알리는 방식으로 코드를 작성했을때, nil을 전달 받은 getMovieDescriptionCount함수의 코드를 보자.

 

[nil(Null)을 사용하여 코드가 망가지는 모습]

 

  우리가 애러 표현을 위해 습관적으로 nil을 적용 했을때 망가지는 코드의 모습이다. getMovieDescriptionCount는 예외처리 코드 때문에 성공하는 로직을 작성할 수 없다. 코드도 길어지고 가독성도 떨어졌다. nil check하는 if문이 별게 아닌 것 처럼 보이지만 최소한의 인지적 노력을 해야하기 때문에 코드를 읽는데 방해가 된다.

 

[애러가 정상적인 동착 처럼 보이도록 default  value를 적용]

 

  이런 경우에는 nil이 아니라 default 값을 넘기고 array인 경우 empty array를 넘겨서 정상적인 동작으로 보이도록 처리 될 수 있다. (class인 경우엔 Null Object Pattern을 사용할 수 있다.) 우리가 작성하는 대부분의 코드는 "if {성공} else {아무것도 하지 않음}" 인 경우가 많기 때문에 습관적으로 사용하는 nil 처리만 잘해도 코드가 굉장히 깔끔해 질 수 있다. 

 

  정상적인 동작처럼 보이도록 디자인 하기 어려운 경우에는 아래와 같이 여러 상태들의 조건을 확인하여 예외 처리 코드를 적용한다.

 

[애러를 분기 하여 예외 처리]

 

  이 경우도 마찬가지로 if의 인지적 노력이 필요함으로 가독성이 매우 떨어지고 예외 처리 코드 때문에 코드의 일관성을 잃어 버리게 된다. 또한 2~10 line은 예외에 대한 내용이 구체적이지 않기 때문에 문제를 식별하기 굉장히 어렵다. 12~28 line은 Error Code를 정의하여 예외처리를 처리를 하였다. Error Code는 실패에 대한 명확한 이유를 알 수 있기 때문에 문제를 식별하고 구체적인 예외처리를 할 수 있다. 하지만 이 역시 일반적인 제어의 흐름 속에 예외 처리 코드가 섞여 있어서 코드의 일관성을 잃어 버렸다. 

 

[사용자 정의 Error]

 

  이런 경우 사용자 Error 정의하고 do try catch구문으로 처리한다. 사용자 Error의 사용은 오류를 명시적인 이름으로 정의 할 수 있으며, 오류의 가능성이 있는 함수를 사용하는 클라이언트 코드의 일반적인 제어의 흐름과 오류 처리 코드를 물리적으로 분리 시킬 수 있다.  따라서 이제 do try 블록 안에는 성공하는 로직만 존재한다. 

 

  사용자 정의 Error는 상위 레이어로 throw 될 때 OCP를 위배 할 수 있다. 상위 레이어로 오류를 전달은 wrapping하여 rethrow 하도록 디자인 할 수 있다.

 

'CleanCode' 카테고리의 다른 글

javascript AOP  (0) 2016.10.18
boolean type parameter의 모호성  (0) 2016.10.15

자바스크립트를 사용한 프로젝트를 진행했을 때의 일이다. 이미 개발된 100여 개 정도 되는 API들에 대해 몇 가지 공통적인 전/후 처리 작업을 해줘야 되는 일이 생겼는데, 누락되는 부분 없이 안정성 있게  로직이 추가되어야 했다. 당시 지급으로 처리되어야 할 급한 이슈여서 따로 라이브러리를 찾아보진 않았고 아래와 같이 간단한 AOP 모듈을 만들어 문제를 해결했다. 


var AOP = (function(){
	function crossCut(target, pointCut, policy){
		var reg = new RegExp(pointCut);
		for(var joinPointName in target){
			if(reg.test(joinPointName)){
				policy(joinPointName);
			}
		}
	};
	return {
		injectBefore : function(advice, target, pointCut /* regular expression */){
			function beforePolicy(joinPointName){
				var joinPoint = target[joinPointName];
				target[joinPointName] = function(){
					return advice.call(target, joinPoint, arguments);
				};	
			};
			crossCut(target, pointCut, beforePolicy);
		},
		injectAfter : function(advice, target, pointCut /* regular expression */){
			function afterPolicy(joinPointName){
				var joinPoint = target[joinPointName];
				target[joinPointName] = function(){
					var ret = joinPoint.apply(target, arguments);
					return advice.call(target, arguments, ret);
				};
			};
			crossCut(target, pointCut, afterPolicy);
		}
	};
})();


AOP.injectBefore의 첫 번째 파라미터는 전처리 로직인 advice에 해당하는 함수,  두 번째  파라미터는 전처리가 필요한 target class, 세 번째 파라미터는 해당 클래스에서 전처리가 필요한 함수들의 regular expression 값이다.


function beforeLog(joinPoint, arg){	// 전처리 로직
	console.log("beforeLog [" + arg[0] +"]");
	return joinPoint.apply(this, arg);
};
function afterLog(arg, ret){			// 후처리 로직
	console.log("afterLog [" + ret + "]");
	return ret;
};

function MsgMaker() {};			// 전/후 처리가 필요한 class
MsgMaker.prototype.makeHello = function(name){
	console.log("makeHello");
	return "hello " + name;
};

AOP.injectBefore(beforeLog, MsgMaker.prototype, "^make");	// MsgMaker Class에서 make로 시작하는 모든 함수에 대해 전처리 추가
AOP.injectAfter(afterLog, MsgMaker.prototype, "^make");	// MsgMaker Class에서 make로 시작하는 모든 함수에 대해 후처리 추가

var maker = new MsgMaker();
console.log(maker.makeHello("world"));

//결과
/*
beforeLog [world]
makeHello
afterLog [hello world]
hello world
*/


prototype이 아닌 property로 함수를 추가했다면, 아래와 같이 .prototype을 빼야한다. 

AOP.injectAfter(afterLog, MsgMaker, "^make");


'CleanCode' 카테고리의 다른 글

Error handling - 우아하게 실패하는 방법  (0) 2019.07.20
boolean type parameter의 모호성  (0) 2016.10.15

개발을 하다보면 boolean 타입의 파라미터를 넘기는 코드들을 자주 접할 수 있다. 간혹 이러한 코드들은 가독성을 떨어뜨리는데, 괄호 안에 존재하는 boolean의 의미를 알기 위해서는 선언부까지 확인해야 되기 때문이다.


read(key, true);


심한 경우에는 아래와 같이 boolean 타입이 여러개가 오는 코드도 본적이 있다. 이런 경우에는 코드의 가독성도 문제가 되지만, 개발자의 실수로 파라미터의 순서가 바뀌게 된다면 원인을 찾기 힘든 버그를 만들어 낼 것이다. 


read(key, true, false);


아래처럼 enum과 같은 타입을 정의하여 사용하면 가독성은 물론이고, 만약 개발자의 실수로 파라미터의 순서가 바뀌기라도 한다면 컴파일러는 이 잘못된 부분을 정확히 짚어낼 것이다. 


enum class CACHE { YES, NO };
enum class SORT { ASC, DESC };

read(key, CACHE::YES, SORT::DESC);


'CleanCode' 카테고리의 다른 글

Error handling - 우아하게 실패하는 방법  (0) 2019.07.20
javascript AOP  (0) 2016.10.18

+ Recent posts