日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

編寫更清晰代碼:去掉所有多余的類型

來源: 責編: 時間:2023-10-13 14:34:36 239觀看
導讀最近,在 r/swift 子論壇上,我偶然發現了一篇介紹“整潔架構”項目示例的帖子。這引起了我的興趣,于是我決定在 GitHub 上下載并仔細研究。帖子截圖初看代碼頗為復雜,讓我感到迷惑。但在下載和深入研究后,我發現所有組件都

最近,在 r/swift 子論壇上,我偶然發現了一篇介紹“整潔架構”項目示例的帖子。這引起了我的興趣,于是我決定在 GitHub 上下載并仔細研究。A3x28資訊網——每日最新資訊28at.com

帖子截圖帖子截圖A3x28資訊網——每日最新資訊28at.com

初看代碼頗為復雜,讓我感到迷惑。但在下載和深入研究后,我發現所有組件都整合在一起,項目實現了想要的功能。但我發現該項目的網絡模塊的復雜性較高。僅兩個簡單的網絡查詢操作竟涉及如此多的文件,讓人難以理解,讓我頗為驚訝。A3x28資訊網——每日最新資訊28at.com

因此,我決定對網絡層進行重構,使其更加模塊化,并對整體組合和用戶界面進行了小幅優化。為此,我創建了獨立的項目對原始項目代碼進行重構,你可以在文末找到原始項目和我重構后的項目鏈接。A3x28資訊網——每日最新資訊28at.com

網絡層——消除嵌套和多余類型

原項目的網絡層通過協議和類型結構實現了高度模塊化,每個協議和類型分別負責特定功能,大致結構如下:A3x28資訊網——每日最新資訊28at.com

NetworkManager -> RequestManager -> RequestProtocol -> DataParser -> DataSource -> Repository -> UseCase

上述每一個類型都承擔了網絡過程的一部分職責,例如 DataParser 負責數據解析,如果想改變數據的解析方式,可以通過替換新的 DataParser 來實現,這種組合性是一項優點。A3x28資訊網——每日最新資訊28at.com

但問題在于,由于這些類型相互嵌套,使人難以整體理解,且每個類型都存于單獨的文件中。許多通過 Swinject 解析器進行注入,這使得整個網絡層的工作流程變得難以追蹤。正如 r/swift 中的一名評論者所言,這為代碼增加了一層不必要的“中間層”。A3x28資訊網——每日最新資訊28at.com

更令人費解的是,盡管作者增加了許多協議和類型來提高代碼的靈活性,但其中存在很多硬編碼的默認值。例如,DataParser 被直接編碼在代碼中,而 RequestProtocol.request() 的創建僅通過協議本身的擴展方法來實現。這種在增加了類型和復雜性后未充分利用它們的優勢的做法,實在讓人覺得可惜。A3x28資訊網——每日最新資訊28at.com

為了消除冗余的嵌套以及不必要的類型和協議,我們可以引入一個全新的方法:modelFetcher。A3x28資訊網——每日最新資訊28at.com

static func modelFetcher<T, U: Codable>(    createURLRequest: @escaping (T) throws -> URLRequest,    store: NetworkStore = .urlSession) -> (T) async -> Result<BaseResponseModel<PaginatedResponseModel<U>>, AppError> {    let networkFetcher = selfworkFetcher(store: store)    let mapper: (Data) throws -> BaseResponseModel<PaginatedResponseModel<U>> = jsonMapper()    let fetcher = self.fetcher(        createURLRequest: createURLRequest,        fetch: { request -> (Data, URLResponse) in            try await networkFetcher(request)           }, mapper: { data -> BaseResponseModel<PaginatedResponseModel<U>> in                       try mapper(data)                      })    return { params in          await fetcher(params)         }}

此函數的設計旨在保持與原代碼相同的組合功能,但未采用協議(protocols)和類型(types),而是通過直接注入操作行為來實現。需要說明的是,如果這樣更方便,你還可以將其構造成一個帶閉包的結構體,而不僅限于閉包。A3x28資訊網——每日最新資訊28at.com

接下來,實際的請求獲取閉包創建過程被大大簡化,唯一會變化的是請求創建部分。A3x28資訊網——每日最新資訊28at.com

