代码复用应当这样做(2)
代码复用应该这样做(2)
以上是对一个对象中各函数间的代码复用。另一种情况是这被比较的两份或者多份代码不在同一个对象中,这应该怎么办呢?我们可以采用的办法比较多,首先一种比较直观的办法就是运用“抽取类”将共同的部分抽取到一个工具类中,为其它各类所调用。比如,看看这个例子:
我们有个遗留系统在大量地方需要获取当前服务器时间,该功能在过去版本中这样写:
后来JDK升级以后该方法被废掉了,所有获取当前时间的代码都要被改成这样:
但随后的问题就来了,整个系统有数十处获取当前时间的代码,它们都要被修改,这就很烦人了。此外,还有一些代码是要获取当前的月份:
它们都十分相似,但功能又不完全相同。这时,可以将它们提取出来形成一个DateUtil工具类:
这样,系统中所有获得当前时间与当前月份的代码都这样写:
采用工具类的方法非常实用,被合并的代码即使分散在系统的各个角落,各自功能没有任何联系,都可以采用。该方法可以有效解决代码变更中的“散弹枪式修改(Shotgun Surgery)”问题,即一个需求的变更导致分散在各处的代码修改。如果将分散在各处的代码统一成一个工具类,则需求的变更就仅仅变成对这个工具类的修改,“散弹枪式修改”的问题就得以解决。
另外一种与该方法比较类似的方法,就是提取并封装成实体类。当重复代码被分散在原程序的多个代码段中,并且其内部存在着较强的业务相关性时,可将这些代码提取并封装成一个实体类,该实体类应当体现出这种业务相关性。在前一种方法中,工具类仅仅表现为一堆方法的集合,而该方法中,该实体类体现的是一定的业务逻辑,并在使用时需要实例化。看看这个例子:
在许多系统中,诸如Servlet或Action都需要通过request读取前端传送的数据,但这些数据需要频繁通过转换转成各种数据类型。这些转换过程往往会造成大量重复代码:
但所有这些操作都存在一个内在的联系,即都是在通过request读取前端数据,因此我们将它们封装成一个Reader类,并将request作为参数,在实例化时传递过去:
这样,在编写这类似的程序时就不再那么麻烦了:
这样的设计好处多多。首先,如果出现什么因JDK的调整需要修改转换程序的写法时,修改Reader类中的相应方法就可以了,过去这类似的“散弹枪式修改”得到抑制。其次,如果有其它的方法过去需要传递request的,现在将不再需要传递,而是替换成传递reader,这将可以一定程度实现对web容器的解耦。不仅如此,如果今后需要修改设计,不使用request传递数据,而是采用其它方式,则只需要修改Reader的实现就可以了,程序可维护性与易变更性得到提高。
(续)
相关文档
遗留系统:IT攻城狮永远的痛
需求变更是罪恶之源吗?
系统重构是个什么玩意儿
我们应当改变我们的设计习惯
小步快跑是这样玩的(上)
小步快跑是这样玩的(下)
代码复用应该这样做(1)
代码复用应该这样做(2)
代码复用应该这样做(3)
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
很多人都是这样写的,但这在许多时候不是一个好的设计,因为返回null以后,是不是会影响到后续的客户程序还要处理null,处理null就意味着封装不够完全。当然不是绝对的,看具体情况,已经设计者的习惯。
不得不说你可能还没有看懂这段代码,这段代码是在还未封装成bean前,一个一个读取request中的数据要做的事情。当然,现在很多框架将这一步都自动完成了,但现在我讨论的是遗留系统,是数年前开发的遗留系统。
代码复用? 我只遵循几个规则,其中有一条叫,如果代码中出现了两次以上重复的代码,那你应该要考虑重构了. 复用/重构不是一开始就能考虑到的,但要尽量考虑全面,这东西换个说法,也叫做编程经验。
哎,让我说啥呢,你自己体味吧
很多人都是这样写的,但这在许多时候不是一个好的设计,因为返回null以后,是不是会影响到后续的客户程序还要处理null,处理null就意味着封装不够完全。当然不是绝对的,看具体情况,已经设计者的习惯。
处理null,封装不够完全?为什么有这种想法。
在编程世界中,无 就是 null。 空,没有 代表 ""。
我只是想说明,这里不太合理,希望你可以改正一下。
像判断null,我写了一个小工具,批量判断null..
为甚不直接
以上是对一个对象中各函数间的代码复用。另一种情况是这被比较的两份或者多份代码不在同一个对象中,这应该怎么办呢?我们可以采用的办法比较多,首先一种比较直观的办法就是运用“抽取类”将共同的部分抽取到一个工具类中,为其它各类所调用。比如,看看这个例子:
我们有个遗留系统在大量地方需要获取当前服务器时间,该功能在过去版本中这样写:
Date now = new Date();
后来JDK升级以后该方法被废掉了,所有获取当前时间的代码都要被改成这样:
Calendar calendar = Calendar.getInstance(); Date now = calendar.getDate();
但随后的问题就来了,整个系统有数十处获取当前时间的代码,它们都要被修改,这就很烦人了。此外,还有一些代码是要获取当前的月份:
Calendar calendar = Calendar.getInstance(); int thisMonth = calendar.get(Calendar.MONTH)+1;
它们都十分相似,但功能又不完全相同。这时,可以将它们提取出来形成一个DateUtil工具类:
/** * 处理日期时间工具类 * @author fangang */ public class DateUtil { /** * @return 获得当前Calendar */ public static Calendar getCalendar(){ Calendar calendar = Calendar.getInstance(); return calendar; } /** * @return 获得今年 */ public static int getThisYear(){ return getCalendar().get(Calendar.YEAR); } /** * @return 获得本月 */ public static int getThisMonth(){ return getCalendar().get(Calendar. MONTH)+1; } /** * @return 获得当前时间 */ public static Date getNow(){ return getCalendar().getDate(); } }
这样,系统中所有获得当前时间与当前月份的代码都这样写:
Date now = DateUtil.getNow(); int thisMonth = DateUtil.getThisMonth();
采用工具类的方法非常实用,被合并的代码即使分散在系统的各个角落,各自功能没有任何联系,都可以采用。该方法可以有效解决代码变更中的“散弹枪式修改(Shotgun Surgery)”问题,即一个需求的变更导致分散在各处的代码修改。如果将分散在各处的代码统一成一个工具类,则需求的变更就仅仅变成对这个工具类的修改,“散弹枪式修改”的问题就得以解决。
另外一种与该方法比较类似的方法,就是提取并封装成实体类。当重复代码被分散在原程序的多个代码段中,并且其内部存在着较强的业务相关性时,可将这些代码提取并封装成一个实体类,该实体类应当体现出这种业务相关性。在前一种方法中,工具类仅仅表现为一堆方法的集合,而该方法中,该实体类体现的是一定的业务逻辑,并在使用时需要实例化。看看这个例子:
在许多系统中,诸如Servlet或Action都需要通过request读取前端传送的数据,但这些数据需要频繁通过转换转成各种数据类型。这些转换过程往往会造成大量重复代码:
double amount = Double.valueOf(req.getParameter(“amount”)).doubleValue(); int count = Integer.valueOf(req.getParameter(“count”)).intValue(); String name = new String(req.getParameter(“name”).getBytes(),"GBK"); Date now = DateUtil.getDate(req.getParameter(“now”),"yyyy-MM-dd");
但所有这些操作都存在一个内在的联系,即都是在通过request读取前端数据,因此我们将它们封装成一个Reader类,并将request作为参数,在实例化时传递过去:
/** * 默认的读取器,读取POST提交的数据 * @author fangang */ public class Reader { private HttpServletRequest req = null; /* * Constructor with request * @param HttpServletRequest */ public Reader(HttpServletRequest req) throws IOException { this.req = req; } /* * @param name of a parameter * @return the data of Object */ public Object getData(String name) throws IOException { Object data = req.getParameter(name); if (data==null) { throw new IOException("读取器未读出相应数据:"+name); } return data; } /* * @param name of a parameter * @return the data of double */ public double getDouble(String name) throws IOException { return Double.valueOf(getString(name)).doubleValue(); } /* * @param name of a parameter * @return the data of int */ public int getInteger(String name) throws IOException { return Integer.valueOf(getString(name)).intValue(); } /* * @param name of a parameter * @return the data of String */ public String getString(String name) throws IOException { return getData(name).toString(); } /* * @param name of a parameter * @return the data of Chinese String */ public String getChinese(String name) throws IOException { return new String(getString(name).getBytes(),"GBK"); } …… }
这样,在编写这类似的程序时就不再那么麻烦了:
Reader reader = new Reader(req); double amount = reader.getDouble(“amount”); int count = reader.getInteger(“count”); String name = reader.getChinese(“name”); Date now = reader.getDate(“now”);
这样的设计好处多多。首先,如果出现什么因JDK的调整需要修改转换程序的写法时,修改Reader类中的相应方法就可以了,过去这类似的“散弹枪式修改”得到抑制。其次,如果有其它的方法过去需要传递request的,现在将不再需要传递,而是替换成传递reader,这将可以一定程度实现对web容器的解耦。不仅如此,如果今后需要修改设计,不使用request传递数据,而是采用其它方式,则只需要修改Reader的实现就可以了,程序可维护性与易变更性得到提高。
(续)
相关文档
遗留系统:IT攻城狮永远的痛
需求变更是罪恶之源吗?
系统重构是个什么玩意儿
我们应当改变我们的设计习惯
小步快跑是这样玩的(上)
小步快跑是这样玩的(下)
代码复用应该这样做(1)
代码复用应该这样做(2)
代码复用应该这样做(3)
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
1 楼
LinApex
2014-01-13
我能说,这一句就不合理么?
if (data==null) {
throw new IOException("读取器未读出相应数据:"+name);
}
返回null即可。
还有其他的全都是 return Integer.valueOf(getString(name)).intValue();
没有考虑到复杂一点的业务判断逻辑...
如果有经验的应该这样考虑...
1.封装一个Map工具
2.将parameter值封装进Map.
3.将map转成bean.
其他的日志我就不研究你的了。
代码复用? 我只遵循几个规则,其中有一条叫,如果代码中出现了两次以上重复的代码,那你应该要考虑重构了. 复用/重构不是一开始就能考虑到的,但要尽量考虑全面,这东西换个说法,也叫做编程经验。
if (data==null) {
throw new IOException("读取器未读出相应数据:"+name);
}
返回null即可。
还有其他的全都是 return Integer.valueOf(getString(name)).intValue();
没有考虑到复杂一点的业务判断逻辑...
如果有经验的应该这样考虑...
1.封装一个Map工具
2.将parameter值封装进Map.
3.将map转成bean.
其他的日志我就不研究你的了。
代码复用? 我只遵循几个规则,其中有一条叫,如果代码中出现了两次以上重复的代码,那你应该要考虑重构了. 复用/重构不是一开始就能考虑到的,但要尽量考虑全面,这东西换个说法,也叫做编程经验。
2 楼
fangang
2014-01-13
LinApex 写道
我能说,这一句就不合理么?
if (data==null) {
throw new IOException("读取器未读出相应数据:"+name);
}
返回null即可。
if (data==null) {
throw new IOException("读取器未读出相应数据:"+name);
}
返回null即可。
很多人都是这样写的,但这在许多时候不是一个好的设计,因为返回null以后,是不是会影响到后续的客户程序还要处理null,处理null就意味着封装不够完全。当然不是绝对的,看具体情况,已经设计者的习惯。
LinApex 写道
还有其他的全都是 return Integer.valueOf(getString(name)).intValue();
没有考虑到复杂一点的业务判断逻辑...
如果有经验的应该这样考虑...
1.封装一个Map工具
2.将parameter值封装进Map.
3.将map转成bean.
其他的日志我就不研究你的了。
代码复用? 我只遵循几个规则,其中有一条叫,如果代码中出现了两次以上重复的代码,那你应该要考虑重构了. 复用/重构不是一开始就能考虑到的,但要尽量考虑全面,这东西换个说法,也叫做编程经验。
没有考虑到复杂一点的业务判断逻辑...
如果有经验的应该这样考虑...
1.封装一个Map工具
2.将parameter值封装进Map.
3.将map转成bean.
其他的日志我就不研究你的了。
代码复用? 我只遵循几个规则,其中有一条叫,如果代码中出现了两次以上重复的代码,那你应该要考虑重构了. 复用/重构不是一开始就能考虑到的,但要尽量考虑全面,这东西换个说法,也叫做编程经验。
不得不说你可能还没有看懂这段代码,这段代码是在还未封装成bean前,一个一个读取request中的数据要做的事情。当然,现在很多框架将这一步都自动完成了,但现在我讨论的是遗留系统,是数年前开发的遗留系统。
3 楼
fangang
2014-01-13
LinApex 写道
代码复用? 我只遵循几个规则,其中有一条叫,如果代码中出现了两次以上重复的代码,那你应该要考虑重构了. 复用/重构不是一开始就能考虑到的,但要尽量考虑全面,这东西换个说法,也叫做编程经验。
哎,让我说啥呢,你自己体味吧
4 楼
LinApex
2014-01-13
fangang 写道
LinApex 写道
我能说,这一句就不合理么?
if (data==null) {
throw new IOException("读取器未读出相应数据:"+name);
}
返回null即可。
if (data==null) {
throw new IOException("读取器未读出相应数据:"+name);
}
返回null即可。
很多人都是这样写的,但这在许多时候不是一个好的设计,因为返回null以后,是不是会影响到后续的客户程序还要处理null,处理null就意味着封装不够完全。当然不是绝对的,看具体情况,已经设计者的习惯。
处理null,封装不够完全?为什么有这种想法。
在编程世界中,无 就是 null。 空,没有 代表 ""。
我只是想说明,这里不太合理,希望你可以改正一下。
像判断null,我写了一个小工具,批量判断null..
5 楼
fangang
2014-01-13
你这样说,想必也有你的道理,咱求同存异
6 楼
eve
昨天
public static Calendar getCalendar(){ Calendar calendar = Calendar.getInstance(); return calendar; }
为甚不直接
return Calendar.getInstance()