본 포스팅은 Fault Tolerance 를 의역한 내용입니다.
잘못된 내용이 있을 수 있습니다.
Fault Tolerance
현대 앱은 움직이는 수 많은 파트들과 복잡한 프로세스로 만들어 진다. 때때로 이것들은 동작을 멈추거나 문제가 생긴다. 우리는 이것들을 미연에 방지하려고 할 수는 있지만, 현실적으로 완전히 error-free 하게 만들 수 는 없다. 따라서 우리는 항상 예상치 못한 방식으로 무언가 고장날 수 있음을 인지하고 이를 적절하게 관리할 수 있어야 한다.
다른 말로 우리는 fault tolerance 가 있어야 한다.
Fault tolerance is the property that enables a system to continue operating properly in the event of the failure of some (one or more faults within) of its components.
fault tolerance란 시스템 내부에 어떤 문제가 발생해도 계속 동작할 수 있도록 하는 특성을 의미한다.
내 경험 상 종종 falut tolerance 는 무시되거나 과소평가 받곤 한다. 수 많은 테스트를 작성한다고 해도 필연적으로 문제가 발생할 수 밖에 없고, 이것은 특히 높은 접근성이 우선 시 될 때 더욱 중요하다.
error boundary는 우리의 리액트 애플리케이션이 fault tolerant 하도록 한다.
Error Boundaries
가장 손 쉬운 방법은 error boundaries 이다. 현재 API는 아래와 같이 오직 comopnentDidCatch 메소드와 함께 class components 로만 구현이 가능하지만, react-error-boundary를 사용하면 좀 더 손 쉽게 시작할 수 있다.
Drawing the Right Fault Lines
error boundary를 애플리케이션에 추가하는 것은 단 몇 줄이면 되지만, 진짜 까다로운 것은 error boundary를 배치할 적절한 위치를 찾는 것이다. 보통 골디락스 원칙에 따라 적당한 양의 error boundary를 구현하지만 '적당한 양'이라는 것은 무엇일까?
먼저 아래 나열된 두 극단을 살펴보자.
Not Enough Error Boundaries
첫 번째, 애플리케이션 최상단에 있는 단일 error boundary
// ⚠️ I'll use react-error-boundary in these examples
import ErrorBoundary from "react-error-boundary";
import App from "./App.js";
ReactDOM.render(
<ErrorBoundary>
<App />
</ErrorBoundary>,
document.getElementById("root")
);
대부분의 사람들이 사용하는 방식과 유사할 것이다. 이것은 서버 랜더링된 애플리케이션이 실패하는 것과 본질적으로 동일하다. 이것은 일부분만 실패해도 전체가 실패해버리는 문제를 가지고 있다.
애플리케이션의 일부만 동작하지 않아도 전체 애플리케이션을 사용할 수 없는 경우라면 적절한 접근 방식이겠지만, 보통 대부분의 경우에서는 그렇지 않다.
fault tolerance 를 다시 살펴보자.
Fault tolerance is the property that enables a system to continue operating properly in the event of the failure
단일 error boundary 는 fault tolerance 를 제공하지 못한다.
Too Many Error Boundaries
모든 컴포넌트를 error boundary 로 감싸는 다른 극단을 살펴보자.
이 접근 방식의 문제들은 좀 더 복잡하기 때문에 디테일한 예시를 통해 단일 error boundary 보다 왜 더 안좋을 수 있는지 살펴보자.
function CheckoutForm(props) {
return (
<form>
<CartDescription items={props.items} />
<CreditCardInput />
<CheckoutButton cartId={props.id} />
</form>
);
}
// Everyone gets an error boundary!
<form>
<ErrorBoundary>
<CartDescription items={props.items} />
</ErrorBoundary>
<ErrorBoundary>
<CreditCardInput />
</ErrorBoundary>
<ErrorBoundary>
<CheckoutButton cartId={props.id} />
</ErrorBoundary>
</form>
현실에서는 위와 같은 예시에서 각 컴포넌트는 위에서 처럼 inline으로 감싸지 않고, 각 컴포넌트에서 export 된 error boundary로(react-error-boundary의 withErrorBoundary 같은 고차 컴포넌트) 감싸지겠지만, 이 부분은 무시하고 넘어가도록 하자.
첫 눈에 보기에 error boundary를 세분화 할수록 단일 실패가 애플리케이션 전체에 미치는 영향을 줄일 수 있다. 이것은 fault tolerance 처럼 들린다. 그러나 error의 영향을 최소화하는 것이 fault tolerant 와 같은 것은 아니다.
CreditCardInput 컴포넌트에 문제가 생겼다고 가정해보자.
<form>
<ErrorBoundary>
<CartDescription items={props.items} />
</ErrorBoundary>
<ErrorBoundary>
{/* Uh oh! Something broke in here 😢 */}
<CreditCardInput />
</ErrorBoundary>
<ErrorBoundary>
<CheckoutButton cartId={props.id} />
</ErrorBoundary>
</form>
Half-Broken UI is Full-Broken UX
CreditCardInput 컴포넌트는 자신의 error boundary를 갖고 있으므로 CheckoutForm 컴포넌트의 나머지 컴포넌트에는 에러를 전파하지 않을 것이다. 그러나 CheckoutForm 컴포넌트는 CreditCardInput 없이는 사용할 수 없다.
CheckoutButton 컴포넌트와 CardDescription 컴포넌트는 아직 마운트되어 있어 user는 화면에서 이 컴포넌트들을 확인할 수 있고, checkout 하려고 시도할 것이다. 그러나 credit card info를 입력하지 않았다면 어떻게 될까? 신용카드를 입력한 후 CreditCardInput 컴포넌트에 문제가 생긴 것이라면 state가 유지될까? user들이 로그아웃을 시도하면 어떻게 될까?
이런 경우 이 컴포넌트 작성자도 해당 케이스에서 무엇이 발생할지 알 수 없으며 사용자도 마찬가지일 것이다.
Generic Error Boundary, Generic Fallback
만약 컴포넌트가 아무런 경고 없이 화면에서 사라진다면 매우 혼란스러울 것이다.
그렇지 않다면 아마도 공통 fallback UI를 사용할 것이다. 없는 것 보다는 낫지만, 모든 구성 요소를 이런 error boundary로 감싸면 fallback 은 모든 UI에 대해서 정확하게 랜더링되어야 한다. 각 구성 요소들은 각기 다른 레이아웃을 요구하기 때문에 이는 사실 상 불가능에 가깝다. fallback 은 헤더와 같은 페이지 레벨 단위의 섹션에서는 유용하지만 작은 버튼 아이콘과 같은경우에는 적합하지 않다.
모든 컴포넌트를 error boundary로 감싸는 것은 UX를 떨어뜨릴 수 있고 혼란스럽게 만들 수 있다.
We debated this decision, but in our experience it is worse to leave corrupted UI in place than to completely remove it
우리는 이 결정에 대해 논의했지만 우리의 경험 상 UI를 완벽히 제거하는 것보다 불완전한 UI를 그대로 두는 것이 더 좋지 않다.
💡 Performance Penalty
error boundary 는 내재된 복수의 overhead가 존재한다. 그래서 과도하게 사용하면 성능에 부정적인 영향이 있을 수 있다. 단, 이 문제는 모든 곳에서 error boundary를 사용했을 때 발생한다.그러니 이런 문제 때문에 error boundary를 사용하지 않을 필요는 없다.
The Right Amount of Error Boundaries
요약하자면, 충분하지 않는 error boundary 는 애플리케이션을 필요 이상으로 중단시키고, 너무 많은 error boundary는 UI를 망가뜨릴 수 있다.
결국 '적절한 양' 이라고 할 수 있는 수치가 명확하게 있는 것은 아니다. '적절한 양'이라는 것은 어떤 애플리케이션인지에 따라 다르기 때문이다.
✅ 가장 좋은 접근 방식은 feature boundary를 식별하고 그 곳에 error boundary를 두는 것이다.
Finding Features
무작위의 앱에서 feature boundary를 식별할 수 있는 feature
에 대한 보편적인 정의는 존재하지 않는다. 다만, 가이드라인으로 사용할 수 있는 일반적인 몇가지 패턴은 존재한다.
헤더, 네비게이션, 메인 콘텐츠, 사이드바, 푸터 등으로 나누어진 각 컴포넌트들은 일정한 독립성을 유지하지만 전체 사용자 경험에 기여한다.
트위터를 예로 들어보면, 페이지에 명확히 구분되는 섹션과 기능이 존재함을 알 수 있다. 각 센션의 레이아웃과 스타일링은 분명한 구분을 나타내며, 이는 중요한 포인트가 될 수 있다.
✅ 시각적으로 독립된 섹션들은 독립된 기능을 할 가능성이 높고, 이를 기준으로 error boundary가 위치해야 한다.
특정 섹션의 구성 요소에 문제가 생기면 해당 섹션만 중단되고, 다른 섹션에 영향을 주어서는 안된다.
A Recursive Line of Questioning
✅ 특정 컴포넌트에서 에러가 발생하면 형제 구성요소에 어떻게 영향을 미칠 것인가?
위 질문은 error boundary 의 위치를 결정하는데 있어 좋은 질문이다.
앞선 CheckoutForm 컴포넌트를 예시로 한다면, CreditCardInput 이 실패했을때 CheckoutButton 과 CardDescription 에 어떻게 영향을 줄 것인가?
Twitter Deep Dive: The Page
이후에 다뤄질 내용들은 작성자의 개인적인 의견과 편견이 많이 담겨 있다. 기능들이 어떻게 동작 하는지, 작성자가 예상하는 방식에 따라
작성되었다. 따라서 글의 내용이 '정답'이 아닐 수 있으며 단순히 프로세스를 이해하는데 집중하자.
첨부된 화면에는 Home, Trends for you, Who to follow 3가지 섹션이 존재한다. Who to follow 섹션을 깊게 파고 들어 보자.이 구성 요소가 중단되면 형제 요소들 또한 중단 되어야 하는가?
Who to follow 섹션을 고려할 때 만약 Who to follow 섹션이 중단되었을 때 Home, Trends sections 또한 중단 되어야 하나?
이 질문에는 명확하게 그렇지 않다 라고 생각한다. 다른 섹션들은 서로 의존하지 않으므로 여기에 error boundary를 설정하는 것이 좋다.
Twitter Deep Dive: Who To Follow
Who to Follow 를 살펴보면 명확하게 나누어져 있는 3가지 섹션을 확인할 수 있다. title, the list of users to follow 그리고 show more 버튼이 있다. 여기에서도 같은 질문을 반복할 수 있다.
list of users to follow 컴포넌트가 중단되면 title과 show more 버튼 역시 중단되어야 하나?
이 경우에는 다소 명확하지 않지만 아마도 그래서는 안된다. title과 show more 버튼은 그대로 유지되어야 한다.
따라서 이 경우에 대답은 yes, error boundary를 추가하자.
Twitter Deep Dive: Follower Recommendation
한 단계 더 깊이 들어가서 follower recommendations을 보자.
만약 사용자의 name과 동작이 중단되면 follow 버튼 역시 중단 되어야 하나?
여기서도 대답은 yes, 사용자의 이름과 동작이 사라지면 우리가 누구를 follow 하는지 알수 없고 follow 버튼이 사라지면 추천을 받았지만 아무런 동작을 할 수 없다.
Testing your Fault Tolerance
error boundary 와 fault tolerance 를 사용하고 나서 테스트하기 위한 가장 좋은 방법은 고의로 망가뜨려 보는 것이다.
function CreditCardInput(props) {
// What happens if I messed up here? Let's find out!
throw new Error("oops, I made a mistake!");
return <input className="credit-card" />;
}
이것은 새로운 구성 요소를 추가할 때마다 해보는 작업이며, 이를 통해 애플리케이션이 에러를 어떻게 처리하는지 확인하기 유용한 방법이었다. 단, 이러한 throw 문을 커밋하지 않도록 주의해야 한다.
'React' 카테고리의 다른 글
React | Redux 사용하기 (feat. 리액트를 다루는 기술) (0) | 2022.03.21 |
---|---|
React | 리덕스 키워드 정리 (0) | 2022.03.18 |
React | 구글 리캡챠(reCaptcha) v2 사용하기 (0) | 2022.02.25 |
React | 카카오 맵 API 사용하여 지도 띄우기 (0) | 2022.02.23 |
React | do not nest ternary expression (Nextjs , typescript, airbnb style guide) (0) | 2022.02.10 |