static func characterFetcher(    store: NetworkStore = .urlSession) -> (CharacterFetchData) async -> Result<BaseResponseModel<PaginatedResponseModel<CharacterModel>>, AppError> {    let createURLRequest = { (data: CharacterFetchData) -> URLRequest in                          var urlParams = ["offset": "/(data.offset)", "limit": "/(APIConstants.defaultLimit)"]                          if let searchKey = data.searchKey {                              urlParams["nameStartsWith"] = searchKey                          }                          return try createRequest(                              requestType: .GET,                              path: "/v1/public/characters",                              urlParams: urlParams                          )                         }    return self.modelFetcher(createURLRequest: createURLRequest)}

優化后,我們無需深入到許多不同的文件中,也無需理解眾多的協議和類型,因為我們可以通過直接注入閉包來實現相同的行為。NetworkStore 負責實際將數據發送到網絡,我們將其傳遞到構造函數中是為了方便后續的測試模擬(如果有需要的話)。A3x28資訊網——每日最新資訊28at.com

下面的例子展示了如何通過使用行為替代類型,將原始項目中的協議和類型進行轉換:A3x28資訊網——每日最新資訊28at.com

protocol NetworkManager {    func makeRequest(with requestData: RequestProtocol) async throws -> Data}class DefaultNetworkManager: NetworkManager {    private let urlSession: URLSession    init(urlSession: URLSession = URLSession.shared) {        self.urlSession = urlSession    }    func makeRequest(with requestData: RequestProtocol) async throws -> Data {        let (data, response) = try await urlSession.data(for: requestData.request())        guard let httpResponse = response as? HTTPURLResponse,        httpResponse.statusCode == 200 else { throw NetworkError.invalidServerResponse }        return data    }}

這段代碼還可以繼續優化變得更簡潔:A3x28資訊網——每日最新資訊28at.com

static func networkFetcher(    store: NetworkStore) -> (URLRequest) async throws -> (Data, URLResponse) {    { request in     let (data, response) = try await store.fetchData(request)     if let httpResponse = response as? HTTPURLResponse,     httpResponse.statusCode != 200 {         throw NetworkError.invalidServerResponse     }     return (data, response)    }}

可以看出,我們在移除類型和協議的情況下實現了相同的功能。A3x28資訊網——每日最新資訊28at.com

另一個案例是通過函數創建一個 JSON 映射器,并將其作為閉包返回,保留協議的靈活性,卻不依賴協議。例如:A3x28資訊網——每日最新資訊28at.com

static func jsonMapper<T: Decodable>() -> (Data) throws -> T {    let decoder = JSONDecoder()    decoder.keyDecodingStrategy = .convertFromSnakeCase    return { data in            try decoder.decode(T.self, from: data)           }}

在我看來,與基于協議/類型的方法相比,這種組合方式讓網絡層的實現變得更為直觀和簡潔。A3x28資訊網——每日最新資訊28at.com

這并不意味著你不應使用協議,但在選擇使用協議和類型時,應明確了解其用途,并思考是否真的需要為每 2-3 行代碼創建一個完整的類型。A3x28資訊網——每日最新資訊28at.com

項目模塊劃分

總體上,應用程序的模塊劃分還算理想。然而,我覺得可以進一步完善項目,方法是對網絡模塊進行明確的劃分。讓我們思考一下:應用程序真的需要了解它將使用哪個 JSON 映射器作為網絡特性嗎?我們是否可以更改網絡特性的JSON映射器而不破壞整個結構?如果網絡模塊能夠自主處理這些內容,那就更好了,這樣我們可以專注于使用它的主要目的:獲取超級英雄數據。A3x28資訊網——每日最新資訊28at.com

我們應該限制網絡模塊接收的內容,僅限于有意識地改變的部分,如用于測試的輸入,而不過多暴露。此外,我們可以只公開實際使用的部分,例如fetcher功能,而不是整個NetworkStore模塊的所有底層特性,并將其設為public。A3x28資訊網——每日最新資訊28at.com

值得注意的是,網絡模塊不應涉及域的內容,最好將ArkanaKeys依賴從整個項目中獨立出來,單獨置于網絡模塊中。擁有一個完全隔離的網絡模塊,可以讓我們在制作任何關于漫威超級英雄的應用時,輕松地復用所有的網絡邏輯。A3x28資訊網——每日最新資訊28at.com

在提供的示例代碼中,我僅進行了“虛擬模塊化”操作,沒有為網絡模塊創建獨立的框架,也沒有將ArkanaKeys的依賴關系轉移到那里。相反,我創建了一個文件夾并加入了訪問控制,模擬了完全獨立框架的情形。這樣做是為了使演示項目簡潔,實際上,你只需創建一個框架并添加到項目中即可。A3x28資訊網——每日最新資訊28at.com

另一個更遠大的目標是將 UI 和演示邏輯進行分離。目前這兩者相當耦合,我覺得這并不是問題。我刪除了 Presentation 文件夾,并把它們和 UI 層放在一起,因為在這一點上,很難想象使用 HomeViewModel來做除了 HomeView以外的事情,但這是一個組織代碼的個人喜好問題。A3x28資訊網——每日最新資訊28at.com

我最終使用了一個簡單的 Container類來代替 Swinject,但這也是個人喜好的問題。無論如何,解析器/容器應該避免嘗試解析太多具體的網絡類型,比如 NetworkManager, DataSource, Repositories和UseCases。在這種情況下,讓我們注入 NetworkStore(我用來替換 NetworkManager的類型)并直接解析UseCase 的依賴。A3x28資訊網——每日最新資訊28at.com

UI 層的優化更新

以下是關于 UI 層的一些優化更新,通過減少縮進和刪除 AnyView類型來提高可讀性和性能。將 View從 body中提取出來以提高可讀性,在我看來,盡可能減少縮進到只有幾個級別是有幫助的。原始應用程序在 HomeView中達到了 13 個縮進級別!而且,它是應用程序的根視圖,所以從一開始就盡可能地使其可讀是一個好主意。通過將 homeView提取為一個計算屬性,我們可以很容易地將縮進減少到只有五個級別。
示例如下:A3x28資訊網——每日最新資訊28at.com

public var body: some View {    NavigationStack {        ZStack {            BaseStateView(                viewModel: viewModel,                successView: homeView,                emptyView: BaseStateDefaultEmptyView(),                createErrorView: { errorMessage in                                    BaseStateDefaultErrorView(errorMessage: errorMessage)                                   },                loadingView: BaseStateDefaultLoadingView()            )        }    }    .task {        await viewModel.loadCharacters()    }}

我想最后提一下的是,這個應用使用了一個 BaseStateView,它接受四個不同的 AnyView來表示應用的不同狀態,比如成功、空、錯誤等。BaseStateView使用泛型來代替 AnyView會更合適,因為 AnyView對于 SwiftUI 來說并不總是性能很好。這樣會提高性能,但是一個缺點是,它讓我們必須傳入我們想要的具體的 View,比如成功/空/創建/加載,而不是讓它們在構造函數中自動為我們完成。
示例如下:A3x28資訊網——每日最新資訊28at.com

struct BaseStateView<S: View, EM: View, ER: View, L: View>: View {    @ObservedObject var viewModel: ViewModel    let successView: S    let emptyView: EM?    let createErrorView: (_ errorMessage: String?) -> ER?    let loadingView: L?    ...}

為了提高可讀性,你可以使用如SuccessView、EmptyView等名稱。A3x28資訊網——每日最新資訊28at.com

在 SwiftUI 的上下文中,使用單一基礎控制器/視圖的方法可能不太符合習慣。與直接將所有這些狀態處理器添加到基礎視圖上相比,以 ViewModifiers的形式將它們組合起來并添加感覺更為自然。不過,每種方法都有其優劣之處。如果你想強調構造函數的使用,并且想通過減少 ZStacks 的使用來實現,那么這種方法也是可取的。A3x28資訊網——每日最新資訊28at.com

struct ErrorStateViewModifier<ErrorView: View>: ViewModifier {    @ObservedObject var viewModel: ViewModel    let errorView: (String) -> ErrorView    func body(content: Content) -> some View {        ZStack {            content            if case .error(let message) = viewModel.state {                errorView(message)            }        }    }}

結論

衷心感謝 mohaned_y98 提供的啟發和出色的示例項目!本文基于清晰的架構原則,采用了與原始項目不同的風格進行探索。相較于我所重構的項目,原始項目有其獨特的優勢,你可根據項目需求選擇適合的設計方案。A3x28資訊網——每日最新資訊28at.com

在盡量保留初衷的同時,我對項目進行了重構,增強了其人體工程學和可讀性。鑒于用戶界面或展示層已經構建得非常穩固,我未在這些方面投入過多精力。如果從頭開始,我可能會選擇不同的編碼方式,但現有的代碼編寫得恰到好處,且運作正常。A3x28資訊網——每日最新資訊28at.com

原始項目和我重構后的項目鏈接放在下方,歡迎下載閱讀我重構后的項目。你認為我忽略了哪些方面?你會有哪些不同的實現方法?A3x28資訊網——每日最新資訊28at.com

