【翻譯】WWDC 2019 :優秀的開發習慣

suiling· 2019-06-18
本文來自 potato04 ,作者 suiling

本文翻譯自 WWDC 2019 Session 239
演講者:Josh Tidsbury

譯者:potato04

成功的APP開發需要掌握方方面面的東西。了解可納入開發流程的實踐以提高你的生產力,提升你APP的性能和穩定性。學習如何提高通過Xcode編寫的代碼質量。獲得一些有價值的開發技術的切實理解。

早上好,我是Josh,來自蘋果技術布道團隊。我們的團隊與像你這樣來自世界各地的開發者一起工作是難以置信的榮譽。我們的目標是幫助你們開發出真正優秀的APP。在與你們的交流中我們學習到了很多,得以了解您所采用的流程,面臨的挑戰,目標和愿景。我們學習可以幫助你擺脫困境的技巧和工具,雖然我們聽到的每個故事有些許不同,但無論來自世界何方,它們均有諸多的共同主題。

當你想象手藝這個詞時,你首先可能會聯想到設計。開發人員和工程師從事的也是手藝,畢竟手藝的定義如下:

規劃、制作和執行的技巧

用心、熟練或創造性地制作和生產

image.png

代碼是用手寫出來的,它涉及非常多的技能,在構建APP時需要創造力的技術和抉擇。今天我想和你談談這門“手藝活”所在乎的事情,將這種在乎融入到你的代碼、storyboards和產品中。開始看起來可能很容易,但結合對當今我們開發者提出的所有要求來看,有時會相當困難。手藝的技能水平隨著時間而發展,這需要敬業、耐心和專注。這是關于學習去享受過程中帶來的樂趣,幾乎與到達目的地時的一樣多。這個過程的一部分是將那些開始時需要強烈并有意識關注的事情轉換為習慣,類似于實際中駕駛汽車,我們駕駛時有意識地關注的事物數量會隨著時間的推移而減少,因為我們已經將這些事物轉換為自然而然的習慣,在App開發中我們也能做到同樣的事情。

image.png要做到這一點,就意味著要擁抱好習慣拋棄陋習。當開發一個APP時,開發者需要關注非常多的細節,而這些細節很少有被使用我們APP的用戶直接觀察到,然而他們能感受到因這些細節影響的性能、可靠性和穩定性。

image.png

我們沒有足夠的時間關注這里所有的細節。今天我想花一些時間來回顧一些有希望改進我們開發工作中的實踐,將它們納入到常規的工作流程后再形成習慣,這在將來會把我們從挫敗、麻煩和浪費時間的困境中拯救出來。我相信你們大部分人已經在實踐很多相關東西了,但也許還有一些沒能成為你的習慣,說不定在這里會受到一些啟發而去實踐更多。

image.png

首先我們來看組織:

1. 組織

除了APP開發人員的身份外我還是一名木工,對我來說是很好的從現代生活逃離的一種方式。有一點可以肯定的是,在干凈的工作間中更加容易打造出又好又漂亮的家具。如果你的工作臺是雜亂無章、毫無條理的,那么很難去找尋所需的工具和材料,不得不為你目前的工作騰出空間而攪亂更多的東西,花費了較往常更多的時間同時過程中也伴隨著更多的意外和錯誤。

image.png

我們團隊每年接觸很多Xcode項目,有一些實踐經驗可以幫助你確保工作空間的干凈整潔,讓你能夠最好地進行工作。

Xcode項目受益于Group帶來的代碼結構和組織。讓你程序各部分的代碼文件一目了然,在你嘗試修復Bug時,快速地為你做好準備。

image.png利用Group以功能模塊的角度來組織你的項目,這與用戶操作App的方式邏輯上一致。我們經常看到有些項目使用文件類型來分組,或者干脆就不分組,這樣對想要快速了解源文件如何相關聯的人來說沒有任何好處。

