随机分享(230910):Typescript 中 Any 关闭类型检查 & Linux 中的内存占用

- random typescript frontend

(没有干货,全是湿货…不过至少写一些总比完全没有写强?)

本周遇到的 Bug

遇到了两个前端相关的 bug,排查了很久,不过最后发现都是人的问题而非代码的问题…

  1. CI 坏了还能跑?
    • 现象:某前端项目,其他人参加开发的时候发现 master 分支无法 npm install,但之前这个 repo 一直在正常更新版本,看 CI 日志也一切正常
    • 原因:发现问题是上游某依赖方对已发布的包重新发布,导致文件 hash 变化,npm install 时实际文件 hash 和 lock 中 hash 不一致,所以失败;CI 之所以能跑是因为流水线里加了一层 cache,只要 packages-lock.json 不变就会复用之前的 cache,而恰巧上游重发包之后这个文件一直没变过,所以每次跑 CI 都是拉的已有的 cache,没有实际在流水线里执行 npm install,未能即使暴露故障
    • 解决:重建 packages-lock.json,让 CI 中的 cache 无效
  2. 本地坏了,线上是好的?
    • 现象:某前端项目,例行更新依赖库版本,发布前自测发现某功能测试环境不可用;但相同功能在线上一切正常
    • 原因:拉线上版本回本地排查,发现线上的版本和实际代码的主干版本不一致(!);查阅发布记录,发现线上版本最近发布已经是一年多之前。和开发了解,原来现在的这个前端项目是原来的两个前端项目合并而来的,部署的时候其实要部署两次,但合并后的部署只部署了另一个模块,而没有部署当前模块,所以现在线上跑的实际上还是合并前的版本。
    • 解决:修复代码问题,本地验证通过后发布线上;补充 readme 说明发布时需要两个模块都发布

Typescript 中 Any 关闭了类型检查

某后端项目,因为历史原因代码中有较多 any。最近发现代码中某处接受用户输入的位置有问题,默认值的类型不正确但依然通过了编译。示例如下:

// 不要这样用!
const names: string[] = (req.body as any).names ?? '';
//    ^string[]                           ^any     ^string

注意到这里 nullish coalescing (??)的默认值是个 string 而非 string[]。这段代码感觉上上不应该通过编译,但是因为 (req.body as any)any 类型禁用了类型检查,因此编译时不会再检查缺省值,实际上可以编译通过。而如果不巧后续有函数需要使用 string[] 才有的方法,而 req.body 中的 names 的确为 undefined,就会导致问题。

要解决这个问题,需要提供了更强的类型提示,让 Typescript 的检查可以正常执行。

// 直接标记可能的类型:string[] 或 undefined
const names: string[] = (req.body as any).names as string[] | undefined ?? '';

// 通过一个自定义 type 来实现
type Nullable<T> = T | undefined | null;
const names: string[] = (req.body as any).names as Nullable<string[]> ?? '';

Typescript Playground

Linux 中的内存占用

Linux 进程内存不同计算方法的区分:VSS, RSS, PSS, USS


  ┌────────┐
  │        │
  │        │        ┌────────┐
  │ Unused │        │        │
  │  (A)   │        │ ...    │◄───┐
  │        │        │        │    │Other
  │        │        ├────────┤    │Program's
  │        │        │        │    │Share
  ├────────┤        │ ...    │◄───┘ (D)
  │        │        │        │
  │ Used   │        ├────────┤
  │  (B)   │        │        │
  │        │        │My Share│
  │        │        │   (C)  │
  └────────┘        └────────┘

   Exclusive         Shared

可以把进程的内存占用视作上图。首先程序有自己独占的虚拟内存空间(Exclusive),其中可以分为已经使用了的(B)和属于自己但还未使用的(A)。其次进程还会使用一些共享内存(Shared),例如 so 动态运行库和 mmap 映射。考虑到这些共享内存多个进程都会用到,将其完全计算在某个特定进程名下听起来就不太合理,因此这里可以考虑类似于现实中的"公摊面积",根据实际使用的进程数把这部分内存占用平摊成 N 份,当前进程只计算其中一份(C),剩余的计算在其他进程下(D)。

由此我们可以得到四种不同的计算方法,见下表

指标 含义 组成 说明 图例
VSS 虚拟内存集合(Virtual Set Size) 所有进程地址空间中的所有内存 进程可以访问的虚拟内存空间大小 A+B+C+D
RSS 常驻内存集合(Resident Set Size) 进程当前实际使用的物理内存 实际分配的内存,不需要缺页中断就可以使用 B+C+D
PSS 共享内存集合(Proportional Set Size) 进程当前实际使用的物理内存,按比例分配共享内存 按比例分配共享内存,适用于多个进程共享同一块内存的情况 B+C
USS 独立内存集合(Unique Set Size) 进程独占使用的物理内存 只包含进程独占使用的物理内存,不包括共享库和映射的文件 B

Via:B站视频:用什么指标来衡量我的程序占用了多少内存

其他

最后顺带一提,本博客目前把 RSS 改为了全文输出模式(参考这篇文章实际 commit),希望可以帮到在 RSS 阅读器中阅读本博客的读者。