原始項目: https://github.com/Mohanedy98/swifty-marvel我重構后的項目:https://github.com/terranisaur/Demo-SwiftyMarvelousA3x28資訊網——每日最新資訊28at.com

譯者介紹

劉汪洋,51CTO社區編輯,昵稱:明明如月,一個擁有 5 年開發經驗的某大廠高級 Java 工程師,擁有多個主流技術博客平臺博客專家稱號。A3x28資訊網——每日最新資訊28at.com

原文標題:Clean Code Review: Removing All the Extra Types,作者:Alex ThurstonA3x28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-13493-0.html編寫更清晰代碼:去掉所有多余的類型

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Tailwind CSS 真有那么好嗎?討厭它的前六大原因

下一篇: 如何更優雅的編程?面向接口編程四大法寶!

標簽:
  • 熱門焦點
  • MIX Fold3包裝盒泄露 新機本月登場

    小米的全新折疊屏旗艦MIX Fold3將于本月發布,近日該機的真機包裝盒在網上泄露。從圖上來看,新的MIX Fold3包裝盒在外觀設計方面延續了之前的方案,變化不大,這也是目前小米旗艦
  • Redmi Buds 4開箱簡評:才199還有降噪 可以無腦入

    在上個月舉辦的Redmi Note11T Pro系列新機發布會上,除了兩款手機新品之外,Redmi還帶來了兩款TWS真無線藍牙耳機產品,Redmi Buds 4和Redmi Buds 4 Pro,此前我們在Redmi Note11T
  • 5月iOS設備性能榜:M1 M2依舊是榜單前五

    和上個月一樣,沒有新品發布的iOS設備性能榜的上榜設備并沒有什么更替,僅僅只有跑分變化而產生的排名變動,剛剛開始的蘋果WWDC2023,推出的產品也依舊是新款Mac Pro、新款Mac Stu
  • 帥氣純真少年!日本最帥初中生選美冠軍出爐

    日本第一帥哥初一生選美大賽冠軍現已正式出爐,冠軍是來自千葉縣的宗田悠良。日本一直熱衷于各種選美大賽,從&ldquo;最美JK&rdquo;起到&ldquo;最美女星&r
  • 印度登月最關鍵一步!月船三號今晚進入環月軌道

    8月5日消息,據印度官方消息,月船三號將于北京時間今晚21時30分左右開始近月制動進入環月軌道。這是該探測器能夠成功的最關鍵步驟之一,如果成功將開始圍
  • 線程通訊的三種方法!通俗易懂

    線程通信是指多個線程之間通過某種機制進行協調和交互,例如,線程等待和通知機制就是線程通訊的主要手段之一。 在 Java 中,線程等待和通知的實現手段有以下幾種方式:Object 類下
  • 新電商三兄弟,“抖快紅”成團!

    來源:價值研究所作 者:Hernanderz 隨著內容電商的概念興起,抖音、快手、小紅書組成的&ldquo;新電商三兄弟&rdquo;成為業內一股不可忽視的勢力,給阿里、京東、拼多多帶去了巨大壓
  • 朋友圈可以修改可見范圍了 蘋果用戶可率先體驗

    近日,iOS用戶迎來微信8.0.27正式版更新,除了可更換二維碼背景外,還新增了多項實用功能。在新版微信中,朋友圈終于可以修改可見范圍,簡單來說就是已發布的朋友圈
  • AI藝術欣賞體驗會在上海梅賽德斯奔馳中心音樂俱樂部上演

    光影交錯的鏡像世界,虛實幻化的視覺奇觀,虛擬偶像與真人共同主持,這些場景都出現在2019世界人工智能大會的舞臺上。8月29日至31日,“AI藝術欣賞體驗會”在上海
Top 主站蜘蛛池模板: 如皋市| 凌源市| 光泽县| 安陆市| 彭州市| 城市| 类乌齐县| 仪征市| 介休市| 泰州市| 黄浦区| 穆棱市| 迭部县| 甘肃省| 鄢陵县| 海晏县| 宁强县| 那坡县| 田阳县| 昌乐县| 册亨县| 拉孜县| 武宁县| 汉沽区| 尉犁县| 顺义区| 禹城市| 岢岚县| 贵定县| 垣曲县| 治县。| 奉贤区| 平度市| 阿荣旗| 科技| 揭西县| 白银市| 永寿县| 保德县| 邛崃市| 稻城县|