此外分組有助于你的Xcode項目結構與實際的文件系統結構相匹配。從Xcode 9開始,當你在項目中新建Group同時會在磁盤上創建一個文件夾來存放組內的文件。這意味著無論當你從項目、源代碼管理或者磁盤上查看都是相似的結構,有助于減少疑惑和混亂。

image.pngStoryboard是非常強大的工具,通過可視化的方式構建用戶界面。我們確實遇到了許多項目將所有的UI一股腦的全部放在一個Storyboard中,沒有理由可以這么做。

image.png感謝Storyboard之間可以引用。應該針對應用程序的主要部分分別創建各自的Storyboard,然后通過引用將它們連接起來。你會發現這樣能很好的隔絕各自獨立的變化,并使你在大型團隊合作中更加簡單,避免那些令人討厭的合并沖突的風險。就像你不會把所有的代碼放在一個文件中一樣,所以千萬也不要把所有的UI放在一個Storyboard文件中。

image.png

將項目文件保持在最新狀態是確保Xcode幫你解決并避免問題累積的關鍵方法。如果你定期處理這真不能算是一個問題,要是放置不理,在未來確實會引起問題。首先,當你更新到新版本的Xcode時,有時候需要你來決定讓Xcode更新項目設置和項目文件到最新的格式。因此,除非你有重要的原理不這么做,否則我們建議你在出現彈出框提示或在issue navigator出現警告時執行此操作。image.png

其次,確保你的項目使用新的構建系統New Build System,該功能于2017年首次推出。它在性能、依賴管理方面作出了重大改進,對采用Swift包起到至關重要的作用。它從Xcode 10起始作為默認的構建系統,你可以通過從File菜單下的Projects Settings中進行查看驗證。

image.png作為木工,我們常常會保留一些小的廢料以防以后可能會用到,直到都要裝滿它們時我們才不得不接受這樣一個現實:這些小廢料從未真正被使用到項目中去。開發人員也有這個偏好,好在有一個(比起木工)更容易的決定,由于你已經將項目代碼加入源代碼管理之中,你有吧? 去掉那些不需要并且沒有使用的代碼,不要僅僅將它們注釋掉。萬一有天你想要把它找回來,如果你真的需要的話,你也可以從那個文件的歷史版本將其找回來,扔掉這些“小廢料” 吧。

image.png

另外一件我們不想失去控制的事情是警告Warning。為此,你和你的團隊應該開始零警告的實踐,包含警告的代碼不應該被簽入,你應該像對待錯誤一樣對待警告,盡快地修復它們。如果我們的項目有著成千上萬個警告,其大部分的原因都是因為開發人員放棄并且沒有安排時間去修復它們而累積起來的。此外,如果你在維護這樣的一個項目,當新的警告產生時你也根本發現不了。

通過以下這些方式讓你的工作空間和項目有條無紊并整潔干凈,這對你的APP的長期健康和成功起到至關重要的作用:

  • 利用Group來組織你的項目,與文件系統結構相對應

  • 利用Reference拆分過大的Storyboard

  • 確保項目文件是最新的

  • 清除廢棄、舊的代碼;

  • 出現警告時就找到并解決引起它的根本原因。

做好這些事情使得您的項目變得靈活,你的開發流程在項目生命周期中將運轉得更好。image.png

2. 追蹤

說起源碼控制,在搭建項目時你就應該啟用它。我們確實遇到過許多沒有源代碼管理的項目,特別是那些獨立開發團隊的項目。在你搭建Xcode項目時,你只需要確保這個復選框被選中就可以了,這樣你的項目就會使用Git進行源代碼管理。

image.png現在你可以隨時回過頭看看你過去的修改,當你提交當前的變更集時會發生的變化以及更容易發現任何類型的錯誤。啟用源代碼管理之后,那么還應該注意這些事情使得更加有用和高效:

首先,每次提交的粒度小一點。經常性地檢查讓你的當前代碼和分支,保持小增量的更新,讓這些變更盡可能的局部性和自包含。當你以后需要線索或解決回歸bug的時候,這會給你提供一條回顧的路徑。同時因為你做的這些都是小的變更,從而降低引入侵害的幾率。

其次,編寫有用的commit messages。因為有一天我們都會問出這個希望自己能回答的問題:當時究竟在想什么。當你試圖回憶當時代碼發生變更時的情形和原因時,你的commit messages就是給你未來的筆記。

即使你是獨立開發人員也像參與大型團隊那樣進行源代碼控制。這樣意味著修復bug和添加新功能都可以通過分支來完成,一旦你完成之后便可壓扁合并回主分支或dev分支,同時使用有意義的commit message。如今你有多種選擇和模式可以遵循,建議你嘗試使用它們,找到最適合你的那種并將其集成到你的開發流程中。

image.png這就是追蹤。源代碼的管理對成功的現代App開發工作流程至關重要,請將它作為你項目的一部分、視作日常實踐的一部分。保持較小的提交,填寫有用的commit message,以及利用分支的幫助進行管理和隔離因修復bug或添加新功能引起的代碼變更。

image.png

3. 文檔

在我看來,對代碼清晰度和可維護性貢獻最大的是注釋和文檔,它們對你和你的同事們來說就像面包屑導航那樣的作用。有人說我不需要代碼注釋,我的代碼是自記錄(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 + /組合鍵,您需要的所有占位符文本將自動生成,填充空白就好了。

image.png

按住option并點擊任何使用該函數的地方都會顯示跟你已經熟悉并喜愛的原生SDK/Swift標準庫風格類似的快捷幫助彈出框。image.png

注釋和文檔對你的時間來說真是低投入高回報的投資,收益在整個項目周期中都會源源不斷。所以請為你的代碼添加有用的注釋,讓讀者能更好地理解原始作者的思路。為你的變量選擇更具描述性的名字,為你的函數、屬性、結構體和枚舉都添加上說明文檔。

image.png

4. 測試

接下來,我要談論一下測試,特指單元測試。為此我還要介紹一下Marshall, 他是我們Swift和開發工具的布道師,一個非常聰明和友善的伙伴,恰好也是一部Swift領域的Lint 對講機。

image.png每當我提交代碼進行評審的時候,我都做好了迎接海嘯般深刻見解和反饋的準備,這幫助我從形式和功能上改進所寫的內容。前幾天,Marshall在單元測試的另外一個主題上也把我引向了正確的方向。現在,我必須承認,我在寫單元測試方面并沒有無懈可擊的記錄,我并不是不欣賞它們帶來的潛在價值或者是對它們不熟悉,而是我總是傾向于在寫完實際代碼之后再來寫單元測試,我老想把它放到最后來做。

然而,有一天我在為某APP 的新功能實現數據模型層的時候,Marshall跟我說:“你在做這些事的時候,最好添加一個單元測試來確保字典和結構體這兩種表示之間的來回轉換能夠正常工作”。

image.png我的真的想不通這在將來怎么可能會出問題,不過我還是聽從Marshall的意見給這個簡單的流程加上了單元測試。跑了下單元測試,看到綠色的復選標記表示測試通過時,我感到無比的滿足,所以我提交了代碼進行評審。我再次想起這個測試是幾周后我們需要給這個結構體添加額外的數據,修改之后運行時也沒有問題,我的工作做完了,對嗎?就準備簽入代碼了,然后我想起來運行一下那個單元測試,果然我因為忘了修改字典反序列化的方式被單元測試抓了現行。這個Bug可能會在我們實現UI的時候才會暴露出來,那樣毫無疑問會浪費我們相當多的時間去找到原因。所以感謝Marshall提醒我將單元測試作為日常實踐的一部分,Marshall:"不客氣,Josh!"

編寫單元測試相當重要,即使那些看上去貌似比較簡單的代碼正如我碰到的這個例子一樣。代碼的易受影響性帶來了潛在的回歸bug,鑒于此我們并沒有那么多的時間進行徹底地測試,讓我們將Xcode作為一組額外的“眼睛”。

因此,將單元測試的實現作為常規開發實踐的一部分,并在每次提交之前運行它們。單元測試也是持續集成的關鍵組成部分,所以你可以為此做好準備。 測試是用戶永遠不會真正看到的隱藏細節之一,但是可能會帶來不同的后果,給用戶帶來極致的體驗或是APP重要數據受到破壞造成的令人沮喪。

image.png

5. 分析

如今有多種形式的分析可以作為你部分日常工作流程,其中一些需要額外時間的投入,也有一些可以在后臺中為你工作,你甚至都不需要去管它。

一個叫Network link Conditioner的工具非常有用,APP開發往往在網絡性能良好的家中或辦公室,卻不是你的APP通常被使用的典型環境。

image.png因此,通過啟用Network link Conditioner可以人為地將網絡性能限制為類似典型蜂窩網絡的性能,甚至是較差的網絡環境,你會驚訝于在這種加載和爭用條件中產生的問題數量,這樣你的客戶就可以避免。

在你的Scheme設置中有一些Sanitizer選擇器,通過它們可以在開發周期里發現多種多樣的錯誤。Address Sanitizer將監視類似內存錯誤和緩沖區溢出的問題,內存問題通常是導致安全漏洞的原因,因此使用Address Sanitizer可以幫助你確保不會將這些問題暴露給線上。

image.png通過啟用Thread Sanitizer可以在你使用模擬器測試或調試你的APP時發現數據競爭問題。數據競爭是指兩個未同步的線程,而且至少其中一個試圖對同一塊數據進行寫操作,這可能的惱人錯誤會讓程序運行出現不可預料的問題甚至出現內存異常。

Undefined Behavior Sanitizer可以捕獲諸如除零錯誤、浮點數的轉換超出范圍或溢出、以及未對齊指針等bug,當程序出現未定義的行為時它可能會崩潰,也可能不如預期的方式工作,又或者看上去沒問題,但在不同的時間看似沒有任何理由的不同結果。Sanitizer可以在幫助你擺脫令人沮喪的bug,在它們對你的項目造成嚴重破壞之前。

最后要介紹的是Main Thread Checker,它確保你沒有在后臺線程中對AppKit和UIKit的API的無效調用。舉個例子,如果你在主線程之外的線程上去更新界面,它可能會導致界面更新丟失、視覺錯亂、數據丟失和崩潰。有時候這些bug因為間歇性地出現而變得非常難以追蹤,而啟用這個功能對性能的影響非常少,所以我們推薦你盡可能就留著啟用它。

在調試你的App時請時刻關注性能和資源利用率,確保app盡可能高效地利用系統資源。首先可以使用調試儀表盤,這些儀表盤可以在你構建和運行項目時隨時在Xcode中的Debug Naviagtor中找到,你可以通過它們查看整個App生命周期的CPU、內存、磁盤和網絡利用率,快速了解你的App是否通過網絡正在連接未知的服務器或者不斷地輪詢某個端點,消耗大量的帶寬和電量。

image.png最后你可以通過Profile中的Instruments按鈕進行進一步的深入分析。我經常使用的其中一個是Time Profiler,它允許你查明代碼的哪些段落占用了最多的周期,并且允許我們縮小那段也許應該變成異步或者被不可擴展的方式實現的代碼的范圍。

image.png分析是一個相當廣泛的主題,但我在這描述的大多數工具只需要你記得啟用就行了。使用Newwork link Conditioner模擬典型的或者較差網絡條件;經常使用Sanitizers和Checkers,只要把它們打開就好了;定期的查看調試儀表盤,并密切關注你APP的足跡和性能;利用Instruments分析你的App,深入研究并更精確地解決那些問題。將這些小小的努力轉化為習慣后將大大提高你APP的性能和可靠性。

image.png

6. 評估

我住在多倫多的時候有一個車庫,后來將它改成了我的木工車間,這是一個舒適的空間,它完全都屬于我。

image.png但是自從搬到灣區以后,我就再也沒有屬于自己的空間了。我去過這里的各種共用的社區木工店,因為現在必須與其他人共享工具和空間所以有時候會感到沮喪。但是我沒有意識到我反而應該感謝的是有機會向商店里的其他人交流想法以及問問他們對于在做事情的意見。

我覺得這在App開發中的類比就是代碼評審。過去幾年我都是以獨立開發人員的身份完成許多App的開發,這就好像我擁有自己的工作間一樣,不可思議得快速和靈活,只有自己的意見才重要。缺點是你自己沒有從同事或同行中學習更好地使用語言、框架和SDK的機會。

雖然解決問題的辦法通常有多種,但總有更好的那一種。比如那種更簡潔或者在性能、可維護性和可靠性方面更突出的方法。不能因為它起作用了就意味著它必然是正確的,就不能通過某種方式可以去明顯地改進它。(譯者:結合個人工作間 vs 共享工作間, 個人開發 vs 團隊開發的優缺點來理解這段話)

蘋果的所有團隊都有一項未經評審的代碼不能并入項目的政策。我們團隊通過這個過程互相學到了很多東西,代碼風格更加一致,更不用說在可靠性方便的改進了。它還確保我們整個團隊更熟悉更廣泛的代碼庫,使得我們可以解決的bug和功能的范圍也更廣。我有幸處在這么一只經驗豐富的團隊,這讓事情變得簡單。但是如果你經營著自己的公司或者是獨立開發者,試著找找你所在地區或者來自世界各地的伙伴,并與他們互相進行代碼評審。或許還可以從聚會、當地會議或者共享辦公室去找找。所以,現在你已經打算將代碼評審納入你的開發實踐了吧。image.png

好的代碼評審是怎么樣的?首先,意味著需要花時間去理解每一行變更的代碼,匆匆一瞥是沒有意義的。其次,構建項目并跑起來看看,不要假設原作者之前做過,特別是在你記錄中看到最后的提交是一次合并時。運行這些測試,首先這么做是提醒你去檢查實際上是否有這些測試以及測試是否通過,記住編譯通過不代表不存在某種方式上的破壞。徹底閱讀這些注釋和文檔,我的意思是它們有的,對吧?接著尋找是否有拼寫和語法錯誤。繼續查找變量名的拼寫錯誤,作為一個加拿大人,我有長期使用colour作為顏色變量名的習慣,這讓我的團隊在查找color變量時簡直抓狂。確保代碼庫中的函數、變量名的一致性可以更容易的查找,也節省時間。

可能會覺得這個過程在短期內也許減緩了您的速度,但從長遠來看,通過減少潛在的錯誤和問題,將毫無疑問地為您節省時間、金錢和客戶。你作為開發人員的經驗在將來處理類似的模式或挑戰時受益匪淺。

image.png7. 解耦

開發人員致力于創建小而精的可重用、可測試的代碼,畢竟我們不想總是一次次的編寫重復代碼。

image.png

包和框架給了我們更集中的方式來維護代碼的機會,并且以便攜的方式提供功能,不僅僅是你當前的App,其他的App也能利用這些成果。

如果你的App包含Extensions并且將它們的共享代碼打包成Framework,那么你的應用二進制包大小會減少,因為你的主App和Extensions可以共享這個Framework。當然,創建包也給你了在社區分享你努力成果的機會,特別現在與Xcode 11包的集成更緊密了。但是除了你App、共享框架、包和庫中的代碼外,還需要附帶優秀的文檔才能讓其他人更好去使用。

image.png因此,將框架和包作為你拆分代碼庫的一種方式,這可以讓你的成果作用于多個正在開發或維護的app。

Framework可以幫你減少二進制包的大小,然后你可以將成果與社區共享,但請務必附帶優秀的文檔。

image.png

8. 管理

最后一個我想談論的是依賴管理,尤其是搞清楚它給你的項目帶來的好處和風險。使用Swift包、框架和其他庫有很多好處,但是在你開始使用之前,了解庫里面包含的內容以及在帶來潛在的問題是非常重要的。

image.png

你要確保了解依賴包對數據的影響,你要對App的內容負責,包括對用戶數的所作所為。

確保Framework沒有收集不必要的指標或設備信息,確保它沒有把數據從你的設備發送出去。

同樣也要了解和研究給定依賴包的依賴。

畢竟引入包含依賴的依賴包后意味著你App的安全和成功與這整條鏈掛鉤了。

image.png最后還有另外一種可能性會破壞你的App,如果依賴包不維護了怎么辦?或者直接就消失了呢?重要的是你要有一個計劃決定你在任何時候遇到這些情形時該如何去處理,畢竟你在項目中引入了一個新的依賴后,項目的未來就真的依賴它了。

那么是你準備自己來修復這個公開的bug呢?還是將它轉為內部的項目并維護它,又或者你計劃不得不把那個依賴在以后全部清除出去以及包括因此帶來的必要工作?

使用外部依賴包(如Swift包)可以讓你更快地完成工作,并避免重復創造社區中已經存在的工具。本著掌握它們用途的態度, 確保它們只按照你期望的那樣做,務必要它們尊重使用你App的用戶的隱私。做好它們在未來出錯或者消失的應對措施計劃。在項目中添加新的依賴時問問自己這些問題,把這件事變成你的習慣,你將最大限度地發揮出它們的長處并得到長遠的回報。

image.png

總結

對于App開發項目,有時候覺得項目的最后10%的工作花費的時間與前90%的時間一樣長。但是我認為嘗試通過將這些實踐轉化為習慣之后,你可以避免這種感覺。

通過有效的組織你的工作區,讓你更快更高效的工作,專注于實際代碼。

利用源碼控制追蹤你的代碼庫,可以減少回歸bug的幾率,加快調查可能導致bug的原因。

編寫有幫助有意義的注釋和文檔,你可以在未來閱讀這些代碼以及每次使用自己編寫的類、結構體、函數時減少負擔成本。單元測試能在最后一刻拯救你,在引入一些回歸bug的時候。

Sanitizers和checkers提供對你的代碼持續分析的能力,它們在后臺運行,你都甚至不用去操心。儀表盤和Instruments確保你高效地利用系統資源,允許你精確地追蹤性能和其他問題。代碼評審不僅僅是評估代碼樣式和功能,更是一個巨大的學習機會,讓你提升你的開發技能并與你的開發團隊在社區進行分享。將你的項目分解為更小的、可重用的包和框架,幫助你讓你的成果延伸到多個項目,并允許你共享它,這樣做同樣有利于減少二進制包的大小。最后,使用外部依賴(比如Swift包)可以重用社區中已經開發好的功能,幫助你更快完成工作。但是一定要把關好它們的所作所為, 記住了嗎?包括它對你用戶數據做了什么,也要做好沒有它們的準備。

將這些實踐作為你工作的一部分,只會給項目每個階段增加一點點時間,但是從長遠來看將會為你節省不可思議的時間,確保你的項目基業長青。我希望今天為你提供的想法和建議可以讓你思考如何進一步提升作為開發人員的手藝。你正在想納入的這些實踐可以提升你工作質量和穩定性,把這些自覺的努力轉換為習慣將允許讓你將精力投入到更重要的領域。那些使用你APP的人也許不能說出個所以然,但是能感覺到你投入這些工作所帶來的關愛,你能為自己打造的這些而自豪。謝謝!

【全文完】

广东26选5开奖结果查 大众麻将游戏免费下载 什么是百搭麻将 东篱然怎么赚钱 支付宝除了领红包还有什么赚钱的 聚星彩票网址 三四线城市的人都怎么赚钱 如何利用租房赚钱 老版单机捕鱼达人 室内儿童碰碰车赚钱吗 南方还是北方卖鱼最赚钱 新世佳彩票群 坐着轻松赚钱 两个美女卖身赚钱 波克麻将官网 李逵劈鱼50元提现 3赚钱宝350 dcdn修改