본문 바로가기
기타

리팩토링 스터디 #6.5 - 내용 미리보기

by 유세지 2021. 1. 8.

2021년의 첫 리팩토링 스터디입니다. 새해에도 어김없이 스터디는 쭉 진행되고 있습니다. 이번 시간에는 데이터 조직과 조건부 로직을 정리하는 내용이 주를 이룹니다. 데이터 조직에서는 어떠한 대상을 호출할때 어떤 형태로, 어떻게 호출할 것인지를, 조건부 로직에서는 if문과 같은 조건문을 최대한 간결하고 명확하게 보이도록 하는 몇 가지 기법들을 알아보게됩니다.

 

데이터 조직을 다루는 방법부터 시작하겠습니다.

 

파생 변수를 질의 함수로 바꾸기

서로 다른 코드들을 결합하는 가변 데이터는 종종 문제의 원인이 됩니다. 한쪽에서 수정한 값이 다른쪽에서 문제를 일으킬 수도 있기 때문에 최대한 지양하는 것이 좋지만, 현실적으로 그건 어렵습니다. 그래서 우리가 할 수 있는 최선의 방법은 유효 범위를 최소한으로 줄이는 것입니다.

 

한 가지 예시로, 계산 과정이 간단한 변수들을 제거하는 방법이 있습니다. 이런 변수들은 계산 과정을 보여주는 것이 의미를 전달하는데 더 명확할 때가 자주 있기 때문에 굳이 변수로 만들어 줄 필요가 없는 경우엔 그대로 사용해줍니다. 다만 불변값을 계산하는 변수라면 계산 결과가 바뀔 일이 없으니 그대로 사용하는 것도 괜찮습니다.

 

간단히 변수 값이 갱신되는 지점을 찾아 해당 변수의 값을 계산해주는 함수를 만들고, 함수 호출로 대체하면 됩니다.

 

 

참조를 값으로 바꾸기 & 값을 참조로 바꾸기

객체를 다른 객체에 중첩하면 내부의 객체를 참조나 값으로 취급할 수 있습니다. 참조로 다룰지, 값으로 다룰지는 프로그래머의 선택이지만 둘은 전혀 다른 결과를 가져오기 때문에 굉장히 중요한 문제입니다. Call by Value와 Call by Reference는 이미 너무나도 유명한 주제 중 하나입니다. 

 

 

값을 이용한 호출을 이용하면 원본 주소의 값 변경 없이 마음껏 활용할 수 있습니다. 즉, 원본은 불변 데이터가 되는 것입니다. 반대로 참조에 의한 호출은 원본의 값을 변경해 이를 참조하고 있는 객체 모두에게 영향을 미칩니다. 한 데이터 영역을 공유해야하는 여러 객체가 있다면 참조에 의한 호출을 사용하는 것이 편리할 것입니다. 코드의 사용 용도에 따라 적절한 방법을 골라 적용하면 됩니다.

 

다만 이 방법 같은 경우엔 잘못 사용했을경우 말 그대로 오류가 나게 됩니다. 처음 코드를 짤때부터 어떤 방법을 써야 할지 고려해서 작성해야한다고 생각하는데, 그래서 이것을 리팩토링의 범주로 넣어서 적용해야 될지는 쉽게 납득이 가지 않았습니다. 분류를 하자면 버그 픽스라고 봐야 할 것 같다는 생각이 듭니다.

 

그와 별개로 값과 참조에 관련한 부분에 관해서는 쓸게 꽤 많으니 따로 정리를 해두어야겠습니다.

 

 

매직 리터럴 바꾸기 

혹시 매직 리터럴(magic literal) 이라는 단어를 들어본적 있으실지 모르겠습니다. (저는 여기서 처음 보았습니다.) 보통 소스 코드에 등장하는 일반적인 숫자들을 뜻한다고 하는데, 예를 들면 9.8(중력상수) 이나 3.14(원주율) 같은 주로 특정한 계산에 사용되는 숫자들이 그 예시입니다.

 

이러한 숫자들은 코드를 작성한 사람이나 배경지식이 있는 사람들에겐 의미있는 수가 될 수 있지만, 모르는 사람이 보기엔 출처를 알 수 없는 숫자일 수 있습니다. 따라서 그 의미를 명확히 하기 위해 상수를 정의하여 매직 리터럴 대신 사용하는 것이 좋은 방법이 될 것 입니다.

 

그렇다고 const one = 1; const two = 2; 와 같은 의미없는 코드들을 쓰라는 것은 당연히... 아닙니다. 이 경우에는 상수를 남용하는 케이스가 될 수 있겠네요. 한시라도 빨리 리팩토링 해줘야 할 대상입니다.

 

 

조건문 분해하기

