웹 어플리케이션에서 UI Design 과 Frontend 개발에 대하여

웹 어플리케이션에서 UI Design 과 Frontend 개발에 대하여

How to develop web application

광의적인 의미에서 Web Application (혹은 Web Service) 은 규모의 차이를 불문하고 현대 IT 서비스의 핵심이 되어가고 있다. 이러한 Application 의 개발은 흔히 건축으로 비유하곤 하는데 실제 이루어지는 과정을 보면 많이 다르다는 것을 느낄 수 있다.

소프트웨어 개발은 건축과 유사한 점이 있지만 사실은 매우 다르다.

탄탄한 설계를 기반으로 각 전문가가 해당 분야를 만들고 재료의 표준화와 기술의 정형화가 이루여저있는 건축과는 달리, 프로그래밍은 거대한 벽화를 다양한 역할을 가진 사람들이 그려나가는 협업과정에 가깝다고 본다. 각자 그림을 그리는 실력과 스타일이 다르기 때문에 어떤 부분은 정교하게 묘사되고 어떤 부분은 날림이 되기도 한다. 잘된 스케치는 결과물의 퀄리티를 올려줄 것으로 “기대”되지만 “보장”하진 못한다. 또한 옆사람이 맡은 부분을 보지 않고 혼자 작업하거나, 톤/매너를 관리하지 않으면 괴상망측한 작품이 나오기 쉽상이다.

재밌는 부분은 이 협업에서 전혀 다른 여러 역할을 가진 사람이 함께 같은 목표를 가지고 나아가야 한다는 점이다. 특히 디자이너와 개발자의 협업 퀄리티는 제품의 완성도에 지대한 영향을 끼칠 수 밖에 없다.

How to design your application

Font 와 Typography

프론트엔드 개발자의 입장에서 새로운 프로젝트를 시작할때 가장 중요한 것은 역시 브랜딩이 아닐까 한다. 어떠한 색상을 기본으로 하여, 폰트, 타이포그래피 등 일견 “나중에 바꾸면 되지 뭐” 할만 한 것들이 사실은 변경하는 것이 쉽지 않다.

특히 타이포그래피의 부분이 그러한데, 폰트는 각각 미묘하게 다른 사이즈를 가지고 있고 같은 10px 이라고 해도 실제 렌더링 된 모습에 따라 9px 로 보이기도 하고 11px 로 보이기도 하는 등 생각보다 다양한 배리에이션을 가진다.

폰트 차이에 따른 표시의 예시 이미지
동일한 사이즈임에도 전혀 다른 형태로 보여진다.

고정폭 폰트를 써야하는 경우라던가, 폰트의 너비가 다른 폰트와 상이한 경우에는 폰트 변경이 UI 전체 퀄리티에 큰 영향을 끼친다. 또한 폰트는 (특히 한중일의 경우) 최적화를 많이 한 woff2 의 경우에도 용량이 매우 크다. 여러가지 폰트를 쓰는 경우의 가독성에 대해서야 디자인 측면에서의 고려가 있어야 하겠지만, 엔지니어의 입장에서 폰트의 사이즈에 따른 다운로드 속도가 느리게 되면 다운로드가 다 끝나고 폰트 렌더링을 다시하는 브라우저의 특성을 고려했을때 일종의 “깜빡” 거리는 현상이 나타나게되고 이는 곧 사용자 경험을 해칠 수 있다.

웹 어플리케이션 개발시에 또 고려해야할 점이 검색엔진 친화성이 있는가 하는 점이다.

<div class="home">
	<h1>안녕하세요</h1>
</div>

<div class="home>
  <span class="headline">안녕하세요</span>
</div>

위의 코드에서 h1span 은 주어진 style 에 따라 browser 에서는 동일한 모습을 보일 수 있지만, 검색엔진 크롤링 측면에서는 h1 쪽을 더 중요하게 생각한다.  때문에 기본적인 타이포그래피를 설계하고 중요도에 따라 텍스트 혹은 이미지 태그를 배치하면 디자인적 일관성에 웹 문서로서의 완성도 또한 높일 수 있다.

Colors

색상의 경우 처음 결정된 주색과 보조색 이 없이 개발을 시작하면 비슷한 색상의 수많은 배리에이션이 난립하는 것을 경험하게 된다. color variable 로  red 로 지정하는 것을 예를 들어 보자.

$red: #ff0000;

