Utility First, Component Second

,

유틸리티 우선(Utility-First) 방법론은 종종 “유틸리티 클래스를 잔뜩 쓰는 방식”으로 이해됩니다. 그러나 이 방법론에서 또 하나의 핵심은 컴포넌트를 언제 만들 것인가라는 시간축 관점에 있습니다.

tailwindcss의 유틸리티 우선 방법론은 우선 유틸리티 클래스로 제품을 빠르게 만들고, 중복이 실제로 발생하는 바로 그 시점에 컴포넌트를 구성하라는 전략으로 이해돼야 합니다.

이렇게 이해한다면 유틸리티 우선 방법론은 초기 CSS가 마주했던 문제를 해결하기 위해 등장한 객체 지향 CSS와 아토믹 CSS를 종합하고 계승‧발전시킨 방법론으로 위치지을 수 있습니다.


규모가 커질수록 유지보수를 염두에 둔 CSS 설계가 결정적으로 중요해집니다 그러지 않으면 악몽이 벌어질 수 있습니다.

CSS 등장 초기에 유행한 방법은 내용에 기반한 네이밍으로 재사용성을 떨어뜨려 CSS 파일이 점점 커지게 만들었고, 클래스 중첩 정의를 통해 특정도(Specificity) 충돌을 일으키거나 클래스를 위치 종속적으로 만들어 유지보수성을 떨어뜨렸습니다. 최악의 경우 이쪽을 고쳤더니 엉뚱한 곳이 깨지는 일이 발생했습니다.

훌륭한 CSS 개발자들은 오래전부터 이 문제를 해결하기 위해 여러 방법을 개발했습니다. 제가 여기서 언급할 것은 객체 지향 CSS(OOCSS)와 아토믹(Atomic) CSS, 유틸리티 우선(Utility-First) 방법론입니다. 여러 방법론이 있지만 이 세 가지가 가장 대표적이라고 봅니다.

이 글은 객체 지향 CSS가 컴포넌트를 통해 문제를 해결했음을 밝히고, 뒤따라온 난점인 컴포넌트 범위 잡기의 어려움에 대해 설명합니다.

아토믹 CSS는 객체 지향 CSS와는 다르게 컴포넌트를 대체로 배제하고 CSS 정의를 원자화(유틸리티 클래스)함으로써 초기 방법의 문제를 해결하려 했습니다. 그러나 이는 유지보수성이 하락하는 문제를 낳았습니다.

유틸리티 우선 방법론은 아토믹의 유틸리티를 흡수하고, 객체 지향의 컴포넌트를 긍정하되 범위를 잡는 시점을 늦춤(Component Second)으로써 두 방법론의 문제를 해결했습니다.

저는 그간 tailwindcss의 유틸리티 우선 방법론에서 컴포넌트 구성 시점 문제가 충분히 강조돼 오지 않았다고 생각합니다. 그래서 유틸리티 우선 방법론과 아토믹 방법론이 차이가 없는 것처럼 이야기되기도 합니다. 그러나 유틸리티 우선 방법론은 컴포넌트를 되도록 배제하는 것이 아니라 긍정한다는 점에서 차이가 있습니다.

또한 유틸리티 우선 방법론은 객체 지향 CSS의 컴포넌트라는 장점을 흡수하면서도 구성 시점이라는 시간축 관점을 제시함으로써 CSS의 난제를 해결하는 데 커다란 기여를 했다고 봅니다.

결국 저는 Utility-First에서 중요한 것은 Utility뿐만이 아니라 First라고 주장하려고 합니다.

가장 중요한 원칙: 단일 진실 공급원

우선 단일 진실 공급원(Single Source of Truth) 원칙에 대해 설명하고 OOCSS와 Utility-First가 각각 이를 어떻게 구현했는지 설명합니다. 이를 통해 단지 기법이 아니라 원리에 대해 이해할 수 있게 될 것입니다.

좋은 설계를 위해 가장 중요한 원칙은 단일 진실 공급원 원칙입니다. CSS 차원에서 말한다면 하나의 모양은 하나의 클래스에서 나와야 한다는 것입니다.

객체 지향 CSS는 컴포넌트를 통해 이를 구현했습니다. 전체 사이트를 여러 개의 컴포넌트로 구성하면, 하나의 수정이 필요할 때 딱 한 군데의 코드만 고치면 되니 재사용성과 유지보수 효율성이 극대화됩니다.

예컨대 부트스트랩의 버튼 컴포넌트가 그렇게 작성돼 있습니다. 모든 버튼은 .btn과 그 변형태(btn-primary, btn-secondary…)로 구성되고 다른 버튼 정의는 존재하지 않습니다.

만약 전체 버튼의 모서리를 좀더 둥글게 하고 싶다면 .btn 클래스만 고치면 됩니다. Primary 버튼을 고치고 싶다면 .btn-primary 클래스 딱 하나만 고치면 됩니다. 유지보수 효율이 극대화됩니다.

또한, 하나의 모양이 하나의 공급원에서 나오게 하려면 비슷한 모양을 만들 때 이미 존재하는 클래스를 사용하므로 구현 속도가 빨라집니다. 우리에게 .btn 클래스가 있다면 버튼을 구현하기 위해 머리를 싸맬 필요가 없는 것이죠.

아토믹 CSS는 (대체로) 하나의 속성에 하나의 클래스라는 기치를 통해 단일 진실 공급원 원칙에 접근했습니다. 이는 유틸리티 클래스라 부르는 클래스 모음을 통해 구현됩니다.

예컨대 너비 50%라는 정의는 오직 .w-50이라는 클래스에만 존재하게 하는 것이죠. 아토믹은 이를 통해 CSS가 점점 커지고, 수정할 때마다 방대한 CSS 더미를 뒤져야 하는 수고를 없애고자 했습니다.

아토믹은 초기 개발 속도에서 상당한 이점이 있습니다. 이미 존재하는 유틸리티 클래스들이 있기에 CSS를 전혀 작성하지 않고도 빠르게 제품을 완성할 수 있었습니다.

컴포넌트 vs 유틸리티 → 종합

객체 지향 CSS: 범위 설정의 어려움

객체 지향 CSS는 여러 개의 객체, 그 중에서도 재사용 컴포넌트들을 구성하고 이를 조합해 사이트를 만듭니다. 앞서 본 부트스트랩의 버튼이 대표적인 컴포넌트입니다. 객체 지향 CSS는 초반에 객체를 일일이 코딩할 때는 속도가 느립니다만, 객체가 대부분 완성되고 난 뒤에는 속도가 매우 빨라집니다.

그러나 객체 지향 CSS를 사이트에 전격 도입하게 되면 방법론이 답해주지 않는 질문을 마주하게 됩니다.

첫째, 재사용되지 않을 것이 분명한 것을 컴포넌트로 만들고 있어야 하는가 하는 문제입니다. 헤더, 내비게이션, 푸터가 대표적입니다(다른 경우도 많습니다).

두 번째 의문이 곧 따라옵니다. 컴포넌트의 경계를 어떻게 설정해야 하는가입니다. 재사용 가능성이라는 기준이 있지만 쉽지 않고, 당장 재사용되지 않는 컴포넌트에 대해서는 기준이 쓸모 없습니다.

예컨대 내비게이션 전체가 컴포넌트여야 할까요? 아니면 내비게이션 레이아웃과 내비게이션 아이템은 구분되는 컴포넌트로 구현해야 할까요?

즉, 객체 지향 CSS의 두 난제는 범위 설정 문제라고 할 수 있습니다.

  1. 컴포넌트 제작 범위: 모든 것을 컴포넌트로 만들어야 하는가?
  2. 컴포넌트 자체의 범위: 컴포넌트를 얼마나 크게 혹은 얼마나 작게 만들어야 하는가?

1번 질문은 실용적인 타협책이 존재합니다. 모든 것을 컴포넌트로 만들거나, 재사용될 것들만 컴포넌트로 만들고 나머지는 전통적 방법론을 따르는 것입니다. 이 역시 생산성을 떨어뜨리기는 하지만 크지 않습니다. 물론 타협에 불과해, 진정한 해결은 유틸리티 우선 방법론이 제공하게 되지만 말입니다.

그런데 2번 질문은 생산성을 실질적으로 떨어뜨립니다.

예컨대 아래와 같은 디자인이 있습니다. 컴포넌트는 몇 개나 설정해야 할까요? 즉, 어디부터 어디까지를 컴포넌트로 지정해야 할까요?

아래처럼 크게 잡을 수도 있습니다.

그러나 아래처럼 세부적으로 잡을 수도 있을 겁니다. (컴포넌트를 내부적으로 세 부분으로 나눈다는 뜻이 아니라 독립적인 세 개의 컴포넌트를 만든다는 뜻입니다.)

이렇게 작은 컴포넌트 3개로 나누면 제목 컴포넌트, 책 나열 컴포넌트, 텍스트가 아래쪽부터 차올라오면서 말줄임표까지 달아주는 컴포넌트를 각각 다른 곳에서 재활용할 수 있을 것입니다.

그러나 실제 프로젝트에서는 그렇지 않았습니다.

