背景现状
项目每积累到一定程度,代码的重构优化是必经之路。
试卷项目初期,整体过错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>