그런데 이 색상의 가독성을 위해서 함께 쓸 더 어두운 색상을 필요로 하게 된 경우

$dark-red: #ee0000;

이런식으로 네이밍 하게 되는 경우가 많고 이는 $darker-red 라던가 $darkest-red 라던가의 난립을 만드는 경우가 많다. 또한 다양하게 쓰이는 대표적인 gray 는 명도와 채도를 바꾸어가면서 여러 셋을 준비하기 쉬운데, 이 또한 초기에 어느정도 범위를 명시하지 않으면 이 디자이너는 저 그레이, 저 디자이너는 또 새로운 그레이 색상을 만들어 내는 등 매번 새로운 색상이 추가되어 관리하기에도 개발하기에도 매우 어려워지게 마련이다.

다행히 sass 와 less 등과 같은 css 컴파일러를 사용하게 되면 이렇게 번거로운 방법으로 관리하지 않아도 된다. 기본적으로 디자이너의 작업 방식 자체도 한 색상의 베리에이션을 만들때 어둡게 혹은 밝게 해가며 작업한다는 것을 봤을때 아래와 같은 방법이 훨씬 효율적인 것을 알 수 있다.

$red: #ff0000;
$dark-red: darken($red, 10%);

이를 다시 아래처럼  $primary 색상이나 $background-color 등의 변수에 정의해두면 색상을 글로벌하게 변경해야 되ㅇ는 경우 훨씬 수월하게 작업이 가능하고 모든 컴포넌트를 뜯어가며 수정해야할 필요가 없기 때문에 매우 편리하게 브랜딩 색상을 변경할 수 있다.

$primary: $red;
$background-color: $white;
$common-border-color: lighten($primary, 20%);
Component

React 를 비롯하여 많은 Frontend Application Framework 들이 component 기반의 개발 방식을 제공하고 있다. 컴포넌트는 어플리케이션에 전반적인 안정감과 일관성을 가져다 준다.

<form class="signin">
  <input name="email" />
  <button class="btn" type="submit">
    SUBMIT
  </button>
</form>

위 버튼은 .btn 이라는 글로벌 클래스에 스타일을 적용하여 사용하게 된다. 당연하게도 웹 어플리케이션에서 UI 는 특정한 룩앤필을 지향하게 마련이다. 사용자에게 일관성을 제공하여 액션에 대한 기대감을 주게 하기 위함이다. 때문에 공통적인 컴포넌트는 반드시 미리 어느정도의 배리에이션을 두고 디자인을 진행해야 한다.

모든 페이지를 한명이 디자인 하는 경우는 드믈고, 혼자 하는 경우에도 페이지를 기반으로 디자인을 진행하다보면 각 페이지의 요소가 미묘하게 달라질 때가 많다. 또한 개발의 과정에서 feature 개발에 집중한 나머지 각 페이지의 버튼의 스타일링이 미묘한 차이를 보이는 경우가 생기는데 이는 이 페이지에서 쓰이는 버튼이 저 페이지의 버튼과 같거나 혹은 다르다는 것을 인식하지 못하고, 여러사람이 개발을 하게 될 때 나타나는 현상이다.

padding 이나 margin, width 와 height 등과 같은 요소들을 공유하는 동일 혹은 유사 컴포넌트의 경우 먼저 배리에이션을 마련해 놓고 각 페이지의 디자인을 진행하는 것이 좋다. 그렇지 않으면 QC 과정에서 이 페이지의 padding 과 동일한 뎁스의 저 페이지의 padding 이 다르고, 저 버튼의 크기가 미묘하게 다른 것에 대해 힘든 커뮤니케이션을 가져야 하는 일이 많다.

디자이너: 1px 왼쪽으로 옮겨주시구요, 2px 크게 해주세요
개발자: 뭐가 다른데요?

제대로된 프론트엔드 개발자라면 저런 대답은 지양해야 하겠지만, (디자이너 분들의 말씀은 그저 옳은 것입니다) 복잡하고 스트레스 받는 QC 과정에서는 자기도 모르게 수비적인 반응을 보일 수 있다는 것 또한 유념해야 할 것이다.

Storybook 을 사용하면 컴포넌트 레벨로 개발을 진행할 수 있다.

게다가 모든 프로젝트가 상대적으로 초기에는 일정에 대한 부담이 적다고 봤을때 Storybook 과 같은 라이브러리를 활용하여 컴포넌트 레벨에서의 완성도를 미리 확보해두면 이후 일정에서 많은 에너지를 축적해 둘 수 있다. 디자인이 변경된다 하더라도 컴포넌트로 구성되어 있으면 작업이 상대적으로 수월하기 때문에 그 과정에서의 불협화음을 많이 줄일 수 있다.

