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

综合技术 2016-04-15

在我和同事结对时,发现数据库中多个表中,分别都会有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)
}
gender: #if($user.gender == 1)男#elseif($user.gender == 2)女 blabla….

当然前端这样写有些难看,那我们使用宏来代替,比如

#displayGender($user.gender)

。这里我想留一个疑问:如果想国际化呢?你的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以数字值存到数据库中,真是最好的方法?

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

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

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

因为是性别可能的值不多,所以,前端代码写成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这样有限值的字段,我会优先使用枚举包装起来,持久化时,我会优先使用人看得懂的字符串。

深度一些的思考:

  • 本文的标题是个问题吗?
  • 设计模式能解决本文标题的问题吗?呵呵
  • 为什么人们趋向于使用数字而不是字符串?
  • 为什么架构会腐化?
  • 架构师不写代码?

责编内容by:翟志军 (源链)。感谢您的支持!

您可能感兴趣的

Database design for large amounts of data I would like to store stock trading data for 1000 symbols. The data is actually ...
基于可靠消息方案的分布式事务(二):Java中的事务... 前言:在上一篇文章 基于可靠消息方案的分布式事务:Lottor介绍 中介绍了常见的分布式事务的解决方案以及笔者基于可靠消息方案实现的分布式事务组件Lot...
Starting MongoDB Database Software In this blog post, we will cover how to start MongoDB database software in the t...
Mule 4: Database Connector (Part 1) Lots of people are wondering about the new features in Mule since Mule releas...
Working with schemaless data with GraphQL on Postg... Working with schemaless data with GraphQL on Postgres Exploit po...