개발일지

[Swift] 어떻게 UX를 올릴 수 있을까? - Result 본문

개발일지/iOS

[Swift] 어떻게 UX를 올릴 수 있을까? - Result

Seobe95 2024. 2. 22. 17:01

0. 나만 그런가..?

개발자로서 작업을 하다가 보면, 콘솔창에 찍히는 print와 console 등으로 인해 왜 안되는지, 뭐가 문제인지 찾아볼 수 있게 된다. 그러다 보니 정작 앱을 테스트하다 제대로 동작이 안 되는 경우 무덤덤하게 Xcode를 보게 된다. 그러면서 에러를 해결하고, 다시는 그 화면을 안 보게 되는 듯이 작업을 하게 된다.

 

하지만 에러가 발생하는것이 당연한 경우가 있다. 사용자가 로그인을 할 때 잘못된 비밀번호를 입력했을 경우, 서버단에서 문제가 발생했을 경우, 우리는 에러를 사용자에게 알려야 한다. 나 같은 경우 뭔가 덤덤해져서 catch 문 내에서 print() 처리만 하고 이후에 그냥 넘기게 되고, 다른 레포들을 보아도 그러한 부분들이 몇 개 보이기도 한다. 이러한 점을 조금 잡아주면, 사용자가 뭐가 문제인지 알게 되고 '내가 어떻게 해야 제대로 되겠구나'하는 생각이 들지 않을까 싶었다. 

1. 개요

프로젝트를 진행하면서 이것 저것 다양한 기능을 구현하다가, 다음과 같은 상황이 있었다.

1. 특정 작업이 진행되는 도중 에러가 발생하면 그에 맞는 결과를 사용자에게 Alert로 표시하고 싶었다.
2. Switch문을 사용하여 각 에러에 맞는 문구(String)를 반환하도록 했다.

 

이러한 작업을 통해 사용자들에게 어떤 문제로 인하여 작업이 완료되지 못했는지를 알려주고 싶었다. 이러한 작업을 하고 프로젝트 레포로 풀리퀘를 날렸는데, 팀원분께서 Result<>타입이 있다는 것을 알려주었고, 이를 알아보게 되었다. 

 

공식문서에 따르면, 'Result 타입은 성공한 경우와 실패한 경우를 나타내는 값으로, 각 경우에 관련된 값을 포함한다.'라고 한다. 즉, 작업에 성공한 경우와 실패한 경우에 따른 값을 반환하는 것이다.

 

2. 비교 및 적용

위에서 말했던 상황은

  • 로그인
  • 특정 게시물 신고
  • 프로필 & 닉네임 변경

이렇게 세 가지 상황이다. 먼저 Result를 사용하지 않았던 경우를 먼저 보자.

func login() -> String? {
    do {
    	// 로그인 로직...
        return nil
    } catch {
    	let errorMessage = emailLoginErrorHandler(error: error as NSError)
        return errorMessage
    }
}

func emailLoginErrorHandler(error: NSError) -> String {
    switch error.code {
    case 17020 :
        return "네트워크 에러입니다. 네트워크 상태를 확인해주세요."
    case 17010 :
        return "비정상적인 요청입니다. 잠시 후 다시 시도해주세요."
    case 17008 :
        return "이메일 형식이 알맞지 않습니다."
    case 17009 :
        return "비밀번호가 일치하지 않습니다."
    case 17004 :
        return "비밀번호가 일치하지 않습니다."
    default:
        return "에러입니다. 잠시 후 다시 시도해주세요."
    }
}

 

이런 식으로 에러가 발생한 경우 String값으로 넘겨주고, 에러가 발생하지 않은 경우 nil을 반환하여 옵셔널 체이닝을 사용해 View단에서 알러트처리를 하게 되었다. 

 

사실 에러처리를 하는 데 있어 Result타입을 사용하거나 사용하지 않는다고 해서 성능이나 로직이 크게 변화한다거나 하지는 않는다. 그럼에도 굳이 사용하게 된 이유는 다음과 같다.

  1.  Result 타입을 사용하게 되면 내가 어떤 의도로 사용했는지 다른 팀원들이 파악하기 편함
  2. 애플에서 이런 상황에서 사용하라고 만들어 줌
  3. 다양한 곳에서 사용되는 에러 타입이라면, 한 번 만들어두면 재사용하기 편하다.
  4. 자동완성..

이런 이유로 사용하게 되었고, 다른 팀원들이 내 코드를 이해하기 편하도록 하는 것이 가장 중요하다고 판단했다. 

 

이번에는 신고하기 기능에서 Result 타입 사용 코드를 확인해 보자.

enum ReportError: Error {
    case invalidMemo
    case isNotLogin
    case firebaseError
    case duplicatedReport
}

func fetchReportMemo () async -> Result<Bool, ReportError> {
    guard let user = ... else {
    	return .failure(.isNotLogin)
    }
    
    ...
    
    do {
        ...
        return .success(true)
    } catch {
    	return .failure(.firebaseError)
    }
}

func fetchReport(memo: Memo, type: String, reason: String) async -> String? {
    let result = await fetchReportMemo(memo: memo, type: type, reason: reason)
    
    switch result {
    case .success:
        return nil
    case .failure(let failure):
        var errorMessage: String
        switch failure {
        case .firebaseError :
            errorMessage = "서버에러가 발생하였습니다. 잠시 후 다시 시도해주세요."
        case .invalidMemo:
            errorMessage = "메모가 유효하지 않습니다. 다시 시도해주세요."
        case .isNotLogin:
            errorMessage = "로그인 중이 아닙니다. 로그인 후 시도해주세요."
        case .duplicatedReport:
            errorMessage = "이전에 신고한 메모입니다. 2회 이상 신고할 수 없습니다."
        }
        return errorMessage
    }
}

이런 식으로 코드를 작성했다. 위의 Result 타입을 사용하지 않은 경우와 그렇게 큰 차이는 있지 않다. 하지만 다른 사람이 재사용하기 쉽다는 점과, 자동완성이 된다는 점, 유지보수가 되기 쉽다는 점이 장점인 것 같다. 

 

3. 마치며

위와 같은 작업을 통해 사용자들이 작업이 잘 완료되었는지, 안 됐다면 어떤 이유로 잘 안되었는지를 확인할 수 있고 이에 따른 대처를 할 수 있도록 환경을 만들어주었다.

 

어떠한 코드를 작성할 때, 결과는 같지만 과정은 다른 경우가 많다. 정답은 없겠지만 이러한 경우에는 내가 편하고, 다른 사람이 읽기 쉬운 코드가 가장 효율이 좋은 것 같다.


제가 만든 프로젝트 한번 보고 가세요! 다운까지 받아주신다면 감사드리겠습니다.

모아는 장소에 대한 추억과 메모를 남길 수 있는 앱이에요. 내가 좋아하는 장소에서 내 메모뿐만이 아닌, 다른 사람의 추억과 메모를 확인해 보세요!