Page

결국 웹은 페이지로 구성된다. 페이지 디자인 또한 기본적으로 웹 컴포넌트를 가져다 쓰는 형태로 시작하는 것이 좋다. 회원 가입 폼은 common padding 을 갖는 일종의 공통 container 안에 email 과 password 라는 input 컴포넌트를 가져다 두고, submit 버튼은 primary 컬러를 사용한 button 을 배치한다.

  • 회원가입 폼
    • container
      • input
        • type: email
      • input
        • type: password
      • button
        • theme: primary
        • text: 가입하기

위의 계층도를 보면 디자이너의 일도 줄어들거니와 개발자도 디자이너가 원하는 형태의 뷰를 손쉽게 그리고 정확하게 구현할 수 있다. 또한 위의 회원가입 폼 이라는 명칭에서도 쉽게 유추할 수 있지만, 같은 형태의 폼을 전체 어플리케이션에서 구현한다면 디자이너 입장에서는 굳이 노가다(?)성 반복 작업을 하지 않아도 개발자가 알아서 디자이너의 의도대로 개발이 척척 진행되는 마법을 경험할 수 있다.

결국 모든 웹 페이지는 개별 컴포넌트의 조합 이라고 볼때, 어느정도 선까지는 이러한 방식으로 상당한 생산성을 확보할 수 있다. 그러나 아무리 컴포넌트 단위 디자인 및 개발이 중요하다고는 해도 모든 요소를 공유할 수는 없는 노릇이고 특히 랜딩 페이지와 같이 이른바 간지(?)를 내세우고 또한 마케팅 및 기타 요소에 의해 변경되는 빈도가 잦을 경우 컴포넌트를 만들기보다는 버리기 쉽도록 공통 요소가 아니라는 점을 명시해주면 프론트 개발 완성도를 높일 수 있다.

Should designer listens to developer?

"아니 디자이너가 개발자의 요구를 다 들어줘야 하냐?" 하는 생각이 든 디자이너도 있겠지만 안타깝게도 프론트 개발이 없이는 아무리 좋은 디자인이라도 세상에 나오지 못하게 된다. 프론트엔드 개발자의 중요한 역할이 디자이너와 협업이고, 디자이너 또한 반대로 마찬가지다. 오히려 위의 제안 사항들은 개발의 용이성에 디자인 완성도를 높이기 위한 전략이라고 이해해주시길 부탁드린다.


How to develop frontend application

이어서 개발쪽 이슈를 알아보자.

Scaffolding

한 프로젝트의 시작에 앞서 가장 먼저 고려해야 하는 요소는 단연코 scaffolding 이고 프로젝트의 설계도 혹은 밑그림이라고도 말할 수 있겠다. React 의 경우는 component 와 container 를 기본으로 하여 각 route 에 해당하는 page 를 구성하는 방식 혹은 atomic design  과 같은 방법론을 많이 사용하고 있다. 사실 어떠한 패턴이 더 좋은 것이냐 하는 논의 보다는 어떠한 단위로 코드의 재사용성을 확보할 것이냐가 더더욱 중요하다.

건축에서 기원된 scaffolding 은 실건축에 앞서 만드는 임시 구조로 해석하면 된다.

웹 어플리케이션은 필연적으로 두가지 부분으로 나뉜다.

  1. 보여주는 부분
  2. 데이터를 주고 받는 부분

MVC, MVVC, FLUX 등등 다양한 아키텍쳐가 위세를 떨치고 있고, 어떤 프레임워크 혹은 라이브러리에는 분명 대세가 있기 마련이지만, 이는 scaffolding 설계에 있어서는 오히려 크게 고려하지 않아도 된다. 결국 어떤 화면을 보여줄때 사용할 함수를 어떤식으로 관리할 것이냐의 이야기이기 때문이다.

보여주는 부분
  • View - 보여주는 부분  Presentational - 보여주기만 하면 되는 부분 Contrallable - 조작이 필요한 부분  

보여주는 부분은 다시 어떻게 보면 당연하게도 크게 PresentationalControllable 두가지로 나눌 수 있다. 각각의 컴포넌트는 사용되는 방식에 따라 서로의 서브셋이 되기도 하는 특성을 가지게 된다.

이를 어떻게 나눌 것이냐에 대한 것인데 다양한 방식이 있지만 오히려 단순하게 코드 길이를 기준으로 하는 것이 편리하다.  코드를 쓰는 것은 결국 사람이고 재사용성에 대한 논의 또한 어떻게 하면 타이핑을 덜 할 수 있을까에서 부터 시작되었다. 어느정도 코드 수가 적당하냐에 대한 의견은 다양할 수 있지만, 하나의 컴포넌트에시 일어나는 이벤트 나 해당 라이프 사이클 을 추적해야 하는 일이 복잡해질때 왠만하면 분리해주는 것이 좋다. 혹은 html 이나 그에 대응되는 코드가 너무 길어지면 이때도 한번 서브 컴포넌트로 나누는 것을 고려해보자. 재사용이 필요없는 컴포넌트인데도 분리해야 할까 하는 생각이 들 수 있지만, 파일까지 분리는 하지 않아도 추상적인 의미로 나눠놓는 것은 해당 컴포넌트를 이해하는데 큰 도움이 된다.

언급한 김에 추상화는 코드 분리의 시작점이 된다. 특히 외부 라이브러리를 사용하는 경우에는 거의 무조건 추상화를 고려해보는 것이 좋다. 옆으로 컨텐츠가 흐르는 Carousel UI 를 구현할때 인기 있는 swiper 라이브러리를 가져다 쓴다면 이 것을 직접 코드 안에 포함시키지 말고, carousel.js 라는 형태로 추상화 시켜서 필요한 옵션을 명시하는 방식으로 실행하도록 만들자.  특히 예를 든 UI 컴포넌트는 반드시 추상화를 하여 라이브러리에 문제가 생겼을때 코드를 뒤져가면서 수정하는 일은 없어저야 한다.

lodash 같은 함수들의 집합인 경우는 굳이 추상화가 큰 도움이 되지 않을수도 있지만, 날짜 포멧, 계산 로직, 데이터 파싱 등과 같이 일관성 유지가 필요하고 테스트를 써야하는 경우는 우선적으로 고려하도록 한다. 그렇지 않으면 어떠한 이유로든 대규모 코드를 변경해야 할때 어플리케이션의 완성도를 심각하게 손상시킬 수 있다.

데이터를 주고 받는 부분
  • Data - 데이터를 주고 받는 부분  API - 서버와 정보를 주고 받는 부분 Parser - 정보를 뷰에서 보여주기 좋도록 가공하는 부분  

위에서 잠깐 언급했듯이 Parser 는 추상화 레벨을 올려 분리해두어야 한다. 가장 큰 이유는 테스트 코드다. 유닛 테스트는 이제 우리나라에서도 선택이 아닌 필수적인 부분이 되어가고 있다. 이는 당연한 것이 IT 서비스의 가장 큰 이점은 유저의 반응에 따라 빠르게 움직일 수 있다는 것인데, 이는 프론트 코드와 백엔드 코드의 잦은 변경을 뜻한다. 따라서 업데이트가 잦고 이에 따라 코드가 깨져서 버그나 에러가 발생할 여지가 많아지게 된다. 이를 최소한 (혹은 최대한) 으로 억제할 수 있는 것이 테스트 코드고 테스트 주도 개발 (TDD) 가 약간은 저물고 있다고 하지만 오히려 유닛 테스트의 중요도는 더더욱 커지고 있다.

Parser 의 종류는 데이터의 표현식을 변경해주는 방식이나, 데이터를 조합하여 새로운 데이터를 만들거나, 데이터의 무결성을 보장하는 등으로 나눌 수 있다. 이는 결국 Data 의 형식이 중요하다는 것이고 이 데이터는 비지니스 로직으로 설명할 수 있기 때문에 일반적으로 거기에 맞추어 분리해주면 된다. src/parsers/user.js  처럼 parser 들을 보아서 한꺼번에 관리하거나 src/components/User.parser.js  의 형태로 components 의 서브 함수 처럼 관리한다거나 아니면 API 호출 에서 아주 밸리데이션을 한다거나 다양한 방식이 있지만, 원칙적으로 테스트 코드를 작성할 수 있고 분리가 가능하다면 어떤 형태라도 무방할 것이다.

Parser 뿐만 아니라 KeyEvent 에서 ESC Key 를 가르치는 넘버 코드, View 의 상태를 가리키는 변수 형태, API-KEY 등 처럼 다양한 곳에서 쓰일 수 있거나 수정이 많지 않은 변수들은 따로 분리해서 미리 나누어 두자. 요는 한 파일이 너무 커지지 않게 적당한 선에서 나누는 것이고, 사람이 쓰는 코드들이니 함께 써야하는 것은 미리미리 나누자는 것이다.

APIRESTFul 인지 GraphQL 인지가 중요한게 아니라 추상화 할 수 있는 형태로 분리하는 버릇을 들이자. fetch(‘/users/me’) 하는 코드보다 getMyProfile 이 유지보수 하는 입장에서 훨씬 의미를 파악하기 용이하다. 당연한 것 같지만 API 호출하는 코드도 직접 fetch 하거나 XMLHttpRequest 를 만들지 말고, HTTP Method 에 따라 분류하는 등 기준을 갖고 공통적인 부분을 한 코드로 녹여내도록 하자.

유념해야할 것으로 이러한 프로젝트 셋업은 어느 순간 끝나고 이제부터 코딩하는 것이 아니라 개발하는 과정에서 꾸준히 업데이트 시켜야 한다. 얼마나 분리해야할지 모르는데 처음부터 코드를 분리하는 것은 지나친 파편화를 가져올 수도 있다. 개발을 하면서 복잡함을 단순화 시켜가는 과정으로 받아들여야지 어떠한 형태를 이상적으로 보는 것은 옳지 못하다. 애초에 웹 서비스를 개발한다는 의미는 꾸준히 완벽을 향해 가까워지는 과정으로 완성된 상태라는 것은 존재하지 않는다.

CSS

최근 프론트엔드 개발자의 위상이 급격히 올라가면서, 이른바 퍼블리셔 직업군의 축소 혹은 프론트엔드 엔지니어로의 전환이 이루어지고 있다. 웹 표준이나 브라우저 파편화가 심화되면서 역으로 이러한 현상이 가속화되고 있다. 불과 5~6년 전만 해도 프론트엔드? 하면 그거 퍼블리셔가 jQuery 스트립트 짜는거 아니야? 했던 상황임을 돌이켜보면 얼마나 상황이 바뀌었는지 알 수 있다.

결국 웹은 페이지로 구성된다. 아무리 컴포넌트가 중요하다고 해도 페이지 개발에서 공통 컴포넌트라 하더라도 개성이 필요하고 이에 따라 베리에이션이 생겨난다. 또한 랜딩 페이지와 같은 홍보 역할을 하는 경우 완전히 그 페이지에 맞는 디자인을 적용해야할 경우도 무시할 수 없다.이러한 경우에는 전체 컴포넌트 전체의 스타일에 옵션을 주지 말고 해당 페이지의 sub class 로 정의하여 사용하도록 한다.

<div class=“home”>
  <button class="button">
    홈
  </button>
</div>

위 구조의 경우 특수 클래스를 주고 싶을때는 아래의 예와 같이 .home 에 차일드 클래스 .button 을 주어서 컨트롤 하지 말고

<style>
...
.home .button {
  height: 30px;
}
...
</style>

.home-button 와 같은 서브 클래스 형태로 구성하는 편이 좋다.

<style>
.home-button {
  height: 30px;
}
</style>

<button class="button home-button">
  홈
</button>

왜냐하면 특히 SPA 의 경우 하나의 Root 컴포넌트 아래로 많은 children 컴포넌트들이 중첩되는 방식으로 개발이 되는데 이러한 경우 의도치 않게 하위의 button 에 영향을 주고 이것이 cascading 되어 스타일이 적용되는데 어디에서부터 시작된 스타일링인지 찾기 어려운 구조를 만들게 된다.

React Component 의 경우에도 마찬가지로 각 컴포넌트가 각자의 스타일을 갖게 하되, 부모 컴포넌트에 해당 child 컴포넌트의 스타일을 재정의하지 말고 새로 등록하여 hierachy 에 영향을 주지 않도록 만들자.

<div className="Home">
  <Button className=“Home__button” />
</div>

const Button = ({ className }) => (
  <button 
    className={`Button ${className}`}
    ...
  />
)

이 밖에도 css framework 라던가 어떤 pre-processor 를 사용할 것인가 등에 대한 다양한 이야기가 있을 수 있으나 취향에 가까워지기 때문에 언급하지 않겠다.


이러다가 영원히 퍼블리싱 안할거 같아서 일단 퍼블리싱.

To-dos

  • How to develop