유지보수와 확장성을 고려한 HTML/CSS 구조 전략
전부터 생각해 오던것이 있었다.
왜 웹개발자들은 java나 javascript는 어떻게든 잘 써보려고 노력하면서 html/css는 이토록 무관심한가. 왜 html/css 작업을 하는 사람들조차 좀 더 머리가 굵어지면 javascript, java 테크를 타려고만 하고 html/css를 버리는가. 그냥 내 눈에 띄지 않는것 뿐일까. 그럼 눈에 안띄는 이유는 무엇일까.
이 글은 웹접근성에 대한 이야기가 아니다.
기술적인 관점에서 어떻게 하면 html/css 그 자체를 좀 더 잘 써볼수 있을까에 대해서 그동안 고민해온 내용이다.
이해를 위해서는 기본적인 html/css에 대한 지식이 필요하다. 깊이 알 필요는 없다. 나도 잘 모른다.
글의 기승전결을 어떻게 해야 좋을지 몰라 그냥 항목들을 나열해 놓았다.
언제나와 같이 의견이 접수되는대로, 생각이 정리되는대로 글을 계속 보완할 생각이다.
그럼 시작하자.
className을 지을때는 camelCase, under_score 보다는 hypen(-)을 사용하자
- 쓰기 편하다: shift를 안눌러도 되서 새끼손가락이 편하다
- 단어를 수정하기 쉽다: camelCase, under_score를 사용하게 되면 에디터에서 하나의 문자로 인식이 되어 단어가 조합되어 있는 경우 방향키를 여러번 입력해야 한다. hypen을 사용하면 마치 공백처럼 조합키(opt+arrow)로 문자열 사이를 이동할 수 있다.
이해가 잘 안된다면 지금 에디터를 열고sweetPotato
와sweet_potato
,sweet-potato
를 쳐놓고 비교해 보자.
camelCase와 under_score 사이의 고민은 단순한 취향문제일 수 있으나, hypen의 사용은 그 둘과는 확실히 다른점이 있다. 많은 오픈소스도 hypen을 사 용하고 있고 html의 어트리뷰트 등도 hypen을 사용하고 있다.
모듈화를 생각하자
DOM handling이나 CSS styling을 하다보면 섬세하지 못한 설계 때문에 고통을 받는 경우가 많다. DOM탐색이나 CSS의 cascading은 기본적으로 html 문서 전역에 걸쳐 이루어지기 때문이다.
무엇이 문제이고 어떻게 해결할 수 있는지 하나씩 살펴보자.
id 보다는 className을 활용하자
This attribute assigns a name to an element. This name must be unique in a document.
html 스펙에서 id에 대한 내용이다. id는 문서내에서 유일해야 한다고 되어있다. 이러한 스펙 때문에 단일 엘리먼트를 핸들링할때는 id를 사용하는 경우가 많. 하지만 정말로 해당 이벤트나 스타일이 단 한번만 사용하게 될지 충분히 고민해야한다. 자신이 없다면 그냥 className을 사용하는것을 추천한다.
성능이나 기타 이유로 id 사용을 피할 수 없다면, 스펙을 어기는 일이 안생기도록 충분히 unique한 네이밍을 해야 할 것이다.
(나는 최근에도 한 페이지에 #container
라는 아이디가 두개 있는 것을 보았다)
태그선택자의 사용을 지양하자
먼저 예제코드를 보자.
네이밍은 언제나 고통스럽고 귀찮은 일이기 때문에 위처럼 태그1에 스타일을 먹이거나 DOM 탐색을 수행하는 경우가 자주 보인다. 하지만 html 태그는 얼마든지 중첩되어 쓰일 수 있다. 다음의 예제를 보자.
테이블 내에 다른 테이블이 중첩해서 들어가게 되었다. .info
테이블에서 th
, td
태그에 직접 스타일을 주었기 때문에 내부의 .etc
테이블의 th
, td
에 속성이 상속되었다. #article .info .etc th
에는 상속된 값을 초기화하기 위한 코드가 추가되었다.
예제를 아주 단순화했기 때문에 별것 아닌것처럼 보이지만 테이블이 5겹정도 중첩되어 있는 경우를 상상해보자. 예제코드를 짜기도 두렵다.
다시 한번 떠올려 보자. html 태그들은 얼마든지 중첩될 수 있다. 당신이 아무리 조심한다고 해도 당신과 협업하는 누군가, 혹은 후에 당신의 코드를 물려받을 후임자는 고통받게 될 것이다. 그리고 그건 그의 잘못이 아니다.
그러니 가장 위험한 태그선택자를 사용하는것은 자제하도록 하자.
짧고 단순한 네이밍을 지양하자
위의 예제를 다시한번 살펴보자.
.info
, .etc
등 아주 단순한 이름을 사용하고 있다. css의 cascading 규칙 때문에 클래스명을 짧고 단순하게 짓고 cascading 시키는 경우가 많다. 하지만 이 또한 문제다. 다음을 보자.
#job
섹션을 만들고 내부에 또 정보를 표시하는 테이블인 .info
를 만들었다. 지금으로선 문제가 없어보일지 모르겠다.
각각 #article .info
, #job .info
로 네임스페이스를 잘 분리한 것처럼 보인다.
하지만 이또한 html 태그의 중첩을 피할 수 없다. 다음을 보자.
*#job
이 #article
내부로 들어가 버렸다. 이제 #job .info
테이블은 #article .info
의 스타일을 그대로 상속받게 되었다. 세상에. .active
처럼 상태를 나타내는 클래스를 상속받는 경우는 문제가 더욱 심각할 것이다.
혼자서 작업하는 경우에는 내가 무슨 단어를 사용했었는지 외우면 그만이라 생각할 수 있다. (물론 절대 추천하지는 않는다) 하지만 여럿이서 작업을 하고 있다면 지금 당장 짧고 단순한 네이밍은 그만두자. 사실 세상에 혼자 짜는 코드라는건 없다.
모듈화를 해보자
위에서 태그선택자와 짧은 네이밍에 대해 문제점만 지적하고 해법을 제시하지 않았다. 이제 그 이야기를 해보려 한다.
BEM(Block-Element-Modifier)이라는 방법론이 있다. 자세한 내용은 링크로 대체한다. 이 방법론을 접하고 비로소 html 모듈화에 대한 고민을 시작할 수 있었다. 나는 BEM의 Block을 독립적이고, 재사용 가능한 모듈이라는 개념으로 접근해 보았다.
BEM에서는 .block__element--modifier
식의 괴랄한 네이밍을 사용한다. BEM에 대해 소개하고 있는 tuts+의 An Introduction to the BEM Methodology을 살펴보면 이해하기 좀 더 쉬울 것이다. tuts+ 사이트 자체도 BEM을 따르고 있으므로 링크로 들어가 소스를 살펴보면 좋다. (처음 접했다면 아마 경악을 하게 될 것이다.)
그리고 내가 추천하려는게 바로 이 방식이다.
className을 길게 풀어씀으로서 얻는 장점은 다음과 같다.
- className이 중복되는 것을 막는다: 중복되는 사례가 생기면 신기할 것 같다.
- 직관적이다: 짧은 클래스명을 사용할때는
.active .icon-home
같은 코드에서는 무엇이 active 되었는지가 불명확했다. BEM의 방식을 따르면.gnb__home--active .icon-home
같은 형태가 되어 직관직이다. - 전체 DOM tree를 살펴보지 않아도 된다: 짧은 클래스명을 사용할때는
.menu
라는 영역이 있을때 이게.home
의 메뉴인지.article
의 메뉴인지는 DOM구조를 살펴보기전에는 알수가 없다. 하지만 BEM의 방식을 따르면.home__menu
같은 형태가 되어 어느 block의 element인지 한번에 알 수 있다.
썩 괜찮지 않은가? :)
내 경우에는 element 구분자 __
, modifier 구분자--
가 너무 과하게 느껴져서 각각 -
, __
로 바꿔 사용하고 있다.
위에 문제가 있던 코드를 수정하면 다음과 같은 형태가 될 것 같다.
네이밍이 별로 마음에 안들수도 있겠지만, 일단 cascading의 악몽에서 벗어난것에 집중하자(..) 드디어 모듈별 독립성을 확보하게 되었다. 야호!
사실 이 개념은 완전히 새로운 것이 아니다. 유수의 jQuery 플러그인들만 보아도, 플러그인의 이름이나 약자를 prefix 붙여 일종의 네임스페이스개념으로 사용하고 있다. 보고 배우자.
DOM 탐색
탐색레벨을 낮고 단순하게 유지하자
위에서 className을 길게 풀어쓰는데 동의하지 않았다면, 이 내용을 보고 다시한번 생각해보자.
쿼리선택자를 통해 특정 엘리먼트를 찾는 경우를 생각해보자. 다음과 같은 코드가 있다.
var auth_info = $('.article > .foot p > .auth-info')
.article
의 자식노드인 .foot
의 자손노드인 p
의 자식노드인 .auth-info
를 찾고 있다. 길다. 탐색레벨이 길어질 수록 성능에 악영향을 미친다. DOM 탐색은 비용이 많이 드는 작업이다.
사실 문제는 성능뿐만이 아니다. 만약 .auth-info
가 .foot
이 아니라 .article .head
로 옮겨가게 되면 어떨까? p
와 .auth-info
사이에 div
가 하나 더 들어가게 되면? 당장에 저 쿼리는 못쓰게 되 어버린다.
실제 서비스를 하다보면 HTML의 구조가 바뀌는일은 매우 빈번하다. 그러므로 탐색레벨은 낮고 단순하게 유지하는 것이 유지보수에 좋다. 쿼리를 단순하게 하려면? 길고 unique한 className을 사용하면 된다.
최근에는 브라우저 테스트 자동화에 대한 요구가 커지고 있는데, 테스트코드의 유지보수에 있어서도 매우 중요한 개념이라 할 수 있겠다.
css 제어에 있어서도 마찬가지다. 위의 쿼리를 응용해 살펴보자.
.article .foot .auth-info { background-color: yellow;}
만약 여기서 .auth-info
의 배경색을 빨간색으로 오버라이드하고 싶다면, 위 코드에서 어느 부분에 변경자 클래스를 추가해야 할까? 가능한 경우의 수는 다음과 같다.
.article
앞.article
자신 (.aricle.article-red
).foot
과.article
사이.foot
자신 (.foot.foot-red
).foot
과.auth-info
사이.auth-info
자신 (.auth-info.auth-info__red
)
정말 끔찍하다. 어느부분에 클래스를 추가할지 감이 안온다. 탐색레벨이 깊어질 수록 경우의 수는 더욱 늘어난다.
탐색레벨을 한번 낮춰보자.
.article-auth-info { background-color: yellow;}
이제 가능한 경우의 수는 다음과 같다.
.article-auth-info
앞.article-auth-info
자신 (.article-auth-info.article-auth-info__red
)
줄어든 탐색레벨만큼 고민도 줄었다 lol (물론 이건 css 얘기고, html에서는 아직 문제가 해결된 것이 아니다)
환경에 대한 변경자로 root node를 활용하자
위의 탐색레벨 이야기에서, css에서의 변경자 위치는 해결이 되었지만 html에서의 변경자의 위치는 해결을 하지 못했다. 이제 그 이야기를 해볼까 한다. 일단 경우의 수는 두가지가 있다. 바로 영향을 받는 엘리먼트 자신과 root node다.
개별 엘리먼트와 유저가 직 접적인 상호작용을 하고있을때는 엘리먼트 자신에 변경자를 두어 제어한다. 그 외에 특정 상태에 대한 변경이 필요할때는 root node에 변경자(className)를 부여하여 변경을 가한다.
root node가 무엇인가? 나는 다음의 세가지를 root node로 취급한다. 각각의 쓰임새와 함께 살펴보자.
<html>
: client의 상태에 대한 정보를 담는다.
css, js 속성의 지원여부나 모바일/데스크탑 등의 환경정보
<html class="js lt-ie10 cssAnimation mobile">
<body>
: user의 상태에 대한 정보를 담는다.
접근중인 컨트롤러, 실행중인 액션, 로그인, AB테스트의 대상 여부 등
<body class="logged-in myinfo abtest-a">
- 모듈의 root(BEM에서의 Block): 개별 모듈의 상태에 대한 정보를 담는다.
<div class="msgbox msgbox__unread">
어느곳에서 어떤 정보를 처리할지는 정답이 없다. 다만 이렇게 규칙을 정해두고 한정된 곳에서 상태들을 제어하면 컨트롤하기 수월하다. js에서 이벤트를 delegate 시키는것과도 비슷한 느낌이다. 핸들러가 흩어져 있으면 성능도 안좋고 제어하기도 힘든것처럼..
그런데 이것도 사실 내가 생각한 개념은 아니고 facebook, twitter, github, modernizr 이런데서 이미 다 하고 있는거다. 보고 배우자.
상태값 제어
작업을 하다보면 템플릿언어(freemarker라든가 jade라든가)와 javascript가 하나의 파일에서 서로 문법의 개성을 뽐내며 스파게티처럼 뒤엉켜 있는것을 볼 수 있다. (정말이다)
개인적으로는 이것을 백단코드를 깔끔하게 유지하기 위해 더러운 코드는 다 프론트엔드로 던져버린 음모로 보고 있다. (이건 농담이다)
이를 해결하려고 하면 파일의 분리가 필요한데, 이 경우 페이지의 상태값을 js로 전달할 방법이 필요하다.
많은이들이 익히 알다시피 자바스크립트 전역변수를 사용하는건 별로 아름답지 못한 아이디어다. 그렇다면 이 데이터들은 어디에다 실어서 통신을 해야하나.
나는 HTML5 스펙의 data-*
attribute에서 답을 찾았다.
Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements.
자바스크립트 전역변수나 <input type="hidden">
을 사용하는 대신에, 그냥 태그의 data-*
어트리뷰트에다 데이터를 실어서 통신하면 된다. 어느 엘리먼트에 어트리뷰트를 선언할지는 위에서 다뤘다.
상호작용하는 엘리먼트에 직접 선언하든지, <body>
에다가 마음껏 선언하면 된다. <body>
에다 선언하는 경우는 웬지 탐색비용도 절감될 것 같은 기분이다. (이건 비교적 최근 떠오른 아이디어로, 검증해보진 않았다..)
CSS 속성은 한줄에 하나만 선언한다
- 가독성이 좋다