# 随机分享（230910）：Typescript 中 Any 关闭类型检查 & Linux 中的内存占用

LLMS 索引： [llms.txt](/llms.txt)

---

（没有干货，全是湿货...不过至少写一些总比完全没有写强？）

## 本周遇到的 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。最近发现代码中某处接受用户输入的位置有问题，默认值的类型不正确但依然通过了编译。示例如下：

```typescript
// 不要这样用！
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 的检查可以正常执行。

```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](https://www.typescriptlang.org/play?#code/MYewdgzgLgBATgUwI4wLwwN4CgYwEYgAmAngFyYC+WFA3FlgPQMxggygC2ADgJYA2CGAjhwQcRs1CRYYAIYcEEctDg8wAcwDaAXTQwAFIiQA6AiRiyIFsMQCUxuQqsB+ZzADk7uk3bgIIAWM+EHV9R0VjACsQNX13ABp3W1s6CQtCQhgOMUFCBAAzNQQoHnAsKWgWeUVlKFUNHT1DZFMiYgsrWRt7cM6rFTUtXQAfGABXMDzCsARM1w8veh8xGEixyuBEWShBLpAoAAthGChiLgQsU-OYADkxvj5ZPAEAHgAVAD49N5hRiamiplRmB7nw6BUZNUIAAmWr1IZNIytcyWax2BxQjq3UFPV4DBraL7zTx0IA)



## 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站视频：用什么指标来衡量我的程序占用了多少内存](https://www.bilibili.com/video/BV11z4y1L7Xx)

## 其他
最后顺带一提，本博客目前把 RSS 改为了全文输出模式（参考[这篇文章](https://www.godo.dev/tutorials/hugo-full-text-rss/)，[实际 commit](https://github.com/jerrylususu/jerrylususu.github.io/commit/89e1c23d0b350baac0378ad735127cf17fb66c4b)），希望可以帮到在 RSS 阅读器中阅读本博客的读者。
