快捷搜索:  汽车  科技

java牵扯算法吗(当匈牙利命名法遇到)

java牵扯算法吗(当匈牙利命名法遇到)我们这次的重构目标,是要保持老系统表不动的情况下,完全重写。可是新系统是 Java 语言来开发,Java 可是驼峰命名标准的,当这个匈牙利命名法的表迁移到驼峰命名法的 Java 语言会怎么样?比如 c_name 这个命名,到 JAVA 里之后,是改为 CName 呢,还是 cName 呢?好像怎么都有点奇怪,不过最后我们还是选择了 CName ,将类型的前缀完全大写,至少看着稍微正常一点,没那么反人类况且这个命名法,也不是一无是处,还是有一定的优点的,至少我一眼就可以看出这个字段的类型。 只不过在今天看起来有点怪怪的,不太符合当今的设计风格,如果放到人名上就更有意思了:我们这次的重构目标,是要保持老系统表不动的情况下,完全重写。可是新系统是 Java 语言来开发,Java 可是驼峰命名标准的,当这个匈牙利命名法的表迁移到驼峰命名法的 Java 语言会怎么样?

java牵扯算法吗(当匈牙利命名法遇到)(1)

背景

之前在做一个老项目重构的时候,由于数据库不能改动,所以还是继续沿用之前的老数据库。保险公司嘛,哪怕加了个互联网保险的 title,业务和系统还是偏传统的,数据模型不会轻易地更新;所以这个系统年代比较久远,而且它的数据库表命名方式采用的还是匈牙利命名法,导致在重构时因为这个命名方式恶心了我好久……

匈牙利命名法

匈牙利命名法(Hungarian notation),由1972年至1981年在施乐帕洛阿尔托研究中心工作的-程序员 查尔斯·西蒙尼发明,这位前辈后面成了微软的总设计师

这个命名法的特点是,在命名前面增加类型的前缀,就像这样:

  • c_name - 姓名,字符串(Char)类型
  • n_age - 年龄,数字(Number)类型
  • t_birthday - 生日,日期/时间(Time)类型

可不要小看这个命名法,当年可是很流行的,而且直到今天还是有一些系统仍然在沿用这个命名标准,比如微软的 Win32 API:

java牵扯算法吗(当匈牙利命名法遇到)(2)

况且这个命名法,也不是一无是处,还是有一定的优点的,至少我一眼就可以看出这个字段的类型。

只不过在今天看起来有点怪怪的,不太符合当今的设计风格,如果放到人名上就更有意思了:

  • 赵四
  • 谢大脚
  • 刘能
当匈牙利命名法遇到 JAVA

我们这次的重构目标,是要保持老系统表不动的情况下,完全重写。可是新系统是 Java 语言来开发,Java 可是驼峰命名标准的,当这个匈牙利命名法的表迁移到驼峰命名法的 Java 语言会怎么样?

比如 c_name 这个命名,到 JAVA 里之后,是改为 CName 呢,还是 cName 呢?好像怎么都有点奇怪,不过最后我们还是选择了 CName ,将类型的前缀完全大写,至少看着稍微正常一点,没那么反人类

  • c_name -> CName
  • n_age -> NAge
  • t_birthday -> TBirthday
当匈牙利命名法遇到 JAVA

我们这次的重构目标,是要保持老系统表不动的情况下,完全重写。可是新系统是 Java 语言来开发,Java 可是驼峰命名标准的,当这个匈牙利命名法的表迁移到驼峰命名法的 Java 语言会怎么样?

比如 c_name 这个命名,到 Java 里之后,是改为 CName 呢,还是 cName 呢?好像怎么都有点奇怪,不过最后我们还是选择了 CName ,将类型的前缀完全大写,至少看着稍微正常一点,没那么反人类

  • c_name -> CName
  • n_age -> NAge
  • t_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 为什么要这么设计,既然已经指定了规范,为什么还要开个后门呢?如果限制死了这个功能,那么所有序列化的库不就可以统一了,再也没有这种恶心的不一致问题! ​

但对比以上三个序列化库,我觉得都没错,Jackson/FastJson 按照规范的方式来,老老实实地通过 getter 方法来获取,而 Gson 就有点暴力,直接访问私有属性,各有优势。


推荐阅读:Google一面挂,疯刷1000道JAVA面试题,上岸华为

猜您喜欢: