오류와 복구
문제의 상태와 사용자가 할 수 있는 복구 행동을 함께 제공합니다.
목표
오류를 기술 예외의 출력이 아니라 사용자가 원래 목표로 돌아가도록 돕는 경로로 설계한다. 오류를 예방할 수 있으면 제거하고, 발생하면 실패한 범위, 보존된 상태와 실제 다음 행동을 설명한다.
오류를 쓰기 전에 확인한다
- 애초에 선택할 수 없는 option을 제거하거나 제약할 수 있는가?
- 시스템이 값을 자동으로 보정하거나 안전한 기본값을 쓸 수 있는가?
- 입력 전에 형식과 제한을 알려 예방할 수 있는가?
- 더 적절한 component나 flow로 오류 자체를 없앨 수 있는가?
- 사용자가 지금 해결할 수 있는 문제인가?
문구를 고치는 것으로 구조 문제를 덮지 않는다.
오류 메시지의 네 요소
1. 실패한 대상
오류가 발생했어요가 아니라 무엇을 끝내지 못했는지 말한다.
2. 현재 상태
입력, 저장된 값, 이미 완료된 일부 작업이 유지되는지 설명한다.
3. 복구 행동
사용자가 지금 할 수 있는 가장 직접적인 행동을 제공한다.
4. 필요한 이유
안전하고 도움이 될 때만 원인을 설명한다. 보안 정보나 내부 stack을 그대로 노출하지 않는다.
초안을 저장하지 못했어요.
입력한 내용은 이 화면에 남아 있어요.
연결 상태를 확인한 뒤 다시 저장해주세요.범위에 맞는 표현
Field 오류
- 해당 control 가까이에 표시한다.
- 어떤 값이 필요한지와 수정 방법을 말한다.
- input과 programmatically 연결한다.
Section 오류
- 화면 나머지를 사용할 수 있으면 실패한 section만 상태를 바꾼다.
- 재시도도 같은 section 범위로 제한한다.
Toast 오류
- 사용자가 현재 화면에서 바로 다시 행동할 수 있고 긴 설명이 필요 없을 때 사용한다.
- 사라지기 전에 읽고 복구할 수 없는 중요한 오류에는 사용하지 않는다.
Dialog 오류
- 작업 손실, 중요한 정책, 사용자의 결정이 필요할 때 사용한다.
- 단순 network 실패마다 dialog로 흐름을 막지 않는다.
Page 오류
- 주 데이터 전체를 사용할 수 없을 때 같은 content frame에 보여준다.
- navigation과 돌아갈 경로를 유지한다.
입력과 저장 오류
- submit 후 입력값을 지우지 않는다.
- 여러 오류가 있으면 summary에서 개수와 field link를 제공한다.
- 첫 오류 field로 focus를 이동한다.
- 서버 validation을 field 오류와 form 수준 오류로 분류한다.
- 자동 저장 실패는
저장됨상태로 남기지 않는다. - 충돌은 일반 실패가 아니다. 최신본, 현재 변경, 선택 가능한 해결 방법을 제공한다.
비동기·부분 오류
- 작업 요청 실패와 작업 실행 중 실패를 구분한다.
- 일부 성공이면 성공·실패·건너뜀 개수를 각각 말한다.
- 재시도가 중복 결과를 만들지 않는지 확인한다.
- 실패 record만 filter하거나 다운로드할 경로를 제공한다.
- 최신 상태를 확인할 수 없으면 마지막 확인 시각과 stale 상태를 말한다.
보안과 권한 오류
- 계정 존재, 인증 정보, 내부 권한 구조를 과도하게 노출하지 않는다.
- 자세한 원인을 말할 수 없어도 사용자가 값을 수정해야 하는지, 다시 인증해야 하는지, 기다리거나 문의해야 하는지는 구분한다.
- 권한이 없으면 일반 network 오류로 숨기지 않는다.
- 문의가 필요하면 복사 가능한 오류 ID, 발생 시각과 문의 경로를 제공한다.
- 기술 정보는 핵심 문장 뒤의 보조 영역에 둔다.
어조
- 사용자를 주어로 비난하지 않는다.
안 돼요만 말하지 않고 가능한 다음 행동을 말한다.- 해결할 수 없는 문제를 사용자가 해결할 수 있는 것처럼 약속하지 않는다.
- 심각한 오류에서 농담, 이모지, character 감정 표현을 피한다.
- 실제 손실과 영향은 긍정형으로 돌려 모호하게 만들지 않는다.
접근성
- field 오류에
aria-invalid와aria-errormessage또는aria-describedby를 연결한다. - submit 실패 summary는 heading과 오류 link를 가진다.
- 새로운 오류는 필요한 경우
role="alert"로 알리되 입력마다 반복하지 않는다. - 색, icon, border만으로 오류를 전달하지 않는다.
- retry button의 이름에 실패 대상을 포함한다.
- 오류가 사라진 뒤 focus를 갑자기 이동하지 않는다.
- toast만 사용하는 경우 충분한 노출 시간과 keyboard 접근 가능한 action을 보장한다.
예시
| 피한다 | 사용한다 |
|---|---|
| 오류가 발생했습니다. | 실행 기록을 불러오지 못했어요. |
| 잘못 입력하셨습니다. | 이메일 주소 형식을 확인해주세요. |
| 권한이 없습니다. | 발행 권한이 필요해요. 워크스페이스 소유자에게 요청할 수 있어요. |
| 처리에 실패했습니다. 다시 시도해주세요. | 12건 중 3건을 발송하지 못했어요. 실패한 대상만 다시 발송할 수 있어요. |
| 저장할 수 없습니다. | 최신 변경과 충돌해 저장하지 않았어요. 입력한 내용은 유지했어요. |
<FormField id="email" label="이메일" error="@ 뒤에 도메인을 포함한 이메일 주소를 입력해주세요.">
<Input type="email" />
</FormField>피해야 할 사용
- 구조로 예방할 수 있는 오류를 문구만 고쳐 유지
- 모든 오류를 같은 toast로 표현
- 서버 오류 시 입력과 필터 초기화
- 사용자가 해결할 수 없는 문제에 무한 재시도 제공
- 부분 성공을 전체 실패로 표시
- 기술 stack과 내부 코드만 노출
- 색상과 느낌표만으로 심각도 전달
- 실패를 character의 감정이나 사용자 탓으로 표현
검토 체크
- 오류 자체를 예방하거나 제거할 방법을 먼저 검토했다.
- 실패한 대상과 범위가 첫 문장에 있다.
- 입력과 이미 완료된 작업이 유지되는지 말한다.
- 실제로 실행 가능한 복구 행동을 제공한다.
- field, section, toast, dialog, page 중 범위에 맞는 표현을 쓴다.
- 부분 성공, 충돌, 권한, stale을 일반 오류와 구분한다.
- 보안 정보를 노출하지 않으면서 다음 행동은 명확하다.
- 오류와 control이 programmatically 연결돼 있다.
- 색과 icon 없이도 오류를 이해할 수 있다.