大廠(chǎng)的 SDK 寫(xiě)法,偷學(xué)到了!
自己動(dòng)手寫(xiě) SDK 的經(jīng)驗(yàn)技巧分享
大家好,我是魚(yú)皮。
最近因?yàn)楣ぷ餍枰?,自己?dòng)手寫(xiě)了一些項(xiàng)目的通用 SDK。在編寫(xiě)的過(guò)程中,我閱讀和參考了不少公司中其他大佬寫(xiě)的 SDK,也總結(jié)了一些開(kāi)發(fā) SDK 的經(jīng)驗(yàn)和技巧,給大家分享下~

在此之前,必須先給大家解釋一下啥是 SDK。
啥是 SDK ?
SDK(Software Development Kit)即 軟件開(kāi)發(fā)工具包 ,就是幫助我們開(kāi)發(fā)出軟件的工具集合,除了代碼之外,一般還要搭配文檔、示例等。
一般 SDK 都是需要 引入 到項(xiàng)目中使用的。比如學(xué) Java 的朋友最早接觸的 JDK,就是用來(lái)開(kāi)發(fā) Java 軟件的工具包,使用時(shí)需要編寫(xiě) 類(lèi)似 import java.util.*
的語(yǔ)法來(lái)引入。此外,大部分的 SDK,都是需要通過(guò)人工或項(xiàng)目管理工具,將其文件下載到指定路徑才能引入。

使用 SDK 有什么好處呢?
舉個(gè)例子,假設(shè)公司有很多系統(tǒng)都需要實(shí)現(xiàn)文件上傳功能。之前看過(guò)我文章的朋友應(yīng)該知道,一個(gè)優(yōu)秀的文件上傳功能并不好做,要考慮很多點(diǎn),比如分塊、斷點(diǎn)續(xù)傳、秒傳、文件存儲(chǔ)、文件管理等。
文件上傳設(shè)計(jì):https://mp.weixin.qq.com/s/3QXe4MSObJTP43M2gXWSlA
顯然,我們不需要給每個(gè)系統(tǒng)都去開(kāi)發(fā)文件上傳,而是只需要有一個(gè)團(tuán)隊(duì)舍身而出,編寫(xiě)一套 通用的 文件上傳 SDK,然后讓需要實(shí)現(xiàn)同樣功能的系統(tǒng)引用就行了,這樣就 大大減少了工作量、提高了開(kāi)發(fā)效率 。

有點(diǎn)前人造車(chē),后人享樂(lè)的意思~
編寫(xiě) SDK 又稱(chēng) 造輪子 ,好的輪子不僅能夠幫助團(tuán)隊(duì)省時(shí)省力,還能夠減少一些項(xiàng)目在相同功能上的差異。就不要說(shuō)同一個(gè)功能,小王寫(xiě)的要運(yùn)行 1 秒,小李寫(xiě)的要運(yùn)行 1 小時(shí)!
而假設(shè)每個(gè)系統(tǒng)都去開(kāi)發(fā)同樣的功能,那就是 重復(fù)造輪子 ,在大多數(shù)情況下,不是明智之舉。
理解了啥是 SDK 后,來(lái)看看如何寫(xiě)出優(yōu)秀的 SDK 吧~
手寫(xiě) SDK 經(jīng)驗(yàn)總結(jié)
好的 SDK 應(yīng)該具有簡(jiǎn)單易用、通俗易懂、便于擴(kuò)展、高效穩(wěn)定等特點(diǎn)。
易用性
如今,現(xiàn)成的輪子實(shí)在太多了!如何讓你的輪子脫穎而出呢?那就要先提升 SDK 的易用性。
我自己在技術(shù)選型時(shí),就會(huì)傾向于優(yōu)先選擇簡(jiǎn)單易用的 SDK,最好是幾行代碼就能輕松使用,而不是必須要我讀完老長(zhǎng)一份文檔,再寫(xiě)個(gè)幾十行代碼才能生效。
就和產(chǎn)品說(shuō)明書(shū)一樣,太復(fù)雜直接把人勸退。

