恕我标题党,原来的标题是打算叫:形式正确 ≠ 内容正确, 但又太抽象了,所以就借个噱头吧。
这篇算是有感而发,就记录在杂谈里,文章格式也用不严谨的方式随笔而记 ~
起因
在掘金灌水时,突然发现的沸点:
评论上都说是总监在 PUA 员工,所以就挺有兴趣的瞅了瞅。
问题还是很有趣,大眼一看,跟下面评论的一样,感觉不出有什么问题。
但细眼一看,这代码最大的问题,就是听君一席话,如听一席话 ...
大家想一下,我们做代码封装的目的是什么?抽象接口、隐藏细节。
那细节确实隐藏了(或者说从来没有过),就剩黑盒了 ...
打个比方,评论上写到了工厂模式,那就拿工厂做个对比:
如果一个肥皂工厂说你需要给我原料,然后生产肥皂出来,但原料是个小秘密。那你要给什么原料呢?
何况,这个工厂也没说他能出来一个肥皂,出参也是一个盲盒 ...
再比如,大家开发看后端文档,如果后端文档不写出入参,是不是要骂娘?
那其他人用这个封装方法,是不是也会骂娘?
所以说,但凡需要多人开发的项目/团队,为了协作或者可维护,都不应该会允许这样的设计出现(单人开发的项目那就随意吧,管他洪水滔天,要怎么维护自己说的算[狗头])
轻与重
继续说一说,原因是还是有很多人同学在评论里对此有疑问,特别是有的同学觉得是在过度设计:
你们没有接口文档吗?咱做过实际业务都知道 ,有的接口有好几十个的参数还存在嵌套对象和数组的情况 万一接口被后端改了你再又要来这里改?不麻烦吗?
恰恰相反,你只有这一层设计好了,才不用去改变页面逻辑,这在 JAVA 中其实属于 BO 层架构,在大前端(iOS、Android、前端)里也一样,其实是一层 RequestModel 的实现。封装请求相关的细节,提供明确的 API 对外使用。
还有同学会觉得这里参数写一遍,业务调用入参的时候再写一遍,太啰嗦了
如果有良好的业务管理者实现,那在前端来讲完全也可以做轻,用一个 map 实现即可。
例如
/// API 定义
export const apiDefine = {
// XXX
saveFormSystemConfigParam: {
url: 'xxx',
method: 'POST',
} as any,
// XXX
getListByItemCategory: {
url: 'xxx',
method: 'GET',
} as any
}
/// 业务层代码
export function xxx({ ... }) {
...
// 入参处理
let response = request(
...apiDefine.saveFormSystemConfigParam,
params: {
title: ...
},
)
// 回参处理
...
}
这样是符合前端思维的轻,合理的减少一层,但笔者还是推荐做重,当遇到多处页面甚至多个项目使用相同 API 调用时,那就有很好的可复用性 ~
样例
可能很多同学也还不是很理解到底有什么用,也有同学在评论里想知道到底要怎么写 ~
那我就拿这沸点里的方法举几个例子吧(笔者用 TS 习惯了,所以后面代码是 TS,但 JS 也是一样的)
先放一下问题的代码:
以第一个 API 为例,首先会先这么写:
export function getListByItemCategory(
id: string,
title?: string,
sort: string = 'asc',
) {
return request(
url: 'xxx',
method: 'GET',
params: {
title: title,
categoryId: id,
sort: sort
},
)
}
这样使用者一眼就看得出需要必传什么哪些参数,哪些参数有默认值。
有一点不知道看官有没有注意到 categoryId: id, 这是故意写成这样来进行说明的。参数解构,最直观的好处:当服务端把
id->categoryId,就不用大海捞针的从 View 层找赋值了,特别是如果存在的页面还有多个的时候,一处修改,多处实现,这才是封装的好处 ~
以上的实现只是最基础的方式,有人问了,如果参数太多,参数嵌套要怎么解?
不优雅(去掉 type 就可以是 JS 代码)可以如下实现,虽然还是传的 data 但至少可以看出各项参数的定义
export function saveFormSystemConfigParam(
data: {
id: string,
title?: string,
otherParams?: [{
configId: string
}]
}
) {
return request(
url: 'xxx',
method: 'POST',
data: object,
)
}
当然,更优雅的做法就是要定义出入参 Model,而对于 TS 来说,Model 可以用 Interface 来定义即可。
export interface ConfigRequestParam {
/**
* id
*/
id: string
/**
* 标题
*/
title: string
/**
* ...
*/
otherParams: Array<ConfigRequestParam>
}
export function saveFormSystemConfigParam(data: ConfigRequestParam) {
return request(
url: 'xxx',
method: 'POST',
data: data,
)
}
那这么写优雅在哪里呢?
比如你还是直接调用这个方法
saveFormSystemConfigParam({
id,
title: '',
otherParams: []
});
看起来还是传一个字典,但注释、类型都变得很直观了
甚至针对 API 的单元测试也变得更可行了。
更完善一点,出参也加上类型,让出入参都清晰。
export async function getListByItemCategory(
id: string,
title?: string,
sort: string = 'asc',
): Promise<ConfigRequestParam> {
let response = await request(
url: 'xxx',
method: 'GET',
params: {
title: title,
categoryId: id,
sort: sort
},
)
return response.data
}
远瞻
还有同学说,这太啰嗦了,前端人不屑这么写,那有更进一步的解决方案吗?
确实有!
可以看出这些代码都是很有规律的,所以完全可以通过后端接口/后端文档来生成这一部分的模版代码 ~
再进一步,前端模型和后端模型是不是也可以用一致的?那我们是不是可以用 Rust 梭哈前后端,直接卷死在座的所有开发[狗头]。
(当然,Rust 在前端生成的 Wasm 确实体积太大了,不符合 Web 的性能考量。但时代一直在发展,在网速越来越快的当下,可以确定这确实是一个上升趋势。)
最后说下感想,代码是写给人看的,代码的好坏不在于你用了什么框架、什么设计模式,而在于随便抽出一段代码都可以让别人知道你这是在做什么 ~
如果对你开发学习有丝丝作用,请点个赞,谢谢。[开心]