文档章节

性别字段存储时应该使用的字符串,还是数字?

翟志军
 翟志军
发布于 2016/03/30 09:32
字数 2055
阅读 2048
收藏 8

在我和同事结对时,发现数据库中多个表中,分别都会有gender这个字段。比如A表,B表,C表。这三个表中,gender字段都是int类型。但是同一性别,在各个表中的值是不同的。比如A表中,1代表男,在B表中却代表了女,在C表中代表未知。

我突然意识到这背后存在更大的问题。从而引发我对“性别字段存储时应该使用的字符串,还是数字?”这个问题思考。也许已经有前辈思考过这个问题并写在某本书的某页,如果有,请告知。谢谢。

 

0代表女,1代表男

首先,你可能会问,对于这样的问题还用想吗?不是都使用数字吗?0代表女,1代表男。

其实,淘宝就是这么做的:

html代码是这样的:

 

这时,我会问如果这个用户没有填写性别信息呢?那你可能将原来的实现改成0代表空,1代表男,2代表女。我提醒你,当你开发的是一个大型网站时,你要将原来的“0代表女”改成“0代表空”,不会那么容易。历史数据要处理。你还需要修改所有用到0,1的代码,即使你使用的是常量代替而不是魔法数字,也不会容易到哪里去。

 

有经验的程序员

是的,有经验的程序员写代码时,一开始就会想到这个问题,所以一开始就设计“0代表空,1代表男,2代表女”。从前端到后端都统一使用数字。比如:

class User{
   final static int GENDER_NULL = 0
   final static int GENDER_MALE = 1
   final static int GENDER_FEMALE = 2
   int gender
}
class UserController{
    void saveUser(int gender)
}
<div>gender: #if($user.gender == 1)男#elseif($user.gender == 2)女 blabla….</div>

当然前端这样写有些难看,那我们使用宏来代替,比如<div>#displayGender($user.gender)</div>。这里我想留一个疑问:如果想国际化呢?你的displayGender怎么实现的?

实习生来了

某天公司招来了一个实习生要实现一个活动申请表页面。领导觉得这个功能应该不难,所以就将这个任务分配给他。他为了表现自己,哐啷哐啷很快就写完了,还得到了领导的表扬。但实习生根本没有参照前面有经验的程序员的写法(有时不是他的问题,可能是没有人告诉他需要参照某个功能的写法来实现)有意识一些的实习生还知道将gender的值写成常量,没有意识的,可能你只有去到前端页面看源码才能知道0, 1分别代表什么。

class ActivityApply{
     final static int GENDER_MALE = 0
     final static int GENDER_FEMALE = 1
     int gender
}

如果他没有参照前面有经验程序员的写法,我不确定他是否会重用那个前端宏。所以,讲到这里,你应该明白,有时你设计好的“重用”,并不一定会被重用。为什么呢?:P

这里不是故意贬低所有实习生,只是情节需要。

 

0和1到底放在哪里?

也许你意识到了(通常不包括架构师),我们需要统一将gender常量的值放在某个地方。那位有经验的程序员将其放到了User类中。这样,所有使用的gender的地方都应该变成User.GENDER_MALE blabla,如 activityApply.gender = User.GENDER_MALE。

也许有人想到了,建立一个Gender的类,或者枚举不就行了。比如:

public enum Gender {
    UNKNOWN(0), MALE(1), FEMALE(2);
    private final int value;
    Gender(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

然后使用的时候就变成了:

user.gender = Gender.UNKNOWN
activityApply.gender = Gender.MALE

问题是不是解决了?就算是实习生来了,也能保证大家的gender的值是一致的。前提是他要知道关于gender的值我们取的都Gender枚举里的值。不论是入职时老员工跟他说,还是他自己发现的。

问题解决了?并没有。当前端发来了个gender参数时,我们如何校验这个参数呢?比如前文提到的淘宝表单里,我们看到:

_fm._0.g就是gender参数。

校验时,我们的controller里,有人可能会写成:

if(gender == 2){
     user.gender = Gender.FEMALE
}else if(gender == 1){
     user.gender = Gender.MALE
}else {
     user.gender = Gender.UNKNOWN
}

高明一些人的会在gender枚举中加入一个静态方法: 

public static Gender genderOf(int aGenderValue){
        for (Gender gender : Gender.values()) {
            if (gender.value == aGenderValue) {
                return gender;
            }
        }
        return Gender.UNKNOWN;
    }

然后校验时,

user.gender = Gender.genderOf(genderParameter).value

 

 

Gender以数字值存到数据库中,真是最好的方法?

以上,我们的思路看似没有问题。只是,我们没有看到其中的假设。以上思路的假设是:

  1. 代码使用者知道有Gender这个枚举类,然后再使用Gender枚举赋值给User.gender字段。

  2. 对于数据库中的0、1、2,只有我们的程序进行解释,其它程序里可能使用的是10、11、12

  3. 因为是性别可能的值不多,所以,前端代码写成if elseif elseif else,没所谓。但要知道,我们服务不仅仅输出html,还会输出json等其它格式。当然,你可以将这部分逻辑封装起来,这样别人就可以重用了。但是你这里又假设了“TA人知道你的重用”存在,然后正确使用。

首先,你可能认为这些不是问题。我猜想你给出的理由是:

关于1,一进入公司,我们就培训他,gender就使用这个类。或者写一个开发文档。

关于2,我们不需要其它程序解释这个数据库里的值,其他程序都是通过调用我们程序的。

关于3,性别来来回回就那几个,不会扩展到哪里去。

 

问题不在于Gender而在于别处

我觉得你这些理由是有道理的,但是不是最好的。

关于1:我们的代码应该设计得尽量可靠,可靠到连代码使用者都不会使用错。而User、ActivityApply的gender是int和String这类基础数据类型,不管你培训还是写开发文档,都会给代码使用者有写错的机会。更好的办法是什么,使用枚举类型。这样就可以由编译器来给我们检查代码使用者有没有调用错了。而且,这也解放了代码使用者的大脑。当你在写user.setGender(value)时,如果setGender接收的是一个枚举,IDE自然会提示你,gender有哪些值。

这很像地铁上的门写着“请勿倚靠”,你就认为真没有人倚靠?在合理成本内,地铁门应该设计成就算10个200斤的人倚靠都不怕。

这时,你会提问,如果User gender使用的枚举,那么我们怎么持久化到数据库中呢?如果使用ORM框架,它会给你解决。如果不使用ORM,也可以有技巧解决的。这个问题留你自己思考。但是,有一点需要提下,你的业务代码不应该和数据库这样具体技术耦合!可以看看我写的《耦合的本质》

关于2:如果养成了所有有限值内的字段都使用数字来存到数据库中的习惯,问题就没有使用gender这么简单了。在未来的几年,你的代码会充满魔法数字,最终架构腐化。

关于3:和2是同一个问题。

 

小结

回答本文标题的问题:出现像gender这样有限值的字段,我会优先使用枚举包装起来,持久化时,我会优先使用人看得懂的字符串。

深度一些的思考:

  1. 本文的标题是个问题吗?

  2. 设计模式能解决本文标题的问题吗?呵呵

  3. 为什么人们趋向于使用数字而不是字符串?

  4. 为什么架构会腐化?

  5. 架构师不写代码?

 

如果觉得这文章对你有帮助,可以赞赏10元:

© 著作权归作者所有

共有 人打赏支持
翟志军

翟志军

粉丝 348
博文 76
码字总数 79851
作品 2
深圳
程序员
私信 提问
加载中

评论(12)

花雨归来
亲身感受,不管什么字段,能用字符串就用字符串,除了金额、打印次数这些太明显在哪里都是一个数字的东西,流水号(我的流水号是连续的数字)我都用字符串,而且日期只用yyyyMMdd8位字符串,然后你就会发现,开发起来真是爽,不用担心类型转换异常了,和其他系统(后台系统或者前台json,不管啥都用字符串)交互全是字符串解析、校验、对比贼爽,数据迁移也是爽,还是那句话,除了金额这种需要数学计算的需要转换,其他就是一个字,爽!!!
l
lzszone
为什么会影响检索效率? 我认为会影响建索引的时间...检索效率几乎不影响
最后的夏天
最后的夏天
>>在合理成本内,地铁门应该设计成就算10个200斤的人倚靠都不怕

不让倚靠是怕夹住或者门故障突然开了掉下去吧不是怕人把门挤坏了吧 ,哈哈跑个题
啦啦啦拉拉
啦啦啦拉拉
根据国家标准 GB2261 性别选项有如下六个数字

0 - 未知的性别
1 - 男性
2 - 女性
5 - 女性改(变)为男性
6 - 男性改(变)为女性
9 - 未说明的性别
秋水逍遥
秋水逍遥
这里的重点是:建模的时候,“性别”是一个领域概念,值得用一个单独的构造(类/接口/枚举)来表示。只有在持久化这样的细节中,才需要考虑它的存储形式是数字、字符串还是其它。从大多数人直接用数字或字符串来表示性别来看,我看出两个错误的倾向:一个是“以数据为中心的设计”,另一个是“基本类型偏执”。
翟志军
翟志军

引用来自“chencliff”的评论

我就习惯有限值内的字段都使用数字来存到数据库中。原本数据库脱离了我的程序就是一堆谁也用不了的无意义的数据,不脱离我的程序,你很容易就能知道1/2/3/4都是什么
不要忘记了。除了你的程序去读这些数据,还可能有其他程序来读。第二,同一程序的不同版本也会读这些数据。
chencliff
chencliff
我大部分都会写成enum,还有一部分会写成public static final int,用起来很方便
chencliff
chencliff
我就习惯有限值内的字段都使用数字来存到数据库中。原本数据库脱离了我的程序就是一堆谁也用不了的无意义的数据,不脱离我的程序,你很容易就能知道1/2/3/4都是什么
Sonnet
Sonnet
以前处理这个的时候,都是1和0,默认就要选一个,不能为空,是不是有点粗暴?
叫我刀刀
叫我刀刀

引用来自“莫铭”的评论

引用来自“叫我刀刀”的评论

直接写汉字不行吗?

如果从数据库方面考虑,我不会使用字符。一个是占用空间问题,一个是检索效率问题。
有道理
MariaDB基础(1)--数据类型

MariaDB介绍 MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。在存储引擎方...

xjxiaolei
2015/10/25
0
0
Mysql支持的数据类型(总结)

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://mrxiong.blog.51cto.com/287318/1651098 一.数值类型 Mysql支持所有标准S...

lingfeng72
2016/03/29
26
0
如何在mysql中实现自然排序

背景 熟悉mysql的同学应该清楚,mysql在对字符串做order by排序时是按照字典序进行排序的,但是如果字符串中包含数字的话(我们称这种类型的字符串为alphanumeric),仅按照字典序的排序结果...

wooyoo
2017/04/16
0
0
MySQL 高性能表设计规范

原文出处:高广超 良好的逻辑设计和物理设计是高性能的基石, 应该根据系统将要执行的查询语句来设计schema, 这往往需要权衡各种因素。 一、选择优化的数据类型 MySQL支持的数据类型非常多,...

高广超
2017/07/27
0
0
elasticsearch mapping

es的mapping设置很关键,mapping设置不到位可能导致索引重建。如何更好的设置mapping? 请看下面各个类型介绍^_^ core types 每一个JSON字段可以被映射到一个特定的核心类型。JSON本身已经为...

allantaylor81
2015/08/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Java 源代码和 C 源代码的运行区别

与其他程序的执行方式和编译方式不同。 Java 源代码需要进行编译成字节码后在 Java 虚拟机上运行,这样 Java 程序能够保持独立性和跨平台功特性。 请参考下图。 https://www.cwiki.us/pages...

honeymose
今天
3
0
Apache限定目录解析PHP,限制user_agent,PHP相关的配置

Apache限定目录解析PHP 配置前访问upload/index.php [root@test-a ~]# curl -x192.168.77.139:80 'www.test.com/upload/index.php'This is upload diretory 配置,/usr/local/apache2.4/......

野雪球
今天
5
0
java.util.Concurrent.Exchanger源码

类图 源码: package java.util.concurrent;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent......

狼王黄师傅
今天
6
0
Kubernetes里的secret最基本的用法

Secret解决了密码、token、密钥等敏感数据的配置问题,使用Secret可以避免把这些敏感数据以明文的形式暴露到镜像或者Pod Spec中。 Secret可以以Volume或者环境变量的方式使用。 使用如下命令...

JerryWang_SAP
昨天
5
0
2018-11-20学习笔记

1. python数据类型: 给变量赋值什么样的值,变量就是什么样的类型 给变量赋值整数,变量就是整数类型 给变量赋值字符串,变量就是字符串类型 123 和“123”一样吗? 在python中 单引号 与双...

laoba
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部