C++中匈牙利命名法是否该淘汰 匈牙利命名法为何被淘汰


C++中匈牙利命名法是否该淘汰 匈牙利命名法为何被淘汰

文章插图
背景之前在做一个老项目重构的时候 。由于数据库不能改动 。所以还是继续沿用之前的老数据库 。保护安全险公司嘛 。哪怕加了个互联网保护安全险的 title 。业务和系统还是偏经典的 。数据模型不会轻易地更新;所以这个系统年代比较久远 。而且它的数据库表命名方式选用的还是匈牙利命名法 。导致在重构时因为这个命名方式让人作呕了我好久……
匈牙利命名法匈牙利命名法(Hungarian notation) 。由1972年至1981年在施乐帕洛阿尔托研究中心工作的-程序员 查尔斯·西蒙尼发明 。这位前辈后面成了微软的总设计师 。
这个命名法的特点是 。在命名前面增加类型的前缀 。就像这样:c_name – 姓名 。字符串(Char)类型n_age – 年龄 。数字(Number)类型t_birthday – 生日 。日期/期间(Time)类型可不要小看这个命名法 。当年可是很流行的 。而且直到今天还是有一些系统仍然在沿用这个命名标准 。比如微软的 Win32 API:
C++中匈牙利命名法是否该淘汰 匈牙利命名法为何被淘汰

文章插图
况且这个命名法 。也不是没有一处擅长的技术 。还是有一定的优点的 。至少我一眼就可以看出这个字段的类型 。
只不过在今天看起来有点怪怪的 。不太符合当今的设计风格 。如果放到人名上就更有意思了:男赵四女谢大脚男刘能当匈牙利命名法接触 java咱们这次的重构目标 。是要保持老系统表不动的情况下 。完全重写 。可是新系统是 Java 语言来研究 。Java 可是驼峰命名标准的 。当这个匈牙利命名法的表迁移到驼峰命名法的 Java 语言会怎么样?
比如 c_name 这个命名 。到 Java 里之后 。是改为 CName 呢 。还是 cName 呢?好像怎么都有点奇怪 。不过最后咱们还是选择了 CName。将类型的前缀完全大写 。至少看着稍微正常一点 。没那么反人类
c_name -> CNamen_age -> NAget_birthday -> TBirthday当匈牙利命名法接触 JAVA咱们这次的重构目标 。是要保持老系统表不动的情况下 。完全重写 。可是新系统是 Java 语言来研究 。Java 可是驼峰命名标准的 。当这个匈牙利命名法的表迁移到驼峰命名法的 Java 语言会怎么样?
比如 c_name 这个命名 。到 Java 里之后 。是改为 CName 呢 。还是 cName 呢?好像怎么都有点奇怪 。不过最后咱们还是选择了 CName。将类型的前缀完全大写 。至少看着稍微正常一点 。没那么反人类
c_name -> CNamen_age -> NAget_birthday -> TBirthday序列化的问题刚确定了命名方式 。还没开心多久 。我就碰到了一个非常难受的问题……
由于是 Spring 全家桶 。Web 层使用的也是 Spring MVC 。Spring MVC 的默认 jsON 处理库是 Jackson 。在 Web 层返回 JSON 后 。数据就成了这个样子:
{"nid":1,"ctitle":"Effective JAVA"}可我这个 POJO 类是将匈牙利命名法的字段转了大写 。它长这样啊:
public class Book { private Integer NId; private String CTitle; public Integer getNId() {return NId; } public void setNId(Integer NId) {this.NId = NId; } public String getCTitle() {return CTitle; } public void setCTitle(String CTitle) {this.CTitle = CTitle; }}大写字段名 。在转 JSON 之后变成了小写……要是把这个小写的字段给了前端 。前端命名肯定会用小写 。那前端在发送到后端时候一定也是小写 。
那由于咱们出入参序列化都是 Jackson。对于 Jackson 来探讨 。怎么出就怎么进 。还是能解析出来的 。看似也没啥问题 。只是让人作呕了一点 。前后端一个大写一个小写 。
不过……事情并没有那么简单 。后端不会将所有的报文都作为 POJO 的字段 。会存在一些动态的字段 。用 Map 存放 。可 Map 存放的这些字段 。Jackson 在输出时不会转为小写 。还是保留原有的大写形式 。这样就会导致给前端的字段 。虽然都是匈牙利风格 。但有那么一些大写有那么一些小写……虽然前端不知道打不打人 。但我可不敢这么玩
不同序列化库的处理机制不同Jackson 的匈牙利命名法处理其实 Jackson 序列化之后转小写的原因也很好解释 。Java 的设计规范 。就是 Bean 中的属性用 private 修饰 。然后提供 getter/setter 来提供读取/写入 。那么在序列化时 。Jackson 通过字段的 Getter 来访问属性值 。甚至用 Getter 方法来解析属性名 。
Java 的 getter 方法命名标准是 。将小写驼峰转大写驼峰 。Jackson 在通过 getter 方法名解析字段名时 。将 getNID 解析为 nid 了 。所以导致最终输出的字段名为小写的 nid 。
FastJson 的匈牙利命名法处理本来是想定做化一下 Jackson 的命名处理的 。但想了一下觉得没必要 。毕竟是咱们命名不标准 。何必去改这个命名处理机制呢 。划不来 。
所以我又尝试着换一种 JSON 库去处理这个命名问题 。先试一试阿里的 FastJSON 。看看这个国产库的处理怎么样:
{"cTitle":"Effective JAVA","nId":1}看到这个序列化结果时 。我差点把我键盘按钮上面的 Backspace 按断了……一样是通过 getter 方法解析属性名 。两个库的解析规则还能不一样……
在 FastJson 里 。c 是小写了 。可 Title 里的 T 还是大写 。@#¥%……&此处省略100字……
冷静一下之后 。心里默念了几遍:“不怪别人 。是咱们自己的命名问题 。不符合标准人家怎么解析都不关你事……”
不过 JAVA 的生态这么好 。JSON 库也不止这两个 。再换一个就是 。Google 的 Gson 也很不错嘛!
Gson 的匈牙利命名法于是我又换成了 Gson 。配置完 Spring MVC Gson Converter 之后 。输出结果:{"NId":1,"CTitle":"Effective JAVA"}终于换到一个能正常显示原始字段名的 JSON 库了 。不过它既然能保持原有字段名 。而不是 getter 里解析的属性名 。那么它肯定不是解析 getter 方法名的
于是我又去翻了下 Gson 的源码 。发现它是直接 getDeclaredFields() 。然后makeAccessible 。最后直接通过 Field.getValue的方式直接获取属性值的 。
相比 Jackson 和 FastJson 里通过 getter 获取属性列表 。然后通过调用 getter 方法来获取属性值的方法来探讨 。强烈访问私有属性这种做法还是太暴力了 。不过我喜欢 。至少它能轻松解决了我的问题
其他的序列化问题除了 JSON 这种文本形式的序列化之外 。一些二进制的序列化也会有这个尴尬的问题 。获取属性列表/属性值 。到底是用解析 getter 方法的方式 。还是直接 makeAccessible 暴力访问私有属性呢?
这个我测试了一下 。比如在 Dubbo 的默认序列化方式(Dubbo 简化的Hession2)中 。仍然是 getDeclaredFields 。然后访问私有属性
在 JDK 的内置序列化 ObjectOutputStream 中 。也是 getDeclaredFields 。然后访问私有属性 。
不过这种 getDeclaredFields。然后访问私有属性值的方式 。也会有一些劣势 。比如在接触代码混淆时 。私有属性的值会被全部打乱 。而 public 的方法却不会 。所以在接触混淆的代码时 。这种方式就会乱套了 。而通过 getter 方法解析的方式就不会有问题 。
所以吧 。这个获取属性的方式并没有对错 。怎么都可以 。不过我认为还是应该通过 getter/setter 的方式来操作 。符合 JAVA 的规范 。
总结Java 里访问私有属性值 。标准的方式是通过 getter 方法 。但还是提供了一个 makeAccessible 操作 。可以让咱们直接访问私有属性或者私有方法 。
一直不太明白 JDK 为什么要这么设计 。既然已经指定了规范 。为什么还要开个后门呢?如果限制死了这个功能 。那么所有序列化的库不就可以统一了 。再也没有这种让人作呕的不一致问题!
【C++中匈牙利命名法是否该淘汰 匈牙利命名法为何被淘汰】但对比以上三个序列化库 。我觉得都没错 。Jackson/FastJson 遵从规范的方式来 。老老实实地通过 getter 方法来获取 。而 Gson 就有点暴力 。直接访问私有属性 。各有优势 。
介绍浏览:Google一面挂 。疯刷1000道JAVA面试题 。上岸华为