随机分享(230910):Typescript 中 Any 关闭类型检查 & Linux 中的内存占用
- random typescript frontend
(没有干货,全是湿货…不过至少写一些总比完全没有写强?)
本周遇到的 Bug
遇到了两个前端相关的 bug,排查了很久,不过最后发现都是人的问题而非代码的问题…
- 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 无效
- 现象:某前端项目,其他人参加开发的时候发现 master 分支无法
- 本地坏了,线上是好的?
- 现象:某前端项目,例行更新依赖库版本,发布前自测发现某功能测试环境不可用;但相同功能在线上一切正常
- 原因:拉线上版本回本地排查,发现线上的版本和实际代码的主干版本不一致(!);查阅发布记录,发现线上版本最近发布已经是一年多之前。和开发了解,原来现在的这个前端项目是原来的两个前端项目合并而来的,部署的时候其实要部署两次,但合并后的部署只部署了另一个模块,而没有部署当前模块,所以现在线上跑的实际上还是合并前的版本。
- 解决:修复代码问题,本地验证通过后发布线上;补充 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[]> ?? '';
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 |
其他
最后顺带一提,本博客目前把 RSS 改为了全文输出模式(参考这篇文章,实际 commit),希望可以帮到在 RSS 阅读器中阅读本博客的读者。