레이아웃 패턴까지 익히고 나니, 스타일 자체는 어느 정도 자유롭게 다룰 수 있게 되었다. 그런데 프로젝트가 커질수록 새로운 문제가 나타났다. 스타일 파일이 수천 줄로 불어나면서, 어떤 규칙이 어디에 있는지 찾기 어려워지고, 하나를 고치면 엉뚱한 곳이 깨졌다. 코드를 쓰는 능력과 그것을 질서 있게 유지하는 능력은 별개였고, 나는 후자가 부족했다.
이번 편은 스타일을 큰 규모에서 질서 있게 유지하는 방법론과 구조화를 다룬다. 왜 방법론이 필요한지, 이름으로 구조를 드러내는 규칙, 작은 목적의 클래스를 조합하는 접근, 우선순위를 층으로 관리하는 기능, 그리고 규칙을 중첩해 정리하는 방식까지 살펴본다. 이 도구들을 익히면 스타일이 아무리 커져도 통제 가능한 상태를 유지할 수 있다.
1. 왜 방법론이 필요한가
혼자 작은 페이지를 만들 때는 방법론이 필요 없다. 규칙이 몇 개 되지 않으니 어디에 뭐가 있는지 다 기억할 수 있다. 하지만 프로젝트가 커지고 여러 사람이 함께 작업하기 시작하면 상황이 달라진다. 나는 스타일이 일정 규모를 넘어서면서, 아무 원칙 없이 규칙을 쌓아 올린 대가를 혹독하게 치렀다. 방법론은 이 혼란을 막는 약속이다.
가장 큰 문제는 우선순위의 충돌이다. 규칙들이 서로 이기려고 점점 강한 선택자를 쓰게 되고, 결국 아무도 예측할 수 없는 우선순위 전쟁이 벌어진다. 나는 이 전쟁을 여러 번 겪었다. 스타일 하나를 바꾸려는데 그 규칙이 다른 강한 규칙에 눌려 먹히지 않고, 그걸 이기려고 또 강한 규칙을 더하는 악순환이었다. 방법론은 이 악순환을 끊는다.
또 다른 문제는 스타일의 파급 범위를 알 수 없다는 것이다. 하나의 규칙이 의도한 곳 말고 어디에 또 영향을 주는지 모르면, 수정이 두려워진다. 나는 어떤 클래스를 고쳤다가 전혀 예상 못 한 페이지가 깨지는 경험을 하고 나서, 스타일의 영향 범위를 명확히 하는 것이 얼마나 중요한지 배웠다. 좋은 방법론은 이 범위를 예측 가능하게 만든다.
방법론의 핵심 목표는 우선순위를 낮고 평평하게 유지하는 것이다. 규칙들이 비슷한 세기를 가지면, 나중에 선언된 것이 이긴다는 단순한 규칙만으로 결과를 예측할 수 있다. 나는 이 평평함이 대부분의 방법론이 공통으로 추구하는 가치라는 걸 알았다. 어떤 방법론을 택하든, 우선순위를 통제 가능하게 만드는 것이 근본 목적이다.
방법론은 또한 이름을 통해 구조를 소통하는 수단이다. 클래스 이름만 봐도 그것이 무엇이고 어디에 속하는지 알 수 있다면, 코드를 처음 보는 사람도 구조를 파악할 수 있다. 나는 이름이 곧 문서라는 관점에서 방법론을 바라본다. 잘 지은 이름은 별도의 설명 없이도 그 코드가 무엇을 하는지를 스스로 말해준다.
어떤 방법론도 완벽하지는 않으며, 각자 장단점이 있다. 중요한 것은 팀이 하나의 원칙에 합의하고 일관되게 지키는 것이다. 나는 방법론 자체의 우열보다, 정한 원칙을 끝까지 지키는 일관성이 더 중요하다고 생각한다. 제각각인 훌륭한 규칙보다, 통일된 평범한 규칙이 큰 프로젝트에서는 대개 더 잘 작동한다.
2. 이름으로 구조를 드러내기
널리 쓰이는 방법론 하나는 클래스 이름에 구조를 담는 명명 규칙이다. 독립적인 덩어리, 그 안의 요소, 그리고 변형된 상태를 이름의 형태로 구분하는 것이다. 나는 이 규칙을 따르면서, 클래스 이름만 봐도 그것이 어떤 덩어리의 어느 부분인지, 어떤 변형인지 즉시 알 수 있게 되었다. 이름이 구조를 그대로 반영하는 것이다.
이 규칙에서 덩어리는 독립적으로 존재할 수 있는 컴포넌트를 뜻한다. 카드나 메뉴처럼 그 자체로 의미 있는 단위다. 나는 먼저 화면을 이런 독립적인 덩어리들로 나눈다. 각 덩어리는 자기만의 이름을 갖고, 다른 덩어리와 섞이지 않는다. 화면을 독립적인 조각들로 분해하는 이 사고방식이, 구조화된 스타일의 출발점이 된다.
덩어리 안의 부분들은 그 덩어리에 속한 요소로 표현한다. 카드의 제목이나 본문처럼, 덩어리를 이루는 구성 요소들이다. 이름에서 이 요소가 어떤 덩어리에 속하는지가 드러나므로, 다른 덩어리의 같은 이름 요소와 헷갈리지 않는다. 나는 이 소속 관계가 이름에 명시되는 것이, 큰 프로젝트에서 특히 유용하다고 느낀다.
변형은 덩어리나 요소의 다른 상태를 나타낸다. 강조된 카드나 비활성화된 버튼처럼, 기본형에서 변형된 모습이다. 나는 변형을 별도의 표시로 이름에 담아, 기본형과 변형을 명확히 구분한다. 그러면 하나의 덩어리가 가질 수 있는 여러 모습이 이름으로 정리되어, 어떤 변형들이 존재하는지 한눈에 파악된다.
이 명명 규칙의 큰 장점은 우선순위가 평평하게 유지된다는 것이다. 대부분의 규칙이 클래스 하나로 표현되므로 세기가 비슷하고, 그래서 서로 예측 가능하게 덮어쓸 수 있다. 나는 이 평평함 덕분에 우선순위 전쟁에서 벗어날 수 있었다. 이름은 길어지지만, 그 대가로 얻는 예측 가능함이 훨씬 값지다.
이 규칙은 빌드 도구 없이도 쓸 수 있다는 점에서 진입 장벽이 낮다. 별도의 설정이나 도구 없이, 이름 짓는 약속만 지키면 된다. 나는 간단한 프로젝트에도 이 규칙을 적용해서, 규모가 작을 때부터 구조를 잡아둔다. 처음부터 구조를 갖춰두면, 나중에 프로젝트가 커져도 큰 개편 없이 자연스럽게 확장할 수 있다.
3. 작은 목적의 클래스를 조합하기
전혀 다른 접근도 있다. 하나의 클래스가 하나의 작은 목적만 수행하게 만들고, 그런 클래스들을 여러 개 조합해 스타일을 완성하는 방식이다. 간격을 주는 클래스, 색을 주는 클래스, 정렬하는 클래스를 각각 만들어 요소에 함께 붙이는 것이다. 나는 이 방식을 처음 봤을 때 낯설었지만, 익숙해지니 나름의 강력함이 있었다.
이 방식의 장점은 빠른 작업 속도다. 새 스타일 규칙을 쓰지 않고, 이미 만들어진 작은 클래스들을 조합하기만 하면 되기 때문이다. 나는 이 방식으로 마크업 안에서 스타일을 바로 조합하며, 별도의 스타일 파일을 오가지 않고도 화면을 빠르게 만들 수 있었다. 시제품을 빠르게 만들어야 할 때 특히 유용하다.
또 다른 장점은 새 스타일이 거의 늘지 않는다는 것이다. 작은 클래스들은 재사용되므로, 화면을 아무리 많이 만들어도 스타일 파일의 크기가 크게 늘지 않는다. 나는 명명 규칙 방식에서 컴포넌트마다 새 규칙이 쌓여 파일이 불어나던 것과 대조적이라고 느꼈다. 조합 방식은 정해진 클래스 집합을 계속 재사용한다.
이 방식에도 단점은 있다. 요소에 클래스가 잔뜩 붙어, 마크업이 클래스의 벽처럼 보인다는 것이다. 클래스가 많아지면 마크업만 봐서는 이게 무슨 컴포넌트인지 알기 어렵다. 나는 이 점이 조합 방식의 가장 큰 약점이라고 본다. 스타일은 간결해지지만, 그 대가로 마크업의 가독성이 희생되는 것이다.
이 방식은 대개 자주 쓰는 값들을 미리 정해진 체계로 제공한다. 임의의 값이 아니라 정해진 간격, 색, 크기 중에서 고르게 하는 것이다. 나는 이 제약이 오히려 일관성을 강제한다는 점이 마음에 든다. 개발자가 아무 값이나 쓰는 대신 정해진 체계 안에서 고르니, 화면 전체의 값들이 자연스럽게 통일된다.
어느 방식이 더 나은지는 프로젝트에 따라 다르다. 나는 재사용되는 명확한 컴포넌트가 많으면 명명 규칙을, 빠른 반복과 실험이 중요하면 조합 방식을 고려한다. 둘 중 하나만 고집하기보다, 프로젝트의 성격과 팀의 익숙함을 따져 고르는 것이 현명하다. 도구는 목적을 위한 수단일 뿐, 그 자체가 목적이 아니다.
4. 우선순위를 층으로 관리하기
방법론과 별개로, 우선순위 자체를 다루는 강력한 기능이 표준에 들어왔다. 규칙들을 명시적인 층으로 나누고, 층의 순서로 우선순위를 정하는 것이다. 나는 이 기능을 알고 나서, 선택자의 세기로 다투던 우선순위 문제를 훨씬 깔끔하게 다룰 수 있게 되었다. 층으로 나누면 세기 싸움 자체가 필요 없어진다.
이 층 기능의 원리는 선택자의 세기보다 층의 순서가 먼저 고려된다는 것이다. 나중에 선언된 층의 규칙은, 선택자가 아무리 약해도 앞선 층의 강한 규칙을 이긴다. 나는 이 원리가 우선순위를 다루는 방식을 근본적으로 바꿨다고 느낀다. 이제 규칙이 이기게 하려고 선택자를 강하게 만들 필요 없이, 적절한 층에 두기만 하면 된다.
실무에서는 대개 초기화, 기본, 컴포넌트, 유틸리티 같은 층을 순서대로 정한다. 초기화가 가장 약하고 유틸리티가 가장 강하도록 순서를 정해두면, 각 규칙을 성격에 맞는 층에 배치하는 것만으로 우선순위가 자연스럽게 정리된다. 나는 이 층 순서를 프로젝트 시작 시 정해두고, 모든 규칙을 해당하는 층에 넣는다. 우선순위가 구조가 되는 것이다.
이 층 기능은 강제로 우선순위를 이기게 하는 낡은 수단을 대체한다. 예전에는 규칙을 무조건 이기게 하려고 강제 표식을 붙였는데, 이건 한번 쓰기 시작하면 걷잡을 수 없이 번지는 함정이었다. 나는 층 기능을 쓰면서 이 강제 표식에 거의 의존하지 않게 되었다. 층의 순서로 우선순위를 통제하니, 억지로 이기게 만들 필요가 없어진 것이다.
외부에서 가져온 스타일과 내 스타일의 관계도 층으로 깔끔하게 정리할 수 있다. 외부 스타일을 약한 층에 두면, 내 스타일이 자연스럽게 그 위에서 필요한 부분만 덮어쓸 수 있다. 나는 남의 스타일을 가져다 쓸 때, 그것을 낮은 층에 배치해 내 규칙이 우선하도록 한다. 우선순위 다툼 없이 외부 스타일과 공존하는 깔끔한 방법이다.
층 기능은 방법론과 결합할 때 특히 강력하다. 명명 규칙으로 이름을 정리하고, 층으로 우선순위를 관리하면 두 문제가 동시에 해결된다. 나는 이 둘을 함께 써서, 이름은 구조를 드러내고 층은 우선순위를 통제하게 한다. 각 도구가 다른 문제를 맡아주니, 스타일이 커져도 질서가 유지된다.
5. 규칙을 중첩해 정리하기
스타일을 정리하는 또 하나의 도구는 규칙을 중첩해 쓰는 기능이다. 관련된 규칙들을 하나의 덩어리 안에 계층적으로 담을 수 있게 된 것이다. 어떤 컴포넌트의 규칙과 그 안 요소들의 규칙, 그 상태별 규칙을 한자리에 모아 쓸 수 있다. 나는 이 중첩 덕분에 흩어져 있던 관련 규칙들을 한 덩어리로 묶어 관리하게 되었다.
중첩의 가장 큰 장점은 관련된 것을 한곳에 모으는 것이다. 예전에는 한 컴포넌트에 관한 규칙들이 파일 곳곳에 흩어져, 하나를 고치려면 여러 곳을 오가야 했다. 나는 중첩으로 컴포넌트의 모든 규칙을 한 덩어리에 담은 뒤로, 그 컴포넌트를 다룰 때 한 곳만 보면 되게 되었다. 응집도가 높아지니 유지보수가 훨씬 쉬워졌다.
중첩 안에는 상태별 규칙이나 화면 크기별 규칙도 함께 담을 수 있다. 컴포넌트의 기본 규칙 옆에 마우스를 올린 상태나 넓은 화면에서의 규칙을 나란히 두는 것이다. 나는 이렇게 한 컴포넌트의 모든 변화를 한자리에 모아, 그것이 상황에 따라 어떻게 변하는지를 한눈에 파악한다. 관련된 변화들이 함께 있으면 이해가 빠르다.
다만 중첩은 얕게 유지하는 것이 중요하다. 중첩을 깊게 하면 선택자가 길고 강해져, 앞서 애써 낮춘 우선순위가 다시 높아진다. 나는 중첩을 몇 단계 이내로 얕게 유지하려 애쓴다. 깊은 중첩은 관련된 것을 모으는 장점보다, 우선순위를 높이고 코드를 읽기 어렵게 만드는 단점이 커진다. 중첩은 정리 도구이지 계층을 깊게 파는 도구가 아니다.
중첩을 쓸 때는 평평한 규칙을 먼저 두고 중첩을 뒤에 배치하는 것이 읽기 좋다. 컴포넌트 자체의 규칙을 먼저 적고, 그 아래에 세부 요소나 상태의 중첩을 두는 것이다. 나는 이 순서를 지켜서, 덩어리를 볼 때 큰 규칙부터 세부로 자연스럽게 시선이 흐르게 한다. 정리 도구도 정리된 방식으로 써야 그 효과가 산다.
정리하면 방법론은 우선순위를 평평하게 유지하고 이름으로 구조를 소통하며, 층 기능은 우선순위를 명시적인 순서로 관리하고, 중첩은 관련된 규칙을 한자리에 모은다. 나는 이 도구들을 함께 써서, 스타일이 아무리 커져도 질서를 잃지 않게 한다. 코드를 쓰는 것만큼 그것을 유지하는 것이 중요하다. 마지막 편에서는 스타일의 성능과 피해야 할 안티패턴을 다루며 CSS를 마무리한다.