浅谈:如何写出不被总监开除的代码(一篇沸点有感)

563 阅读5分钟

恕我标题党,原来的标题是打算叫:形式正确 ≠ 内容正确, 但又太抽象了,所以就借个噱头吧。

这篇算是有感而发,就记录在杂谈里,文章格式也用不严谨的方式随笔而记 ~

起因

在掘金灌水时,突然发现的沸点:

juejin.cn/pin/7153825…

image.png

评论上都说是总监在 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 也是一样的)

先放一下问题的代码:

image.png

以第一个 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: []
});

image.png

看起来还是传一个字典,但注释、类型都变得很直观了

甚至针对 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 的性能考量。但时代一直在发展,在网速越来越快的当下,可以确定这确实是一个上升趋势。)

最后说下感想,代码是写给人看的,代码的好坏不在于你用了什么框架、什么设计模式,而在于随便抽出一段代码都可以让别人知道你这是在做什么 ~

如果对你开发学习有丝丝作用,请点个赞,谢谢。[开心]