V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mouyase
V2EX  ›  程序员

前端/客户端有什么办法来处理后端/服务端返回的不规范数据吗

  •  1
     
  •   mouyase ·
    mouyase · 335 天前 via Android · 11968 次点击
    这是一个创建于 335 天前的主题,其中的信息可能已经有所发展或是发生改变。
    本人是前端,技术栈 ts/java/oc ,web 和 app 都在做。



    现在经常遇到后端返回的值让人难以琢磨。



    比如同样都是表示是/否,或者打开/关闭两种状态,有点时候返回值是 0/1 ,有时候是 1/0 ,有的时候是 1/2 ,有的时候是 true/false ,有的时候是"on"/"off",还有的时候干脆就是为否就没有这个字段了。



    或者是同样都是用户 id ,有时候字段叫做 user_id ,有时候叫 UserID ,有时候就只叫 id 。



    然后在业务逻辑中经常会出现从不同接口拿到的同一个值,但是是在同一处 UI 显示。就导致 ts 类型定义得定义好几种不同的类型用来兼容。



    各位大佬们有什么好的办法来处理这种情况吗?
    123 条回复    2024-01-02 09:50:05 +08:00
    1  2  
    iseki
        101
    iseki  
       334 天前 via Android
    不要尝试兼容,否则后患无穷,严格校验数据,出错就报 bug 让后端改。一旦提到兼容,你很难定义清楚兼容的边界,到时候指不定出什么幺蛾子。
    @bianhui 反爬应该用工程手段解决,而不是让人直接在代码里写 abcd 。至于你说的第二个问题,接口和内部实现无关,不应因为内部实现影响接口,这点对前后端都一样。
    iseki
        102
    iseki  
       334 天前 via Android
    当然命名风格还是应该尽量统一一下,不要同一个接口都混合不同的命名风格。
    iseki
        103
    iseki  
       334 天前 via Android
    此外后端如果不是 bff ,你不能要求它完全按照前端页面设计,这 API Web 前端用完了客户端可能还得用呢,不是说你展示在一个地方后端接口就一定是一个字段,前端该做的事不能偷懒。
    petergui
        104
    petergui  
       334 天前
    null , nil ,undefined , [key1:val1],"" , "bool".
    ToB : graphQL,BFF 中间层,把使用到数据掌握到自己手上,也方便。
    ToC: openapi 过一遍最多了。 看人。一般是在 response 上做 callback 适配。 业务紧,时间紧,人员紧。要求不了太多。业务的上游,实现迭代的下游。 以和为贵。人熟好办事。
    leokun
        105
    leokun  
       334 天前
    前端能兜住杂乱的数据也是一种本事
    ruxuan1306
        106
    ruxuan1306  
       334 天前   ❤️ 2
    我看之前楼层都在情绪输出,我来说说我的思考吧。



    两个前提:
    1. 老板只在意业务功能是否实现,没人在意前后端之间的接口质量;
    2. 督促后端交付高质量接口的沟通成本远大于前端写几行 map 的开发成本;

    故结论,前端兼容。



    问题是,对于前端的某一字段,不同的后端提交、响应不同,甚至值的类型也不同。
    导致在写 TypeScript 时,就要定义大量 TS 类型,还很难起名。
    但仔细想想我们真的需要这些“一次性”类型吗?



    看个例子,假设我们有两个接口:

    ```JavaScript
    // id 查 user
    const res = fetch.get('/user', { id: 1 })
    console.log(res.body) // { id: 1, name: 'alice' }

    // id 改 user 名
    const res = fetch.post('/user', { user_id: '1', new_username: '' })
    console.log(res.body) // { code: 1 }
    ```

    很难受吧,查和改的字段居然不一致。
    那我用户 ID 究竟叫 id 还是 user_id ?类型究竟是 number 还是 string ?

    很简单,按前端方便来:

    ```
    type User = {
    userId: string,
    username: string
    }

    <div>
    <div>用户 ID:{{userId}}</div>
    <div>用户名:<input type="text" value="{{username}}" /></div>
    </div>
    ```



    ???



    因为 JavaScript 里常用驼峰命名,userId 通常也只是展示,不会涉及什么数值运算,绑定 input 之类也比较方便…

    噢,哈哈,你肯定早就知道这些,我知道你是在疑惑,接口怎么办?

    别急。



    我们知道,TypeScript 类型系统很好很强,除非 any 泛滥。
    要想抑制 any 渗入 TS ,就要观察 any 从哪产生:

    什么函数返回 any ?

    - JSON.parse()
    - fetch()
    - ...

    喔,外部数据流入 TS 时,就是 any ,有才有德的我知道,此时我应该立刻手动为标记类型:

    ```JavaScript
    // 接口响应类型
    type GetUser = { id: number, name: string }

    // 前端易用类型
    type User = { userId: string, username: string }

    function fetchUser(userId: string) {
    // 外部数据
    const res = fetch.get('/user', { id: Number(userId) })
    console.log(res.body) // { id: 1, name: 'alice' }

    // 标记类型 any -> GetUser
    const data: GetUser = res.body

    // 转为前端易用的格式
    const user: User = {
    userId: String(data.id ?? ''),
    username: data.name
    }
    return user
    }
    ```

    很好很完美,我们成功封装了一个接口请求函数,它能通过 string 的 userId ,查到 User 数据,以前端想要的格式。

    但多看一眼,我们真的有必要定义 GetUser 吗?

    简化下可以吗:

    ```JavaScript
    type User = { userId: string, username: string }

    function fetchUser(userId: string): User {
    const res = fetch.get('/user', { id: Number(userId) })
    return {
    userId: String(res.body.id ?? ''),
    username: res.body.name
    }
    }
    ```

    噢,虽然 res.body 是 any ,虽然按照 TypeScript 最佳实践我应该马上声明它的类型,但事实上,完全可以将这个明晰 any 的过程延迟到数据转换一并进行。

    这样一切就变得简单多了,只要我守好数据流入和流出的关口,那无论多离谱的后端,我都不需撕逼,分钟对接:

    ```JavaScript
    function updateUser(userId: string, username: string): boolean {
    const res = fetch.post('/user', {
    user_id: Number(userId),
    new_username: username
    })
    return res.code === 1
    }
    ```


    于是作为前端:
    在开发阶段,我以最舒适的姿势定义 State 的数据结构、绑定 UI 组件。
    在联调阶段,我直接对着浏览器抓包的响应体,实现关口函数内的转换。
    完全摆脱各接口字段定义不一致、实际响应和接口文档不符、string 和 number 类型没对上之类来回沟通筋疲力尽的鸡零狗碎。



    再温故一遍:
    1. 老板只在意业务功能是否实现,没人在意前后端之间的接口质量;
    2. 督促后端交付高质量接口的沟通成本远大于前端写几行 map 的开发成本;
    ruxuan1306
        107
    ruxuan1306  
       334 天前
    粘出来格式不好看,原文来自: https://zexi.notion.site/b9f941b097054bb1a28de1de61535adf
    phpfpm
        108
    phpfpm  
       334 天前
    保留现场,闪退,避免带来更多问题
    Quarter
        109
    Quarter  
       334 天前 via iPhone
    @bianhui 你说出这话真的已经工作过了吗?你们团队都没有项目负责人和技术负责人的么,都没有人把控项目把控技术的人,这不叫道德绑架,因为你个人能力问题导致增加别人工作量我当然可以拒绝或者向上反馈提出意见,如果你个人确实因为能力不满足工作需要的话团队会做出合理的决策,我不知道你们的团队到底是多小多随意,但是我建议你也多出去看看,不少正规的公司正经的团队还是对于技术有要求的,也有明确的管理规范,想想到底是谁在抬杠,而且从你这种觉得给别人增加工作量也无所谓的所谓做人准则,我个人建议您这边也抬高一下自己的要求,别人一般都是考虑怎么不给别人添麻烦,所以也别侃侃而谈教别人做人

    最后,我根本没必要道德绑架谁,我只是在讲述自己观点,你可以有自己的想法和思路,至于最终能力如何、工作态度如何,你现在以及以后的团队会给出一个符合情况的评价的,多说无益
    fy1006
        110
    fy1006  
       334 天前 via iPhone
    前端拿到原型后自行抽象对象结构数据模型的人少,如果这样做了就会有一层转换层。
    Quarter
        111
    Quarter  
       334 天前 via Android
    @ruxuan1306 并没有情绪输出,合理反驳,大家有方案的也可以说,老板也确实不在意具体代码质量,只是说大家工资差不多甚至前端工资更低,并不能接受后端只是从懒的角度莫名其妙给别人增加工作量,如果确实有合理解释也行,除此以外,你可能会低估这些工作量,或许几行 map 并不能搞定工作量,而且越觉得我再加几行、我再加几十行、我再加几百行就能解决,那只会越加越多,反正你都会接,最后搞不定反正也都是前端问题,反馈团队 leader ,如果团队也不作为,那就跑路吧,除非老板给了更多的工资,不然研发的地位已经够低了,没必要在研发里前端还自立一个地位最低
    twofox
        112
    twofox  
       334 天前
    @ruxuan1306 我觉得后端接口写成这么稀烂,会丢人

    我前后端都写
    kneo
        113
    kneo  
       334 天前 via Android
    有些人,自己做不到写优秀的代码,还有脸喷别人“没受过社会毒打”“太理想化了”“那么规范干嘛”“不能要求别人”。一副劣币嘴脸。幸好只是网上相见。这辈子不会和你们共事。这种人要敢来我公司就一个字:滚。
    vczyh
        114
    vczyh  
       334 天前 via iPhone
    要不就开发前讨论定好,要不就适配,不能太理想化了
    vczyh
        115
    vczyh  
       334 天前 via iPhone
    还有就是老项目就是这样,很难保持一种风格
    LLaMA2
        116
    LLaMA2  
       334 天前
    @ruxuan1306 点赞是我的态度。支持你


    “人生,本来就没有什么意义” ---- 五条人
    rabbbit
        117
    rabbbit  
       334 天前
    专门搞一个 api 文件夹放各种接口请求,然后:
    这样无论他的 api 怎么变动,都有一个中间层来轻松适配。
    大概是这样吧,好久没写 ts 了

    interface foo {
    id: string;
    }

    const getFoo = async (): Promise<foo> => {
    const resp = await fetch('/api/foo').then((response) => response.json());
    resp.id = resp.user_id;
    return resp;
    }

    export { getFoo }
    rabbbit
        118
    rabbbit  
       334 天前
    中间的那个 resp 就让它 any 吧,没必要管
    0xLittleFi
        119
    0xLittleFi  
       334 天前
    前端内部一套定义,从后端取值的时候防腐一层,后端很多时候取其他领域的数据也是这么做的。自身稳定,不稳定的交给防腐层做。
    iseki
        120
    iseki  
       333 天前
    @ruxuan1306 你这样搞百害无一利啊,今天兼容了,明天又变样了你兼容不兼容,某日几种模式冲突了你怎么办,屎山多了你糊出 bug 了怎么办。这种接口本来就一挺简单的事,从源头把问题都解决了对大家都好
    Perolong
        121
    Perolong  
       333 天前
    这其实就不是方法对不对的问题,一味的妥协做兼容迟早会出事,不管是前还是后
    mouyase
        122
    mouyase  
    OP
       331 天前
    @rabbbit 其实我们也想做类似的兼容,但是有时候还会出现不同的有的数据 userid 和 id 是相同的但是有的是不同的类似情况,直接盖过去有时还会有问题。
    mouyase
        123
    mouyase  
    OP
       331 天前
    @ruxuan1306 好文,值得学习和思考
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5515 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:06 · PVG 17:06 · LAX 01:06 · JFK 04:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.