问题:为什么要代码重构?
最近,项目底层数据库升级,用的库呢是当下最热门的hadoop生态系统。数据库部门丢了两差异文档给我们写应用的,心想语法不会改动太大,大不了就是ctrl+r替换点关键字啥的。在制定mmp时,自信满满的跟经理报了一个很短的时间。
开始时,一切还挺顺利的,建表语句啥的改个关键字就完事了。
-- 旧
CREATE TABLE A (f_1 string,f_2 long....);
-- 新
CREATE SYSTABLE A (f_1 string,f_2 long...);
一看,这时间不绰绰有余嘛,喝喝茶,写一写自测用例完全够用。应届生来问问题,还能抽身去指点一下,俨然一位大佬模样。前前后后改了几个语法,不是很难,默认分区换个名字、函数换个名字啥的,测起来很快。
直到一个问题出现,执行下面这条sql时报错了,我也没多想,把问题发给了研究数据库的同事,然后就去睡午觉了。
SELECT f_1,f_2,f_3 FROM XXX WHERE f_3 < '2018-11-05 12:15:00'
后来得知,这次升级后底层scala解析sql的时候,完全不支持带空格的字符串比较。(内心os:fk)简单介绍下,目前用的这款数据库不支持Date类型,所以把日期数据以字符串存储。另外中存法就是存成数值类型,其实后者是比较好的方式,方便比较,但是当初因为某些原因没有选择数值型。
后续为了解决问题花了不少时间,但是这就不是本文的重点,不再这里详述。
总之,sql需要大改o(╥﹏╥)o,好在项目不是很大,利用ctrl+shift+f搜索出总共有三处都需要替换成新的sql,需要修改的部分如下:
//需要查询的字段
String name = (String) queryParams.get("name");
//需查询的字段类型,1:字符型、2:数值型、4:日期型
Integer type = (Integer) queryParams.get("type");
//关键字
Object query ;
switch(type){
case 1:
if(null == (query = queryParams.get("queryText") ){
break;
}
//拼接字符串类型的条件
if(oracle库 || gp库){//支持不区分大小写
sql = " and lowercase(x) like '%" + a.toLowerCase() + "%'";
}
if(hive库){//严格区分大小写
sql = " and x like '%a%'";
}
break;
case 2:
if(null == (query = queryParams.get("queryNumber") ){
break;
}
...
break;
}
以上代码在三个地方实现了三次,刚好最近读到了部分代码重构的博客,当即决定把这部分代码提取出来。这样以后再改版数据库就不用再改三个地方啦。
同时每个case分支里还有针对不同数据库的判断逻辑:
if(dataBaseType == 1) {
xxxx....
}
if(dataBaseType == 2) {
yyyy....
}
由于if、for、switch嵌套太多,sonar大哥早报警了哈哈哈。所以决定把这段代码好好重构下。
好在之前打下了基础,在收到产品需要运行在多个版本的数据库的需求时,经理就安排我使用抽象工厂模式对底层基础sql进行封装。
public class Factory{
public static ISql getBizSql() throws Exception{
//从配置文件中获取
int dataBaseType = ...;
switch(dataBaseType){
case 1:
return new OracleSqlImpl();
case 2:
return new HiveSqlImpl();
case 3:
default:
return new GpSqlImpl();
}
}
}
public interface ISql{
//create
public String createTable();
//drop
public String dropTable();
//getCount
public String getCount();
}
//抽象父类,提供公用方法,子类与父类写法一致时无需单独实现。
public abstract class AbsSql implements ISql{
//针对部分sql所有数据库的写法一样时,可以不用在具体子类中实现
@Override
public String getCountSql(){
return "select count(1) from xxx";
}
}
//oracle实现
public class OracleSqlImpl extends AbsSql{
@Override
public String createTable(){
return "create table xxx (f_1 number,f_2 varchar(255))";
}
}
//hive实现
public class HiveSqlImpl extends AbsSql{
@Override
public String createTable(){
return "create table xxx (f_1 number,f_2 string)";
}
//针对父类的写法无法使用的情况,子类可以单独再次重写
@Override
public String getCountSql(){
return "select count(*) from xxx";
}
}
//gp实现
public class GpSqlImpl extends AbsSql{
@Override
public String createTable(){
return "create table xxx (f_1 numeric,f_2 varchar(255))";
}
}
以上拼接条件的sql是个漏网之鱼,改版不会太难,只需要在上述代码中添加拼接条件的实现即可。
public interface ISql{
//组装拼接查询条件
public String buildSqlWithCondition();
}
//抽象父类,提供公用方法,子类与父类写法一致时无需单独实现。
public abstract class AbsSql implements ISql{
//组装拼接查询条件
@Override
public String buildSqlWithCondition(){
//需要查询的字段
String name = (String) queryParams.get("name");
//需查询的字段类型,1:字符型、2:数值型、4:日期型
Integer type = (Integer) queryParams.get("type");
//关键字
Object query ;
switch(type){
case 1:
if(null == (query = queryParams.get("queryText") ){
break;
}
...
//调用拼接字符串比较的逻辑
appendStringCondition();
break;
case 2:
if(null == (query = queryParams.get("queryNumber") ){
break;
}
...
//调用拼接数值等于比较的逻辑
appendNumberCondition();
break;
}
}
//拼接字符串全模糊匹配抽象方法,由子类具体实现
abstract StringBuilder appendStringCondition();
//拼接数值等于比较抽象方法,由子类具体实现
abstract StringBuilder appendNumberCondition();
}
//oracle实现
public class OracleSqlImpl extends AbsSql{
@Override
public StringBuilder appendStringCondition(){
return "xxx";
}
@Override
public StringBuilder appendNumberCondition(){
return "xxx";
}
}
//hive实现
public class HiveSqlImpl extends AbsSql{
@Override
public StringBuilder appendStringCondition(){
return "xxx";
}
@Override
public StringBuilder appendNumberCondition(){
return "xxx";
}
}
//gp实现
public class GpSqlImpl extends AbsSql{
@Override
public StringBuilder appendStringCondition(){
return "xxx";
}
@Override
public StringBuilder appendNumberCondition(){
return "xxx";
}
}
这次代码重构的灵感部分来自于“大话设计模式”,第一章就很有意思,后来翻阅第15章,标题叫做《就不能不换DB吗?》,讲述的就是抽象工厂模式,结合这次代码重构,我对这种设计模式的印象更加深刻了。