Swift Error重构优化详解

背景现状

项目每积累到一定程度,代码的重构优化是必经之路。

试卷项目初期,整体错误Code较少,直接使用更便于处理错误状态,因此便全部归整到一个单独的 NetWorkError.ResponseCodeType 中,但是随着项目功能的丰富,各个功能模块越来越多,模块错误的处理也各不相同,每个模块都关联了所有的错误Code,后续还会持续增长,导致越来越难以维护。

enum ResponseCodeType: Int {
  case success = 0
  case tokenExpire = 11001
  case overVerifyCode = 11011
  case verifyCodeExpire = 11002
  case verifyCodeIncorrect = 11003
  case autoLoginFailed = 11004
  case appidLoginFailed = 11005
  case phoneIsRegisted = 11006
  case phoneHasBinded = 11010
  case joinedBeePlan = 11100002
  case uploadRepeate = 11020005
  case wechatHasBinded = 11010017
  case phoneHasBindedOtherWeChat = 11010022
  case todayIsSignIned = 11140003
  case subjectCountLimit = 11150004
  case invalidTagName = 11160002
  case alreadyExistsTagName = 11160003
  case outOfMaxTagsCount = 11160004
  case notRegisterHomework = 11010033
  case notSupportNumber = 11010028
  case wrongTeamCode = 11210005
  case classNotFound = 11210006
  case nicknameExists = 11210007
  case joinClassThreeTimes = 11210008
  case identityNickNameExists = 11210014
  case checkClassCodeMax = 11210016
  case createClassMAx = 11210015
  case joinTeamMax = 11210017
  case studentCountMax = 11210018
  case other = -99999
}

问题分析

提前分析、明确目标。

期望结果

  • 错误处理分为两部分:通用、自定义模块,二者各自处理
  • 拓展性强,各个模块可自定义并处理错误,基类代码保持稳定不变
  • 支持点语法、穷举校验,使用清晰便捷

技术选型

根据期望结果,可以大致选定技术方向

  • 拓展性:泛型、协议
  • 类型穷举:枚举

优化解决

前后对比,不断调优。

Error模型

  • 区分通用和自定义模块
  • 将 ResponseCodeType 降为通用Code类型,可以将其类型固定
  • 替换 NetWorkError,使用 ModuleRespError 作为基类Error,通过泛型为外部模块提供自定义能力

优化前

struct NetWorkError: Error {
  var code: ResponseCodeType = .other
  var msg: String { code.errorString }
}

优化后

/// 错误类型描述
public protocol ISErrorProtocol {
  var errorString: String { get }
}
public enum ModuleRespError<T: ISErrorProtocol>: Error {
  /// 对应模块自定义类型code
  case type(_ value: T)
  /// 基类请求code
  case baseType(_ value: ResponseCodeType)
  /// 错误提示归整
  public var mapErrorString: String {
      switch self {
      case .type(let value):
          return value.errorString
      case .baseType(let value):
          return value.errorString
      }
  }
}

基类Request

使用协议的类型占位符 associatedtype,便于后续进行 rawValue 的枚举映射

  • 分层处理错误类型,基类错误放到基类请求的回调中处理,抛出模块的错误code

在ISTargetType协议中关联错误码类型 associatedtype ErrorCodeType: RawRepresentable

public protocol ISTargetType {
  /// 错误码类型,由各模块自定义
  associatedtype ErrorCodeType: RawRepresentable
}

优化前

/// 根据 ISTargetType 枚举类型调用接口,返回 model
static func requestISType<T: ISTargetType>(_ server: T,
                                         completion: @escaping (_ model: NetworkModelResponse?, _ code: ResponseCodeType) -> Void) {
  // ...
  Network.IS.fetchDataDic(server) { dataDic in
      guard let dataDic = dataDic,
            let model: NetWorkResponseModel = NetWorkResponseModel.deserialize(from: dataDic) else {
          completion(nil, .other)
          return
      }
      // 判断code 是否为token过期
      let codeValue = model.ret ?? ResponseCodeType.other.rawValue
      // errorType
      let codeType = ResponseCodeType(rawValue: codeValue) ?? .other
      // 基类Code处理,token过期
      NetWorkRequest.checkTokenDidExpire(codeType)
      // 抛出的code:基类、模块混在一起
      completion(model, codeType)
  }
}

优化后

