올리버 조셉 애쉬의 “OOCSS 객체 수정 방법(Methods for modifying objects in OOCSS)”(2012년 9월) 번역글이다. 객체를 약간 변형할 때 어떤 방법을 사용할 것인가 하는 주제를 다룬 글로 세 번째 번역이다. 다른 두 개는 아래와 같다.

  1. 니콜라스 갤러거는 “HTML 시맨틱과 프론트엔드 아키텍처에 대해”(2012년 3월)에서 “멀티 클래스” 패턴과 “싱글 클래스” 패턴이라고 명명하면서 자신은 다양한 변종을 만들 때 더 효과적이기 때문에 “멀티 클래스” 패턴을 지지한다고 밝혔다.
  2. 해리 로버츠는 “@extend를 사용해야 할 때와 믹스인을 사용해야 할 때”(2014년 11월)에서 소스 연관성이 깨지고 특정도에 혼란이 오기 때문에 @extend를 웬만하면 사용하지 말라고 권장했다.
  3. 이 글은 @extend를 사용하면 유지보수하기가 오히려 힘들어지고, 소스가 지저분해진다는 점을 좀더 구체적으로 보여 준다.

각주는 모두 역자가 붙인 것이다. [ ] 안의 말은 역자가 이해를 돕기 위해 추가한 말이다.


CSS 객체 수정자(modifier)1를 구현하는 방법은 두 가지가 있다. 하나는 CSS 객체를 확장하는 것이고, 다른 하나는 HTML 작성시에 클래스를 이어 적기 해서 해결하는 것이다. 이 글에서, 나는 OOCSS에서 수정자의 목적을 소개하고 두 방법의 장단점을 살펴 보면서 @extend때로 빛 좋은 개살구(fools gold)라고 주장할 것이다.

수정자란 무엇인가?

OOCSS의 발상을 처음 접하는 사람들을 위해, 객체 수정자의 원리를 적용할 수 있는 간단한 예를 살펴 보자.

.widget {
  padding: 10px;
}

widget 객체가 있다. 이 객체의 목적은 이름에 잘 나타나 있다. widget엔 패딩이 있지만, 가끔은 예컨대 외곽선이 있는 위젯을 보여 줘야 한다고 해 보자. DRY 방법론을 따르면 widget 객체를 확장하고 수정할 테니, 다음처럼 하지 않는다.

.widget {
    padding: 10px;
}

.widget-alt {
    padding: 10px;
    border: 1px solid #ccc;
}

새로 만든 widget-alt 객체(alternative widget의 약어)에는 외곽선이 있지만, widget의 padding을 중복하고 있다. 디자인으로 돌아가 보자. 모든 위젯에는 padding이 있음이 분명하다. 이 시점에서, 우리는 패턴이 있음을 알아채야 한다. 원래의 widget 객체를 확장하면 padding 속성 선언 중복을 피할 수 있다.

.widget,
.widget-alt {
  padding: 10px;
}

.widget-alt {
  border: 1px solid #ccc;
}

전처리기로 확장(extend)하기

CSS를 구성할 때, 속성을 공유하게 하기 위해 늘 이렇게 수동으로 선택자를 확장하는 것은 실용적이지 않다. 여러 종류의 위젯이 있다고 생각해 보자. 매번 직접 작성한 그룹 선택자2로 돌아가서 그걸 확장해야 한다. @extend 마법을 사용하지 않고 말이다.

/* Our pattern */
.widget {
  padding: 10px;
}

.widget-alt {
  @extend .widget;
  border: 1px solid #ccc;
}

정말로, 만약 OOCSS를 쓰고 있다면, CSS 전처리기로 이렇게 하는 것은 특히 손쉬운 일이다. 간단하게 @extend를 사용하면 앞서 작성한 것과 똑같은 CSS가 나온다. 프로젝트가 커질수록 이 방식이 훨씬 쉽다.

확장(extend)할 때 문제점

widget 객체의 예제를 확장해 보자. 스타일을 정의한 자손이 몇 개 더 있다.

/* SCSS */

.widget {
  padding: 10px;

  h1 {
    margin-bottom: 10px;
  }

  p {
    /* 간단한 팁, 전처리기는 모든 CSS3 `hsl` 값을 `rgb`로 변환한다. `hsl`은  떠올리기에 훨씬 쉽고, 따라서 색상을 적용하기도 쉽다. 내가 전처리기를 좋아하는 또 하나의 이유다. */
    color: hsl(0, 0%, 10%);
  }
}

