目錄
設計好接口的 36 個錦囊
| 接口參數校驗
| 修改老接口時,注意接口的兼容性
| 設計接口時,充分考慮接口的可擴展性
| 接口考慮是否需要防重處理
| 重點接口,考慮線程池隔離
| 調用第三方接口要考慮異常和超時處理
| 接口實現考慮熔斷和降級
| 日志打印好,接口的關鍵代碼,要有日志保駕護航
| 接口的功能定義要具備單一性
| 接口有些場景,使用異步更合理
| 優化接口耗時,遠程串行考慮改并行調用
| 接口合并或者說考慮批量處理思想
| 接口實現過程中,恰當使用緩存
| 接口考慮熱點數據隔離性
| 可變參數配置化,比如紅包皮膚切換等
| 接口考慮冪等性
| 讀寫分離,優先考慮讀從庫,注意主從延遲問題
| 接口注意返回的數據量,如果數據量大需要分頁
| 好的接口實現,離不開 SQL 優化
| 代碼鎖的粒度控制好
| 接口狀態和錯誤需要統一明確
| 接口要考慮異常處理
| 優化程序邏輯
| 接口實現過程中,注意大文件、大事務、大對象
| 你的接口,需要考慮限流
| 代碼實現時,注意運行時異常(比如空指針、下標越界等)
| 保證接口安全性
| 分布式事務,如何保證
| 事務失效的一些經典場景
| 掌握常用的設計模式
| 寫代碼時,考慮線性安全問題
| 接口定義清晰易懂,命名規范
| 接口的版本控制
| 注意代碼規范問題
| 保證接口正確性,其實就是保證更少的 bug
| 學會溝通,跟前端溝通,跟產品溝通
前言
作為后端開發,不管是什么語言,Java、Go 還是 C++,其背后的后端思想都是類似的。我們做后端開發工程師,主要工作就是:如何把一個接口設計好。所以,今天就給大家介紹,設計好接口的 36 個錦囊。
設計好接口的 36 個錦囊
| 接口參數校驗
入參出參校驗是每個程序員必備的基本素養。你設計的接口,必須先校驗參數。
比如入參是否允許為空,入參長度是否符合你的預期長度。這個要養成習慣哈,日常開發中,很多低級 bug 都是不校驗參數導致的。
比如你的數據庫表字段設置為 varchar(16),對方傳了一個 32 位的字符串過來,如果你不校驗參數,插入數據庫直接異常了。
出參也是,比如你定義的接口報文,參數是不為空的,但是你的接口返回參數,沒有做校驗,因為程序某些原因,直返回別人一個 null 值...
| 修改老接口時,注意接口的兼容性
很多 bug 都是因為修改了對外舊接口,但是卻不做兼容導致的。關鍵這個問題多數是比較嚴重的,可能直接導致系統發版失敗的。新手程序員很容易犯這個錯誤哦~
所以,如果你的需求是在原來接口上修改,尤其這個接口是對外提供服務的話,一定要考慮接口兼容。
舉個例子吧,比如 dubbo 接口,原本是只接收 A,B 參數,現在你加了一個參數 C,就可以考慮這樣處理:
//老接口
void?oldService(A,B){//兼容新接口,傳個null代替CnewService(A,B,null);
}//新接口,暫時不能刪掉老接口,需要做兼容。
void?newService(A,B,C){...
}
| 設計接口時,充分考慮接口的可擴展性
要根據實際業務場景設計接口,充分考慮接口的可擴展性。
比如你接到一個需求:是用戶添加或者修改員工時,需要刷臉。那你是反手提供一個員工管理的提交刷臉信息接口?還是先思考:提交刷臉是不是通用流程呢?
比如轉賬或者一鍵貼現需要接入刷臉的話,你是否需要重新實現一個接口呢?還是當前按業務類型劃分模塊,復用這個接口就好,保留接口的可擴展性。
如果按模塊劃分的話,未來如果其他場景比如一鍵貼現接入刷臉的話,不用再搞一套新的接口,只需要新增枚舉,然后復用刷臉通過流程接口,實現一鍵貼現刷臉的差異化即可。
| 接口考慮是否需要防重處理
如果前端重復請求,你的邏輯如何處理?是不是考慮接口去重處理。
當然,如果是查詢類的請求,其實不用防重。如果是更新修改類的話,尤其金融轉賬類的,就要過濾重復請求了。
簡單點,你可以使用 Redis 防重復請求,同樣的請求方,一定時間間隔內的相同請求,考慮是否過濾。
當然,轉賬類接口,并發不高的話,推薦使用數據庫防重表,以唯一流水號作為主鍵或者唯一索引。
| 重點接口,考慮線程池隔離
一些登陸、轉賬交易、下單等重要接口,考慮線程池隔離哈。如果你所有業務都共用一個線程池,有些業務出 bug 導致線程池阻塞打滿的話,那就杯具了,所有業務都影響了。
因此進行線程池隔離,重要業務分配多一點的核心線程,就更好保護重要業務。
| 調用第三方接口要考慮異常和超時處理
如果你調用第三方接口,或者分布式遠程服務的的話,需要考慮:
-
異常處理:比如,你調別人的接口,如果異常了,怎么處理,是重試還是當做失敗還是告警處理。
-
接口超時:沒法預估對方接口一般多久返回,一般設置個超時斷開時間,以保護你的接口。之前見過一個生產問題,就是http調用不設置超時時間,最后響應方進程假死,請求一直占著線程不釋放,拖垮線程池。
-
重試次數:你的接口調失敗,需不需要重試?重試幾次?需要站在業務上角度思考這個問題。
| 接口實現考慮熔斷和降級
當前互聯網系統一般都是分布式部署的。而分布式系統中經常會出現某個基礎服務不可用,最終導致整個系統不可用的情況,這種現象被稱為服務雪崩效應。
比如分布式調用鏈路 A->B->C....,下圖所示:
如果服務 C 出現問題,比如是因為慢 SQL 導致調用緩慢,那將導致 B 也會延遲,從而 A 也會延遲。堵住的 A 請求會消耗占用系統的線程、IO 等資源。當請求 A 的服務越來越多,占用計算機的資源也越來越多,最終會導致系統瓶頸出現,造成其他的請求同樣不可用,最后導致業務系統崩潰。
為了應對服務雪崩, 常見的做法是熔斷和降級。最簡單是加開關控制,當下游系統出問題時,開關降級,不再調用下游系統。還可以選用開源組件 Hystrix。
| 日志打印好,接口的關鍵代碼,要有日志保駕護航
關鍵業務代碼無論身處何地,都應該有足夠的日志保駕護航。
比如:你實現轉賬業務,轉個幾百萬,然后轉失敗了,接著客戶投訴,然后你還沒有打印到日志,想想那種水深火熱的困境下,你卻毫無辦法...
那么,你的轉賬業務都需要哪些日志信息呢?至少,方法調用前,入參需要打印需要吧,接口調用后,需要捕獲一下異常吧,同時打印異常相關日志吧,如下:
public?void?transfer(TransferDTO?transferDTO){log.info("invoke?tranfer?begin");//打印入參log.info("invoke?tranfer,paramters:{}",transferDTO);try?{res=??transferService.transfer(transferDTO);}catch(Exception?e){log.error("transfer fail,account:{}",transferDTO.getAccount())log.error("transfer?fail,exception:{}",e);}log.info("invoke?tranfer?end");}
| 接口的功能定義要具備單一性
單一性是指接口做的事情比較單一、專一。比如一個登陸接口,它做的事情就只是校驗賬戶名密碼,然后返回登陸成功以及 userId 即可。
但是如果你為了減少接口交互,把一些注冊、一些配置查詢等全放到登陸接口,就不太妥。
其實這也是微服務一些思想,接口的功能單一、明確。比如訂單服務、積分、商品信息相關的接口都是劃分開的。將來拆分微服務的話,是不是就比較簡便啦。
| 接口有些場景,使用異步更合理
舉個簡單的例子,比如你實現一個用戶注冊的接口。用戶注冊成功時,發個郵件或者短信去通知用戶。
這個郵件或者發短信,就更適合異步處理。因為總不能一個通知類的失敗,導致注冊失敗吧。
至于做異步的方式,簡單的就是用線程池。還可以使用消息隊列,就是用戶注冊成功后,生產者產生一個注冊成功的消息,消費者拉到注冊成功的消息,就發送通知。
不是所有的接口都適合設計為同步接口。比如你要做一個轉賬的功能,如果你是單筆的轉賬,你是可以把接口設計同步。用戶發起轉賬時,客戶端在靜靜等待轉賬結果就好。
如果你是批量轉賬,一個批次一千筆,甚至一萬筆的,你則可以把接口設計為異步。就是用戶發起批量轉賬時,持久化成功就先返回受理成功。
然后用戶隔十分鐘或者十五分鐘等再來查轉賬結果就好。又或者,批量轉賬成功后,再回調上游系統。
| 優化接口耗時,遠程串行考慮改并行調用
假設我們設計一個 APP 首頁的接口,它需要查用戶信息、需要查 banner 信息、需要查彈窗信息等等。那你是一個一個接口串行調,還是并行調用呢?
如果是串行一個一個查,比如查用戶信息 200ms,查 banner 信息 100ms、查彈窗信息 50ms,那一共就耗時 350ms 了,如果還查其他信息,那耗時就更大了。
這種場景是可以改為并行調用的。也就是說查用戶信息、查 banner 信息、查彈窗信息,可以同時發起。
在 Java 中有個異步編程利器:CompletableFuture,就可以很好實現這個功能。
| 接口合并或者說考慮批量處理思想
數據庫操作或或者是遠程調用時,能批量操作就不要 for 循環調用。
一個簡單例子,我們平時一個列表明細數據插入數據庫時,不要在 for 循環一條一條插入,建議一個批次幾百條,進行批量插入。
同理遠程調用也類似想法,比如你查詢營銷標簽是否命中,可以一個標簽一個標簽去查,也可以批量標簽去查,那批量進行,效率就更高嘛。
//反例
for(int?i=0;i<n;i++){remoteSingleQuery(param)
}//正例
remoteBatchQuery(param);
小伙伴們是否了解過 kafka 為什么這么快呢?其實其中一點原因,就是 kafka 使用批量消息提升服務端處理能力。
| 接口實現過程中,恰當使用緩存
哪些場景適合使用緩存?讀多寫少且數據時效要求越低的場景。緩存用得好,可以承載更多的請求,提升查詢效率,減少數據庫的壓力。
比如一些平時變動很小或者說幾乎不會變的商品信息,可以放到緩存,請求過來時,先查詢緩存,如果沒有再查數據庫,并且把數據庫的數據更新到緩存。
但是,使用緩存增加了需要考慮這些點:緩存和數據庫一致性如何保證、集群、緩存擊穿、緩存雪崩、緩存穿透等問題。
-
保證數據庫和緩存一致性:緩存延時雙刪、刪除緩存重試機制、讀取biglog異步刪除緩存
-
緩存擊穿:設置數據永不過期
-
緩存雪崩:Redis 集群高可用、均勻設置過期時間
-
緩存穿透:接口層校驗、查詢為空設置個默認空值標記、布隆過濾器。
一般用 Redis 分布式緩存,當然有些時候也可以考慮使用本地緩存,如 Guava Cache、Caffeine 等。
使用本地緩存有些缺點,就是無法進行大數據存儲,并且應用進程的重啟,緩存會失效。
| 接口考慮熱點數據隔離性
瞬時間的高并發,可能會打垮你的系統。可以做一些熱點數據的隔離。比如業務隔離、系統隔離、用戶隔離、數據隔離等。
-
業務隔離性,比如 12306 的分時段售票,將熱點數據分散處理,降低系統負載壓力。
-
系統隔離:比如把系統分成了用戶、商品、社區三個板塊。這三個塊分別使用不同的域名、服務器和數據庫,做到從接入層到應用層再到數據層三層完全隔離。
-
用戶隔離:重點用戶請求到配置更好的機器。
-
數據隔離:使用單獨的緩存集群或者數據庫服務熱點數據。
| 可變參數配置化,比如紅包皮膚切換等
假如產品經理提了個紅包需求,圣誕節的時候,紅包皮膚為圣誕節相關的,春節的時候,為春節紅包皮膚等。
如果在代碼寫死控制,可有類似以下代碼:
if(duringChristmas){img?=?redPacketChristmasSkin;
}else?if(duringSpringFestival){img?=??redSpringFestivalSkin;
}
如果到了元宵節的時候,運營小姐姐突然又有想法,紅包皮膚換成燈籠相關的,這時候,是不是要去修改代碼了,重新發布了?
從一開始接口設計時,可以實現一張紅包皮膚的配置表,將紅包皮膚做成配置化呢?更換紅包皮膚,只需修改一下表數據就好了。
當然,還有一些場景適合一些配置化的參數:一個分頁多少數量控制、某個搶紅包多久時間過期這些,都可以搞到參數配置化表里面。這也是擴展性思想的一種體現。
| 接口考慮冪等性
接口是需要考慮冪等性的,尤其搶紅包、轉賬這些重要接口。最直觀的業務場景,就是用戶連著點擊兩次,你的接口有沒有 hold 住。或者消息隊列出現重復消費的情況,你的業務邏輯怎么控制?
回憶下,什么是冪等?計算機科學中,冪等表示一次和多次請求某一個資源應該具有同樣的副作用,或者說,多次請求所產生的影響與一次請求執行的影響效果相同。
大家別搞混哈,防重和冪等設計其實是有區別的。防重主要為了避免產生重復數據,把重復請求攔截下來即可。
而冪等設計除了攔截已經處理的請求,還要求每次相同的請求都返回一樣的效果。不過呢,很多時候,它們的處理流程、方案是類似的哈。
接口冪等實現方案主要有 8 種:
-
select+insert+主鍵/唯一索引沖突
-
直接 insert + 主鍵/唯一索引沖突
-
狀態機冪等
-
抽取防重表
-
token 令牌
-
悲觀鎖
-
樂觀鎖
-
分布式鎖
| 讀寫分離,優先考慮讀從庫,注意主從延遲問題
我們的數據庫都是集群部署的,有主庫也有從庫,當前一般都是讀寫分離的。
比如你寫入數據,肯定是寫入主庫,但是對于讀取實時性要求不高的數據,則優先考慮讀從庫,因為可以分擔主庫的壓力。
如果讀取從庫的話,需要考慮主從延遲的問題。
| 接口注意返回的數據量,如果數據量大需要分頁
一個接口返回報文,不應該包含過多的數據量。過多的數據量不僅處理復雜,并且數據量傳輸的壓力也非常大。
因此數量實在是比較大,可以分頁返回,如果是功能不相關的報文,那應該考慮接口拆分。
| 好的接口實現,離不開 SQL 優化
我們做后端的,寫好一個接口,離不開 SQL 優化。
SQL 優化從這幾個維度思考:
-
explain 分析 SQL 查詢計劃(重點關注 type、extra、filtered 字段)
-
show profile 分析,了解 SQL 執行的線程的狀態以及消耗的時間
-
索引優化 (覆蓋索引、最左前綴原則、隱式轉換、order by 以及 group by 的優化、join 優化)
-
大分頁問題優化(延遲關聯、記錄上一頁最大 ID)
-
數據量太大(分庫分表、同步到 es,用 es 查詢)
| 代碼鎖的粒度控制好
什么是加鎖粒度呢?其實就是就是你要鎖住的范圍是多大。比如你在家上衛生間,你只要鎖住衛生間就可以了吧,不需要將整個家都鎖起來不讓家人進門吧,衛生間就是你的加鎖粒度。
我們寫代碼時,如果不涉及到共享資源,就沒有必要鎖住的。這就好像你上衛生間,不用把整個家都鎖住,鎖住衛生間門就可以了。
比如,在業務代碼中,有一個 ArrayList 因為涉及到多線程操作,所以需要加鎖操作,假設剛好又有一段比較耗時的操作(代碼中的 slowNotShare 方法)不涉及線程安全問題,你會如何加鎖呢?
反例:
//不涉及共享資源的慢方法
private?void?slowNotShare()?{try?{TimeUnit.MILLISECONDS.sleep(100);}?catch?(InterruptedException?e)?{}
}//錯誤的加鎖方法
public?int?wrong()?{long?beginTime?=?System.currentTimeMillis();IntStream.rangeClosed(1,?10000).parallel().forEach(i?->?{//加鎖粒度太粗了,slowNotShare其實不涉及共享資源synchronized?(this)?{slowNotShare();data.add(i);}});log.info("cosume?time:{}",?System.currentTimeMillis()?-?beginTime);return?data.size();
}
正例:
public?int?right()?{long?beginTime?=?System.currentTimeMillis();IntStream.rangeClosed(1,?10000).parallel().forEach(i?->?{slowNotShare();//可以不加鎖//只對List這部分加鎖synchronized?(data)?{data.add(i);}});log.info("cosume?time:{}",?System.currentTimeMillis()?-?beginTime);return?data.size();
}
??????????????????????????????????????????
| 接口狀態和錯誤需要統一明確
提供必要的接口調用狀態信息。比如你的一個轉賬接口調用是成功、失敗、處理中還是受理成功等,需要明確告訴客戶端。如果接口失敗,那么具體失敗的原因是什么。
這些必要的信息都必須要告訴給客戶端,因此需要定義明確的錯誤碼和對應的描述。同時,盡量對報錯信息封裝一下,不要把后端的異常信息完全拋出到客戶端。
| 接口要考慮異常處理
實現一個好的接口,離不開優雅的異常處理。
對于異常處理,提十個小建議吧:
-
盡量不要使用 e.printStackTrace(),而是使用 log 打印。因為 e.printStackTrace() 語句可能會導致內存占滿
-
catch 住異常時,建議打印出具體的 exception,利于更好定位問題
-
不要用一個 Exception 捕捉所有可能的異常
-
記得使用 finally 關閉流資源或者直接使用 try-with-resource
-
捕獲異常與拋出異常必須是完全匹配,或者捕獲異常是拋異常的父類
-
捕獲到的異常,不能忽略它,至少打點日志吧
-
注意異常對你的代碼層次結構的侵染
-
自定義封裝異常,不要丟棄原始異常的信息 Throwable cause
-
運行時異常 RuntimeException ,不應該通過 catch 的方式來處理,而是先預檢查,比如:NullPointerException 處理
-
注意異常匹配的順序,優先捕獲具體的異常
| 優化程序邏輯
優化程序邏輯這塊還是挺重要的,也就是說,你實現的業務代碼,如果是比較復雜的話,建議把注釋寫清楚。還有,代碼邏輯盡量清晰,代碼盡量高效。
比如,你要使用用戶信息的屬性,你根據 session 已經獲取到 userId 了,然后就把用戶信息從數據庫查詢出來,使用完后,后面可能又要用到用戶信息的屬性,有些小伙伴沒想太多,反手就把 userId 再傳進去,再查一次數據庫。。。我在項目中,見過這種代碼。。。直接把用戶對象傳下來不好嘛。。
反例偽代碼:
public?Response?test(Session?session){UserInfo?user?=?UserDao.queryByUserId(session.getUserId());if(user==null){reutrn?new?Response();}return?do(session.getUserId());
}public?Response?do(String?UserId){//多查了一次數據庫UserInfo?user?=?UserDao.queryByUserId(session.getUserId());......return?new?Response();?
}
正例:
public?Response?test(Session?session){UserInfo?user?=?UserDao.queryByUserId(session.getUserId());if(user==null){reutrn?new?Response();}return?do(session.getUserId());
}//直接傳UserInfo對象過來即可,不用再多查一次數據庫
public?Response?do(UserInfo?user){......return?new?Response();?
}
當然,這只是一些很小的一個例子,還有很多類似的例子,需要大家開發過程中,多點思考的哈。
| 接口實現過程中,注意大文件、大事務、大對象
如下:
-
讀取大文件時,不要 Files.readAllBytes 直接讀取到內存,這樣會 OOM 的,建議使用 BufferedReader 一行一行來。
-
大事務可能導致死鎖、回滾時間長、主從延遲等問題,開發中盡量避免大事務。
-
注意一些大對象的使用,因為大對象是直接進入老年代的,可能會觸發 fullGC。
| 你的接口,需要考慮限流
如果你的系統每秒扛住的請求是 1000,如果一秒鐘來了十萬請求呢?換個角度就是說,高并發的時候,流量洪峰來了,超過系統的承載能力,怎么辦呢?
如果不采取措施,所有的請求打過來,系統 CPU、內存、Load 負載飚的很高,最后請求處理不過來,所有的請求無法正常響應。
針對這種場景,我們可以采用限流方案。就是為了保護系統,多余的請求,直接丟棄。
限流定義:在計算機網絡中,限流就是控制網絡接口發送或接收請求的速率,它可防止 DoS 攻擊和限制 Web 爬蟲。
限流,也稱流量控制。是指系統在面臨高并發,或者大流量請求的情況下,限制新的請求對系統的訪問,從而保證系統的穩定性。
可以使用 Guava 的 RateLimiter 單機版限流,也可以使用 Redis 分布式限流,還可以使用阿里開源組件 sentinel 限流。
| 代碼實現時,注意運行時異常(比如空指針、下標越界等)
日常開發中,我們需要采取措施規避數組邊界溢出,被零整除,空指針等運行時錯誤。
類似代碼比較常見:
String?name?=?list.get(1).getName();?//list可能越界,因為不一定有2個元素哈
應該采取措施,預防一下數組邊界溢出。正例如下:
if(CollectionsUtil.isNotEmpty(list)&&?list.size()>1){String?name?=?list.get(1).getName();?
}
| 保證接口安全性
如果你的 API 接口是對外提供的,需要保證接口的安全性。保證接口的安全性有 token 機制和接口簽名。
token 機制身份驗證方案還比較簡單的,就是:
-
客戶端發起請求,申請獲取 token。
-
服務端生成全局唯一的 token,保存到 redis 中(一般會設置一個過期時間),然后返回給客戶端。
-
客戶端帶著 token,發起請求。
-
服務端去 redis 確認 token 是否存在,一般用 redis.del(token) 的方式,如果存在會刪除成功,即處理業務邏輯,如果刪除失敗不處理業務邏輯,直接返回結果。
接口簽名的方式,就是把接口請求相關信息(請求報文,包括請求時間戳、版本號、appid 等),客戶端私鑰加簽,然后服務端用公鑰驗簽,驗證通過才認為是合法的、沒有被篡改過的請求。
除了加簽驗簽和 token 機制,接口報文一般是要加密的。當然,用 https 協議是會對報文加密的。如果是我們服務層的話,如何加解密呢?
可以參考 HTTPS 的原理,就是服務端把公鑰給客戶端,然后客戶端生成對稱密鑰,接著客戶端用服務端的公鑰加密對稱密鑰,再發到服務端,服務端用自己的私鑰解密,得到客戶端的對稱密鑰。
這時候就可以愉快傳輸報文啦,客戶端用對稱密鑰加密請求報文,服務端用對應的對稱密鑰解密報文。
有時候,接口的安全性,還包括手機號、身份證等信息的脫敏。就是說,用戶的隱私數據,不能隨便暴露。
| 分布式事務,如何保證
分布式事務:就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布式系統的不同節點之上。
簡單來說,分布式事務指的就是分布式系統中的事務,它的存在就是為了保證不同數據庫節點的數據一致性。
分布式事務的幾種解決方案:
-
2PC(二階段提交)方案、3PC
-
TCC(Try、Confirm、Cancel)
-
本地消息表
-
最大努力通知
-
seata
| 事務失效的一些經典場景
我們的接口開發過程中,經常需要使用到事務。
所以需要避開事務失效的一些經典場景:
-
方法的訪問權限必須是 public,其他 private 等權限,事務失效
-
方法被定義成了 final 的,這樣會導致事務失效。
-
在同一個類中的方法直接內部調用,會導致事務失效。
-
一個方法如果沒交給 spring 管理,就不會生成 spring 事務。
-
多線程調用,兩個方法不在同一個線程中,獲取到的數據庫連接不一樣的。
-
表的存儲引擎不支持事務
-
如果自己 try...catch 誤吞了異常,事務失效。
-
錯誤的傳播特性
| 掌握常用的設計模式
把代碼寫好,還是需要熟練常用的設計模式,比如策略模式、工廠模式、模板方法模式、觀察者模式等等。
設計模式,是代碼設計經驗的總結。使用設計模式可以可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
| 寫代碼時,考慮線性安全問題
在高并發情況下,HashMap 可能會出現死循環。因為它是非線性安全的,可以考慮使用 ConcurrentHashMap。
所以這個也盡量養成習慣,不要上來反手就是一個 new HashMap();
-
Hashmap、Arraylist、LinkedList、TreeMap等都是線性不安全的
-
Vector、Hashtable、ConcurrentHashMap等都是線性安全的
| 接口定義清晰易懂,命名規范
我們寫代碼,不僅僅是為了實現當前的功能,也要有利于后面的維護。說到維護,代碼不僅僅是寫給自己看的,也是給別人看的。所以接口定義要清晰易懂,命名規范。
| 接口的版本控制
接口要做好版本控制。就是說,請求基礎報文,應該包含 version 接口版本號字段,方便未來做接口兼容。其實這個點也算接口擴展性的一個體現點吧。
比如客戶端 APP 某個功能優化了,新老版本會共存,這時候我們的 version 版本號就派上用場了,對 version 做升級,做好版本控制。
| 注意代碼規范問題
注意一些常見的代碼壞味道:
-
大量重復代碼(抽共用方法,設計模式)
-
方法參數過多(可封裝成一個 DTO 對象)
-
方法過長(抽小函數)
-
判斷條件太多(優化 if...else)
-
不處理沒用的代碼
-
不注重代碼格式
-
避免過度設計
| 保證接口正確性,其實就是保證更少的 bug
保證接口的正確性,換個角度講,就是保證更少的 bug,甚至是沒有 bug。所以接口開發完后,一般需要開發自測一下。
然后的話,接口的正確還體現在,多線程并發的時候,保證數據的正確性,等等。比如你做一筆轉賬交易,扣減余額的時候,可以通過 CAS 樂觀鎖的方式保證余額扣減正確吧。
如果你是實現秒殺接口,得防止超賣問題吧。你可以使用 Redis 分布式鎖防止超賣問題。
| 學會溝通,跟前端溝通,跟產品溝通
我把這一點放到最后,學會溝通是非常非常重要的。比如你開發定義接口時,一定不能上來就自己埋頭把接口定義完了,需要跟客戶端先對齊接口。
遇到一些難點時,跟技術 leader 對齊方案。實現需求的過程中,有什么問題,及時跟產品溝通。總之就是,開發接口過程中,一定要溝通好~