Overreacted

UI 엔지니어링의 요소들

2018 M12 30 • ☕️ 7 min read

이전 글은 우리의 지식 빈틈을 인정하는 것에 대해 다뤘습니다. 제가 평범함에 정착하라 말한다고 생각할 수도 있습니다. 그렇지 않습니다! 이곳은 광범위한 분야입니다.

저는 “어디서나 시작”할 수 있고 특정 순서대로 기술을 배울 필요가 없다고 강력히 믿습니다. 하지만 또한 전문 지식을 습득하는 것에 큰 가치를 두고 있습니다. 개인적으로 저는 주로 사용자 인터페이스를 만드는 데 관심이 많습니다.

저는 제가 확실히 알고 있고 가치 있다고 생각하는 것을 궁리해왔습니다. 당연히도 몇몇 기술 (예 : JavaScript와 React)에 익숙합니다. 그러나 경험에서 얻은 더 중요한 교훈은 찾아내기 어렵습니다. 그 교훈을 말로 표현하려고 시도해보지 않았습니다. 이 글은 그 일부를 모아두고 설명하는 첫번째 시도입니다.


기술과 라이브러리에 대한 “학습 로드맵”이 많이 있습니다. 2019년에는 어떤 라이브러리가 인기 있을까요? 2020년은? Vue 또는 React를 배워야할까요? Angular? Redux 또는 Rx는 어떻습니까? Apollo를 배워야할까요? REST 또는 GraphQL? 길을 잃기 쉽습니다. 글쓴이가 틀렸다면 어떨까요?

제 학습에서 가장 큰 혁신은 특정 기술에 관한 것이 아니었습니다. 오히려, 특정 UI 문제를 해결하기 위해 고생했을 때 가장 많이 배웠습니다. 때로는 나중에 도움이 되는 라이브러리나 패턴을 발견하기도 합니다. 다른 경우에는 저만의 해결책을 제시합니다. (좋은 해결책과 나쁜 해결책 모두)

문제를 이해하고, 해결책을 실험하며, 다양한 전략을 적용하는 것의 조합이 제 인생에서 가장 보람있는 학습 경험으로 저를 이끌었습니다. 이 글은 그 문제를 집중적으로 다룹니다.


직접 혹은 라이브러리를 사용해서 사용자 인터페이스를 만들어봤다면, 적어도 이 문제들 중 일부를 마주쳤을 것입니다. 두 경우 모두 라이브러리가 하나도 없는 작은 앱을 만들어 문제를 재현하고 해결하는 것을 권장합니다. 이 문제들 중 어느 것에도 단 하나의 올바른 해결책이란 없습니다. 문제 공간을 탐구하고 가능한 다른 상충 관계(tradeoff)들을 시도하는 데서 배움을 얻습니다.


  • 일관성(Consistency). “좋아요” 버튼을 클릭하면 텍스트가 바뀝니다. “나와 다른 3명의 친구가 이 게시물을 좋아했습니다.” 다시 클릭하면 텍스트가 되돌아갑니다. 쉬워 보이죠. 하지만 이 레이블은 화면의 여러 위치에 있을 수 있습니다. 바뀌어야 하는 다른 시각적 표시(예 : 버튼 배경)가 있을 수 있습니다. 이전에 서버에서 가져온, 마우스를 올려두면(hover) 보이는 “좋아요한 사람” 목록은 이제 당신의 이름을 포함해야 합니다. 다른 화면으로 이동했다가 돌아와도 해당 게시물은 좋아한 것을 “잊어서는” 안됩니다. 심지어 로컬(local) 일관성만으로도 이러한 일련의 문제들이 만들어집니다. 하지만 표시된 데이터를 다른 사용자가 수정할 수도 있습니다 (예: 우리가 지금 보고있는 소식에 “좋아요”를 눌러서). 어떻게 동일한 데이터를 화면의 다른 부분과 동기화 할 수 있을까요? 어떻게, 언제 로컬 데이터를 서버와 일관되게 만들 수 있을까요? 다른 방법도 있을까요?

  • 응답성(Responsiveness). 사람들은 자신의 행동에 대한 시각적 반응이 부족한 것을 제한된 시간 동안만 용납할 수 있습니다. (터치) 제스처와 스크롤과 같은 지속 동작의 경우 이 제한 시간이 짧습니다. (단일 16ms 프레임을 건너 뛰는 것조차도 “버벅”인다고 느낍니다.) 클릭과 같은 개별 동작의 경우, 사용자가 100ms 이하의 지연 시간은 똑같이 빠르다고 인식한다는 연구가 있습니다. 작업이 오래 걸리면, 시각적 표시를 보여줘야합니다. 그러나 여기에는 직관적이지 않은 문제가 있습니다. 어떤 시각적 표시는 페이지 레이아웃을 “급전환”시키거나 여러 로딩 “단계”를 거쳐서, 실제보다 작업이 더 오래 걸린다는 느낌을 줍니다. 비슷하게, 20ms 이내로 상호 작용을 처리하지만 애니메이션 프레임을 빼먹는 것은, 30ms 이내에 처리하지만 애니메이션 프레임을 빼먹지 않는 것 보다 느리게 느껴질 수 있습니다. 두뇌는 벤치마크가 아닙니다. 어떻게 앱을 다양한 종류의 입력에 지속적으로 빠르게 반응하도록 할 수 있을까요?

  • 지연 시간(Latency). 계산과 네트워크 접속 모두 시간이 걸립니다. 때로는 목표 기기의 응답성을 해치지 않으면서 계산 비용을 무시할 수 있습니다 (반드시 저성능 기기 스펙트럼에서 앱을 테스트해봐야 합니다). 그러나 네트워크 대기 시간을 무시하는 것은 불가피합니다. 몇 초가 걸릴 수 있습니다! 우리의 앱은 데이터 또는 코드가 받아질 때까지 그냥 얼어있을 수는 없습니다. 이는 새로운 데이터, 코드 또는 자산에 의존하는 모든 동작이 잠재적으로 비동기이며 “로드 중”의 경우를 처리해야 함을 의미합니다. 하지만 거의 모든 화면에서 발생할 수 있습니다. 어떻게 “연속적인” 스피너 혹은 빈 “구멍” 을 보여주지 않으면서 대기 시간을 우아하게 처리할 수 있을까요? 어떻게 “급전환”하는 레이아웃을 피할 수 있을까요? 그리고 어떻게 매번 코드를 “다시 짜지” 않고 비동기 의존성을 바꿔야할까요?

  • 탐색(Navigation). UI와 상호작용할 때 우리는 UI가 “안정적”으로 유지될 것으로 기대합니다. 우리 코앞에서 사라지지 않아야 합니다. 탐색은 앱에서 시작 되었든 (예 : 링크 클릭) 또는 외부 이벤트 (예 : “뒤로가기” 버튼 클릭)에 의한 것이든 이 원칙을 준수해야합니다. 예를 들어 프로필 화면의 /profile/likes 탭과 /profile/follows 탭 사이를 왔다갔다해도 탭 영역 외부의 검색 상자가 지워져서는 안됩니다. /다른/ 화면으로 이동하는 것조차도 방 안으로 들어가는 것과 같습니다. 사람들은 나중에 돌아와서 (아마도 몇몇 새 것들과 함께있는) 두고간 것들을 찾기를 기대합니다. 피드의 한 가운데 있다가, 프로필을 클릭했다가, 다시 돌아와서, 피드에서의 위치를 잃어버리는 것 (혹은 다시 로드되기를 기다리는 것)은 당황스럽습니다. 어떻게 중요한 문맥을 놓지지 않으면서 임의의 탐색을 처리하는 앱을 설계할 수 있을까요?

  • 캐시 관리(Staleness). 로컬 캐시를 도입하여 “뒤로가기” 버튼 탐색을 즉시 수행 할 수 있습니다. 이 캐시에는 빠른 액세스를 위해 일부 데이터를 “기억”할 수 있습니다. 이론적으로 다시 가져올 수도 있지만요. 그러나 캐시은 자체적인 문제가 있습니다. 캐시는 상할 수 (stale) 있습니다. 아바타를 변경하면 캐시에서도 변경되야 합니다. 새 게시물을 만들면, 즉시 캐시에 나타나거나 캐시를 무효화해야합니다. 이것은 어렵고 오류가 발생할 수 있습니다. 게시물 올리기가 실패하면 어쩌죠? 캐시는 메모리에 얼마나 오래 머물러야 할까요? 피드를 다시 가져올 때, 새로 가져온 피드를 캐시된 피드와 이어 붙어야 할까요, 아니면 캐시를 버려야 할까요? 어떻게 페이지 매김(pagination) 또는 정렬을 캐시에서 표현할 수 있을까요?

  • 엔트로피(Entropy). 열역학의 두 번째 법칙은 다음과 같습니다. “시간이 지나면 모든 것이 엉망이 된다” (정확한 표현은 아닙니다.) 이것은 사용자 인터페이스에도 적용됩니다. 우리는 사용자 상호 작용과 그 순서를 정확하게 예측할 수 없습니다. 어느 시점에서 우리의 앱은 엄청나게 많은 가능한 상태 중 하나 일 수 있습니다. 우리는 디자인을 통해 결과를 예측 가능하게 하고 제한하기 위해 최선을 다합니다. 우리는 버그 스크린샷을보고 “어쩌다 이렇게 됐지”라고 생각하고 싶지 않습니다. N개의 가능한 상태에 대해, 그 상태들 사이의 N × (N-1) 개의 가능한 전환이 있습니다. 예를 들어 버튼이 5가지 상태 (일반, 활성, 마우스 올림, 위험, 사용 불가) 중 하나 일 수 있는 경우, 버튼을 업데이트하는 코드는 5×4=20 개의 가능한 전환에 대해 정확하거나, 일부는 금지되어 있어야합니다. 어떻게 우리는 가능한 상태들의 폭발적인 조합을 길들이며 시각적 결과를 예측 가능하게 만들 수 있을까요?

  • 우선 순위(Priority). 어떤 것들은 다른 것들보다 더 중요합니다. 대화 상자는 그것을 생성한 버튼의 “위”에 나타나고 컨테이너의 클립 경계를 “벗어나야”할 수도 있습니다. 새로 시작한 작업 (예 : 클릭에 응답)은 이미 시작된 장기 실행 작업 (예 : 접힌 화면 아래의 다음 게시물 렌더링)보다 더 중요할 수 있습니다. 앱이 커짐에 따라, 다른 사람과 팀이 작성한 코드 일부가 프로세서, 네트워크, 화면 부동산(screen estate) 및 번들 크기 예산과 같은 제한된 자원을 두고 경쟁합니다. 때때로 CSS z-index 속성과 같이 공유하는 “중요성”의 척도를 가지고 경쟁자들의 순위를 매길 수 있습니다. 그러나 대체로 실패합니다. 모든 개발자는 자신의 코드가 중요하다고 생각하도록 편향되어 있습니다. 그리고 모든 것이 중요하다면 아무 것도 중요하지 않습니다! 어떻게 독립적인 위젯들이 자원을 두고 싸우지 않고 협력하게 할 수 있을까요?

  • 접근성(Accessibility). 접근성이 떨어지는 웹 사이트는 작은 문제가 아닙니다. 예를 들어, 영국에서는 5 명 중 1명이 장애의 영향을 받습니다. (여기에 멋진 인포그래픽이 있습니다.) 저 스스로도 느꼈습니다. 저는 26살이지만, 얇은 글꼴과 낮은 대비를 가진 웹 사이트를 읽는 것이 힘듭니다. 저는 트랙패드를 더 사용하려고 하며 키보드로 제대로 구현되지 않은 웹 사이트를 탐색해야할 때가 두렵습니다. 우리는 어려움을 겪고 있는 사람들에게 우리의 앱이 끔찍하지 않도록 만들어야 합니다. 좋은 소식은 낮게 달린 과일이 많이 있다는 것입니다. 교육과 도구로 시작합니다. 그러나 우리는 또한 제품 개발자가 옳은 일을 쉽게 할 수 있도록 해야합니다. 접근성을 부차적인 문제가 아닌 기본으로 만들기 위해 무엇을 할 수 있을까요?

  • 국제화(Internationalization). 우리 앱은 전세계에서 작동해야 합니다. 다른 언어를 사용할뿐만 아니라 오른쪽에서 왼쪽으로 읽는 레이아웃을 지원해야합니다. 제품 엔지니어의 노력을 최소화하면서요. 어떻게 지연 시간과 응답성을 희생하지 않으면서 다른 언어를 지원할까요?

  • 전송(Delivery). 우리는 앱 코드를 사용자의 컴퓨터에 가져와야합니다. 어떤 전송과 형식을 사용하고 있나요? 간단하게 들리겠지만 여기에는 많은 상충 관계가 있습니다. 예를 들어 네이티브 앱은 거대한 앱 크기의 비용을 치르면서 모든 코드를 미리 로드하는 경향이 있습니다. 웹 앱은 사용하는 동안 더 많은 지연 시간의 비용을 치르면서 초기 페이로드가 작은 경향이 있습니다. 어떻게 어떤 시점에서 지연 시간을 도입할지 선택할까요? 어떻게 사용 패턴을 기반으로 전송을 최적화할까요? 최적의 솔루션을 얻기 위해 어떤 종류의 데이터가 필요할까요?

  • 회복력(Resilience). 곤충학자라면 버그를 좋아할 수도 있지만, 아마 프로그램에서 보는 것을 좋아하지는 않을 것입니다. 그러나 일부 버그는 필연적으로 제품에 들어갑니다. 그럼 어떤 일이 벌어지나요? 일부 버그는 잘못되었지만 잘 정의된 동작을 유발합니다. 예를 들어, 일부 조건에서 코드가 잘못된 시각적 결과를 표시할 수 있습니다. 그러나 렌더링 코드가 멈춘다(crash)면 어떨까요? 시각적 결과가 일관되지 않아 의미있는 결과를 얻을 수 없습니다. 단일 게시물을 렌더링에서의 멈춤가 전체 피드를 “쓰러트리거나” 추가적인 멈춤을 일으키는 약간 부서진 상태(semi-broken state)가 되지 않아야합니다. 어떻게 코드를 짜야 렌더링과 가져오기(fetching) 실페를 분리하고 나머지 앱을 계속 실행할 수 있을까요? 사용자 인터페이스에 대한 내결함성이란 무엇일까요?

  • 추상화(abstraction). 작은 앱에서 우리는 위의 문제를 해치우기 위해 위해 많은 특별한 경우를 하드 코딩 할 수 있습니다. 그러나 앱은 커지는 경향이 있습니다. 우리는 코드의 일부를 재사용하고, 나누고(fork) 결합(join)하며, 공동으로 작업 할 수 있기를 원합니다. 다른 사람들에게 익숙한 조각들 사이에 명확한 경계를 정의하고, 자주 변경되는 논리를 너무 엄격하게 만들지 않기를 원합니다. 어떻게 특정 UI 부분의 세부 구현 사항을 숨기는 추상화를 만들까요? 어떻게 앱이 성장함에 따라 방금 해결한 문제와 같은 문제를 다시 발생시키는 것을 피할까요?


물론, 언급하지 않은 많은 문제가 있습니다. 이 목록은 결코 완전한 것이 아닙니다! 예를 들어, 디자이너와 엔지니어의 공동 작업, 디버그 및 테스트에 대해서는 언급하지 않았습니다. 아마도 다음 기회에.

특정 뷰(View) 라이브러리 또는 데이터 가져오기(data fetching) 라이브러리를 염두에 두고 이 문제들에 관해 읽고 싶을 것입니다. 그러나 저는 이 라이브러리들이 존재하지 않는다고 생각하고 다시 읽는 것을 권장합니다. 당신은 어떻게 이 문제를 해결하시겠습니까? 작은 응용 프로그램에서 시도해보세요! (당신의 실험을 GitHub에서 보고싶습니다. 제게 트윗해주세요.)

이 문제들에 관해 흥미로운 점은 대부분이 어떤 규모에서든 나타난다는 것입니다. typeahead 또는 tooltip과 같은 작은 위젯에서도, 트위터와 페이스북과 같은 거대한 앱에서도 볼 수 있습니다.

사용중인 앱의 중요한 UI 요소를 생각해보고 이 문제 목록을 살펴보세요. 개발자가 선택한 일부 상충 관계를 설명 할 수 있나요? 비슷한 행동을 처음부터(from scratch) 재현 해보십시오!

저는 라이브러리를 사용하지 않는 작은 앱에서 이러한 문제를 실험하면서 UI 엔지니어링에 대해 많은 것을 배웠습니다. UI 엔지니어링의 상충 관계에 대해 더 깊은 이해를 원하는 모두에게 이런 실험을 권합니다.