我們可以通過(guò)以下幾點(diǎn)提高易用性:
統(tǒng)一調(diào)用
將復(fù)雜的功能進(jìn)行封裝,對(duì)外提供統(tǒng)一的調(diào)用入口,盡量屏蔽一些實(shí)現(xiàn)細(xì)節(jié),減少用戶(hù)調(diào)用的流程和對(duì)參數(shù)的理解成本。
舉個(gè)例子,下面是兩種日期處理函數(shù)。用戶(hù)不需要關(guān)心他們是如何實(shí)現(xiàn)的,只需要知道怎么用、傳遞哪些參數(shù)、得到哪些返回值就行了。
//?第?1?種:需要?new?對(duì)象
DateUtils?dateUtils?=?new?DateUtils();
dateUtils.setDate('2021-08-31');
Date?date?=?dateUtils.parse();
//?第?2?種:直接調(diào)用
Date?date?=?DateUtils.parse('2021-08-13');
那大家覺(jué)得哪種更易用呢?
集中配置
將復(fù)雜的參數(shù)配置化,不需要讓用戶(hù)到處寫(xiě)參數(shù),而是通過(guò)一個(gè)配置文件統(tǒng)一管理。
Java 主流開(kāi)發(fā)框架 SpringBoot 就是典型的例子,假如用戶(hù)想改變內(nèi)嵌服務(wù)器啟動(dòng)的端口、亦或是改變數(shù)據(jù)庫(kù)的連接地址,不需要寫(xiě)代碼,而是改一下配置文件就行了:
#?服務(wù)器配置
server:
??port:?8081
#?數(shù)據(jù)庫(kù)配置
db:
?ip:?10.0.0.1
此外,這樣也便于維護(hù)項(xiàng)目和實(shí)現(xiàn)多環(huán)境。
良好命名
給 SDK 的函數(shù)取名稱(chēng)時(shí),盡量讓它符合用戶(hù)的習(xí)慣。
比如具有解析功能的函數(shù),可以叫 "parseXXX";判斷是否為空的函數(shù),可以叫 "xx.isEmpty" 等。最好能做到讓用戶(hù)不看文檔,只通過(guò)函數(shù)名稱(chēng)和參數(shù),就知道你這個(gè)函數(shù)是做什么的。

因此,想要寫(xiě)出好的 SDK,首先要多用、多參考別人的 SDK,養(yǎng)成習(xí)慣后你就會(huì)發(fā)現(xiàn),大家起名兒都差不多。
但也要注意一點(diǎn),如果可以,盡量不要讓你 SDK 中的類(lèi)名(函數(shù)名)和別人的完全一致,否則可能給用戶(hù)帶來(lái)困擾:這么多同名的函數(shù),我該用哪個(gè)呢?哪個(gè)是你開(kāi)發(fā)的 SDK 呢?
可理解性
有時(shí),用戶(hù)可能不滿(mǎn)足于簡(jiǎn)單地使用你的 SDK,而是希望閱讀你的 SDK 源碼來(lái)進(jìn)一步了解,用的才更放心。
因此,除了易用外,還要讓你的 SDK 便于理解,不能金玉其外敗絮其中。
這個(gè)就和編碼習(xí)慣有很大的關(guān)系了,無(wú)論是寫(xiě) SDK 也好,還是做項(xiàng)目也罷,都要做到以下幾點(diǎn):
結(jié)構(gòu)清晰
把代碼按照功能或類(lèi)別進(jìn)行整理,放到指定的目錄下。常見(jiàn)的做法有分包、分層等,讓人一眼就知道每個(gè)目錄下的文件的作用。
比如下面這個(gè)經(jīng)典的 Java 項(xiàng)目結(jié)構(gòu),service 目錄是編寫(xiě)業(yè)務(wù)邏輯的、constant 是存放常量的、utils 是存放工具類(lèi)的等等,都很清晰:

統(tǒng)一風(fēng)格
統(tǒng)一風(fēng)格的目標(biāo)很簡(jiǎn)單:讓項(xiàng)目看起來(lái)是同一個(gè)人寫(xiě)出來(lái)的。
比如代碼縮進(jìn)都用 4 個(gè)空格、命名都用駝峰式等。尤其是功能相似的代碼,一定要保持命名和用法的統(tǒng)一!比如解析文本的函數(shù),不要一會(huì)叫 "parseXXX"、一會(huì)兒又叫 "jiexiXXX",給用戶(hù)都整樂(lè)了~
但實(shí)際上,團(tuán)隊(duì)開(kāi)發(fā)中,很難做到這點(diǎn)。因此才需要有一套通用的代碼規(guī)范,大家都去遵守規(guī)范,才能讓項(xiàng)目更好理解、更便于維護(hù)。
編寫(xiě)注釋
最好給 SDK 中的每個(gè)類(lèi)和函數(shù)的 開(kāi)頭 都加上注釋?zhuān)@樣用戶(hù)在使用 SDK 時(shí),甚至都不需要看文檔,直接看代碼注釋就能知道它是干嘛的、怎么用。
隨便打開(kāi) Java SDK 或者網(wǎng)上知名 SDK 中的一個(gè)函數(shù),一般都能看到這些注釋?zhuān)▽?duì)函數(shù)功能的描述、參數(shù)含義、返回值含義等:

說(shuō)明文檔
除了注釋外,還要編寫(xiě)一個(gè)說(shuō)明文檔(用戶(hù)手冊(cè)),包括如何引入 SDK 、有哪些功能、應(yīng)該怎么使用等等,甚至還可以補(bǔ)充一些關(guān)鍵的實(shí)現(xiàn)細(xì)節(jié)、以及常見(jiàn)的問(wèn)題列表。
這點(diǎn)也會(huì)極大地影響用戶(hù)的選擇。就我個(gè)人而言,沒(méi)有文檔的 SDK 我一般是不會(huì)選用的,萬(wàn)一出了事我找誰(shuí)呢?

可擴(kuò)展性
編寫(xiě) SDK 的一大難點(diǎn)是:不僅要考慮到大部分通用的使用場(chǎng)景,還要滿(mǎn)足小部分用戶(hù)定制化的需求。
因此,SDK 的可擴(kuò)展性是很重要的,但怎么提升呢?
輕量依賴(lài)
一方面,我們可以盡量減少 SDK 本身對(duì)其他類(lèi)庫(kù)的依賴(lài)。
舉個(gè)例子,假如你要做一個(gè)很輕小的工具類(lèi),可能只有幾十 KB,那就沒(méi)有必要再引入一個(gè)幾百 KB 的依賴(lài)庫(kù)了,得不償失!別人也不樂(lè)意用??!
輕量依賴(lài)不僅可以減少 SDK 的體積,更關(guān)鍵的是可以減少依賴(lài)沖突的可能性。我自己也曾經(jīng)遇到過(guò)很多次這樣的尷尬局面:引入一個(gè)工具類(lèi)后,整個(gè)項(xiàng)目就跑不起來(lái)了!

自定義實(shí)現(xiàn)
為了提高 SDK 的通用性和靈活性,在設(shè)計(jì) SDK 時(shí),除了提供默認(rèn)實(shí)現(xiàn)外,建議提供一個(gè)通用接口或抽象類(lèi),讓用戶(hù)來(lái)繼承,編寫(xiě)自己的實(shí)現(xiàn)方式。
舉個(gè)例子,假設(shè)我們要編寫(xiě)一個(gè)日期解析類(lèi),默認(rèn)的解析規(guī)則是按照短橫分割字符串:
//?按照?'-'?切分
date?=?DateUtils.parse('2021-01-18')
如果只能做到這點(diǎn),那這個(gè) SDK 就很死板。因?yàn)橛脩?hù)可能想按照冒號(hào)或其他規(guī)則來(lái)解析。
怎么實(shí)現(xiàn)呢?
我們可以允許用戶(hù)自己傳入分割字符:
//?按照?'-'?切分
parseRule?=?':'
date?=?DateUtils.parse('2021-01-18',?parseRule)
還可以讓用戶(hù)自己來(lái)控制解析的方式:
//?自定義解析器
interface?MyParser?extends?Parser?{
??//?需要用戶(hù)自己實(shí)現(xiàn)
??void?doParse();
}
//?指定解析器
date?=?DateUtils.parse('2021-01-18',?MyParser.class);
這兩種方式在 SDK 的設(shè)計(jì)中屢見(jiàn)不鮮,此外還可以讓用戶(hù)自行編寫(xiě)或指定配置文件,也能提高靈活性。
高效穩(wěn)定
其實(shí),開(kāi)發(fā) SDK,尤其是在大廠(chǎng)開(kāi)發(fā) SDK,是個(gè)很 “坑” 的工作,我相信做過(guò)的朋友會(huì)感同身受。
因?yàn)殡S著使用你 SDK 的用戶(hù)越來(lái)越多,可能會(huì)發(fā)現(xiàn)各種各樣莫名其妙的問(wèn)題;而且 SDK 作為相對(duì)底層的依賴(lài),對(duì)使用方的影響也是無(wú)法估量的。所以,不想經(jīng)常加班改 Bug 的話(huà),就要保證你 SDK 的穩(wěn)定性。
我們需要注意以下幾點(diǎn):
1. 測(cè)試
為了保證每個(gè)功能都是正常的,我們可以編寫(xiě) 單元測(cè)試(UT)來(lái)最大程度上地覆蓋 SDK 的功能和代碼。
尤其是每次改動(dòng)代碼后、發(fā)布新版本之前,都要再完整地執(zhí)行一遍測(cè)試,不要盲目自信。

此外,還可以通過(guò) 壓力測(cè)試 來(lái)估算 SDK 的執(zhí)行效率,比如每秒最多同時(shí)執(zhí)行 3 次、每次要執(zhí)行 500 毫秒等。建議將這些信息補(bǔ)充到說(shuō)明文檔中,給用戶(hù)一些預(yù)期。當(dāng)然也可以嘗試通過(guò)壓測(cè)來(lái)優(yōu)化 SDK 的性能。
2. 兼容性
重要的函數(shù)和接口盡量減少改動(dòng),尤其是函數(shù)名、入?yún)⒑头祷刂担?/p>
舉個(gè)例子,SDK 0.1 版本時(shí),函數(shù)的定義是這樣的:
boolean?isValid(String?str)
結(jié)果突然在 0.2 版本時(shí)改成了:
String?checkValid(StringBuilder?str)
這樣就會(huì)導(dǎo)致用戶(hù)升級(jí)時(shí)一臉懵逼,怎么報(bào)了一堆找不到函數(shù)呢?

因此,對(duì)于比較大的改動(dòng),可以新寫(xiě)一個(gè)函數(shù),并且給老函數(shù)打上類(lèi)似 @Deprecated
的注釋?zhuān)硎疽堰^(guò)時(shí),引導(dǎo)用戶(hù)去用新的。
此外,還可以在 版本號(hào) 上做做文章,小改動(dòng)時(shí)只改變小版本號(hào),比如 0.0.1 到 0.0.2;大改動(dòng)時(shí)才改變大版本號(hào),比如 1.0 到 2.0。這樣可以給用戶(hù)一個(gè)預(yù)期:這次改動(dòng)很大,可能會(huì)存在不兼容。
3. 暴露異常
要讓用戶(hù)感知到 SDK 代碼中可能拋出的異常,交給他們?nèi)ミM(jìn)行相應(yīng)的處理,防止出現(xiàn)一些意料之外的錯(cuò)誤。

此外,SDK 要合理地打印日志,尤其是異常日志,在出了問(wèn)題時(shí)要讓調(diào)用者知道是出了什么問(wèn)題,便于排查。
以上就是本期分享,建議學(xué)編程的同學(xué)多自己動(dòng)手寫(xiě) SDK,并且按照本文的總結(jié)去優(yōu)化它,對(duì)提升編程能力真的很有幫助!
最近整理了我原創(chuàng)的 140 篇編程經(jīng)驗(yàn)和技術(shù)文章,歡迎大家閱讀,一起成長(zhǎng)!??
指路:https://t.1yb.co/ARnD
我是魚(yú)皮,最后求個(gè) 點(diǎn)贊 ,這將是我持續(xù)創(chuàng)作的最大動(dòng)力,謝謝 ??