31

[解锁新姿势] 兄dei,你代码需要优化了

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ%3D%3D&%3Bmid=2247492201&%3Bidx=1&%3Bsn=e92a68218221fdf862e4792776a2b06d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

点击上方 匠心零度 ,选择“ 设为星标

做积极的人,而不是积极废人

7r6fMf.jpg!mobile

来源:https://juejin.im/post/6844903983744548877

黑客(程序员)也是创作者,与画家、建筑师、作家一样。     ——《黑客与画家》

前言

在我们平常开发过程中,由于项目时间紧张,代码可以用就好,往往会忽视代码的质量问题。甚至有些复制粘贴过来,不加以整理规范。往往导致项目后期难以维护,更别说后续接手项目的人。所以啊,我们要编写出优雅的代码,方便你我他,岂不美哉?

下面分享一些我在开发中常用的编码中 小建议 ,如有不妥,欢迎大家一起交流学习。

卫语句

卫语句,就是把复杂的条件表达式拆分成多个条件表达式。比如 多个 if-elseif-else 嵌套, 可以拆分成多个 if 。如下面代码

代码:

-------------------- before  --------------------

public void today() {
if (isWeekend()) {
if (isFee()) {
System.out.println("study Android");
} else {
System.out.println("play a game");
}
} else {
System.out.println("go to work");
}
}


-------------------- after (建议) --------------------

public void today() {

// 提前过滤掉`特殊情况`
if (!isWeekend()) {
System.out.println("go to work");
return; // 提前return
}

//提前过滤掉`特殊情况`
if (isFee()) {
System.out.println("study Android");
return; // 提前return
}

// 更关注于 `核心业务`代码实现。
System.out.println("play a game");
}

提前过滤掉 特殊 情况,更关注 核心 业务逻辑

小函数

我们平常开发的时候,应该编写 小而美 函数,避免 函数过长 。一般函数最好在15行以内( 建议 ) 我们看看下面代码:

-------------------- before  --------------------

if (age > 0 && age < 18){
System.out.println("小孩子");
}

if (number.length() == 11){
System.out.println("符合手机号");
}

-------------------- after (建议) --------------------

private static boolean isChild(int age) {
return age > 0 && age < 18;
}

private static boolean isPhoneNumber(String number) {
return number.length() == 11;
}

if (isChild(age)){
System.out.println("小孩子");
}

if (isPhoneNumber(number)){
System.out.println("符合手机号");
}
复制代码

把判断语句抽取成一个个 小函数 , 这样代码更加清晰明了。

迪米特法则

概念:

迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽 可能少了解 。例如 当一条语句中 一个对象出现两个 .student.getName().equals("张三") ) 就是代码坏味道的表现,如下代码所示。

代码:

-------------------- before  --------------------

public class Student {

private String name;

public Student(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

public static void main(String[] args) {

Student student = new Student("张三");

// 注意看这里,
// 这里获取 student的name属性,在根据name属性进行判断
if (StringUtils.isNotBlank(student.getName()) && student.getName().equals("张三")) {
System.out.println("我的好朋友是 " + student.getName());
}
}


-------------------- after (建议) --------------------

public class Student {

... 省略name代码

// 新增一个 判断是否是我的好朋友方法
public boolean isGoodFriend(){
return StringUtils.isNotBlank(this.name) && this.name.equals("张三");
}
}

public static void main(String[] args) {

Student student = new Student("张三");

// 根据迪米特法则,把判断逻辑,抽取到 Student 内部,暴露出方法(isGoodFriend)
if (student.isGoodFriend()){
System.out.println("我的好朋友是 " + student.getName());
}
}

IDEA/Android Studio 抽取方法快捷键: option + command + M

Map 提取对象

我们在平常开发中,会使用到 map ,但是在面向对象开发理念中,一个 map 的使用,往往就会错过了 Java Bean 。建议使用 Java Bean 更直观。如下代码:

public static void main(String[] args) {

-------------------- before --------------------
Map<String, String> studentMap = new HashMap<>();
studentMap.put("张三", "男");
studentMap.put("小红", "女");
studentMap.put("李四", "男");

studentMap.forEach((name, sex) -> {
System.out.println(name + " : " + sex);
});

-------------------- after (建议) --------------------

List<Student> students = new ArrayList<>();
students.add(new Student("张三", "男"));
students.add(new Student("小红", "女"));
students.add(new Student("李四", "男"));

for (Student student : students) {
System.out.println(student.getName() + ":" + student.getSex());
}
}

笔者在编写这点时候,有所顾虑。肯定有小伙伴跳出来说, mapbean 不是一样吗?用 map 我还可以省去思考如何命名 Class 呢。但是从代码规范来说,这样代码设计不是更符合 Java 面向对象 的思想吗?

Stream

Java 8 API添加了一个新的抽象称为流 Stream ,可以让你以一种声明的方式处理数据。使得代码调用起来更加优雅~ 直接来看代码:

public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("张三", "男"));
students.add(new Student("李四", "男"));
students.add(new Student("小红", "女"));
students.add(new Student("小花", "女"));
students.add(new Student("小红", "女"));

-------------------- before --------------------
//统计男生个数
//传统的 for each 循环遍历
long boyCount = 0;
for (Student student : students) {
if (student.isBoy()) {
boyCount++;
}
}

System.out.println("男生个数 = " + boyCount);

-------------------- after (建议) --------------------

//统计男生个数
//stream 流遍历
long count = students.stream()
.filter(Student::isBoy) // 等同于.filter(student -> student.isBoy())
.count();

System.out.println("男生个数 = " + boyCount);
}

