본문 바로가기
기타

리팩토링 스터디 #2 - 리팩토링 원칙 & 코드에서 나는 악취

by 유세지 2020. 9. 21.

2주차 리팩토링 스터디입니다. 이번 스터디의 내용은 챕터 2: 리팩토링 원칙부터 챕터 3: 코드에서 나는 악취까지의 내용입니다.

 

본 포스팅에서는 스터디를 진행하며 나왔던 이야기 위주로 정리합니다. 모두 곁들여서 들으면 좋을 내용들이고, 챕터에 대한 자세한 내용은 리팩토링 스터디 #1.5 - 내용 미리보기를 참고해주세요.

 

그럼 시작하겠습니다.

 

 

테스트의 중요성

책에서는 테스트의 중요성을 집요하리만큼 역설하고, 저자 스스로도 테스트를 사랑한다고 말합니다. 스터디원들도 테스트의 필요성에 대해서는 대체로 동의하였지만 저자만큼의 빈도로 테스트를 수행하는 사람은 없었습니다. 필요에 의한 테스트를 꾸준히 진행하는 건 훌륭한 습관이지만 자칫 테스트를 위한 테스트가 될 수 있다는 것은 조심해야 할 것 같습니다.

 

테스트 자체에 익숙하지 않거나 경험이 없었던 스터디원들을 위한 조언으로, 내가 짠 로직이(코드가) 맞는지 머릿 속으로 생각하는 과정을 코드로 옮기는 것이 테스트이니 낯선 과정에 너무 부담갖지 말고 진행하면 좋다고 합니다.

 

 

" 테스트의 시작은 인간지능(Human Intelligence)를 인공지능(Artificial Intelligence)로 바꿔주기! " 

 

 

테스트는 인공지능에게

 

코드 길이와 성능

저자는 코드의 길이가 늘어나는 것을 경계해 리팩토링을 망설이지 말 것을 권장합니다. 저자의 경험과 주변 개발자들의 이야기를 바탕으로 코드 길이가 늘어나더라도 그로 인해 설계가 명확해진다면 프로그래머의 작업 시간은 훨씬 짧아지게 된다고 역설합니다. 또한 그 정도 코드 길이가 늘어나는 것은 실성능에 거의 영향을 미치지 않으니 무시해도 좋은 수준이라며 독자들을 안심시킵니다.

 

스터디를 함께 진행하는 멤버들도 코드를 보고 작성하는 것은 결국 사람의 영역이기에 맞는 이야기라며 공감하였습니다. 실제로 프로그래밍에서 사람의 발목을 잡는 것은 성능 문제보다는 읽기 힘든 코드일 경우가 더 많기도 하고, 이러한 성능 문제는 UI/UX적인 부분에서 해결 방법을 찾을 수도 있기 때문입니다.

 

다만 코드의 길이가 성능에 영향을 끼치지 않는다는 점은 경계해야 한다는 의견도 나왔습니다. 코드 길이가 짧아진다고 시스템이 빨라지는 것은 아니라는건 일반적으로는 맞는 이야기이지만, 특수한 케이스에서는 코드의 길이 또한 중요히 다루어져야하는 문제입니다.

 

아주 작은 오차도 큰 영향을 미칠 수 있는, 예를 들면 우주선에 내장될 프로그램을 만든다던가... 와 같은 극단적인 예시도 물론 있겠지만, 그게 아니더라도 주위에서 쉽게 찾을 수 있는 예시로 프론트엔드가 있습니다.

 

프론트엔드 개발자라면 자바스크립트 라이브러리에 .min 등의 이름이 붙어있거나, 열어서 내용을 확인해보면 줄바꿈이나 공백등이 사라진 빽빽한 코드만 남게 되는 파일을 보신적이 있으실겁니다. 그 이유가 바로 코드의 길이가 성능에 무시하지 못할 수준의 영향을 미치기 때문입니다.

 

 

 

숨 쉴 공간도 부족해보입니다

 

 

따라서 이런 경우 공백을 없애던, 줄바꿈을 없애던 하는 최적화 작업이 필요합니다.

 