/// T.ErrorCodeType: 遵循 RawRepresentable 协议的泛型
/// Result<Success, Failure> 拆分成功、失败逻辑
static func requestISResultType<T: ISTargetType>(_ server: T,
                                               result: @escaping ((Result<NetWorkResponseModel, ModuleRespError<T.ErrorCodeType>>) -> Void)) {
  // ...
  Network.IS.fetchDataDic(server) { dataDic in
      // 接口数据处理
      guard let dataDic = dataDic,
            let model: NetWorkResponseModel = NetWorkResponseModel.deserialize(from: dataDic),
            let retCode = model.ret else {
          // 接口错误,默认基类错误
          let error: ModuleRespError<T.ErrorCodeType> = .baseType(.other)
          result(.failure(error))
          return
      }
      if retCode == 0 {
          // 成功返回
          result(.success(model))
          return
      }
      // 请求失败
      if let baseType = ResponseCodeType(rawValue: retCode) {
          result(.failure(.baseType(baseType)))
          // 优先处理基类错误code,例如 token失效
          NetWorkRequest.checkTokenDidExpire(baseType)
      } else if let retValue = retCode as? T.ErrorCodeType.RawValue,
                let moduleType = T.ErrorCodeType(rawValue: retValue) {
          // 解析并返回模块错误码
          result(.failure(.type(moduleType)))
      }
  }
}

模块调用

  • 各模块自定义ErrorCode,互不干涉
  • 通过泛型参数定义ErrorCode类型
  • 使用Result<Success, Failure>,消除结果可选值,成功失败二选一,区分处理
  • 限制失败Error类型,仅需处理当前模块和基础错误,无需关注其他类型错误

优化前

public func queryDemo(with params: [String: String], completionHandler: @escaping (_ model: DemoModel?, _ code: ResponseCodeType) -> Void) {
  NetWorkRequest.requestISType(GroupQueryServer.createGroup(params)) { model  in
      // ...
      let code = model.ret ?? -1
      let type = ResponseCodeType(rawValue: code) ?? .other
      guard type == .success,
            let result = DemoModel.deserialize(from: model.data) else {
          completionHandler(nil, type)
          return
      }
      completionHandler(.success(resultModel))
  }
}
logic.queryDemo(with: params) { model, code in
	// 只能通过解包model来判断接口的成功或失败
	guard let model = model else {
		// 失败处理
		handleFail(code: code)
	return
}
	// 成功处理
	hanldeSuccess()
}
private func handleFail(code: ResponseCodeType) {
  // ...
	// 当前模块错误处理
	let showWarning = code == .wrongTeamCode || code == .classNotFound
	// UI处理
	warningLabel.isHidden = !showWarning
  // 提示
  CEProgressHUD.showTextHUD(code.errorString)
}

优化后

public enum StudyGroupRespCode: Int, ISErrorProtocol {
  case wrongTeamCode = 11210005
  case classNotFound = 11210006
  case nicknameExists = 11210007
  case joinClassThreeTimes = 11210008
  case identityNickNameExists = 11210014
  case checkClassCodeMax = 11210016
  case createClassMAx = 11210015
  case joinTeamMax = 11210017
  case studentCountMax = 11210018
  case folderLevelLimit = 11210027
  case curIdentifierError = 11210011
  case clockFrequencyInvalid = 11210036
  case other
}
public func queryDemo(with params: [String: String], completionHandler: @escaping ((Result<ClassItemModel, ModuleRespError<StudyGroupRespCode>>) -> Void)) {
// 基类请求
NetWorkRequest.requestISResultType(GroupQueryServer.createGroup(params)) { result in
  switch result {
  case .success(let success):
      // 结果处理que
      if let resultModel = ClassItemModel.deserialize(from: success.data) {
          // 转换模块模型model
          completionHandler(.success(resultModel))
      } else {
          // 转化失败,默认other
          completionHandler(.failure(.type(.other)))
      }
  case .failure(let error):
      // 抛出的模块错误
      completionHandler(.failure(error))
  }
}
logic.queryDemo(with: params) { result in
	// 通过 Result 划分结果状态
	switch result {
	case .success(let model):
		// 成功处理
		hanldeSuccess()
	case .failure(let error):
		// 失败处理
		handleError(error)
	}
}
// 示例为简单处理,若需精细化处理错误,拆分优化后的代码,逻辑明显更加清晰
private func handleError(_ error: ModuleRespError<StudyGroupRespCode>) {
	switch error {
	case .type(let code):
		// ...
		// 当前模块错误处理
		let showWarning = code == .wrongTeamCode || code == .classNotFound
		// UI处理
		warningLabel.isHidden = !showWarning
		// 提示
		CEProgressHUD.showTextHUD(code.errorString)
	case .baseType(let error):
		// 基类错误处理
		CEProgressHUD.showTextHUD(error.errorString)
	}
}

总结

至此,我们已经了解了有关ErrorCode的重构优化的大体逻辑,从后续的开发流程结果可以看出,确实对项目的Code混乱增长有了良好的控制,各模块只需要关注处理自己的异常code,降低了维护代码难度,后续也会持续关注和优化。

参考资料

  • Result 还是 Result<T, E: Error>

以上就是Swift Error重构优化详解的详细内容,更多关于Swift Error重构优化的资料请关注编程教程其它相关文章!

下一章:Swift设计思想Result&lt;T&gt;与Result&lt;T, E: Error&gt;类型解析

 背景知识Cocoa API 中有很多接受回调的异步方法,比如URLSession的dataTask(with:comp ...