优秀程序员都在注意的 10 个点

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);
}

<typo id="typo-290" data-origin="是个" ignoretag="true">是个</typo>程序员都知道哪个更好!

还有一种见过很多次的代码:

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 时,请谨慎地定义它的入参。

如果<typo id="typo-3735" data-origin="把" ignoretag="true">把</typo>上述案例中获取两种币种之间的汇率函数:

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);
}

嗅一嗅其中的<typo id="typo-5231" data-origin="坏" ignoretag="true">坏</typo>味道,如果将来需求扩展,比如引进 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!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容