相比与 传统的 For 循环,更推荐大家使用 stream 遍历。 stream 流的链式调用,还有许多骚操作,如 sorted , map , collect 等操作符,可以省去不必要 if-elsecount 等判断逻辑。

多态

Java 三大特性之一, 多态 ,相信大家都不会陌生,多态的好处就是根据对象不同类型采取不同的的行为。我们常常在编写 switch 语句的时候,如果改用多态,可以把每个分支,抽取到一个子类内的覆写函数中,这就更加灵活。

我们有这样一个需求,编写一个简单计算器方法,我们先来看一小段代码:

    -------------------- before  --------------------
public static int getResult(int numberA, int numberB, String operate) {
int result = 0;
switch (operate) {
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
result = numberA / numberB;
break;
}
return result;
}

-------------------- after (建议) --------------------

abstract class Operate {
abstract int compute(int numberA, int numberB);
}

class AddOperate extends Operate {

@Override
int compute(int numberA, int numberB) {
// TODO 在这里处理相关逻辑
return numberA + numberB;
}
}

... SubOperate, MulOperate, DivOperate 也和 AddOperate一样这里就不一一贴出

public static int getResult(int numberA, int numberB, String operate) {
int result = 0;
switch (operate) {
case "+":
result = new AddOperate().compute(numberA, numberB);
break;
case "-":
result = new SubOperate().compute(numberA, numberB);
break;
case "*":
result = new MulOperate().compute(numberA, numberB);
break;
case "/":
result = new DivOperate().compute(numberA, numberB);
break;
}
return result;
}

有小伙伴可能会说,你这不是更复杂了吗?

对比起单纯的 switch ,我们可以这样理解:

  • 虽然在类上有所增加,但是通过多态,把对应操作的逻辑分离出来,使得代码耦合度降低。

  • 加法
    AddOperate
    getResult
    
  • 代码可读性更好,语义更加明确。

但是这里会存在一些问题,如果我们新增一个 平方根平方 等计算方式, 就需要修改 switch 里面的逻辑,新增一个条件分支。下面我们再来看看更进一步的优化。

反射

通过上面例子,我们可以进一步优化,通过 反射 生成对应的 Class ,然后在调用 compute 方法。如下代码:

public static <T extends Operate> int getResult(int numberA, int numberB, Class<T> clz) {
int result = 0;
try {
return clz.newInstance().compute(numberA, numberB);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return result;
}
}


public static void main(String[] args) {
// 调用的时候直接传递 class 即可
System.out.println(getResult(1, 2, SumOpearte.class));
}

根据传入 class 参数,然后生成对应 Opearte 处理类, 对比多态方式,我们这里采用反射,使得代码耦合度大大降低,如果在增加 平方根平方 等计算方式。我们只需要 新增一个 class 继承 Opearte 即可, getResult 不用做任何修改。

需要注意的是,不是所有 switch 语句都需要这样替换, 在面对简单的 switch 语句,就不必要了, 避免 过度设计 的嫌疑。如下代码:

public String getResult(int typeCode) {
String type = "";
switch (typeCode) {
case 0:
type = "加法";
break;
case 1:
type = "减法";
break;
case 2:
type = "乘法";
break;
case 3:
type = "除法";
break;
}
return type;
}

END

如果读完觉得有收获的话,欢迎点【好看】,关注【匠心零度】,查阅更多精彩历史!!!

nmqYriz.gif!mobile

让我“ 好看 ”  bYreamJ.gif!mobile


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK