본문 바로가기
기타

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

by 유세지 2021. 1. 16.

한 주를 마무리하는 리팩토링 스터디의 여덟번째 시간입니다. 이번 시간에는 API 리팩토링에 관한 내용을 다룹니다. 사용하기 쉽고, 이해하기도 쉬운 API를 만들고 개선할 수 있는 방법이 담겨있습니다. 그럼 시작하겠습니다.

 

특이 케이스 추가하기 Introduce Special Case

특정한 데이터의 값을 확인하고 같은 동작을 수행해주는 코드들은 여기저기서 흔히 등장합니다. 이는 중복 코드의 한 종류로, 공통적인 동작을 한 곳에 모아서 사용하는 리팩토링을 적용하기에 안성맞춤입니다. 이런 방법을 특이 케이스 패턴(Special Case Pattern)이라고 부릅니다.

 

이러한 패턴의 가장 큰 장점은 매번 등장하는 식을 대신하여 간단한 함수 호출을 통해 원하는 동작을 간결하게 표현할 수 있다는 점입니다. 특히 이런 단순 동작은 길지 않은 코드로 구현할 수 있어 작업하는 사람마다 그 표현법이 여러갈래로 나눠지기 쉬운데 이렇게 함수 호출을 하게되면 코드의 통일성을 높여주는 효과가 있습니다. 

 

특이 케이스 패턴은 많은 일을 할 수 있습니다. 단순 데이터 값을 반환할때는 리터럴 객체로 준비하고, 특정한 동작을 행해야 한다면 메서드를 넣은 객체로 준비해 반환시켜주면 됩니다. 어떤 기능이 필요하더라도 광범위하게 적용시킬 수 있는 것이 특징입니다.

 

보통은 널(null)이 매개변수로 들어올 경우에 특이 케이스로 처리하는 빈도가 잦아 널 객체 패턴(Null Object Pattern)이라고 부른다고도 하네요. 

 

 

어서션 추가하기 Introduce Assertion

assertion의 의미 (papago)

어서션(assertion)은 단어 그대로 (~~가 사실임을) 주장하다, 단언하다 등의 의미를 갖고 있습니다. 어떠한 대상을 명확히 하겠다는 의도로 사용하는 단어입니다.

 

프로그래밍에서 어서션은 항상 참이라고 가정하는 조건부 문장입니다. 꽤 이전에 올렸던 포스트에서 테스트 라이브러리를 언급하며 어서션을 사용한다고 했었는데, 모카나 쟈스민등을 사용하신 분들이라면 아마 익숙하실겁니다. 오류를 검사하는데 굉장히 유용하고, 실제로도 많이 사용하는데 이 책에서는 어서션을 개발자들 간의 소통의 도구로도 사용합니다.

 

만약 다른 사람이 작성한 조건문을 본다고 가정해보겠습니다. 조건식의 내용에 은유적인 표현(무엇을 기준으로 하는지 모호한 변수)이 있다면 그 코드를 작성한 사람이 아니고서야 의미를 한 번에 파악하기 어렵습니다. 이럴 때를 대비하여 이에 대한 정보를 추가로 제공하는 부분이 꼭 필요한데, 대표적인 예시로 주석이 있겠네요. 하지만 저자는 그보다 더 나은 방법으로 어서션을 활용하기를 강력히 추천하고 있습니다.

 

실제로 이런 어서션을 컴파일타임에 on-off 여부를 결정하는 스위치를 따로 제공하고 있는 언어처럼 추가적인 기능을 기대할수도 있다고 하니 개발자들에겐 편할 수도 있을 것 같네요. 카카오톡이 있지만 슬랙을 사용하는 것과 비슷한 느낌일까요?

 

 

제어 플래그를 탈출문으로 바꾸기 Replace Control Flag with Break

코드의 동작을 변경하는데 사용되는 변수는 보통 그 값을 계산하여 제어 플래그에 설정하고, 다른 조건문에서 검사하는 모양으로 사용됩니다. 이런 코드들은 리팩터링으로 충분히 간소화를 할 수 있지만 흔히 나타나게 되어 제거 대상 1순위라고 보셔도 좋습니다.

 

이러한 제어플래그는 주로 반복문 안에서 많이 보이게 되는데, break문이나 continue등 반복제어자들에 익숙하지 않은 사람이 작성하기도 하고, 함수 안에 여러 개의 return이 보이는 것을 싫어하는 사람이 작성하기도 합니다. 후자의 경우엔 굳이 그렇게 작성 할 필요가 없기에 할 일을 마친 함수라면 return을 통해 명확히 나타내는게 좋다는 것이 저자의 의견이고, 저도 읽으면서 아주 공감했습니다.

 

가끔 IDE에서 구문을 잘못 파악하고 cannot reach state~ 따위의 경고문을 뱉는 경우가 있긴 하지만, 프로그래머의 완벽한 통제(해당 코드에 대한 이해)가 있다면 return문을 사용하는 것이 보기에도 훨씬 명확해보입니다.

 

 

 

질의 함수와 변경 함수 분리하기 Separate Query from Modifier

부수 효과(side effect)는 개발자가 의도한 동작을 어그러뜨리는데 큰 역할을 합니다. 사소한 부수 효과들이 쌓이면 내부적으로 큰 변화가 되고, 이는 다른쪽에서 의도치 못한 결과를 받게되는 요인이 됩니다. 따라서 프로그래머는 이러한 부수 효과들을 최대한 줄이는 방향으로 코드를 만들어내야 합니다.

 