프론트엔드의 최적화 방법의 하나로 번들 사이즈 최적화가 있습니다. 프로젝트에서 임포트 되고있는 번들을 확인하고, 사용하지 않는 번들이 로드되는 경우 제외시켜줍니다.

가장 익숙한 번들 사이즈 최적화의 예시 중 하나가 moment.js의 최적화입니다. 불친절하기로 악명높은 자바스크립트의 Date() 함수를 대신해 널리 사용하는 날짜 관련 라이브러리인 moment.js를 통째로 임포트하게 되면 moment.js에 포함된 locale.js이 함께 번들링됩니다. 여기서 locale.js의 용량이 꽤 크기 때문에 (약 100kb) locale.js를 굳이 사용하지 않는다면 moment.js만 임포트하도록 변경하여 로드되는 양을 줄이는 과정입니다.

 

httparchive에서는 웹사이트들의 데이터를 대략 한 달에 두 번 정도 주기적으로 수집하는데, 권장되는 번들 사이즈의 추세를 알고 싶다면 State of JavaScript의 JavaScript Bytes 섹션을 참고하시면 될 것 같습니다.

 

 

httparchive.org

 

프로그램의 빌드 과정에서, 특정 부분이 계속 히트가 되면(호출이 되면) 자바스크립트의 컴파일러가 이를 끌어와 코드 본문에 넣어버리기도 합니다. 이렇게 되면 외부 파일에서 반복적인 호출을 하는 것보다는 성능적인 부분에서 이득을 볼 수 있습니다. 이것을 인라인(in-line)이라고 부릅니다.

 

다만 여기서 현재 실행중인 함수에 접근하는 arguments.callee를 사용하게 되면 이러한 최적화가 작동하지 않습니다. 컴파일 과정에서 arguments.callee는 그저 arguments.callee일뿐이고, 일단 실행이 되어야지만 의도한 함수처럼 동작하는 (런타임시에 결정되는) 함수이기 때문에 어떤 함수를 인라인 해야할지 알 수 없습니다. 또한 프로그램의 캡슐화를 깨뜨리는 것은 덤입니다.

 

때문에 이러한 이유로 ES5는 엄격모드에서 arguments.callee() 의 사용을 금지하였고, 대신 function 선언을 사용할것을 권장하였습니다.

 

덧붙여서, arguments object에 대한 접근은 굉장히 비싼 연산입니다. 성능 향상과는 거리가 멀다고 하네요.

 

 

한가지 더, Webpack은 우리가 작성한 코드의 구문 분석을 통해 임포트 하고도 사용되지 않는등 쓸데없는 코드들을 자동으로 제거합니다. 이 과정을 통해 번들의 사이즈가 줄어드는데, 이것을 트리 쉐이킹(tree shaking)이라고 합니다. 이러한 최적화 과정을 통해 프로젝트를 가볍게 유지할 수 있도록 기억해두면 좋을 것 같습니다.

 

 

 

작업 순서


보통 우리가 개발을 한다고 하면, 원하는 기능을 구현하고 나서 문제가 되는 부분을 리팩토링 해주는 순서를 따릅니다. 그러나 저자는 선 코딩 / 후 리팩토링이 아닌 선 리팩토링 / 후 코딩을 권장합니다. 리팩토링을 통해 구조를 정리하면 구현에 드는 시간도 자연히 줄어든다는 주장인데 많은 스터디 멤버 분들이 공감하셨고 저에게도 신선한 관점이었습니다. 물론 이러한 작업 순서가 모든 경우에서 가장 효율적인 방법은 아니며, 많은 경우에서 효과를 볼 수 있다는 것이지 함께 작업하는 사람들과 머리를 맞대고 주어진 상황에 맞는 방법을 찾았다면 그 방식이 옳을 수도 있습니다. 이 선택지는 그런 개발 문화를 아직 정립하지 않았을때, 혹은 무엇인가 잘못되어가고 있다고 느낄때 고려해보시는게 좋아보입니다.

 