이런 형태의 제목과 이런 표지 레이아웃, 이런 본문 텍스트가 나뉘어 사용되는 경우는 없었습니다. 제목과 표지와 본문은 프로젝트 전체에서 하나로 묶여 있었죠.

그래서 저는 curation-box라는 이름을 붙여 크게 잡은 컴포넌트(빨간색으로 범위를 잡은 첫 번째 이미지)를 구현했습니다.

이를 통해 우리는 컴포넌트의 범위를 잡는 것이 구체적 상황에서 상당히 어렵다는 것을 알 수 있습니다.

다시 말해, 객체 지향 CSS의 가치는 재활용을 극대화하는 데서 오는데, 이것이 쉽지 않습니다.

처음 만드는 프로젝트에서는 그나마 낫습니다. 대부분의 디자인이 나와 있는 상태에서 시작하므로 디자인을 보고 공통 요소를 뽑아내면 그럭저럭 객체의 범위를 잡을 수 있습니다.

그러나 미래의 유지보수에 대해서는 방법이 없습니다. 미래의 재사용 가능성은 합리적으로 예측할 수 없기 때문입니다. 유지보수를 감안하면 재사용 가능성 측정은 경험에 의존한 추측이 될 수밖에 없습니다.

미래를 오판해 재사용되지도 않을 컴포넌트를 만드는 것은 오히려 비효율을 낳습니다.

원자적(Atomic) 해결책

유틸리티 우선 방법론이 등장하기 전 먼저 원자적(Atomic) 방법론이 등장합니다. 아토믹은 그 자체로 본다면 초기 방법의 문제를 해결하려는 다른 접근이었습니다.

그러나 객체 지향 CSS의 범위 잡기 문제라는 관점에서 해석한다면 아토믹은 객체 지향 CSS의 난제를 알렉산더의 매듭 풀기 방식으로 해결하려는 시도였습니다 그러니까, 컴포넌트를 모두 분해해 유틸리티만 남김으로써 문제 자체를 없앤 것이죠.

아토믹 CSS는 ‘하나의 속성은 단 하나의 클래스에서만 등장한다’는 관점으로 단일 진실 공급원을 구현했습니다.

아토믹 CSS는 빠른 초기 개발 속도를 보장합니다. 필요한 모든 객체가 완비돼 있으므로 CSS를 작성할 필요조차 없죠.

그러나 유틸리티 클래스로는 한계에 부딪힙니다. 위에서 언급한 .curation-box는 클래스 몇 개만으로 훌륭한 디자인을 구현합니다. 그러나 이 박스를 유틸리티 클래스로만 구현하면 많은 클래스가 필요합니다.

완전히 동일한 모양만 있으면 좋을 텐데, 시간이 흐르면서 모양이 조금만 다른 여러 개의 큐레이션 박스들이 생겨났고 이를 한꺼번에 고쳐야 하는 상황이 발생합니다.

조금만 다른 여러 큐레이션 박스는 이제 더이상 전체 찾기 바꾸기로 처리할 수 없습니다. 그렇다고 .Mend-10의 내용을 margin-right: 5px로 바꿀 수도 없습니다.

이제 큐레이션 박스를 유지보수하기 위해 모든 소스를 뒤지며 일일이 수정해야 합니다.

즉, 다시 단일 진실 공급원 문제를 마주하게 됩니다.

유틸리티 우선 방법론의 종합

객체 지향과 아토믹 모두의 후손인 유틸리티 우선 방법론은 두 가지 차원에서 난제를 해결했습니다.

첫째, 아토믹 CSS의 유틸리티를 흡수합으로써 프로젝트 시작 시점의 컴포넌트 범위 문제를 근본적으로 해결했습니다. 객체 지향 CSS에서 유틸리티는 부차적 지위였지만, 유틸리티 우선에서는 시발 주자입니다. 이제 컴포넌트 범위에 대한 고민 없이 곧장 유틸리티들로 코딩을 시작하면 됩니다.

둘째, 그러나 유틸리티 우선 방법론은 아토믹과 달리 컴포넌트를 긍정함으로써 단일 진실 공급원 문제를 해결했습니다. 대신 컴포넌트 구성 시점을 유틸리티 사용 이후로 늦춤으로써 컴포넌트를 크게 잡을지 작게 잡을지 선험적으로 생각할 필요가 없게 만들었습니다.

다시 말해 유틸리티 우선은 아토믹 CSS처럼 컴포넌트를 거부하지 않으면서도 객체 지향 CSS처럼 컴포넌트 범위 문제로 인해 고통받지도 않을 수 있습니다.

그래서 유틸리티 우선이라는 것은 중의적입니다. 유틸리티를 중시한다는 뜻이기도 하고, 컴포넌트 구성 시점을 늦춘다는 뜻이기도 합니다.