특히 이런 부수 효과들이 어디서 일어나는지도 모른채 남용된다면 이 후에 문제를 해결하기가 더욱 어려워집니다. 이를 막기 위한 방법으로 질의 함수는 모두 부수효과가 없어야 한다는 규칙을 만들었는데, 이를 명령-질의 분리(command-query separation)라고 합니다.

 

단순히 값을 반환하는 함수들이 부수 효과를 가지게 된다면, 어디서 어떻게 이 함수를 호출할지 모르는 상황에서 굉장히 위험한 시도라고 볼 수 있습니다. 따라서 이러한 질의 함수(읽기 함수)에는 부수 효과를 넣어서는 안됩니다. 이럴때 사용하는 기법이 바로 질의 함수와 변경 함수 분리하기입니다.

 

우선은 대상 함수를 복제하여 어떤 것을 반환하고, 변경하는지 파악하는게 가장 먼저 시행해야 할 과정입니다. 그리고 새로운 질의 함수에서 부수 효과들을 모두 제거한 뒤 원래 함수를 호출하는 곳에서도 질의 함수를 호출하도록 바꾸어줍니다.

 

그 밑에 변경 함수를 추가해주고, 변경 함수에서 질의 관련 부분을 삭제해주면 이 둘은 완전히 분리가 되게 됩니다. 이런 과정을 거쳐 부수 효과가 있던 위험한 함수는 부수 효과가 없는 덜 위험한 함수 두 개로 나뉘어지게 됩니다.

 

 

 

함수 매개변수화하기 Parameterize Function

비슷한 역할을 하는데 리터럴 값만 다른 둘 이상의 함수가 있다면, 이것 또한 악취를 풍기는 코드라고 할 수 있습니다. 이런 경우엔 매개변수로 달라진 리터럴 값을 받아 처리하면 의미없이 늘어난 여러 함수를 하나의 함수로 줄일 수 있습니다.

 

이렇게 코드를 줄이고 난 뒤엔 원래의 구문을 삭제해야 하지만, 이런 코드들 중에는 예외 상황이 발생했을때의 대처 방법을 갖고 있는 코드도 있을 수 있으니 주석처리 시켜 보존하는 것도 때로는 좋은 선택이 될 수 있습니다.

 

 

플래그 인수 제거하기 Remove Flag Argument

플래그 인수는 호출되는 함수가 갖고있는 여러가지 동작 중 어떤 것을 수행할지 호출하는 쪽에서 정하기 위해 사용하는 인수를 뜻합니다. 여러가지 동작을 한 함수에 담다보니 생겨날 수 밖에 없는 인수 중 하나인데, 특히 API에 이런 경우가 많이 보이는 듯 합니다. 예를 들어 마지막 인수로 1을 전달하면 데이터를 오름차순으로 반환하고, 2를 전달하면 내림차순으로 반환하는 것처럼요.

 

이미 만들어진 함수를 가져다가 사용해야하는 경우에는 어쩔 수 없지만, 함수들의 기능 차이가 잘 드러나지 않아서 명확히 파악하기가 상대적으로 어렵기 때문에, 우리가 이런 함수를 만드는 경우라면 차라리 특정한 기능 하나만 수행하는 명시적인 함수를 제공하는 것이 더 깔끔하게 보일 수 있다는 것입니다.

 

간단히, getData("ascending") / getData("descending") 보다는

getAscendingData(); / getDescendingData(); 가 더 명확한 코드가 되겠습니다.

 

 

 

객체 통째로 넘기기 Preserve Whole Object

객체 통째로 넘기기는 어떤 데이터가 필요해서 요청했을때, 하나의 레코드에서 두 세개의 값을 꺼내 전해주는 것보단 객체를 통째로 전달하고 호출하는 쪽에서 꺼내 쓰는 방식으로 변경하는 것입니다.

 

이렇게 되면 후에 기능이 변화했을때 그에 대응하기가 더 쉽습니다. 이전 방식에선 필요한 데이터가 하나 늘어나면, 함수에서 매개변수도 추가되어야 하고, 그에 따라 함수의 동작도 바뀌어야 합니다. 그러나 처음부터 객체를 통째로 넘겨주게되면 데이터를 사용하는 측에서 코드 한 줄만 추가하면 됩니다. 훨씬 간단히 수정이 가능하죠.

 

또한 여기저기서 요청하다보면 레코드를 여러번 조회해야 하는 경우도 생깁니다. 이런 경우 중복 로직이 되는데, 처음부터 객체를 받으면 이런 비효율적인 로직이 생길 걱정도 없습니다.

 

다만 이 리팩토링은 함수가 레코드 자체에 의존하지 않는 경우에는 수행하지 않습니다. 특히 레코드와 함수가 멀리 떨어져있는 경우 (같은 모듈이 아닐때)에는 신중히 고려해봐야 합니다.

 

이 방법같은 경우엔 객체를 통째로 받아오는 경우도 있지만, 반대로 함수를 호출할때 자신이 가지고 있는 몇몇 정보가 아닌 자기 자신을 함께 전송해버리는 반대의 경우로도 사용할 수 있습니다. getSomething(this); 이런식으로요.

 

주어진 상황에 맞게 잘 활용하시면 되겠습니다.

 

 

 

 

이번 주에 미리 공부할 내용은 여기까지입니다. 그럼 본편에서 뵙겠습니다.

댓글