개발자들의 작업에 관해 책에서 YAGNI 라는 말을 사용하였습니다. "You ain't gonna need it" 의 약자인데 어떤 기능을 구현할때, 실제로 필요한게 아닌 "나중에라도 이런 기능이 필요하지 않을까? 기왕 만드는 김에 미리 좀 해 놓아야지" 라는 생각으로 필요 이상의 기능을 구현하는 경우, 그러지 않는 것을 권장한다는 익스트림 프로그래밍의 원칙입니다.

 

어차피 새로운 요구는 끊임없이 생산되고, 그 상황에 맞추어 개발해나가야지 처음부터 어떤 기능이 필요할 것이라고 예상해서 준비하는 것들은 높은 확률로 쓸 일이 없다는 하나의 개발 방법론입니다.

 

이와 비슷한 방법론으로 KISS (Keep it short and simple, 혹은 keep it simple, stupid) 라는 개발 원칙이 있는데 말 그대로 복잡하게 가지 말라는 뜻입니다. 구조던, 기능이던 최대한 단순하게 엔지니어링 하라는 것이 이 원칙의 골조입니다. 그와 별개로 어떤 기능을 넣을 것인지, 어떻게 작업할 것인지, 이런 것들은 대개 프로그래머의 경험적 요소가 판단에 크게 작용하기 때문에 결국은 많이 해보는 수 밖에는 없을 것 같습니다.

 

"최고의 개발자는 게으른 사람들이다"

 



리팩토링이 필요한 부분

챕터 3의 내용으로, 과연 어떤 부분을 리팩토링 해야 할 것인지에 대해 다루었는데 함수(또는 변수)의 애매한 이름에 대한 내용이 나왔습니다. 그러한 이름들을 명확하게 어떤 동작을 하는지 알 수 있도록 바꿔주어야 한다는 것인데, 그 이전에 함수 이름을 애매하게 정해야 하는 상황으로 돌아가봅니다.

 

보통 "함수 이름을 정하기 힘들다" 라는 상황이 되었다면, 그 함수는 한 가지 동작만을 하는게 아닐 가능성이 높습니다. 행위에 대한 명칭이 딱 떨어지는 언어 체계상 함수가 취해야 할 행동을 그대로 직역하면 될 문제인데 이런 경우는 함수가 여러가지 역할을 도맡아서 하고 있는, 소위 말하는 악취가 나는 코드가 되어있는 상황인 것이죠.

 

그렇기 때문에 함수 이름을 정하는데 어려움을 느낀다면, 어떻게 이름을 정해야 할지 고민하기 이전에 설계부터 문제가 있었을 확률이 높습니다. 우선은 대충 적당한 이름으로 함수를 만들고, 리팩토링 할 때에는 설계 부분을 건드려야 할지도 모르겠네요.

반복문에 대한 이야기도 나왔습니다. 명령적인 함수(for, while)보다는 선언적인 함수(map)을 통해 명확성을 주는 것이 좋다는 의미인데요. iterator와 iterable을 알고 계신다면 어떤 느낌인지 이해하기 쉬우실 것 같습니다. 순회가 가능한(iterable), 객체(iterator)이기에 프로그래머가 구조를 파악하기도, 다루기도 수월해 질 것입니다.

 

 

 

 

마치며

주말 오전에 두 시간 가량 진행된 스터디였기에 멤버들의 피곤한 기색이 보이는 스터디였습니다. 밤을 새가며 발표를 준비하신 멤버도 계셨을 정도로 열심히 참여해주셔서 얻어간 것도 많은 시간이었습니다. 새롭게 알게 된 개념들이 너무 많아서 항상 공부해야겠다고 느끼는 한 주의 마무리가 되었네요. 읽어주셔서 감사하고, 다음 스터디 포스팅으로 뵙겠습니다.

 

잘못된 내용의 지적은 언제든 환영합니다. 감사합니다.

 

 

 

 

- 참고 문서

MDN Web docs - arguments.callee

GoogleWeb Fundamentals - Reduce JavaScript Payloads with Tree Shaking

httparchive.org - State Of Javascript

반응형

댓글