이제 수정자를 만들어 이 객체를 확장하려면 무엇을 해야 할까?

/* SCSS */

.widget-alt {
  @extend .widget;
  border: 1px solid hsl(0, 0%, 90%);
}

/* CSS */

.widget,
.widget-alt {
  padding: 10px;
}

.widget h1,
.widget-alt h1 {
  margin-bottom: 10px;
}

.widget p,
.widget-alt p {
  color: rgb(26, 26, 26);
}

.widget-alt {
  border: 1px solid rgb(230, 230, 230);
}

@extend가 뭔가 문제를 일으킬 수 있는 상황이다. 객체를 확장하면 중첩된 객체를 전부 확장하게 되기 때문이다. 보다시피, widgetwidget-alt에게 자기 자신만 공유하는 것이 아니라 자손들까지 공유하게 된다. h1p 요소가 그런 예다. 이것은 이미 CSS를 필요 이상으로 늘렸고, 결국 (더 큰 프로젝트에서는) 더 긴 선택자를 만들어내게 될 것이다.

예컨대, 사이드바 안에 있는 모든 widget 객체의 하단 마진을 조정해야 한다고 해 보자. 위젯들을 어떻게 특정할 것인가?

/* SCSS */
.l-sidebar {
  .widget,
  .widget-alt {
    margin-bottom: 25px;
  }
}

/* CSS */
.l-sidebar .widget,
.l-sidebar .widget-alt {
  margin-bottom: 25px;
}

우리는 모든 위젯에 스타일을 적용하도록 모든 변형판 목록을 다 나열해야 한다.

이게 그리 많아 보이지 않을 수도 있겠지만, 더 많은 변형판을 만들면서 widget 객체를 확장하기 시작하면(예컨대 widget-primary, widget-secondary), CSS는 순식간에 스파게티처럼 변할 것이다.

HTML에서 클래스를 연이어 적기

한 번 언급하는 것만으로 전체 위젯 종류에 영향을 미칠 수 있다면 훨씬 더 효율적일 것이다. 내가 작업을 HTML로 미루는 편이 낫다고 믿는 이유다. 방법을 보자.

/* SCSS */

.l-sidebar {
  .widget {
    margin-bottom: 25px;
  }
}

.widget {
  padding: 10px;

  h1 {
    margin-bottom: 10px;
  }

  p {
    color: hsl(0, 0%, 10%);
  }
}

.widget-alt {
  border: 1px solid rgb(230, 230, 230);
}

/* CSS */

.l-sidebar .widget {
  margin-bottom: 25px;
}

.widget {
  padding: 10px;
}

.widget h1 {
  margin-bottom: 10px;
}

.widget p {
  color: hsl(0, 0%, 10%);
}

.widget-alt {
  border: 1px solid rgb(230, 230, 230);
}

모양을 제대로 적용하려면, 우리는 HTML에 두 클래스를 사용해야 한다.

<div class="widget widget-alt">
  …
</div>

HTML에 클래스 추가하기를 꺼리는 사람도 있을 것이다. 개인적으로, 나는 요소에 클래스를 연이어 적는 것이 OOCSS에서 객체를 확장하는 가장 좋은 방법이라고 생각한다. 중요한 것은, CSS에서 HTML로 작업을 미룸으로써, 우리가 훨씬 더 효율적인 CSS를 얻었다는 점이다. 이 예제는 수정자를 단 하나만 사용하는 간단한 경우지만, widget 객체에 수정자가 몇 개 더 있는 경우, CSS에서 작업하게 되면 그룹 선택자가 지저분해질 것이라는 점을 금세 알 수 있다.

확장을 사용할 때 효율성

위젯 예시를 다시 한 번 보자. 다만, 이번에는 다른 수정자를 붙일 것이다.

/* SCSS */

.widget {
  …

  h1 {
    …
  }

  p {
    …
  }
}

.widget-alt {
  @extend .widget;

  …
}

.widget-box {
  @extend .widget;

  …
}

/* CSS */

.widget,
.widget-alt,
.widget-box {
  …
}

.widget h1,
.widget-alt h1,
.widget-box h1 {
  …
}

.widget p,
.widget-alt p,
.widget-box p {
  …
}

.widget-alt {
  …
}

.widget-box {
  …
}

[Sass 컴파일 후] 우리가 얻은 것은 다음과 같다.

  • 선택자 3개([선택자가] 3개인 그룹 1개). 원 객체와 수정자가 스타일을 공유함으로써 나온 것.
  • 선택자 6개([선택자가] 3개인 그룹 2개). 원 객체와 수정자 둘의 자손 스타일을 공유함으로써 나온 것.

위의 예에서, @extend를 사용해 widget을 수정하면 25줄 짜리 CSS가 나온다. 반면, 연이어 적은 클래스를 사용하면 19줄이 나온다. 지금 이 예제에서는 차이가 작다. 하지만 더 큰 사례를 보자.

객체가 10개가 있다고 생각해 보자(위젯, 내비게이션 목록 등). 각 객체에는 수정자가 2개씩 있고(widget-alt, widget-box 등), 자손이 평균 2개씩 있다. extend를 사용하면 우리가 얻는 것은 다음과 같다.

  • 선택자 30개([선택자가] 3개인 그룹 10개). 원 객체와 수정자가 스타일을 공유함으로써 나온 것.
  • 선택자 60개([선택자가] 3개인 그룹 20개). 원 객체와 수정자 둘의 자손 스타일을 공유함으로써 나온 것.

선택자당 줄 수를 세어 보면, 수정자와 자손을 모두 선택하는 데만 90줄을 사용하게 됐다. 반면, 같은 객체 샘플에서, HTML에 클래스를 연이어 적는 방법을 사용하면, CSS는 30줄만 사용하게 된다. 물론, 이 방법을 쓰면 HTML에 클래스를 추가해야 한다. 그러나 99%의 경우 이 방법이 더 효율적일 것이다.

결론

요컨대, 나는 개인적으로 객체 수정자 작업을 HTML로 미루는 것이 더 낫다고 믿는다. 이것이 (대부분의 경우) 더 효율적이기 때문만은 아니다. 두 방법 사이의 진정한 차이는 다른 객체 안에 들어간 객체를 수정할 필요가 있을 때 보이기 시작한다. 예컨대, 사이드바 안에 있는 모든 위젯의 하단 마진을 조정하는 것 같은 경우다. extend를 사용하면 객체 수정자 전부를 수동으로 나열해야 하는 경우가 생긴다. 어떤 객체 안의 모든 제목 CSS를 조정할 필요가 있을 때도 비슷하다. 이렇게 할 수 있을 것이다.

.widget {
  padding: 10px;
}

.widget-alt {
  padding: 10px;
  border: 1px solid #ccc;
}

다른 방법으로는, 그냥 HTML에 클래스를 추가할 수 있다. 그러면 CSS 복잡성이 줄어든다.3

.widget {
  padding: 10px;
}

.widget-alt {
  border: 1px solid #ccc;
}

내가 여기서 말한 것과 다른 생각이 있다면, 트위터로 알려 달라. (다른 모든 프론트엔드 개발자들이 그렇듯이, 정말로) 나는 내가 실제로 사용하는 방법들을 매우 비판적으로 검토한다.4 모든 비판은 좋은 것이다.

  1. BEM에 관한 번역에서는 modifier를 “수식어”라고 번역했다. 그런데 지금 번역하는 글에서 modifier는 단지 수식하는 역할만 하지 않는다. 아예 원래 객체의 속성을 다 물려 받아서 새로운 클래스를 만드는 경우도 있다. (물론 이 글은 그런 식의 방법을 추천하지 않고 ‘수식어’로 사용할 것을 권장하고 있지만 말이다.) 그래서 어쩔 수 없이, 좀 맘에 안 들긴 하지만 “수정자”로 번역했다. 여전히 BEM에서는 “수식어”로 번역할 것이다.

  2. .widget, .widget-alt { ... }처럼 여러 선택자를 연이어 써서 정의한 것을 그룹 선택자라고 한다.

  3. 원본글에는 .widget-altpadding: 10px;이 들어있는데, 내 생각엔 없는 게 맞는 듯해 뺐다.

  4. I’m incredibly critical of the methods I use in practice (like any front-end developer should be, really).

- 댓글 기능은 없습니다. 댓글 대신 mail@mytory.net으로 메일 보내 주세요.