这几天,工作中一直在写一些重复性代码,为了让重复性工作妙趣横生,我将注意力转移到编码细节上,而不是期待领导给我分配一些所谓“不着边际的高大上工作内容”。当然我本人并不喜欢大量编码,而是更加倾向于用简短的算法去解决大量的重复工作,因此我每天都会抽出一些时间,用来刷 LeetCode 和 CSDN Daily Algorithm Trainning。
再说一些题外话,我认为大型企业应该培养几位“编码规范导师”,来解决大量的重复性工作造成的工作效率低下以及在其影响下产生的“低质量编码”给项目整体性能产生的负面影响。
以下,是我在工作中总结的一些有趣的编码细节,它们可能不会给项目带来整体效率的提升,但却可以改变你对编码的兴趣以及代码运行逻辑的认识。如果您有更好的编码细节可以在评论区分享讨论,欢迎您的留言。
一、重复定义同类型的变量
示例:
private void example(){
int a = 10;
int b = 20;
System.out.println(a + b);
}
优化:
private void optimized(){
int a = 10, b = 20;
System.out.println(a + b);
}
说明:其实这是一个非常容易忽略的细节,正因为它对整体项目性能影响微乎其微,才会让很多人忽视。假设一个方法中要定义10个这样的变量,你会怎么写?每个变量都写一个 int 类型吗?如果你经常阅读一些复杂算法,你发现这种情况还是很容易出现的。有时编码雅观也是一件值得追求的乐事。
二、有效利用
示例(1):
private int cache = 10;
private Boolean isOptimized(){
return (cache & 1) == 0;
}
private void example1(){
if(isOptimized() != null && isOptimized()) {
System.out.println(isOptimized());
}
}
优化:
private void optimized1(){
Boolean b;
if((b = isOptimized()) != null && b) {
System.out.println(b);
}
}
下面列举一段 ConcurrentHashMap 中的 putVal() 源码,大家可以细品其逻辑
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// ...
}
示例(2)- 罗马数字转阿拉伯数字算法:
private int romanToArabicNumberExample2(String s){
int res = 0;
char cn;
for (int i = 0, n = s.length(); i < n; i++) {
res += getVal(s.charAt(i));
if (i < n - 1) {
if ((s.charAt(i + 1) == 'V' || s.charAt(i + 1) == 'X') && s.charAt(i) == 'I') {
res -= 2;
} else if (s.charAt(i) == 'X' && (s.charAt(i + 1) == 'L' || s.charAt(i + 1) == 'C')) {
res -= 20;
} else if (s.charAt(i) == 'C' && (s.charAt(i + 1) == 'D' || s.charAt(i + 1) == 'M')) {
res -= 200;
} else {
cn = '0';
i--;
}
i++;
res += getVal(s.charAt(i + 1));
}
}
return res;
}
优化:
public int optimized2(String s) {
char c, cn;
int res = 0;
for (int i = 0, n = s.length(); i < n; i++) {
res += getVal(c = s.charAt(i));
if (i < n - 1) {
if (((cn = s.charAt(i + 1)) == 'V' || cn == 'X') && c == 'I') {
res -= 2;
} else if (c == 'X' && (cn == 'L' || cn == 'C')) {
res -= 20;
} else if (c == 'C' && (cn == 'D' || cn == 'M')) {
res -= 200;
} else {
cn = '0';
i--;
}
i++;
res += getVal(cn);
}
}
return res;
}
说明:示例 2 中两者的比较是不是后者代码更简洁,同时避免重复获取重复性使用的元素。这种优化在大量 if else 判断中的优化作用尤为凸显,示例 1 中,一共调用了 3 次 isOptimized1() 方法,由于使用的是 && 逻辑与 判断,在一个 if 子句中两次调用同一个方法,前者如果是 true ,后者都会执行,因此这么写是很不必要的。
回到示例 2,由于 String s 中的每一个 char 字符都会对应 阿拉伯数字 不同的转换状态和情形,因此在获取每一个 i 下标的字符之后,该字符会被重复使用,去进行状态校验。当然这里抛开多线程来讨论这个问题 。
通过这些细节,我们了解到 if else 的执行逻辑就是:当不满足条件时,满足条件之前的所有条件都会被 JVM 执行,即便是 逻辑或 ||, 在一个 if 子句中同时调用两个同样方法,如果前者不为 true, 后者同样会被执行,特别是多个,直到满足条件或都不满足条件为止。就像是 Switch case 一样。
三、硬编码
示例:
private void example(){
Scanner scan = new Scanner(System.in);
System.out.println("How old are you?");
int age = scan.nextInt();
if(age < 16)
System.out.println("Sorry, you're not quite old enough to drive!");
else
System.out.println("Yeah! Happy driving!");
}
优化:
private static HashMap<Integer, String> tips;
static {
tips = new HashMap<Integer, String>() {
{ put(0, "How old are you?"); }
{ put(15, "Sorry, you're not quite old enough to drive!"); }
{ put(16, "Yeah! Happy driving!"); }
};
}
private void optimized(){
Scanner scan = new Scanner(System.in);
System.out.println(tips.get(0));
int age = scan.nextInt();
if(age < 16)
System.out.println(tips.get(15));
else
System.out.println(tips.get(16));
}
说明:这个是老生常谈了吧,虽然每个人都会犯,但尽量避免为好,如果项目中大量使用这种硬编码,后续如果这些提示需要升级,修改也是一件麻烦事情!大家最熟悉的 SpringMVC 中就会出现很多硬编码。在返回视图解析的页面时,我们通常会 return "/xxxx/xxxxx/xxxx.html";。这种写法其实就是典型的硬编码,如果后续页面因升级需要删除或者更名,是不是需要回去修改呢?最好的解决办法是,如果预算足够的话,单独建立一个数据库,来管理这些文件,并为其在 OCR 中创建一个菜单分支,当删除某个文件时,可以对所有返回该视图的方法更改为 return RedirectToPages.PagesLostHtml。后者是一个变量,并且该值是从数据库拿到的,不会存在Hard Code 的问题。
好了,今天先到这里了,该休息了。后续有更多细节,会在这篇博客里继续更新,希望小伙伴更多关注~~