Young Blog

지금 CSS 레거시 코드를 쓰고 있나요?

CSS로 코드를 많이 작성해 본 사람이라면, 규모가 큰 레거시 코드를 가지고 작업하는 것이 얼마나 고통스러운 일인지 충분히 느꼈을 것이다. 다음에 하게 될 CSS 리팩토링 결과물이 아무런 문제 없이 안정적이게 돌아갈 것이라는 것을 어떻게 확신할 수 있을까? 부담스러운 레거시 코드를 만들어내는 것을 어떻게 하면 방지할 수 있을까?

그동안 나는 끔찍한 CSS 코드를 많이 썼다.

장기 프로젝트가 계속 진행될 수록, 코드 구조를 잘 정리해 놓는 것이 점점 더 어려워졌다. 과거에 내렸던 결정이 후에 가서는 나의 발목을 붙잡았는데, 고칠 시간을 내기가 어려웠다. 실질적인 문제를 직접적으로 해결하는 대신 땜빵 식으로 넘어갔기 때문에 선택자 구체성(specificity)이 스물스물 증가했다.

많은 프로젝트를 진행하면서 이런 식으로 대응하다가 다른 프로젝트로 옮겼기 때문에 내 선에서 골치 아픈 상황은 이 쯤에서 끝났다.

그러나 규모가 큰 팀에서 관리하는, 유지보수가 마치 영원히 지속될 것 같은 중요한 프로젝트에서 이런 식으로 일을 하면 상당히 귀찮아진다. 자잘한 기능들이 추가되면서 프로젝트 내내 기술적인 빚(technical debt)이 쌓여만 갔다. CSS 작성 규칙은 점점 바뀌었고, CSS 개발자들에게 행복한 천국을 약속하는 빛나는 신기술들이 등장했다. ‘모두들 값진 경험을 하고 있는 사이에, 나만 한눈팔아서 이것을 놓칠 것이라는 두려움’(신조어. Fear of Missing Out, 다른 사람들이 무엇을 하는지 계속 알고 싶어하는 욕망에서 비롯하는 사회적 불안 증상)이 사람들 사이에 널리 퍼졌고, 동시에 우리는 더 나은 세상에 관한 몽상을 하기 시작했다.

개발자로서 처음 일을 시작했을 때의 나는 단지 무언가 창조를 하고 싶었을 뿐이다. 그러나 팀에 소속되어서 더 큰 규모의 프로젝트에 몸을 담기 시작하면서, ‘레거시 코드’라는 것이 이러한 소망 실현을 방해하고 있다는 두려움에 사로잡히기 시작했다. 아, 팀장이 내가 이것들을 리팩토링 하게 놔두었더라면 얼마나 좋았을까!

나중에는 내가 팀장이 되었고, 과거의 나 자신이 레거시 코드를 만들어 내는 데 한 몫 했다는 것을 깨달았다.

리팩토링 작업 과정(Cycle of rewrites)

엄청난 양의 불만족스러운 CSS 코드를 보고 있으면, 우리의 본능은 당장 모든 코드를 재작성해야 한다고 유혹한다. 자신이 작성한 코드가 아닐수록 그렇게 하고픈 경향이 커진다.

다음은 내가 여러번 겪은 리팩토링 과정이다:

  1. 현재 보유하고 있는 CSS 코드가 불만족스러워진다. 코드 중복을 제거하고 성능을 향상시켜서 좀 더 간단하게 만들 필요가 있다고 생각한다.
  2. 조금만 고칠려고 시작한 작업인데, 결국엔 꽤 많은 양을 다시 만들게 된다. 뭐, 그래도 좋다!
  3. 새로운 기능을 추가하고, 기존 기능은 바꾸고, 팀원을 새로 뽑고, 새로운 CSS 프레임워크와 테크닉을 찾아 나선다.
  4. 시간이 흐른다…
  5. 이제 바꾸고 난 후의 CSS 코드가 불만족스러워지기 시작한다…

무슨 말인지 알 것이다. 진짜 문제가 무엇인지 깨닫기까지 위의 단계를 몇 번 겪었다.

작업의 초점이 너무 재작성/리팩토링을 정확히 하는 것이 맞춰져 있고 후에 팀에서 품질 유지를 어떻게 할지에 관해서는 충분한 논의가 이루어지지 않았다.

리팩토링 작업에는 참여하지 않았으나 후에 유지보수를 해야 하는 사람이라면 누구나 회의론적이고 비관적인 시각으로 과정을 지켜 볼 권리가 있다.

지금까지 했던 일들로 인해 결국 레거시 코드를 더 만들어 버렸다.

‘레거시’ 코드는 무엇을 뜻하는가?

일반적으로 ‘레거시’ 코드가 뜻하는 바는 누군가가 과거에 써 놨으며, 후에 온 개발자가 작업하기에 고통스러운 코드를 뜻한다. ‘레거시 시스템’은 대체해야 하거나 이미 대체되었지만, 완전히 들어내기에는 어려운 것을 지칭한다.

레거시 시스템은 지나간 세월동안 축적된 기술적인 빚의 무게에 짓눌려있다. 일부 경우에는 이제는 더 이상 아무도 흥미를 가지지 않는, 과거에 널리 사용된(old hat) 기술을 토대로 하고 있다. CSS 플렉스박스로 인해 플로팅으로 이루어진 레이아웃은 빠른 속도로 레거시가 되어 가고 있다.

그렇지만 레거시 시스템 대부분은 사랑과 보살핌으로 만들어진 것이다. 때로는 최초 개발자들이 여전히 유지보수 팀에 속해 있을 수도 있다.

나는 기존 코드 베이스를 봐야 할 때, 애자일 회고 제1지침을 마음속으로 되새겨 보는 것을 좋아한다. 이 지침은 기존 코드에 대해 째째하고 편협하게 구는 것을 방지해 준다. 또한 몇 달 후에 내가 작성한 코드를 다시 볼 때도 좀 더 마음 편하게 볼 수 있게 도와준다.

코드에서 무언가 불만족스러운 것을 보더라도, 이것은 그때 당시의 개발자들이 그 시절에 알던 지식과 기술, 능력, 자원, 상황을 고려하여 할 수 있는 한 최선을 다한 결과물이라고 생각하며 이를 진실로 믿는다.

‘레거시’를 다시 생각해보다.

나는 ‘레거시’라는 단어가 흔하게 쓰이는 것이 마음에 들지 않는다. 이 단어는 주관적이고 상대적이며, 상대에 대한 존경심도 없고 전혀 유용하지도 않다.

어떤 사람에게는 그저 레거시 코드 베이스에 불과한 것이 다른 누구에게는 커리어 상에서 가장 훌륭한 업적일 수도 있다(이러한 경우는 레거시 코드가 잘 짜여 있다). 코드가 오래되었다고 해서 그것이 우아하지 않거나, 잘 조직되어 있지 않거나, 성능이 떨어진다거나 또는 이해할 수 없다는 것은 아니다. 반대로 새로 작성된 코드라고 해서 엉망이 아니라는 보장도 없다.

마이클 페더스(Michael Feathers)는 그가 2004년에 쓴 책 레거시 코드와 효율적으로 작업하기에서 ‘레거시 코드’에 대한 여러가지 정의를 내렸다. 책에서 정의내린 바에 따르면, 레거시 코드는 테스트를 통한 검증이 되지 않은 코드이다. 바로 그거다. 잠시 생각을 해보면, 맞는 말이라는 것을 알 수 있다. 오래되거나 새로운 코드 모두 해당된다. 코드가 유지 보수 가능한지, 그리고 이해 할 수 있는지에 초점을 맞춘 정의다. 테스트를 거치지 않은 코드는 확신을 가지고 바꾸기가 어렵다.

비록 페더스는 자바와 같은 강제적(imperative) 객체 지향 언어를 중심으로 설명했지만, 레거시 코드에 대한 그의 주장을 CSS에도 똑같이 적용해서 생각해 볼 수 있다. 다음 불만사항은 자바나 자바스크립트에 적용되는 것 만큼 CSS에도 똑같이 해당된다.

  • 코드의 특정 부분을 테스트 하지 않는다면, 그것이 어떻게 동작하는지 파악하기 어려워진다.
  • 코드는 돌아갈지 몰라도, 왜 돌아가는지 확실히 알지 못한다.
  • 심지어, 최종적인 시스템 작동(behaviour)을 만들기 위해 조합해야 할 코드 베이스가 어떠한 것들인지 모른다.
  • 흥미로운 기능이 돌아가게 하는 바탕이 되는 코드를 추적하는 일이 두렵거나 시간만 낭비할 가능성이 있다.

그래서 레거시 코드에 대한 이러한 개념이 어떻게 CSS 내에서 유용하게 사용될 수 있을까?

지금 하는 리팩토링으로 인해 새로운 레거시 코드가 나올 수 있다.

마지막으로 CSS를 리팩토링하던 때를 떠올려보자. 리팩토링으로 인해 망가진 코드가 하나도 없는지 어떻게 확신했을까? 직접 테스트를 해봐서 알았나? 아니면 이미지 diff를 사용하는 시각적인 회귀 추정 방식으로 변경사항을 추적해봤나? 크로스 브라우징 및 디바이스 테스트는 얼만큼 했나?

원래 의도한만큼 테스트를 하지 않았다고 인정하는 것은 괜찮다. 다만 CSS 코드 베이스가 제대로 구조가 잡히지 않으므로써 생기는 문제점은 후에 골치 덩어리로 발전할 가능성이 있다. 우선적으로는 문제가 되는 부분을 일단 어떻게든 다른 식으로 대체함으로써 이러한 후일의 문제점을 미연에 방지할 수 있다. 그리고 이미 많이들 이렇게 해오고 있다.

직접 크로스 브라우징 테스트를 일일이 하는 것은 시간이 너무 많이 들고, 동일 조건 하에서 반복적으로 수행하기가 어렵다. 과거에는 레이아웃과 디자인 테스트를 자동화 하는 것은 수행하기 어렵고 그 자체가 흔치 않은 일이라고 여겨졌다.

지금까지 리팩토링을 큰 규모로 하거나 코드 재작성을 해오면서, 코드상의 명백한 문제점을 거의 다 고쳤을 것이다. 그러나 막 새로 만든, 우리의 빛나는 코드 창조물이 지금까지 고쳤던 과거의 것처럼 엉망진창 얽힌 머리카락 뭉치(hairball)가 되지 않게 하려면 어떻게 해야할까?

마이클 페더스가 내린 정의에 따르면, 코드가 제대로 돌아가도록 테스트를 제때 하지 않는 이상, 새로 뭔가를 해봤자 레거시 코드만 더 만들어버린 것에 불과하다. 그때 당시에는 상황이 나아졌다고 느낄지도 모르지만, 결국엔 우주에 존재하는 다른 모든 것들과 마찬가지로 코드의 엔트로피 또한 증가할 것이다.

어떻게 하면 코드가 서서히 부패하는 것을 방지할 수 있을까?

CSS는 팀 스포츠다.

내가 작성한 코드를 이어받아 일할 후임(혹은 6개월 후의 나 자신)은 넘겨받은 코드를 이해하고 작성 방식을 그대로 따라해야 할 것이다. 이들이 구체성의 늪에 빠지지 않게 하려면 어떻게 해야할 지 생각해 보아야 한다. 이 작업으로 인해 코드의 다른 부분에는 별다른 영향이 없을 것이라고 어느 정도 확신할 수 있어야 할 것이다.

CSS를 작성하는 데는 팀 단위의 협동이 필요하며, 많은 사람들이 후일에 이 코드를 읽고, 기여할 것이라는 것을 염두해 두어야 한다. 현재는 나 혼자만 책임을 지고 있는 코드라 하더라도, 나중에는 누가 유지보수를 맡게 될지 어찌 알겠는가?

BEM, SMACSS, ITCSS와 같이 최근에 나온 CSS 설계 방법론은 개인 뿐만 아니라 팀 단위의 유지 보수성을 높여준다. 이 방법론들은 구체성을 낮추고, 모듈화를 이끌며, 네이밍을 일관성있게 유지할 수 있는 특정 설계 패턴을 알려준다. 덕분에 코드의 가독성이 높아져서 코드를 읽고 이해해야 하는 사람들에게 도움이 된다. 설계 패턴으로 인해 작성 방식에 제약이 가해지기 때문에 어떤 식으로 코드를 유지보수 할지 감을 잡을 수 있다.

그러나 가장 중요한 것은, 작업에 참여하는 사람들이 코드에 대해 일관적인 접근 방식을 취하는 것에 동의하고 계속해서 그 기준을 준수하며 작업을 하는 것이다. 그럼으로써 팀원들이 전부 동일한 방식으로 작업을 하고, 장기적으로는 표준을 유지하는 데 기여하고 있음을 명백하게 확신할 수 있다.

리팩토링 작업을 할 때는, 지금 당장 코드가 돌아가는 데 집중하는 것보다 더 큰 그림을 볼 필요가 있다. 작업이 끝난 후에도 장기적으로 자기 자신 뿐만 아니라 다른 사람들이 유지보수를 할 것이라는 것을 고려해야 한다.

항상 리팩토링하라

지금까지 대규모의 CSS 리팩토링 작업에 참여한 결과 알게된 것은 몇가지 일들이 패턴을 형성하면서 일어난다는 것이다. 처음으로 리팩토링해야 하는 코드를 보게 되면, 그 전에 그 코드가 쓰여지는 과정에는 참여하지 않았으므로 굉장히 거대한 덩어리를 마주한 느낌일 것이다. 그렇게 많은 양의 코드를 재작성한다는 것은 항상 고통스럽기 마련이다. 사용자들이 실제로 마주하게 되는 부분은 매우 적은 기능을 만들어내기 위해 개발자들은 거의 항상 작업 일정과 완성도 사이에서 타협을 봐야 한다. 그러고 나서 18개월 후에는 또 다른 재작성 문제와 마주하게 된다.

이러한 일이 일어나는 이유 중 하나는 테스트에 들어가는 간접 비용 문제 때문이다. 아무리 작은 변화라도 그것이 후일 아무런 파급 효과를 불러일으키지 않을 것이라고 검증하는 데는 시간이 걸린다. 그렇기 때문에 팀원들은 해야 하는 일들을 놔두고 굳이 작게나마 코드를 개선하는 데 소극적인 것이다.

이러한 소규모 개선은 대부분 흐지부지 미완성으로 끝난다. 코드에 불확실성과 비효율성이 스며들기 시작한다. 기술적인 부채는 쌓여만 가고, 그 결과 또 다른 코드 뭉치(hairball)만 만들어질 뿐이다.

따라서 작업 과정 중간 중간 작은 규모의 리팩토링 작업을 동반해야만 한다. 이는 새로운 기능을 만드는 과정의 일환으로 여겨지므로, 나중에 따로 시간을 내서 해야만 하는 잔업 때문에 골머리를 앓을 필요가 없어진다.

리팩토링을 개발 작업에 포함하려면, 기능이 어떻게 변화되는지 테스팅하는 작업이 덜 까다로워야 한다. 제일 좋은 경우는 테스팅의 자동화가 이루어졌을 때다.

새로운 레거시 코드 생성을 막기 위한 방편

CSS 리팩토링에 관한 글들은 특정 리팩토링 기법에 초점을 맞춘것이 대부분이다. 테스팅을 코드 변경에 대한 안전망의 역할로써 다루는 글은 거의 없었다. 임기응변식의 수동적인 테스팅 방법이나 시각적인 회귀 분석 테스트를 다룬 글들은 Wraith, PhantomCSS, BackstopJS와 같은 이미지 diff 툴에 대해 얘기한다. 이러한 툴들은 환경 설정하는 데 시간이 좀 걸리지만, 최종적으로 보여져야 할 기대값인 ‘참조(reference)’ 이미지와 현재의 페이지, 또는 컴포넌트의 상태가 어떻게 다른지 빨리 파악할 수 있게 해준다. 시각적인 회귀 측정 방법을 사용해서 리팩토링한 코드가 현재의 코드와 동일한 시각적 결과물을 만들어내는지 테스트해 볼 수 있다.

시각적 회귀 측정 방식을 통해 무엇인가 바뀌었음을 알 수 있다. 테스트 자체로는 시스템이 어떻게 동작할 것이라는 예상에 대한 설명이 불가능하다. 테스트가 실패했더라도, 여기서 얻은 시각적인 diff 결과물을 토대로 원래 예상했던 레이아웃은 어떤 것이였으며, 또한 이것이 현재 리팩토링한 코드를 통해 어떤 부분이 다르게 나타나는지에 대한 파악을 해야 한다. 직접 크로스 브라우징 테스트를 하는 것보다 훨씬 더 안정적이고, 정밀하며 속도도 빠른 방법이다.

