本文翻譯自 WWDC 2019 Session 239
演講者:Josh Tidsbury算法
成功的
APP
開發須要掌握方方面面的東西。瞭解可歸入開發流程的實踐以提升你的生產力,提高你APP
的性能和穩定性。學習如何提升經過Xcode
編寫的代碼質量。得到一些有價值的開發技術的切實理解。編程
早上好,我是Josh,來自蘋果技術佈道團隊。咱們的團隊與像你這樣來自世界各地的開發者一塊兒工做是難以置信的榮譽。咱們的目標是幫助大家開發出真正優秀的APP
。在與大家的交流中咱們學習到了不少,得以瞭解您所採用的流程,面臨的挑戰,目標和願景。咱們學習能夠幫助你擺脫困境的技巧和工具,雖然咱們聽到的每一個故事有些許不一樣,但不管來自世界何方,它們均有諸多的共同主題。swift
當你想象手藝
這個詞時,你首先可能會聯想到設計。開發人員和工程師從事的也是手藝,畢竟手藝的定義以下:安全
- 規劃、製做和執行的技巧
- 用心、熟練或創造性地製做和生產
代碼是用手寫出來的,它涉及很是多的技能,在構建APP
時須要創造力的技術和抉擇。今天我想和你談談這門「手藝活」所在意的事情,將這種在意融入到你的代碼、storyboards
和產品中。開始看起來可能很容易,但結合對當今咱們開發者提出的全部要求來看,有時會至關困難。手藝的技能水平隨着時間而發展,這須要敬業、耐心和專一。這是關於學習去享受過程當中帶來的樂趣,幾乎與到達目的地時的同樣多。這個過程的一部分是將那些開始時須要強烈並有意識關注的事情轉換爲習慣,相似於實際中駕駛汽車,咱們駕駛時有意識地關注的事物數量會隨着時間的推移而減小,由於咱們已經將這些事物轉換爲天然而然的習慣,在App
開發中咱們也能作到一樣的事情。
服務器
要作到這一點,就意味着要擁抱好習慣拋棄陋習。當開發一個APP
時,開發者須要關注很是多的細節,而這些細節不多有被使用咱們APP
的用戶直接觀察到,然而他們能感覺到因這些細節影響的性能、可靠性和穩定性。網絡
咱們沒有足夠的時間關注這裏全部的細節。今天我想花一些時間來回顧一些有但願改進咱們開發工做中的實踐,將它們歸入到常規的工做流程後再造成習慣,這在未來會把咱們從挫敗、麻煩和浪費時間的困境中拯救出來。我相信大家大部分人已經在實踐不少相關東西了,但也許還有一些沒能成爲你的習慣,說不定在這裏會受到一些啓發而去實踐更多。app
組織
:
除了APP
開發人員的身份外我仍是一名木工,對我來講是很好的從現代生活逃離的一種方式。有一點能夠確定的是,在乾淨的工做間中更加容易打造出又好又漂亮的傢俱。若是你的工做臺是雜亂無章、毫無條理的,那麼很難去找尋所需的工具和材料,不得不爲你目前的工做騰出空間而攪亂更多的東西,花費了較往常更多的時間同時過程當中也伴隨着更多的意外和錯誤。框架
咱們團隊每一年接觸不少Xcode
項目,有一些實踐經驗能夠幫助你確保工做空間的乾淨整潔,讓你可以最好地進行工做。異步
Xcode
項目受益於Group
帶來的代碼結構和組織。讓你程序各部分的代碼文件一目瞭然,在你嘗試修復Bug時,快速地爲你作好準備。編程語言
利用Group
以功能模塊的角度來組織你的項目,這與用戶操做App
的方式邏輯上一致。咱們常常看到有些項目使用文件類型來分組,或者乾脆就不分組,這樣對想要快速瞭解源文件如何相關聯的人來講沒有任何好處。
此外分組有助於你的Xcode
項目結構與實際的文件系統結構相匹配。從Xcode 9
開始,當你在項目中新建Group
同時會在磁盤上建立一個文件夾來存放組內的文件。這意味着不管當你從項目、源代碼管理或者磁盤上查看都是類似的結構,有助於減小疑惑和混亂。
Storyboard
是很是強大的工具,經過可視化的方式構建用戶界面。咱們確實遇到了許多項目將全部的UI
一股腦的所有放在一個Storyboard
中,沒有理由能夠這麼作。
感謝Storyboard
之間能夠引用。應該針對應用程序的主要部分分別建立各自的Storyboard
,而後經過引用將它們鏈接起來。你會發現這樣能很好的隔絕各自獨立的變化,並使你在大型團隊合做中更加簡單,避免那些使人討厭的合併衝突的風險。就像你不會把全部的代碼放在一個文件中同樣,因此千萬也不要把全部的UI
放在一個Storyboard
文件中。
將項目文件保持在最新狀態是確保Xcode
幫你解決並避免問題累積的關鍵方法。若是你按期處理這真不能算是一個問題,要是放置不理,在將來確實會引發問題。首先,當你更新到新版本的Xcode
時,有時候須要你來決定讓Xcode
更新項目設置和項目文件到最新的格式。所以,除非你有重要的原理不這麼作,不然咱們建議你在出現彈出框提示或在issue navigator
出現警告時執行此操做。
其次,確保你的項目使用新的構建系統New Build System
,該功能於2017年首次推出。它在性能、依賴管理方面做出了重大改進,對採用Swift
包起到相當重要的做用。它從Xcode 10
起始做爲默認的構建系統,你能夠經過從File
菜單下的Projects Settings
中進行查看驗證。
做爲木工,咱們經常會保留一些小的廢料以防之後可能會用到,直到都要裝滿它們時咱們纔不得不接受這樣一個現實:這些小廢料從未真正被使用到項目中去。開發人員也有這個偏好,好在有一個(比起木工)更容易的決定,因爲你已經將項目代碼加入源代碼管理之中,你有吧? 去掉那些不須要而且沒有使用的代碼,不要僅僅將它們註釋掉。萬一有天你想要把它找回來,若是你真的須要的話,你也能夠從那個文件的歷史版本將其找回來,扔掉這些「小廢料」 吧。
另一件咱們不想失去控制的事情是警告Warning
。爲此,你和你的團隊應該開始零警告的實踐,包含警告的代碼不該該被簽入,你應該像對待錯誤同樣對待警告,儘快地修復它們。若是咱們的項目有着成千上萬個警告,其大部分的緣由都是由於開發人員放棄而且沒有安排時間去修復它們而累積起來的。此外,若是你在維護這樣的一個項目,當新的警告產生時你也根本發現不了。
經過如下這些方式讓你的工做空間和項目有條無紊並整潔乾淨,這對你的APP
的長期健康和成功起到相當重要的做用:
Group
來組織你的項目,與文件系統結構相對應Reference
拆分過大的Storyboard
作好這些事情使得您的項目變得靈活,你的開發流程在項目生命週期中將運轉得更好。
提及源碼控制,在搭建項目時你就應該啓用它。咱們確實遇到過許多沒有源代碼管理的項目,特別是那些獨立開發團隊的項目。在你搭建Xcode
項目時,你只須要確保這個複選框被選中就能夠了,這樣你的項目就會使用Git
進行源代碼管理。
首先,每次提交的粒度小一點。常常性地檢查讓你的當前代碼和分支,保持小增量的更新,讓這些變動儘量的局部性和自包含。當你之後須要線索或解決迴歸bug的時候,這會給你提供一條回顧的路徑。同時由於你作的這些都是小的變動,從而下降引入侵害的概率。
其次,編寫有用的commit messages
。由於有一天咱們都會問出這個但願本身能回答的問題:當時究竟在想什麼。當你試圖回憶當時代碼發生變動時的情形和緣由時,你的commit messages
就是給你將來的筆記。
即便你是獨立開發人員也像參與大型團隊那樣進行源代碼控制。這樣意味着修復bug和添加新功能均可以經過分支來完成,一旦你完成以後即可壓扁合併回主分支或dev
分支,同時使用有意義的commit message
。現在你有多種選擇和模式能夠遵循,建議你嘗試使用它們,找到最適合你的那種並將其集成到你的開發流程中。
這就是追蹤。源代碼的管理對成功的現代App
開發工做流程相當重要,請將它做爲你項目的一部分、視做平常實踐的一部分。保持較小的提交,填寫有用的commit message
,以及利用分支的幫助進行管理和隔離因修復bug或添加新功能引發的代碼變動。
在我看來,對代碼清晰度和可維護性貢獻最大的是註釋和文檔,它們對你和你的同事們來講就像麪包屑導航那樣的做用。有人說我不須要代碼註釋,個人代碼是自記錄(self-documenting)的,我對此並不買帳。從算法角度看,良好編寫的代碼確實清晰的描述了它所作的事情,確實是自記錄的。可是它並無說明爲何,爲何會有這些代碼的緣由,這些代碼該如何在更大的上下文中進行適配,也沒有描述當時編寫時採起這種方法的背後緣由。我工做過的最好的開發人員,不只編寫使人難以置信得清晰簡潔的代碼,並且還花時間在代碼中留下有用的審查記錄或註釋,引導將來的讀者領會原做者的意圖。初級開發人員能在這個過程當中受益更多,由於你的經驗會在項目開始到結束時變化很大,開始時作出的決定可能實際上與最後作出的決定不一致。(譯者注:初級開發人員由於經驗的緣故,致使代碼變更的因素更多,所以更加須要清晰描述的文檔和註釋)
怎麼樣算做好的註釋呢?好的註釋假設讀者瞭解所用的編程語言,可以走查代碼序列以及採用的步驟,它真正關注的是代碼起初寫在這裏的緣由和支撐這麼作的理由。舉個例子,這是咱們常常看到而實際沒什麼價值的註釋:
//A constant string id value
let id = "2ADA155F-1529-4D2D-98C4-0E4BD06940E8"
複製代碼
我假設大家大部分人都使用Swift
寫過一些代碼,知道這裏是建立了一個字符串常量來攜帶這個值,可是咱們不知道這個id
是什麼,它的用途以及被硬編碼到APP
中的緣由。給它加了一點註釋後,咱們就明白了這個值存在的緣由,以及它來自哪裏。
// The permanent identifier for this application when interacting
// with the CMS, provided by the vendor of the CMS.
let id = "2ADA155F-1529-4D2D-98C4-0E4BD06940E8"
複製代碼
咱們還能夠更進一步,給變量換個名字後變得更加清晰。
// The permanent identifier for this application when interacting
// with the CMS, provided by the vendor of the CMS.
let cmsApplicationIdentifier = "2ADA155F-1529-4D2D-98C4-0E4BD06940E8"
複製代碼
若是你發現你本身的變量使用了單個字母或者相似id
等方式命名,也許這是你給它們選擇一個更具描述性的名字的好機會。自動補全功能讓Xcode
充滿魅力,你甚至都不用再輸入任何更多東西了。這個案例中的標識符在任意時間使用時在貫穿整個代碼庫都是清晰的。
文檔帶來的好處和註釋類似,但這些好處是遍佈整個應用程序甚至以外的。 當你編寫本身的APP
時,你建立了一層層的抽象和算法,將大段蜿蜒的代碼分解成整齊可測試、可重用的函數。若是你沒有選擇給這些函數添加文檔,那麼你是在強迫你本身每次使用該函數時都得在頭腦中過一遍這個函數的文檔,經常必須從頭瞭解這個函數內部的實現,包括每一個參數的用途以及如何產生的結果。
若是你不知道如何在Xcode
中添加文檔,很簡單,只要你將光標定位至函數簽名的第一行,再按option + command + /
組合鍵,您須要的全部佔位符文本將自動生成,填充空白就行了。
按住option
並點擊任何使用該函數的地方都會顯示跟你已經熟悉並喜好的原生SDK/Swift標準庫風格相似的快捷幫助彈出框。
註釋和文檔對你的時間來講真是低投入高回報的投資,收益在整個項目週期中都會源源不斷。因此請爲你的代碼添加有用的註釋,讓讀者能更好地理解原始做者的思路。爲你的變量選擇更具描述性的名字,爲你的函數、屬性、結構體和枚舉都添加上說明文檔。
接下來,我要談論一下測試,特指單元測試。爲此我還要介紹一下Marshall
, 他是咱們Swift
和開發工具的佈道師,一個很是聰明和友善的夥伴,剛好也是一部Swift
領域的Lint 對講機
。
Marshall
在單元測試的另一個主題上也把我引向了正確的方向。如今,我必須認可,我在寫單元測試方面並無無懈可擊的記錄,我並非不欣賞它們帶來的潛在價值或者是對它們不熟悉,而是我老是傾向於在寫完實際代碼以後再來寫單元測試,我老想把它放到最後來作。
然而,有一天我在爲某APP
的新功能實現數據模型層的時候,Marshall
跟我說:「你在作這些事的時候,最好添加一個單元測試來確保字典和結構體這兩種表示之間的來回轉換可以正常工做」。
個人真的想不通這在未來怎麼可能會出問題,不過我仍是遵從Marshall
的意見給這個簡單的流程加上了單元測試。跑了下單元測試,看到綠色的複選標記表示測試經過時,我感到無比的知足,因此我提交了代碼進行評審。我再次想起這個測試是幾周後咱們須要給這個結構體添加額外的數據,修改以後運行時也沒有問題,個人工做作完了,對嗎?就準備簽入代碼了,而後我想起來運行一下那個單元測試,果真我由於忘了修改字典反序列化的方式被單元測試抓了現行。這個Bug
可能會在咱們實現UI
的時候纔會暴露出來,那樣毫無疑問會浪費咱們至關多的時間去找到緣由。因此感謝Marshall
提醒我將單元測試做爲平常實踐的一部分,Marshall
:"不客氣,Josh!"
編寫單元測試至關重要,即便那些看上去貌似比較簡單的代碼正如我碰到的這個例子同樣。代碼的易受影響性帶來了潛在的迴歸bug,鑑於此咱們並無那麼多的時間進行完全地測試,讓咱們將Xcode
做爲一組額外的「眼睛」。
所以,將單元測試的實現做爲常規開發實踐的一部分,並在每次提交以前運行它們。單元測試也是持續集成的關鍵組成部分,因此你能夠爲此作好準備。 測試是用戶永遠不會真正看到的隱藏細節之一,可是可能會帶來不一樣的後果,給用戶帶來極致的體驗或是APP
重要數據受到破壞形成的使人沮喪。
現在有多種形式的分析能夠做爲你部分平常工做流程,其中一些須要額外時間的投入,也有一些能夠在後臺中爲你工做,你甚至都不須要去管它。
一個叫Network link Conditioner
的工具很是有用,APP
開發每每在網絡性能良好的家中或辦公室,卻不是你的APP
一般被使用的典型環境。
Network link Conditioner
能夠人爲地將網絡性能限制爲相似典型蜂窩網絡的性能,甚至是較差的網絡環境,你會驚訝於在這種加載和爭用條件中產生的問題數量,這樣你的客戶就能夠避免。
在你的Scheme
設置中有一些Sanitizer
選擇器,經過它們能夠在開發週期裏發現多種多樣的錯誤。Address Sanitizer
將監視相似內存錯誤和緩衝區溢出的問題,內存問題一般是致使安全漏洞的緣由,所以使用Address Sanitizer
能夠幫助你確保不會將這些問題暴露給線上。
經過啓用Thread Sanitizer
能夠在你使用模擬器測試或調試你的APP
時發現數據競爭問題。數據競爭是指兩個未同步的線程,並且至少其中一個試圖對同一塊數據進行寫操做,這可能的惱人錯誤會讓程序運行出現不可預料的問題甚至出現內存異常。
Undefined Behavior Sanitizer
能夠捕獲諸如除零錯誤、浮點數的轉換超出範圍或溢出、以及未對齊指針等bug
,當程序出現未定義的行爲時它可能會崩潰,也可能不如預期的方式工做,又或者看上去沒問題,但在不一樣的時間看似沒有任何理由的不一樣結果。Sanitizer
能夠在幫助你擺脫使人沮喪的bug
,在它們對你的項目形成嚴重破壞以前。
最後要介紹的是Main Thread Checker
,它確保你沒有在後臺線程中對AppKit
和UIKit
的API
的無效調用。舉個例子,若是你在主線程以外的線程上去更新界面,它可能會致使界面更新丟失、視覺錯亂、數據丟失和崩潰。有時候這些bug
由於間歇性地出現而變得很是難以追蹤,而啓用這個功能對性能的影響很是少,因此咱們推薦你儘量就留着啓用它。
在調試你的App
時請時刻關注性能和資源利用率,確保app
儘量高效地利用系統資源。首先可使用調試儀表盤,這些儀表盤能夠在你構建和運行項目時隨時在Xcode
中的Debug Naviagtor
中找到,你能夠經過它們查看整個App
生命週期的CPU
、內存
、磁盤
和網絡
利用率,快速瞭解你的App
是否經過網絡正在鏈接未知的服務器或者不斷地輪詢某個端點,消耗大量的帶寬和電量。
最後你能夠經過Profile
中的Instruments
按鈕進行進一步的深刻分析。我常用的其中一個是Time Profiler
,它容許你查明代碼的哪些段落佔用了最多的週期,而且容許咱們縮小那段也許應該變成異步或者被不可擴展的方式實現的代碼的範圍。
分析是一個至關普遍的主題,但我在這描述的大多數工具只須要你記得啓用就好了。使用Newwork link Conditioner
模擬典型的或者較差網絡條件;常用Sanitizers
和Checkers
,只要把它們打開就行了;按期的查看調試儀表盤,並密切關注你APP
的足跡和性能;利用Instruments
分析你的App
,深刻研究並更精確地解決那些問題。將這些小小的努力轉化爲習慣後將大大提升你APP
的性能和可靠性。
我住在多倫多的時候有一個車庫,後來將它改爲了個人木工車間,這是一個溫馨的空間,它徹底都屬於我。
我以爲這在App
開發中的類比就是代碼評審。過去幾年我都是以獨立開發人員的身份完成許多App
的開發,這就好像我擁有本身的工做間同樣,難以想象得快速和靈活,只有本身的意見才重要。缺點是你本身沒有從同事或同行中學習更好地使用語言、框架和SDK的機會。
雖然解決問題的辦法一般有多種,但總有更好的那一種。好比那種更簡潔或者在性能、可維護性和可靠性方面更突出的方法。不能由於它起做用了就意味着它必然是正確的,就不能經過某種方式能夠去明顯地改進它。(譯者:結合我的工做間 vs 共享工做間
, 我的開發 vs 團隊開發
的優缺點來理解這段話)
蘋果的全部團隊都有一項未經評審的代碼不能併入項目的政策。咱們團隊經過這個過程互相學到了不少東西,代碼風格更加一致,更不用說在可靠性方便的改進了。它還確保咱們整個團隊更熟悉更普遍的代碼庫,使得咱們能夠解決的bug
和功能的範圍也更廣。我有幸處在這麼一隻經驗豐富的團隊,這讓事情變得簡單。可是若是你經營着本身的公司或者是獨立開發者,試着找找你所在地區或者來自世界各地的夥伴,並與他們互相進行代碼評審。或許還能夠從聚會、當地會議或者共享辦公室去找找。因此,如今你已經打算將代碼評審歸入你的開發實踐了吧。
好的代碼評審是怎麼樣的?首先,意味着須要花時間去理解每一行變動的代碼,匆匆一瞥是沒有意義的。其次,構建項目並跑起來看看,不要假設原做者以前作過,特別是在你記錄中看到最後的提交是一次合併時。運行這些測試,首先這麼作是提醒你去檢查其實是否有這些測試以及測試是否經過,記住編譯經過不表明不存在某種方式上的破壞。完全閱讀這些註釋和文檔,個人意思是它們有的,對吧?接着尋找是否有拼寫和語法錯誤。繼續查找變量名的拼寫錯誤,做爲一個加拿大人,我有長期使用colour
做爲顏色變量名的習慣,這讓個人團隊在查找color
變量時簡直抓狂。確保代碼庫中的函數、變量名的一致性能夠更容易的查找,也節省時間。
可能會以爲這個過程在短時間內也許減緩了您的速度,但從長遠來看,經過減小潛在的錯誤和問題,將毫無疑問地爲您節省時間、金錢和客戶。你做爲開發人員的經驗在未來處理相似的模式或挑戰時受益不淺。
開發人員致力於建立小而精的可重用、可測試的代碼,畢竟咱們不想老是一次次的編寫重複代碼。
App
,其餘的
App
也能利用這些成果。 若是你的
App
包含
Extensions而且將它們的共享代碼打包成
Framework
,那麼你的應用二進制包大小會減小,由於你的主
App
和
Extensions
能夠共享這個
Framework
。固然,建立包也給你了在社區分享你努力成果的機會,特別如今與
Xcode 11
包的集成更緊密了。可是除了你
App
、共享框架、包和庫中的代碼外,還須要附帶優秀的文檔才能讓其餘人更好去使用。
所以,將框架和包做爲你拆分代碼庫的一種方式,這可讓你的成果做用於多個正在開發或維護的app
。 Framework
能夠幫你減小二進制包的大小,而後你能夠將成果與社區共享,但請務必附帶優秀的文檔。
最後一個我想談論的是依賴管理,尤爲是搞清楚它給你的項目帶來的好處和風險。使用Swift
包、框架和其餘庫有不少好處,可是在你開始使用以前,瞭解庫裏面包含的內容以及在帶來潛在的問題是很是重要的。
你要確保瞭解依賴包對數據的影響,你要對App
的內容負責,包括對用戶數的所做所爲。 確保Framework
沒有收集沒必要要的指標或設備信息,確保它沒有把數據從你的設備發送出去。
一樣也要了解和研究給定依賴包的依賴。 畢竟引入包含依賴的依賴包後意味着你App
的安全和成功與這整條鏈掛鉤了。
最後還有另一種可能性會破壞你的App
,若是依賴包不維護了怎麼辦?或者直接就消失了呢?重要的是你要有一個計劃決定你在任什麼時候候遇到這些情形時該如何去處理,畢竟你在項目中引入了一個新的依賴後,項目的將來就真的依賴它了。 那麼是你準備本身來修復這個公開的bug
呢?仍是將它轉爲內部的項目並維護它,又或者你計劃不得不把那個依賴在之後所有清除出去以及包括所以帶來的必要工做?
使用外部依賴包(如Swift包)可讓你更快地完成工做,並避免重複創造社區中已經存在的工具。本着掌握它們用途的態度, 確保它們只按照你指望的那樣作,務必要它們尊重使用你App
的用戶的隱私。作好它們在將來出錯或者消失的應對措施計劃。在項目中添加新的依賴時問問本身這些問題,把這件事變成你的習慣,你將最大限度地發揮出它們的長處並獲得長遠的回報。
對於App
開發項目,有時候以爲項目的最後10%
的工做花費的時間與前90%
的時間同樣長。可是我認爲嘗試經過將這些實踐轉化爲習慣以後,你能夠避免這種感受。
經過有效的組織你的工做區,讓你更快更高效的工做,專一於實際代碼。 利用源碼控制追蹤你的代碼庫,能夠減小回歸bug的概率,加快調查可能致使bug的緣由。 編寫有幫助有意義的註釋和文檔,你能夠在將來閱讀這些代碼以及每次使用本身編寫的類、結構體、函數時減小負擔成本。單元測試能在最後一刻拯救你,在引入一些迴歸bug的時候。 Sanitizers
和checkers
提供對你的代碼持續分析的能力,它們在後臺運行,你都甚至不用去操心。儀表盤和Instruments
確保你高效地利用系統資源,容許你精確地追蹤性能和其餘問題。代碼評審不只僅是評估代碼樣式和功能,更是一個巨大的學習機會,讓你提高你的開發技能並與你的開發團隊在社區進行分享。將你的項目分解爲更小的、可重用的包和框架,幫助你讓你的成果延伸到多個項目,並容許你共享它,這樣作一樣有利於減小二進制包的大小。最後,使用外部依賴(好比Swift
包)能夠重用社區中已經開發好的功能,幫助你更快完成工做。可是必定要把關好它們的所做所爲, 記住了嗎?包括它對你用戶數據作了什麼,也要作好沒有它們的準備。
將這些實踐做爲你工做的一部分,只會給項目每一個階段增長一點點時間,可是從長遠來看將會爲你節省難以想象的時間,確保你的項目基業長青。我但願今天爲你提供的想法和建議可讓你思考如何進一步提高做爲開發人員的手藝
。你正在想歸入的這些實踐能夠提高你工做質量和穩定性,把這些自覺的努力轉換爲習慣將容許讓你將精力投入到更重要的領域。那些使用你APP
的人也許不能說出個因此然,可是能感受到你投入這些工做所帶來的關愛,你能爲本身打造的這些而自豪。謝謝!
【全文完】