咕咕,我来了,带着写了一年的 typescript,不留一个 enum

库的依赖管理

库的依赖管理问题是我开始写非业务代码后遇到的第一个工程问题,它的复杂性在于保证依赖解析正确的前提下,怎么减少共用包的资源占用(存储、解析)。

python / js 在引包的时候不会带上版本,只用包名称来 resolve,版本控制放在项目中,让项目环境来保证版本正确性。

每种语言都需要一个合适的包管理器。

好处包括

  1. 升级依赖时,如果版本是兼容的,那么代码不需要改,对于项目管理来讲是很简便的
  2. 节省存储、运行时资源,同一个库可以被多个使用者共享(动态链接的方式

坏处有

  1. 环境管理的复杂性带到开发时、发布时、运行时。首先开发者需要工具来管理开发时依赖、固定依赖库的版本、方便地定制库的 resolve 方式(比如 resolve 到一个本地路径,一个网络地址等等);发布的时候需要维护发行版本和底层依赖的版本的对应关系、兼容关系、需要保证 deterministic build;运行时用户需要确保本机环境已经具备运行的依赖条件,引申出去的需求有环境隔离、同一个库的多个版本怎么 resolve 等等。
  2. 依赖约定规则来定义兼容性,比如语义版本 semver

python 社区一般会为每个项目开一个独立的虚拟环境,通过精简依赖,隔离环境来减少环境依赖、隔离影响,但没有解决同一个库的多个版本问题(用的最新版本策略,可能 break 依赖老版本的接口),老的版本管理可能是用 requirements.txt 来做,只记录依赖的版本,比较现代一点的 pipenv 会加上源、版本、hash、多环境的区分,环境隔离的好的话,分发的时候基本就是一个静态链接了。

node 没有做到环境隔离(都存在 node_modules,一层层往上找,直到根目录);可以保证每个库有自己的 node_modules 来确保依赖版本正确,依赖管理很灵活,但也带来了碎片文件、占用大量存储空间、误配置时查找问题比较麻烦;支持可以用 peerDependencies 来依赖环境提供,deterministic 则是用 hash + 版本号来保证,yarn 中另加入了源的 check、monorepo 中共用库提升等等优化;支持 link 来开发时修改库的 resolve 方式。

deno / go 这种设计为网络领域的语言,直接用 url 来去中心化管理依赖,要达成 deterministic build 会有天生的困难,只能约定好所有发版都用新的 url 并且能提供下载,对传输层有特殊的安全要求,版本管理也会有麻烦(url 是否需要带上版本信息?)。

rust 有着比较现代的工具链 cargo。支持环境隔离;多环境;deterministic build;可以保留同一个库的多个版本,编译时会把他们重命名成不同的库,不会有冲突;方便地指定 resolve 方式(local path 或者 url);灵活的打包、分发方式(静态链接 / 动态链接,llvm)。很好地解决了上一章节中的开发时、发布时、运行时问题。

用库还是用服务

主要取决于部署复杂度

  • 库是内部依赖,服务可内可外
  • 库是同构、服务没有要求
    • 异构的部署难度高,依赖基础设施(网络情况、服务发现等等)
    • 同构的问题
      • 多端支持(比如 js 的浏览器和 node
  • 服务引入更多环节、有网络开销、效率待评估
  • 库的效率高、故障处理简单

参数管理

  • 哪些是允许外部修改的参数?
  • 哪些参数是全局的 / 库级别的?
  • 哪些参数是实例级别的 context,允许外部传入?

多端支持

比如 js 的浏览器和 node 运行环境差异很大,需要考虑清楚需要支持的环境,在代码中给对应分支加入支持。

PS
typescript 的 Structural type system 对 enum 真的太不友好了..