if((!ArrayOfStudentList.isEmpty())&&(ArrayOfStudentList.isSortByNumber(upper)) || (!ArrayOfTeacherList.isEmpty())&&(ArrayOfStudentList.isSortByNumber(upper))) { ...

즉석에서 생각나는대로 조건문을 더럽게 작성해보았습니다.

읽고싶은 의욕이 마구 드시나요? 아니라면, 대체 이 코드를 작성한 사람이 누군지 범인을 찾아내고 싶지 않으신가요?

 

복잡한 조건부는 단순하고 명확하게 돌아가야 할 프로그램을 실타래처럼 꼬이게 하는 주된 원흉 중 하나입니다. 이런 경우 코드가 어떤 동작을 하는지는 알 수 있을지 몰라도 어떤 경우에, 왜 그런 동작이 일어나는지는 친절하게 설명해주지 않는 경우가 많습니다.

 

최대한 깔끔하게, 의미를 전달할 수 있는 최적의 방법으로 표현해주는것이 좋습니다. 복잡하고 긴 코드가 들어가야 한다면, 따로 적당한 이름의 함수로 만들어 조건부에서는 호출만 해주는 방법을 사용합니다.

 

물론 괄호 안에 때려박기 좋아하는 제가 할 말은 아닙니다. (조건부는 아니지만)

 

 

취향에 따라 삼항연산자 (?) 를 사용하는것도 가독성을 높이는 좋은 방법입니다.

 

 

조건식 통합하기

위에서는 조건부가 복잡했을때 사용하는 리팩토링 기법이었다면, 이번에는 여러가지 조건을 비교한 후 같은 코드를 실행시키는 경우에 사용하는 기법입니다. 여러개의 if문들이 중첩되어 있는데 조건을 통과했을때 일정한 동작을 해야한다면 여러개의 조건문을 병렬식으로 배치해 둘 필요가 없습니다. && 연산자나 || 연산자를 이용해 보기 불편함이 없을 정도로 합쳐주는 것이 좋습니다.

 

특별한 케이스로 참과 거짓만을 판별해서 반환하는 경우 굳이 if를 쓸 필요 없이, return 문에서 비교하는 것으로도 코드의 낭비를 줄일 수 있습니다. 되도록 가독성을 해치지 않는 범위 내에서 간결하게 코드를 작성하는 것이 좋습니다. 이미 코드가 많이 복잡해진 상황이라면 함수로 따로 추출하여 호출해서 사용하는 방식도 고려해봄직 합니다.

 

 

중첩 조건문을 보호 구문으로 바꾸기

보호 구문은 어떤 함수가 의도한 동작을 하지 않을때, 조치를 취한 후 그 함수에서 빠져나오는 형태의 구문을 의미합니다. 흔히 여러가지 조건을 검사해야하는 중첩 조건문에서 이런 경우가 보이는데 쓸데없는 진행 대신 return 문을 이용해 곧바로 문제 상태를 반환하며 함수에서 빠져나오도록 코드를 작성하면 이 함수가 어떤 동작을 위해 만들어졌는지 명확하게 알 수 있는 경우가 있습니다. 바로 이럴때 중첩 조건문을 보호 구문으로 바꾸기를 사용합니다.

 

간단히 예를 들어 이런식의 구문이 있다면,

if(state1) func1();
else {
	if(state2) func2();
    else { ...
    ...
    }
}

return var1;

 

이런 형태로 바꾸어 주는 것을 의미합니다.

if(state1) return state1Variable();
if(state2) return state2Variable();

return successVariable();

 

상황에 따라 어느 쪽이 더 좋은지 판단한 후 적용해주면 됩니다.

 

 

조건부 로직을 다형성으로 바꾸기

비교해야 하는 조건의 형태가 다양하고 복잡하다면, 아예 구조를 바꿔버리는 방법도 있습니다. 조건문을 줄이고 간단하게 표현하는데 한계를 느꼈다면 다형성을 활용한 클래스를 이용해 조건문을 작성할 수도 있습니다.

 

보통 몇 안되는 조건들을 비교할때는 if/else 절을 사용하고, 다양한 조건들을 검사할 때는 switch/case 문을 사용하는 경우가 많지만 이를 넘어가는 수준의 조건들이라면 큰 조건에 해당하는 (먼저 수행되며 다른 조건들의 분기점이 되는) 부분을 Class로 작성합니다. 그리고 적합한 인스턴스를 만들어 반환하는 함수를 만들어 넣어주고 호출하는 쪽에서는 이 함수를 사용하도록 코드를 수정해줍니다. 이렇게 하면 특정 조건에 따라 여러개의 분기(switch) 를 사용해야 하는 복잡한 조건절을 다형성을 활용한 클래스로 깔끔하게 대체할 수 있습니다.

 

물론 기존의 조건/비교 개념과 클래스를 사용한 방법이 서로 대체 가능하다는 것이 쉽게 받아들여지지 않을 수 있습니다. 저도 실제로 코드를 보기 전까진 이런 식으로 가능할 것이라는 생각이 들지 않았고, 구체적인 방법을 보고서야 "이렇게 사용하면 되겠구나" 하는 감이 잡힌 느낌이었습니다.

 

다수의 분기를 무조건적으로 다형성을 활용한 방법으로 대체해야 한다는건 아니지만, 클래스가 편리한 경우가 분명히 있고 프로그래머들의 수고를 줄일 수 있는 강력한 도구라는 사실을 기억하면 언젠가 꼭 도움이 될 것 같습니다.

 

 

변환 가능한 두 방법

 

 

 

여기까지 데이터 조직과 조건부 로직을 다루는 몇 가지 기법에 대해 알아보았습니다. 같은 기능을 하는 코드라도, 명확하고 간단한 형태를 띄기 위해 여러 방법을 시도할 수 있다는 사실을 알게 되었습니다. 오늘 공부했던 내용을 실제로 적용하게 되었을때, 바뀐 코드를 보고 있으면 굉장히 뿌듯할 것 같습니다. 잊지 않도록 책에 있는 내용들을 여러번 반복해서 읽어봐야겠습니다.

 

리팩토링 스터디 #7은 스터디를 마치고 작성하겠습니다.

댓글