還記得上次我寫過一篇關於實際項目代碼分層和規劃的文章《看完這篇,別人的開源項目結構應該能看懂了》,
在文尾處提到過一些注意事項,其中第一條就是:
- Contorller層參數傳遞建議不要使用HashMap,推薦使用數據模型定義
私信裏竟然有很多小夥伴提問說,爲什麼不能這樣做?
我心裏暗自尋思:難道這麼做的小夥伴都沒有被同事捶嗎?(滑稽)
得嘞,今天咱們就掰扯掰扯這件事,這是實際寫代碼時常忽略的一個問題
是不是有人也這麼寫過?
我自己曾經接手過一個前人留下來的老項目,拿到代碼,導入IDEA的那一刻,我哭出了聲。
因爲它的Controller
層代碼都是類似這樣寫的:
@RestController
@RequestMapping("/index")
public class IndexController {
// 獲取App首頁內容
@PostMapping("/getIndexContent")
public ResponseWrapper getIndexContent( @RequestBody Map<String, Object> paramMap ) {
ResponseWrapper res = new ResponseWrapper();
// 下面開始做傳參有效性的校驗
if (!paramMap.containsKey("article_id")) {
res.setCode(500);
res.setMsg("缺少 article_id 信息");
return res;
}
if (!paramMap.containsKey("page")) {
res.setCode(500);
res.setMsg("缺少 page 信息");
return res;
}
if (!paramMap.containsKey("size")) {
res.setCode(500);
res.setMsg("缺少 size 信息");
return res;
}
if (!paramMap.containsKey("version")) {
res.setCode(500);
res.setMsg("缺少 version 信息");
return res;
}
// ...... 此處省略
}
// ...... 此處省略
}
別的咱先不說,居然明目張膽地在Controller
層裏方法裏用Map
傳參?!簡直喪心病狂了。幸虧下面還有一波傳參有效性的驗證,對於傳遞的參數,我好歹也能猜個大概,不然那真是喵了個咪了。
接下來,我們就好好嘮一嘮:爲什麼不要在Controller
層傳參時使用Map
類型!
Map一時爽,維護爽歪歪
正好,這地方有一個咱小夥伴活生生的例子。
記得之前有個小夥伴提問,問過一個這樣的問題,說他接手了一個別人的老項目,問了我一個類似這樣的問題:
看到沒!
用Map
傳參的第一個(也是最大的一個)弊端就是:這會導致後續接手和維護的人懷疑自己的人生,因爲他根本不知道代碼傳的啥參數,想要構造參數去調試接口只能靠腦補、摸瞎、以及猜測了。
試想一下,其實我們代碼裏任何一個地方的傳參都可以使用Map
來傳,如果真的這麼做了,代碼中連任何數據模型類都不需要定義了,果真如此的話,這樣的代碼咱能看懂嗎?
而且這位小夥伴接手的項目居然還用的是LinkedHashMap
參數,可以說很秀了。
除此之外,緊接着還會帶來下面這個問題。
好用的API工具與你無緣了
我之前寫過一篇文章《前後端都分離了,該搞個好用的API管理系統了!》,聊過現在市面上一些比較好用的、能極大提升前後端開發效率的API管理工具,這對於前後端開發來說,簡直是莫大的福音。
我們就以Swagger
這個API工具爲例,如果Controller
傳參使用Map
的話:
// 獲取App首頁內容
@ApiOperation("獲取App首頁內容")
@PostMapping("/getIndexContent")
public ResponseWrapper getIndexContent( @RequestBody Map<String, Object> paramMap ) {
// ...... 此處省略
}
則API
工具無法讀取具體參數項目和參數類型,所以傳參什麼的也看不出來:
換言之,我如果將上面的Map
傳參改爲自定義數據模型類IndexQueryDto
來傳參的話:
// 獲取App首頁內容
@ApiOperation("獲取App首頁內容(改造後)")
@PostMapping("/getIndexContent")
public ResponseWrapper getIndexContent( @RequestBody IndexQueryDto indexQueryDto ) {
// ...... 此處省略
}
@ApiModel(value = "App首頁內容請求參數實體對象")
class IndexQueryDto {
@ApiModelProperty(value = "文章ID號")
@NotNull(message = "缺少 article_id 信息")
private Long article_id;
@ApiModelProperty(value = "頁面數")
@NotNull(message = "缺少 page 信息")
private Integer page;
@ApiModelProperty(value = "每頁條目數")
@NotNull(message = "缺少 size 信息")
private Integer size;
@ApiModelProperty(value = "App版本號")
@NotNull(message = "缺少 version 信息")
private String version;
// ...... 此處省略set/get方法
}
則類似Swagger
這種API
工具就非常方便地能幫助我們管理參數了:
這樣不管是自己調試,還是前、後端對接口都會方便得多。
同理,除了Swagger
這種API
管理工具之外,像在我的前文《沒用過這些IDEA插件?怪不得寫代碼頭疼》中推薦過的一個非常好用的接口管理插件RestfulToolkit
也無法識別出Map
類型所盛放的具體參數:
但是對於數據模型的定義參數,就能非常清晰的給出參數細節,並方便地提供接口測試:
優秀的註解沒法使用了
還是以文章開頭舉例的代碼來說,不管怎麼樣,寫這段代碼的哥們還是負責的,畢竟兢兢業業地用手工連環if()
判斷完成了所有參數的有效性校驗:
但問題是,我們真的需要這種辣眼睛的手工連環if()
判斷來做參數校驗嗎?
同樣在前文《啥?聽說你還在手寫複雜的參數校驗?》中也說過了,我們其實可以通過註解來方便地規避繁雜的參數校驗工作,但前提是不能使用Map
類型傳參,需要使用數據模型的定義,就像這樣:
class IndexQueryDto {
@NotNull(message = "缺少 article_id 信息")
private Long article_id;
@NotNull(message = "缺少 page 信息")
private Integer page;
@NotNull(message = "缺少 size 信息")
private Integer size;
@NotNull(message = "缺少 version 信息")
private String version;
// ...... 此處省略get/set方法
}
一個NotNull
註解即可搞定,它不香嗎?
Map傳參真的一無是處嗎?
有些小夥伴表示用Map
傳參的好處就是可以隨意擴展,後期變動靈活,想往裏面塞幾個參數就塞幾個參數;而且也省去了各種對象定義和命名的煩惱。
如果非要用Map傳參
如果實在不能避免用Map
傳參,也麻請配備完備的測試用例吧,省得讓後來接手維護的人天天看着代碼懷疑人生了。
通過測試用例,後來接手維護的人也能快速搞清代碼間的參數傳遞和調用,不然真的只能靠腦補畫面去調試了。
噓…
好了,說了這麼多,如果你項目的Controller
層代碼還在使用Map
傳參的話,答應我,二話別說,趕快全部偷偷去改掉,快!速度!跑步前進!