이와는 반대로, 대부분의 다른 자동화된 테스트 방법은 구체적인 예측을 토대로 시스템의 동작(behaviour)을 비교한다. 시스템 전체(end-to-end 테스트), 또는 개별적인 코드 단위(유닛 테스트)로 테스트를 진행한다. 예를 들어 Cucumber 프레임워크를 사용하면 요구사항을 평문 형태로 제공함과 동시에 자동화된 단말 테스트를 할 수 있다. 자동화 테스트만큼 팀내 소통과 협동에 무게를 둘 수 있다.

Galen 프레임워크는 사람이나 기계가 읽을 수 있는 요구사항을 바탕으로 반응형 레이아웃 테스트를 할 수 있는 방법을 제공한다. 여러 브라우저와 기기에서 테스트를 진행할 수 있으며, BrowserStack이나 Sauce Labs와 같은 클라우드 테스팅 서비스도 사용할 수 있다. 이 모든 방법들을 지속적인 통합 업무(continuous integration task)에 포함하여 누구든지 코드 변경사항을 커밋하더라도 테스트를 진행할 수 있도록 해준다. 또한 위의 방법은 각각 개별적으로 사용해도 되고, 시각적 회귀 테스트와 조합하여 사용해도 된다.

가장 중점을 두어야 할 것은, 스스로 혹은 팀원들과 같이 어떻게 하면 CSS 변경사항을 충분히 빠르게 테스트하여 이에 드는 시간과 비용을 최소로 할 수 있을 지에 대해 생각해 보는 것이다.

자동화가 테스팅의 전부는 아니다.

시각적 회귀 테스트나 레이아웃 요구사항 테스트를 하는 것도 좋지만, 더 큰 그림을 그려볼 필요가 있다.

테스트 케이스로 사용 가능한 디자인 시스템

사이트 전체를 대상으로 디자인 테스트를 진행하는 것은 대부분 모두들 피하고 싶어한다. 그러한 테스트를 통해 얻는 것은 정말로 간단하게 생긴 웹사이트 뿐이기 때문이다. 이런 경우 공용 스타일 가이드나 디자인 시스템을 유용하게 사용할 수 있다. 만약 모든 컴포넌트가 URL을 가지고 있다면, 콘텐츠가 자주 바뀌는 사이트 보다는 예측가능한 환경 안에서 테스트를 하는 것이 가능하다.

테스트 가능한 스타일 가이드 사이트를 하나 마련하라. 엄청나게 복잡한 사이트일 필요는 없다. styleguides.io에 도움이 될만한 자료가 많이 있다.

코드 스타일, 린팅(linting), 코드 리뷰 합의하기

자동화된 레이아웃 테스트나 시각적인 회귀 측정 테스트를 통해 알 수 있는 것은 컴포넌트가 예측대로 잘 렌더링 되는지에 대한 사실여부 뿐이다. 팀 내의 작업 방식이 안정성있게 운용되고 있다고 확신하려면 그 이상으로 무언가 더 할 필요가 있다. 리팩토링으로부터 얻을 수 있는 혜택을 오래도록 누리고 싶다면, 모두가 동의하는 공용 코딩 스타일을 마련해야 한다. 그리고 코딩 스타일이 잘 적용되고 있는지 알아보기 위해 정기적으로 코드 리뷰 시간을 가져야 한다.

한번 도전해보자

CSS 코드 파일을 관리하고 있다면, 기본적으로 해야할 일은 품질을 최상으로 유지하면서 레거시 코드 생성은 최소로 하는 작업 과정을 형성하는 것이다.

장기적으로 CSS 코드 구조를 좋게 유지하기 위해서는 설계를 잘 하는 것 이상의 노력이 필요하다. 다른 사람들로 하여금 이러한 작업에 참여하게끔 이끌어야 한다. 디자인 시스템, 코딩 스타일 가이드라인, 코드 리뷰, 테스팅, CSS 설계 패턴을 적절히 취사선택하고 코드 기여자들 모두가 장기적으로 좋은 품질의 코드를 생산해 내는데 집중할 수 있도록 도움을 주자.

코드 기여자들 스스로 이러한 환경을 설정 하는 것은 그다지 어렵지 않다. 아니면 그들에게 하나씩 차례로 과정에 대해 설명해주는 것도 괜찮은 방법이다. 사려깊은 자세로 개발자들을 대한다면 다음 리팩토링 결과물 또한 그만큼 깊이 있고 미래 지향적이게 될 것이다.

Comments