[TIL] 21.04.07.(Wed)

활동 요약

  • PR 피드백 수신
  • PR 피드백 고민 및 반영
    • JSON 디코딩 관련
      • CustomJSONDecoder 타입으로 디코딩 방식 회귀
      • CustomJSONDecoder 타입의 Decode 메서드 로직 변경 (제네릭 타입을 제네릭 메서드로)
      • 에러 처리 로직 추가 (에러 타입)
      • 테스트 메서드 로직 변경: JSON 데이터 디코드 결과 검증 방식 변경
        • XCAssertNoThrow(_:) 활용 검증
    • 코드 작성 스타일 관련
      • 테스트 코드에서도 암시적 옵셔널 타입(!) 사용 지양. 일반 옵셔널(?) 활용 후 throw error
      • Result 타입 활용한 에러 처리 방식 학습. (부제: 야곰의 Pick은?)
    • Class Diagram 관련
      • 자주 사용하는 프로토콜은 채택 사실만 확인할 수 있도록 Class Diagram에 표시
      • 만국박람회 프로그램 Class Diagram 1차 수정본 작성
    • 기타
      • testExample() 메서드 관련 오해 해소
  • PR 피드백 반영 후 추가 커밋 push

요약이 왜 이렇게 긴고..


활동 내용 상세

PR 피드백 수신

킹갓엠페러제너럴충무공마제스티 야곰 야멘!!! 우후!!!!

힘이 되는 피드백 감사합니다..! 💪


PR 피드백 고민 및 반영

JSON 디코딩 관련

CustomJSONDecoder 타입으로 디코딩 방식 회귀

CustomJSONDecoder 타입의 Decode 메서드 로직 변경 (제네릭 타입을 제네릭 메서드로)

에러 처리 로직 추가 (에러 타입)

어제 TIL에서도 언급했던 내용으로, JSON 데이터 디코딩 메서드를 가볍게 래핑하는 과정에서 프로토콜로 기능을 구현할지, 구조체나 클래스와 같은 인스턴스화가 가능한 메서드로 구현할지 고민했습니다. 그리고 오늘 코드 리뷰를 받아보니 인스턴스화가 가능한 타입으로 구현하는 것이 여러 타입에 유연하게 대응할 수 있어 더 나아보인다는 의견을 주셨습니다.

그리고 매 번 인스턴스를 생성할 때마다 타입을 정해주는 방식 보다 메서드를 사용할 때 디코딩할 타입을 지정하는 것이 편의성 측면에서 더 낫다고 생각하여 아래 코드와 같이 제네릭 타입을 제네릭 메서드로 변경했습니다. 아울러 기존 JSONDecoder 타입의 decode(_:from:) 메서드의 에러 처리를 호출하는 쪽에서 함께 담당하는 형식으로 변경하여 테스트를 수행할 때 새로 작성한 메서드에서 에러를 검출할 수 있게끔 만들었어요.

/// Decodable 프로토콜을 준수하는 타입에 대해 타입 이름과 파일 이름 입력만으로 JSON 디코딩을 도와주는 메서드를 제공하는 타입.
class CustomJSONDecoder {
  /// 변환할 타입과 JSON 파일 이름을 전달인자로 받아 지정된 타입으로 디코딩 결과를 반환한다.
  /// - 전달인자에 유효하지 않은 JSON 파일을 입력할 경우 `ExpoAppError.invalidJSONFile` 에러를 던진다.
  /// - Parameter type: 변환할 타입. 모델 타입의 인스턴스를 원하면, `모델타입.self`로 작성한다.
  /// -  Parameter jsonFileName: JSON 파일 이름을 `String` 타입으로 작성한다.
  public func decode<Decoded>(to type: Decoded.Type,
                              from jsonFileName: String) throws -> Decoded? where Decoded: Decodable {
    var decodedResult: Decoded?
    let jsonDecoder = JSONDecoder()
    guard let jsonData: NSDataAsset = NSDataAsset(name: jsonFileName) else {
      throw ExpoAppError.invalidJSONFile
    }
    
    decodedResult = try jsonDecoder.decode(Decoded.self, from: jsonData.data)

    return decodedResult
  }
}

테스트 메서드 로직 변경: JSON 데이터 디코드 결과 검증 방식 변경

  • XCAssertNoThrow(_:), XCAssertThrowsError(_:_:) 활용 검증

