最近看到一段话,对设计模式总结不错:
《设计模式:可复用面向对象软件的基础》,在书中作者提到了一句话:
“找到变化,封装变化”,这才是设计模式的底层逻辑。
- 什么在变化
- 如何封装变化
下面我以一个简单单号生成的案例,说明一个提高代码质量步骤。
1、单号生成逻辑
public static String generateOrderNo() {
String id = "";
try {
// ip生成与转换
String ipAddress = InetAddress.getLocalHost().getHostAddress();
String[] ipSlices = ipAddress.split("\\.");
int rs = 0;
for (int i = 0; i < ipSlices.length; i++) {
int intSlice = Integer.parseInt(ipSlices[i]) << 8 * i;
rs = rs | intSlice;
}
ipAddress = String.valueOf(rs);
// 8位随机大写字符
char[] randomChars = new char[8];
for (int i = 0; i < 8; i++) {
randomChars[i] = (char)(Math.random()*26+ 'A');
}
id = String.format("%s-%d-%s", ipAddress, System.currentTimeMillis(), new String(randomChars));
} catch (UnknownHostException e) {
LOGGER.warn("fail to get :", e);
}
return id;
}
2、从可读性角度优化
可以看到上面生成业务有三块逻辑可以分类,遵循高内聚低耦合原则,进行代码优化:
public static String generateOrderNo() {
String ipAddress = getCurrentUserIp();
long currentTimeMillis = System.currentTimeMillis();
String randomString = getRandomLength(8);
String id = String.format("%s-%d-%s", ipAddress, currentTimeMillis, randomString);
return id;
}
private static String getRandomLength(int length) {
char[] randomChars = new char[length];
for (int i = 0; i < length; i++) {
randomChars[i] = (char)(Math.random()*26+ 'A');
}
return new String(randomChars);
}
private static String getCurrentUserIp() {
String ipAddress = null;
try {
ipAddress = InetAddress.getLocalHost().getHostAddress();
String[] ipSlices = ipAddress.split("\\.");
int rs = 0;
for (int i = 0; i < ipSlices.length; i++) {
int intSlice = Integer.parseInt(ipSlices[i]) << 8 * i;
rs = rs | intSlice;
}
ipAddress = String.valueOf(rs);
} catch (UnknownHostException e) {
LOGGER.warn("fail to get :", e);
}
return ipAddress;
}
2、从可测试性角度优化
通过测试,我们可以发现很多代码异常问题,或没有进行特殊处理的情况,提高代码健壮性。
- 业务代码修改
public String getRandomLength(int length) {
char[] randomChars = new char[length];
for (int i = 0; i < length; i++) {
randomChars[i] = (char)(Math.random()*26+ 'A');
}
return new String(randomChars);
}
public String getCurrentUserIp() {
String ipAddress = null;
try {
ipAddress = InetAddress.getLocalHost().getHostAddress();
// 拆分出转换自己实现转换逻辑,便于进行测试
ipAddress = convertIpStr(ipAddress);
} catch (UnknownHostException e) {
LOGGER.warn("fail to get :", e);
}
return ipAddress;
}
private String convertIpStr(String ipAddress) {
String[] ipSlices = ipAddress.split("\\.");
int rs = 0;
for (int i = 0; i < ipSlices.length; i++) {
int intSlice = Integer.parseInt(ipSlices[i]) << 8 * i;
rs = rs | intSlice;
}
ipAddress = String.valueOf(rs);
return ipAddress;
}
- 测试用例
@Test
public void testConvertIpStrByCal() throws Exception {
// @VisibleForTesting protected
// 对自己实现的算法或业务代码,必须具有可测试
IdGenerator app = new IdGenerator();
String actualRes1 = ReflectionTestUtils.invokeMethod(app, "convertIpStr", "11.1.1.0");
Assert.assertEquals("65803", actualRes1);
String actualRes2 = ReflectionTestUtils.invokeMethod(app, "convertIpStr", "111.1.1.0");
Assert.assertEquals("65903", actualRes2);
}
@Test
public void testConvertIpStrByCal_nullOrEmpty() throws Exception {
IdGenerator app = new IdGenerator();
String actualRes1 = ReflectionTestUtils.invokeMethod(app, "convertIpStr", "");
Assert.assertEquals("", actualRes1);
String actualRes2 = ReflectionTestUtils.invokeMethod(app, "convertIpStr", null);
Assert.assertEquals("", actualRes2);
}
@Test
public void testGenerateRandomAlphameric() {
IdGenerator idGenerator = new IdGenerator();
String actualRandomString = idGenerator.getRandomLength(6);
Assert.assertNotNull(actualRandomString);
Assert.assertEquals(6, actualRandomString.length());
for (char c: actualRandomString.toCharArray()) {
Assert.assertTrue((c>= 'A' && c<= 'Z'));
}
}
@Test
public void testGenerateRandomAlphameric_lengthEqualsOrLessThanZero() {
IdGenerator idGenerator = new IdGenerator();
String actualRandomString = idGenerator.getRandomLength(0);
Assert.assertEquals("", actualRandomString);
actualRandomString = idGenerator.getRandomLength(-1);
Assert.assertNull(actualRandomString);
}
2、对异常处理
在执行测试用例,我们发现会出现各种异常,包括系统异常与业务异常,若没有统一处理,在系统出现故障时就很难排查问题。
- 参数异常
- 异常信息处理
/**
* 所有异常都向外抛出:
*
* @exception UnknownHostException
* @exception IllegalArgumentException
* @return
*/
public String generateOrderNo() {
String ipAddress = null;
try {
ipAddress = getCurrentUserIp();
} catch (UnknownHostException e) {
throw new RuntimeException("host name is empty");
}
long currentTimeMillis = System.currentTimeMillis();
String randomString = getRandomLength(8);
String id = String.format("%s-%d-%s", ipAddress, currentTimeMillis, randomString);
return id;
}
public String getCurrentUserIp() throws UnknownHostException {
// 将源码异常与业务异常分开处理
String ipAddressStr = InetAddress.getLocalHost().getHostAddress();
if (ipAddressStr == null || ipAddressStr.isEmpty()) {
throw new UnknownHostException("host name is empty");
}
String ipAddress = convertIpStr(ipAddressStr);
return ipAddress;
}
private String convertIpStr(String ipAddress) {
// 若进行更细校验,可对ip值与范围校验
if (ipAddress == null || ipAddress.isEmpty()) {
throw new IllegalArgumentException("convertIpStr illegal args");
}
String[] ipSlices = ipAddress.split("\\.");
int rs = 0;
for (int i = 0; i < ipSlices.length; i++) {
int intSlice = Integer.parseInt(ipSlices[i]) << 8 * i;
rs = rs | intSlice;
}
ipAddress = String.valueOf(rs);
return ipAddress;
}
/**
* 参数异常校验
*
* @param length
* @return
*/
public String getRandomLength(int length) {
if (length <= 0) {
throw new IllegalArgumentException("getRandomLength illegal args");
}
char[] randomChars = new char[length];
for (int i = 0; i < length; i++) {
randomChars[i] = (char)(Math.random()*26+ 'A');
}
return new String(randomChars);
}
处理完成后,再执行测试用例进行验证,此时可以看出明确的异常信息