본문 바로가기
가치관 쌓기/개발 돌아보기

빠르게 실패하기(fail-fast) VS 안전하게 실패하기(fail-safe)

by simplify-len 2021. 4. 16.

-

 이 주제에 대해서 "엘레강트 오브젝트-조영호 번역" 책에서 다루고 있습니다. 이론상으로만, 이해하고 있었습니다. 그러나, 최근 사내 서비스를 트러블 슈팅하며, 다른 분의 코드를 리뷰하며 이 주제에 대해서 말할 수 있었던 기회가 있었습니다. 그러나, 역시 이론과 코드에는 거리감이 있었고, 그 상황에서 저또한 명확하게 설명하기 힘들었습니다. 저 스스로는 빠르게 실패하기 를 실천하기 위해서 코드 상에서 노력하고 있는데, 이것을 누군가에게 설명하려니 잘 안되더군요. 그래서 팀원분에게 이 부분을 설명해주면서 헀던 이야기를 공유합니다.

먼저 빠르게 실패하기와 안전하게 실패하기에 대해서 이론으로 살펴보고 난 뒤에, 코드로서 살펴보겠습니다.

이 두 가지는 적절한 상황에 잘 활용하는 것이 중요하지만-, 만약 코딩 중에 둘 중 어느것을 선택해야 한다면, 저 스스로 판단하기로는 빠르게 실패하는 것에 조금더 지지하는 입장입니다.

안전하게 실패하기(fail safe)

먼저 '안전하게 실패하기' 부터 살펴봅시다. 예외 처리라는 것이 만들어진 이유가 즉, 안전하게 실패하기에 적절한 예시일 수 있습니다. 

try {
	...
} catch (Exception e){
 	...
}

버그, 입출력 문제, 메모리 오버플로우 등이 발생한 상황에서도 소프트웨어가 계속 실행될 수 있도록 하는 것이 안전하게 실패하기

빠르게 실패하기(fail fast)

빠르게 실패하는 것은 위 방법의 반대입니다. 문제가 발생하면 곧바로 실행을 중단하고 최대한 빨리 예외를 던집니다.

assert False;

계약에 의한 설계와 연관이 있습니다. 프로덕트 환경에서 소프트웨어가 만약 실패하게 된다면- 모든 실패 지점을 명확하고 문서화되어 있다면 추후 유지보수 관점에서 테스트를 쉽게 추가할 수 있을 것입니다.

계약에 의한 설계 관련 포스팅

 

계약에 의한 설계(Contract By Design) 더 잘 활용하기(with java)

들어가기 계약에 의한 설계(Contract By Design) 라는 용어에 대해서 조영호님의 Object 책에서 처음 접하게 되었습니다. 계약에 의해 설계가 이루어지지 않는 코드에서 발생하는 문제점을 운영중인 프

happy-coding-day.tistory.com

코드로서 이해하기

이론적으로 쉽게 '안전하게 실패하기'와, '빠르게 실패하기' 에 대해서 이해할 수 있었습니다. 그럼 이제 코드로서 살펴봅시다.

    private void setMasterInform(Community community) {
        User master = community.getMasterUser();
        if (Objects.nonNull(master)) {
            masterUser = master.getName();
            masterPosition = master.getPositionName();
        }
    }

처음 이슈가 제기되었던 코드는 위 코드였습니다. 만약 master 가 Null 이라면, setMasterInform 은 어떠한 일도 일어나지 않을 것입니다. 이와 비슷한 형태가 try catch 일텐데, 이또한 안전하게 실패하기 의 한 사례라고 볼 수 있었습니다. 

이 코드에 대해서, "Null 이면 예외를 던지는게 맞다. 그러나, NullPointerException 이 아니라, 우리가 알아볼 수 있는 예외로 변환시켜 던지는 것이 좋다" 라고 코멘트를 남겼습니다.

그러나, 발생했던 이슈는 'Null 일 때 문제가 발생하는거고 히스토리 추적이 불가해서, null 을 무시하게 해야 한다.' 마지막 해결카드이기 때문에, 저는 그러면 try catch 를 사용하여 로그를 남기는 것이 좋지 않을까요? 라고 말씀드렸습니다.

이렇게 말하면서 위 코드를 작성했던 분에게 마침 제가 해결했던 이슈 PR에 대해서 설명하면서 빠르게 실패하기와 안전하게 실패하기에 대한 설명을 드렸습니다. 아래는 마침 해결했던 이슈 PR에 대한 코드입니다.

    @Transactional
    @Override
    public AppletBaseModel saveBaseConfig(Long appletId, AppletBaseModel model) throws IOException, URISyntaxException {

        Applet applet = appletService.getApplet(appletId);

        authorityHelper.checkAppletAdmin(applet, getActor());

        applet = entityConverter.apply(applet, model);
	////// before //////
        Long companyId = applet.getCompany().getId();
        if (model.getIconUrl() != null && !model.getIconUrl().isEmpty()) {
            applet.setIcon(attachFileHelper.buildAttachFile(makeTempIcon(model.getIconUrl()), companyId,
                    SiteAttachableApps.WORKS));
        ----
	////// after //////
        Company company = applet.getCompany();
        Long companyId = company.getId();
        if (Objects.isNull(companyId)){
            throw new NotFoundCompanyException(ExceptionMessageKey.NOT_FOUND_COMPANY);
        }

        if (StringUtil.isNotEmpty(model.getIconUrl())) {
            applet.setIcon(
                    attachFileHelper.buildAttachFile(
                            makeTempIcon(model.getIconUrl()), companyId, SiteAttachableApps.WORKS)
            );
        }
        ----
        return appletModelConverter.toAppletBaseModel(applet);
    }

변경 전 후에 추가된 것은 Long companyId = applet.getCompany().getId(); 이 부분에 대한 예외를 발생시키는 부분을 추가시켰습니다. 레퍼런스 타입의 Long 받는 것은 즉, Null 을 허용될 수 있다는 잠재적인 요소가 존재하고, NullPointerExecption 은 프리미티브 타입의 long 이 존재하는 attachFileHelper.buildAttachFile() 에서 발생하게 됩니다. 

비록, 위 코드의 원초적인 이슈는 위에서 말한 부분은 아닙니다만, 코드의 신뢰성이 깨진 상태(잠재적인 Null 이 존재)에서 코드를 수정하기란 마치, 코드에 희망을 갈구하는 기분입니다.

        Company company = applet.getCompany();
        Long companyId = company.getId();
        if (Objects.isNull(companyId)){
            throw new NotFoundCompanyException(ExceptionMessageKey.NOT_FOUND_COMPANY);
        }

  이슈가 발생할 것 같다면, 빠르게 실패하는 것의 좋은 예시가 아닐까? 싶어, 공유드렸고, 다행히 수긍해주셔서 다시한번 빠르게 실패하기와 안전하게 실패하기에 대해서 코드로서 상기시킬수 있는 기회가 되었습니다.

 

[더 알아보기]

엘레강트-오브젝트 책 내용 - github.com/LenKIM/Book/blob/master/Elegant-object/_05_retire.md

 

LenKIM/Book

"좋은 책📚을 읽는 다는 것은 과거의 가장 훌륭한 사람들과 대화하는 것이다." - 데카르트 - LenKIM/Book

github.com

 

댓글