단일 진실 공급원과 시간축

유틸리티 우선 방법론을 사용하면 이런 식으로 개발하게 됩니다.

<div class="flex gap-4 p-4">

위와 같은 레이아웃 박스가 있었는데, 어느 날 패딩이 같은 레이아웃 박스가 생겼습니다.

<div class="flex gap-4 p-4">

<div class="flex gap-4 justify-between p-4">

이 코드에서 우리는 p-4가 우연인지 필연인지 판단해야 합니다.

만약 모든 상자의 패딩을 공통으로 넣는 것이 목적이라면 우리는 p-4를 별도의 객체로 만들어야 합니다. 한 군데만 고치면 모든 곳이 바뀌도록 아래와 같이 코딩해야 합니다.

<div class="flex gap-4 p-box-padding">

<div class="flex gap-4 justify-between p-box-padding">

이제 우리는 단일 진실 공급원을 찾아 구분해 냈습니다.

여기서 알 수 있는 것은 두 가지입니다.

  1. 단일 진실 공급원은 큐레이션 박스처럼 클 수도 있지만, 프로퍼티 하나만큼 작을 수도 있습니다.
  2. 코드가 같다고 같은 단일 진실 공급원인 것은 아닙니다. .p-4.p-box-padding의 내용은 완전히 같지만 서로 다른 단일 진실 공급원입니다.

다시 말해 맥락이 모든 것입니다.

범위는 숙명, 해법은 시점

CSS 개발자에게 객체의 범위 설정 문제는 숙명입니다.

유틸리티 우선 방법론은 객체 지향 CSS와 아토믹 CSS를 종합해 이를 우아하게 처리하는 방법을 제시했습니다.

유틸리티 우선 방법론은 컴포넌트를 부정하지 않는다고 말합니다. JS 컴포넌트나 CSS 컴포넌트, PHP 컴포넌트를 사용할 수 있다고 합니다. tailwind.css는 CSS 컴포넌트를 만드는 편리한 방법도 제공합니다(@apply).

단지 유틸리티가 우선(First) 사용돼야 한다는 것입니다. 다시 말해 유틸리티 우선 방법론은 컴포넌트 범위 설정 시점을 다중적으로 제시함으로써 개발자들의 인지적 부담을 덜었습니다.

이제 우리는 유틸리티들로 빠르게 시작한 뒤(Utility First) 코드가 중복되는 기미가 보이면 그 시점에 컴포넌트를 만들어야 합니다(Component Second).

저는 유틸리티 우선에서 컴포넌트 구성이 좀더 강조돼야 한다고 봅니다. “우리는 컴포넌트를 허용한다” 하는 기존의 목소리는 수세적으로 들립니다.

좀더 적극적으로 말해야 합니니다. “우리는 컴포넌트가 정말로 필요한 시점까지 미룸으로써 재사용성을 극대화한다” 하고 말입니다.

결론: 유틸리티 우선 방법론

코딩 방법론은 빠르게 구현하고, 효율적으로 유지보수하기 위해 존재합니다. CSS도 다르지 않습니다.

여러 원칙이 있지만, 가장 중요한 원칙은 단연 단일 진실 공급원 원칙이라고 생각합니다.

CSS에서는 단일 진실을 공급하기 위한 방법론이 발전해 왔는데요. 여러 방법이 있지만 객체(컴포넌트와 유틸리티)를 중심으로 살펴 봤습니다.

저는 tailwindcss의 Utility-First 라는 방법론 이름에서 Utility만 강조된 측면이 있다고 생각합니다. 그러나 First가 Utility 만큼이나 중요합니다. 컴포넌트를 구성할 시점이라는 관점을 도입해주기 때문입니다.

다시 말해 유틸리티 우선 방법론은 컴포넌트 구성을 선택이 아닌 시점의 문제로 전환함으로써 CSS 아키텍처의 난제를 해결한 것입니다.

카테고리

,

17년차 풀스택 웹 개발자 Mytory입니다

웹 개발에서도 중요한 것은 개념입니다.
이 블로그에는 제가 개발하며 익힌 개념들을 정리합니다.

워드프레스를 오래 다뤄 왔고 강의도 두 편 찍었습니다.
– 인프런 “워드프레스 제대로 개발하기 어드민 편, 클라이언트 편
– 클래스101 “누구나 할 수 있는 워드프레스 홈페이지 만들기 – 기획부터 출시까지 한 방에 OK

유튜브 채널에 워드프레스 관련 팁들을 올리고 있습니다.

👉 소개 더 보기

대표글

댓글 남기기