变量命名怎么转?下划线、驼峰与自动生成
同一个字段在数据库里叫 user_name、在 JVM 里叫 userName、在常量里叫 USER_NAME —— 三种风格互相转换,最容易出错的地方是「哪里算一个词的边界」。
- 命名转换按下划线、中划线、空格和驼峰边界拆词,但字母与数字之间不拆,所以 sha256Sum 会保持完整而不被切成 sha_256_sum
- 全大写的缩写会被当成一个词,转换结果是 httpUrl 而不是 hTTPUrl,想保留原样式要手工改
- 生成实体类时字段名要转成目标语言的习惯,但序列化注解必须保留原始键名,否则反序列化会失败
- 数据库的可空字段在 TypeScript 里应映射成类型与 null 的联合类型,而不是可选属性,两者含义完全不同
- 未知类型的兜底要按目标语言给,统一返回字符串会在 TypeScript 里产生包装对象而不是原始类型
#命名转换难在哪
命名风格转换的难点不在大小写,而在「怎么把一个名字拆成词」。 拆错一次,整批字段名就都是错的。
三种常见风格之间的映射关系是固定的:
| 风格 | 示例 | 常见场景 |
|---|---|---|
| 下划线 | user_name | 数据库列名、环境变量 |
| 小驼峰 | userName | Java、JavaScript 变量 |
| 大驼峰 | UserName | 类名、接口名、React 组件 |
| 大写下划线 | USER_NAME | 常量、枚举值 |
| 中划线 | user-name | CSS 类名、URL slug |
难点全在边界判定:userName 里 r 和 N 之间是边界,user_name 里下划线是边界,那 sha256Sum 呢?
#拆词边界:数字不是边界
字母与数字之间不算词边界 —— 这是保证 sha256、base64、utf8 这类词不被拆坏的关键。 把数字当边界会得到 sha_256_sum,那是错的。
用 变量命名转换 时的拆词规则:
- 算边界:下划线、中划线、空格、驼峰的大小写切换处
- 不算边界:字母与数字之间
所以 sha256Sum → sha256_sum,v2 → v2(大写下划线风格是 V2,不是 V_2)。这两个例子是判断工具好坏最快的测试。
#全大写缩写怎么处理
连续的相同大小写字母会被当成一个词,所以 HTTPURL 转成小驼峰是 httpurl,而 HTTPUrl 转成 httpUrl。 缩写该怎么切本身没有唯一答案,不同工具的选择不同。
实践中更稳的做法是:在源头就按目标风格写。httpUrl 比 HTTPURL 好拆,userId 比 userID 好拆。命名规范里写清「缩写首字母大写、其余小写」,转换就不会出意外。
转换只处理 ASCII 字母。输入是中文等非 ASCII 内容时,工具会明确提示没有可转换的词,而不是安静地返回一串空字符串 —— 遇到空结果先确认输入是不是英文命名。
#生成代码:字段名与序列化注解
生成实体类时,字段名要转成目标语言的习惯,但序列化注解必须保留原始键名 —— 否则接口回来的 JSON 反序列化不上。 这是自动生成代码最容易埋的坑。
用 JSON 转 TS 接口 时,键名处理有两层规则:
- 不合法的标识符会自动加引号:
{"user-name": 1}生成"user-name": number;而不是user-name: number;(后者是语法错误) - 根接口名会做标识符清洗:
my-type变成MyType
用 SQL 转实体类 时,Go 的输出会带上 json:"原键名" 标签 —— 字段名转成驼峰,标签保留数据库列名。两者必须同时存在,改名后才不会破坏反序列化。
生成代码只是起点,不是终点。类型映射表覆盖的是常见类型(整数、小数、字符串、时间、布尔),遇到数据库自定义类型、枚举、JSON 列、数组,生成结果多半是兜底类型,需要手工改。
#可空字段:可选属性还是联合 null
「数据库可空」和「前端可以不传」是两个不同的概念,映射到 TypeScript 也不该一样。 混用会让类型检查失去意义。
正确的对应关系:
- 数据库允许 NULL → 字段一定存在,值可能是 null →
field: string | null - 接口允许不传这个字段 → 字段可能不存在 →
field?: string
SQL 转实体类 用的是前一种(联合 null),因为表结构里字段一定存在,只是值可以为空。用可选属性表达可空字段,会让「没传」和「传了 null」变得无法区分。
判断标准很实用:这个字段在数据里是否一定出现? 一定出现、值可能是空 → 联合 null;可能整个不存在 → 可选属性。这条同样适用于手写接口定义。
#类型兜底按语言给
未知类型的兜底应该符合目标语言的习惯,统一返回某个类型会在某些语言里出错。 比如在 TypeScript 里统一返回 String(大写 S)得到的是包装对象,不是原始字符串。
各语言的合理兜底:
- TypeScript:
unknown(强制调用方收窄)或any - Java:
Object或具体包装类 - Go:
interface{}或any - Python:无类型注解,或
Any
另外要记住生成器不做语义推断:JSON 里的 null 会映射成 null 类型、数组元素类型取并集([1,"a"] → (number | string)[])、空数组给 unknown[]。这些都对,但都需要你确认是否符合接口实际契约。
#顺手统一代码风格
命名统一了,格式也该统一 —— 两者都属于「团队风格」,混着来会让 review 变成吵架。 生成或修改完代码,顺手过一遍格式化。
用 Python 格式化 时要知道它做什么、也明确不做什么:
- 做:按代码块结构统一 4 空格缩进、运算符两侧补空格、未闭合括号续行缩进一级
- 不做:不展开单行复合语句(
if x: y=1保持原样) - 不做:不清洗括号内侧空格(
f( a , b )原样保留)
压缩方向不动缩进 —— 因为 Python 里缩进就是语法,动一次就可能改变语义。
格式化的前提是代码本身语法正确。少写冒号、括号不配对、缩进层级混乱的代码,格式化只会把错误排得更整齐,不会修好它们。先让代码能跑,再谈风格。
#什么时候该用哪个工具
| 你想做的事 | 用哪个 | 关键约束 |
|---|---|---|
| 下划线转驼峰(或反向) | 变量命名转换 | 数字不算边界,含数字的词不会被拆 |
| 把 JSON 变成 TS 接口 | JSON 转 TS 接口 | 非法键名自动加引号,同名不同结构会拆开 |
| 把建表语句变成实体类 | SQL 转实体类 | 支持 decimal、enum、逗号注释、复合主键 |
| 生成 Java 或 Go 的实体 | JSON 生成实体类 | Go 输出带 json 标签保留原键名 |
| 统一 Python 缩进与空格 | Python 格式化 | 不展开单行语句,不动缩进做压缩 |
流程建议:先定命名规范(写进团队文档)→ 转换工具只用来迁移存量 → 新代码直接按规范写。 靠工具反复转换会积累不一致,规范才是根治办法。
#常见问题
#为什么 sha256Sum 转出来不是 sha_256_sum?
因为字母与数字之间不算词边界。这是有意的:sha256、base64、utf8、sha1 这些词里的数字是名称的一部分,拆开就错了。如果你确实想按数字拆,只能手工处理这几个特例。
#生成的 TS 接口里字段名带引号,能去掉吗?
不能,也不该去。带引号说明这个键名不是合法的 JavaScript 标识符(含中划线、空格、以数字开头),去掉引号就是语法错误。要改的话应该改数据源的键名,而不是改类型定义。
#数据库的可空字段应该用 ? 还是 | null?
用联合 null。? 表示字段可以不存在,而数据库列一定是存在的,只是值可以为空。用 ? 会让「接口没返回这个字段」和「返回了 null」无法区分,前者的正确处理是报错,后者是正常分支。
#同名的嵌套对象为什么生成了两个接口?
因为它们的结构不同。生成器按「名字 + 结构」去重,data.address 与 meta.address 字段不一致时会拆成 Address 和 Address2,各自引用正确的那个。如果强行合并成一个,引用它的属性类型就是错的。
#Python 格式化后缩进变了,会影响运行吗?
会影响,而且可能是致命的 —— Python 用缩进表达块结构。工具只按「def、class、if、for 等关键字 + 括号深度」重排缩进,如果原代码的块结构本身就靠缩进表达(比如多行字符串里的内容),重排可能改变语义。格式化前先提交一次,方便对比。