1. 多走半里路
很多事情並不難,只是缺乏多走半里路的習慣!
反例
public boolean isInValid(String str) {
if (str == null || str.trim().length() == 0) {
return true;
}
return false;
}
多走一步,海闊天空
public boolean isInValid(String str) {
return (str == null) || (str.trim().length() == 0);
}
是個程式設計師都知道哪個更好!
還有一種見過很多次的程式碼:
public boolean isEmptyName() {
return StringUtils.isEmpty(name)? true: false;
}
難道不感覺到多餘嗎?
再走半步,山清水秀
public boolean isEmptyName() {
return StringUtils.isEmpty(name);
}
2. 空物件
NullPointException,讓我們防不勝防;尤其是使用第三方API,如果丟擲這個異常,會讓人有罵街的衝動。所以:若不想踩坑,請先別給別人挖坑。
理想要求:我們生產的所有public帶返回值的方法體中,不應該出現“return null”字樣。空物件就是這一思想的落地實現。
案例:查詢並返回一個名字為steven的老師或者學生。
public static SearchFilter name(String name) {
return human -> human.getName().equalsIgnoreCase(name);
}
public Human find(SearchFilter filter) {
for (Human human : humans) {
if (filter.isMatched(human)) {
return human;
}
}
return new NullHuman(); //如果你返回NULL,總有一天你會捱罵!
}
//Test
assertFalse(edu.find(name("steven")).isNull());
3. 單例項情結
談起設計模式,很多人會覺得高大上,於是當學會了其中一個時,便會不加節制的使用。
單例,GOF中最簡單的一個設計模式,很好用、也非常方便,但說實話很多人在濫用。我們的系統中隨處可見,但用前請叩問自己該類真的是在系統當中有並且只能有一個例項嗎?
否則請拒絕單例項。太多的單例項會給你的自動化測試帶來無窮的煩惱。
4. 充分使用列舉
列舉有一些比較強大的特性容易被忽視,比如列舉物件本身也可以攜帶變數;這一特性讓列舉在更多的場合發揮不可替代的功用。
舉例:不同武器具有不同的殺傷力,殺傷力變數可以攜帶到武器的列舉值中。
public enum EquipmentEnum {
Staff(20), Hammer(10);
double playerRisedAbility;
EquipmentEnum(double playerRiseAbility) {
this.playerRisedAbility = playerRiseAbility;
}
public double getRaiseAbility(double originalAbility) {
return Double.sum(originalAbility, playerRisedAbility);
}
}
5. 提高註釋質量
30%的程式碼註釋比的時代已經成為過去,最好的註釋的就是無需註釋,程式設計師應該致力於通過命名讓註釋消失。
大量的實踐證明,隨著重構頻率越來越高,重構過程中隨著程式碼的變更同步更新註釋的可能性幾乎為零。久而久之,註釋與程式碼就是南轅北轍。
6. 面向客戶的命名
好的命名能夠愉悅讀者的心情,讓人有想一睹作者真面目的衝動!
案例:
一個房地產開發商一個新的樓盤開盤,原價80萬每套,有如下優惠策略,但優惠政策可能隨著樓盤存量情況會不一樣,為開發商寫一個房款計算程式
- 老客戶買二套房可優惠4%,可累加
- 一次性全款客戶可優惠3%,可累加
public class Customer {
Benefiter benefit = new NullBenefiter();
public void is(Benefiter benefits) { //原本的set方法,重新命名會帶來意想不到的效果。
this.benefit = benefits;
}
public double shouldPay(double srcPrice) {
return srcPrice - benefit.benefit(srcPrice);
}
}
@FunctionalInterface
public interface Benefiter {
abstract double benefit(double srcPrice);
default Benefiter and(final Benefiter otherPolicy) {
return srcPrice -> (benefit(srcPrice) + otherPolicy.benefit(srcPrice));
}
}
受益於良好命名,客戶程式碼語義躍然紙上:customer.is(oldCustomer().and(fullPay()));
7. 使用多維陣列
陣列是任何一門相對高階的機器語言的必備資料結構,但是對物件導向開發人員而言,似乎對它有所忽略。
在一些特定的場景下,它能起到意想不到的效果。
案例:美元、法郎、人民幣三幣種間換算。其中美元:法郎=1:2;美元:人民幣=1:8;
public class ExchangeRate {
public static final int DOLLAR = 0;
public static final int FRANC = 1;
public static final int YMB = 2;
private static final double[][] rates = { //二維陣列定義幣種之間的匯率,簡單高效
{1.0d, 2.0d, 8.0d},
{0.5d, 1.0d, 4.0d},
{0.125d, 0.25d, 1.0d}
};
public static double getRateOf(Currency src, Currency tgt) {
return rates[src.ID][tgt.ID];
}
}
public class Cash {
private double value;
private Currency unit;
public Cash(double value, Currency unit) {
this.value = value;
this.unit = unit;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Cash) {
Cash other = (Cash) obj;
return value == other.value * getRateOf(other.unit, unit);
}
return false;
}
}
如果匯率的二維陣列使用配置檔案定義並初始化,那還可以將設計提升一個新的高度——易於擴充套件,甚至不需修改任何Java程式碼即可完成幣種的擴充套件。
8. 規約函式入參
當我們呼叫外部API時,最理想的就是零入參,即便有入參也希望是型別明確且範圍可列舉的,這樣會讓我們使用這個API時更具安全感!心同此理,當你把一個函式宣告為public時,請謹慎地定義它的入參。
如果把上述案例中獲取兩種幣種之間的匯率函式:
public static double getRateOf(Currency src, Currency tgt) {
return rates[src.ID][tgt.ID];
}
定義為
public static double getRateOf(int src, int tgt) {
return rates[src][tgt];
}
雖然函式實現簡單了一些,但可想而知,這個API會讓使用者很忌憚!陣列越界異常肯定是經常發生。
相反通過列舉規約了入參的型別和範圍,給使用者一些固定選項,使用者少了很多顧慮,而我們的程式也更加安全。
所以:我們對外提供的API入參型別儘量少用String、Int這種基本型別,給使用者發揮的空間越大,犯錯的可能越大,因為你把風險暴露在不受控的使用者手裡。
9. 經常進行封裝去重
你是否為反覆地編寫類似“if (tasks != null && tasks.size() > 0)”或者“if (name != null && (!name.isEmpty()))”的程式碼而心浮氣躁,其實你完全可以解救自己。
封裝一下
public class CollectionUtil {
public static boolean isEmpty(Collection collection) {
return collection == null || collection.isEmpty();
}
}
以及
public class StringUtil {
public static boolean isEmpty(String context) {
return context == null || context.isEmpty();
}
}
一切變得非常簡單,心情好了,效率也就高了!
10. 化解if/else的毒素
我們的系統充斥著if/else的邏輯,因為它邏輯“簡單”、易用!但隨著時間推移、需求的擴充套件,有一天你會發現自己都看不懂自己寫的if/else。
所以一開始就要學會拒絕這種邏輯的巢狀,又或者是當你看巢狀的複雜邏輯,大膽的對它進行重構。
案例:輸入一個1-1000的數值N,如果N是3或者3的倍數時,返回“FIZZ”;如果N是5或者5的倍數時, 返回“BUZZ”;既是3的倍數又是5的倍數時, 返回“FIZZBUZZ”;其他的則輸出N。
原實現:披著物件外殼的程式導向程式碼
public String say(int input) throws Exception {
if ( input < 1 || input > 1000){
throw new Exception("Invalid input");
}
if (input % 15 == 0) {
return "FIZZBUZZ";
}
if (input % 5 == 0) {
return "BUZZ";
}
if (input % 3 == 0) {
return "FIZZ";
}
return String.valueOf(input);
}
嗅一嗅其中的壞味道,如果將來需求擴充套件,比如引進7的倍數。照此邏輯,繼續新增if,問題是解決了,但總有一天你會懷疑人生!
改進一:引入責任鏈和模板方法模式
public String say(int input) throws Exception {
if (input < 1 || input > 1000) {
throw new Exception("Invalid input");
}
DefaultParser defaultParser = new DefaultParser(null);
MultiOfThreeParser multiOfThreeParser = new MultiOfThreeParser(defaultParser);
MultiOfFiveParser multiFiveParser = new MultiOfFiveParser(multiOfThreeParser);
MultiOfFifteenParser fifteenPaser = new MultiOfFifteenParser(multiFiveParser);
return fifteenPaser.parse(input);
}
public abstract class Parser {
abstract boolean isFixedResponsibility(int inputContext);
abstract String response(int inputContext) throws Exception;
public String parse(int inputContext) throws Exception {
if (isFixedResponsibility(inputContext)) {
return response(inputContext);
}
if (nextParser != null) {
return nextParser.parse(inputContext);
}
return NULL;
}
}
public class MultiOfFifteenParser extends Parser {
public MultiOfFifteenParser(Parser nextParser) {
super(nextParser);
}
@Override
public String response(int inputContext) throws Exception {
return FLAG_FOR_MULIT_OF_FIFTEEN;
}
@Override
boolean isFixedResponsibility(int inputContext) {
return inputContext % 15 == 0;
}
}
看起來好多了,但感覺好像還有哪兒不太對勁!
改進二:徹底穿上物件的外袍
敏銳的讀者可能還會發現上述改進還存在細微的小問題,那就是say方法裡面的異常分支處理。怎麼還存在一個if,對於一個完美主義強迫症患者而言,它的存在就是一個災難。繼續改進一下:
public String say(int input) throws Exception {
DefaultParser defaultParser = new DefaultParser(null);
MultiOfThreeParser multiOfThreeParser = new MultiOfThreeParser(defaultParser);
MultiOfFiveParser multiOfFiveParser = new MultiOfFiveParser(multiOfThreeParser);
MultiOfFifteenParser FifteenPaser = new MultiOfFifteenParser(multiOfFiveParser);
InvalidNumberParser invalidNumberPaser = new InvalidNumberParser(FifteenPaser);
return invalidNumberPaser.parse(input);
}
public class InvalidNumberParser extends Parser {
public InvalidNumberParser(Parser nextParser) {
super(nextParser);
}
@Override
String response(int inputContext) throws Exception {
throw new Exception("Invalid input");
}
@Override
boolean isFixedResponsibility(int inputContext){
return (inputContext < L_THRESHOLD) || (inputContext > H_THRESHOLD);
}
}
OK,Perfect!