메서드를 수정했으니 테스트 방식도 변경해야겠죠? 이제 에러를 던지는 throwing function으로 구현했으니 XCTAssertNoThrow(_:)XCAssertThrowsError(_:_:)를 활용할 수 있겠네요. 아래 코드 처럼요!

func test_customJSONDecoder_decode() {
  let jsonDecoder = CustomJSONDecoder() // 이제 인스턴스는 한 번만 생성!

  // 한 번의 인스턴스 생성으로 여러 가지 타입 디코딩이 가능!
  XCTAssertNoThrow(try jsonDecoder.decode(to: ExpoIntroduction.self, from: "exposition_universelle_1900"))
  XCTAssertNoThrow(try jsonDecoder.decode(to: [Artwork].self, from: "items"))
}

func test_customJSONDecoder_decode_withInvalidJSONFile() {
  let jsonDecoder = CustomJSONDecoder()

  XCTAssertThrowsError(try jsonDecoder.decode(to: ExpoIntroduction.self, from: "invalidJSONFile")) { (error) in
    XCTAssertEqual(error as? ExpoAppError, ExpoAppError.invalidJSONFile)
  } // 유효하지 않은 파일 이름을 입력하면 에러를 던질텐데, 던지는 에러가 `ExpoAppError.invalidJSONFile`가 맞는지 검증!
}

코드 작성 스타일 관련

테스트 코드에서도 암시적 옵셔널 타입(!) 사용 지양. 일반 옵셔널(?) 활용 후 throw error

PR을 보내면서 아래와 같은 질문을 했어요.

요점은 테스트를 할 때는 nil이 할당되면 안되는 곳에서 nil이 검출되면 안되니 에러 발생 확인이 용이하도록 암시적 추출 옵셔널 타입(!)을 썼는데 어떤가요? 라는 말입니다. 이에 대한 야곰의 답변은..?

이후 야곰의 코멘트와 연계해서 이유 없이 nil이 검출되는 것은 왜 에러가 발생했는지 확인하기 어렵기 때문에 이런 방식의 프로그래밍은 지양하시는듯해요. 그래서 제 코드도 System Under Test (SUT) 인스턴스들에 대해 암시적 추출 옵셔널 타입을 일반 옵셔널로 변경했습니다 (! -> ?).

Result 타입 활용한 에러 처리 방식 학습

지나가는 말씀으로 "반환 받을 타입이 문제라면 Result 타입이라는 멋진 제네릭 타입이 있다"고 말씀하셨어요. 사실 의도를 정확하게 파악하지는 못했지만 오늘 해당 타입을 공부하고 Push를 보내며 추가 질문을 남겼답니다.

본문에 제가 작성했듯이 에러를 던지는 (throw) 것이 아니라 반환 (return)하므로 에러 처리를 위해 do-catch 문을 작성하지 않아도 되므로 이 메서드를 호출할 때 번거로운 일이 줄고 메서드의 실행의 결과가 성공인지 실패인지 더 명확해진다는 장점이 있을 것 같아요. 더욱이 메서드 실행 시 실패의 결과가 nil인 것 보다 훨씬 의미가 잘 전달될겁니다. 하지만 아직 의도를 정확히 이해하지 못해서 실제 코드에 반영하지는 않았습니다. Swift는 알면 알수록 정말 재미있는 언어네요.

Class Diagram 관련

자주 사용하는 프로토콜은 채택 사실만 확인할 수 있도록 Class Diagram에 표시


오호..! 바로 반영~!

만국박람회 프로그램 Class Diagram 1차 수정본 작성

기타

testExample() 메서드 관련 오해 해소

저는 testExample() 메서드를 지금까지 테스트 메서드 목록을 적으면 해당 메서드들을 실행해주는 메서드라고 오해하고 있었어요. 이 번 기회에 주석을 다시 읽어보고 위 캡처 내용처럼 해당 메서드와 주석에 대해 다시 생각해보는 기회를 가졌습니다.


PR 피드백 반영 후 추가 커밋 push 🚀

다음 단계로 가즈아~~! 🔥
이제 정말 UI와의 싸움인가..?


추가로 공부해볼 것

Result 타입을 활용한 에러 처리

Opaque Type (불명확 타입)

Enumeration의 Associated value

좋은 웹페이지 즐겨찾기