重构思维系列-如何写出好代码1

前言:首先要思考一个问题,什么是好代码?好代码和差代码的区别是什么?

有的人写的代码一眼看上去,就感觉很好。有的人写的代码一看上去就感觉很烂。即便如此,对于计算机来说,不管好的代码还是烂的代码,计算机可能都会执行的结果都是一样的没有区别。

所以我在这里大胆的定义,好的代码应该是易于人理解的,烂的代码是只有计算机能读的懂。

下面用一个案例来感受一下把一段平庸的代码,重构成相对好的代码。

案例:

这个案例是一个闹钟service,它会早上、中午、晚上三个时间定时播放指定的音乐,比如中午休息时,会播放60秒,音量持续1分钟;晚上的时候会播放1个小时,音量是15。

看完这个案例,你也要思考一下,如果是你,你会如何重构优化它?

package service.alarmClock;

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class AlarmClock {
    /**
     * 闹钟提醒-功能介绍:
     * 每当早上、中午、晚上时,分别有对应类型的播放音乐,分别对应不同的音量、不同的曲子、不同的播放时长
     * 1、如果是周末就不要响
     * 2、如果是早上7点,播放起床闹钟,    持续1小时,音量3
     * 3、如果是中午12点,播放中午休息闹钟,持续1分钟,音量80
     * 4、如果是晚上12点,播放睡觉催眠曲, 持续30分钟,音量15
     */
    public void run() throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException {

        String soundFileSrc = ""; //定义闹钟铃声的声音文件地址
        int continueSeconds = 0; //闹钟持续的时间(单位是秒)
        int soundVolume = 0;    //闹钟的音量

        while (true) {
            // 每秒循环一次
            Thread.sleep(1000);

            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
            // 获取当前时分秒格式的时间
            String currentTime = formatter.format(calendar.getTime());

            // 周末不提醒,获取今天是星期几,西方周日是第1天,周六是第7天
            int week = calendar.get(Calendar.DAY_OF_WEEK);
            if (week == 1 || week == 7) {
                continue;
            }

            switch (currentTime) {
                case "07:00:00":
                    soundFileSrc = "./wake.mp3";
                    continueSeconds = 60 * 60;
                    soundVolume = 3; //微弱的音量
                    break;
                case "12:00:00":
                    soundFileSrc = "./rest.wav";
                    continueSeconds = 60;
                    soundVolume = 80;
                    break;
                case "00:00:00":
                    soundFileSrc = "./sleep.mp3";
                    continueSeconds = 60 * 30;
                    soundVolume = 15;
                    break;
            }
            System.out.println("当前时间是: " + currentTime);

            if (!soundFileSrc.equals("")) {
                AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(soundFileSrc));

                Clip clip = AudioSystem.getClip();

                clip.open(audioInputStream);

                FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);

                gainControl.setValue(soundVolume);

                clip.start();
                Thread.sleep(continueSeconds * 1000);
                soundFileSrc = "";
                clip.stop();
            }
        }
    }
}

开始重构:

1、对当前时间进行抽象:

上面的案例,当我看到这段代码的时候,让我心理感到特别的不舒服,我的内心迫不及待的想立即先对获取当前时间currentTime和判断是不是周末week这两个地方下手改造一下,把这两个抽出来看看。

这里可能有的人想不开,没有重构之前,Calendar calendar = Calendar.getInstance(); 这一句代码一次执行的结果,获取时间和获取周几两个地方可以复用,但是抽象出两个方法以后,两个地方都要各自执行一次,这不就影响性能了吗。这种思想要批评一下,试想一下,如果所有的地方都要按照这种小的复用而影响大局,使得大局上看着乱的话,当代码行数非常多的时候就会越来越乱,就会牺牲更多的时间和精力去梳理代码逻辑,得不偿失。好代码的标准一定是清晰易读,耦合度低,每个方法、模块单独是一个功能互不影响。像这种对性能的影响微乎其微,如果真的有很大的性能影响的话,再另当别论。


    public String currentTime() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        // 获取当前时分秒格式的时间
        return formatter.format(calendar.getTime());
    }

    // 是否是周末
    public Boolean isWeekend() {
        Calendar calendar = Calendar.getInstance();
        int week = calendar.get(Calendar.DAY_OF_WEEK);
        if (week == 1 || week == 7) {
            return true;
        }
        return false;
    }

这时候核心层代码变成了这样:

    public void run() {

        String soundFileSrc = ""; //定义闹钟铃声的声音文件地址
        int continueSeconds = 0; //闹钟持续的时间(单位是秒)
        int soundVolume = 0;    //闹钟的音量

        while (true) {

            Thread.sleep(1000);

            String currentTime = this.currentTime();

            if (this.isWeekend()) {
                continue;
            }

            switch (currentTime) {
                case "07:00:00":
                    soundFileSrc = "./wake.mp3";
                    continueSeconds = 60 * 60;
                    soundVolume = 3; //微弱的音量
                    break;
                case "12:00:00":
                    soundFileSrc = "./rest.wav";
                    continueSeconds = 60;
                    soundVolume = 80;
                    break;
                case "00:00:00":
                    soundFileSrc = "./sleep.mp3";
                    continueSeconds = 60 * 30;
                    soundVolume = 15;
                    break;
            }
            System.out.println("当前时间是: " + currentTime);

            if (!soundFileSrc.equals("")) {
                AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(soundFileSrc));

                Clip clip = AudioSystem.getClip();

                clip.open(audioInputStream);

                FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);

                gainControl.setValue(soundVolume);

                clip.start();
                Thread.sleep(continueSeconds * 1000);
                soundFileSrc = "";
                clip.stop();
            }
        }
    }

2、对音乐播放功能进行抽象

看到播放音乐那一坨,也让人感到头疼,但那个地方的定义是执行音乐进行播放这样一个功能,所以也应该进行抽象出来。由于播放一个音乐后,需要把音乐路径初始化,所以顺便也把音乐路径、播放时间、播放音量作为对象的属性提取出来,方便播放后进行初始化。

    // 闹钟对象的属性
    private String soundFileSrc;
    private int continueSeconds;
    private int soundVolume;

播放功能抽象

    public void playSound() {
        if (!this.soundFileSrc.equals("")) {
            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(this.soundFileSrc));

            Clip clip = AudioSystem.getClip();

            clip.open(audioInputStream);

            FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);

            gainControl.setValue(this.soundVolume);

            this.initSoundFileSrc();

            clip.start();
            Thread.sleep(this.continueSeconds * 1000);
            clip.stop();
        }
    }

    public void initSoundFileSrc() {
        this.soundFileSrc = "";
    }

再来看一看全景:

package service.alarmClock;

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class AlarmClock {
    /**
     * 闹钟提醒-功能介绍:
     * 每当早上、中午、晚上时,分别有对应类型的播放音乐,分别对应不同的音量、不同的曲子、不同的播放时长
     * 1、如果是周末就不要响
     * 2、如果是早上7点,播放起床闹钟,    持续1小时,音量3
     * 3、如果是中午12点,播放中午休息闹钟,持续1分钟,音量80
     * 4、如果是晚上12点,播放睡觉催眠曲, 持续30分钟,音量15
     */
    private String soundFileSrc;
    private int continueSeconds;
    private int soundVolume;

    public void run() {

        while (true) {
            // 每秒循环一次
            Thread.sleep(1000);

            String currentTime = this.currentTime();

            if (this.isWeekend()) {
                continue;
            }

            switch (currentTime) {
                case "07:00:00":
                    this.soundFileSrc = "./wake.mp3";
                    this.continueSeconds = 60 * 60;
                    this.soundVolume = 3; //微弱的音量
                    break;
                case "12:00:00":
                    this.soundFileSrc = "./rest.wav";
                    this.continueSeconds = 60;
                    this.soundVolume = 80;
                    break;
                case "00:00:00":
                    this.soundFileSrc = "./sleep.mp3";
                    this.continueSeconds = 60 * 30;
                    this.soundVolume = 15;
                    break;
            }
            System.out.println("当前时间是: " + currentTime);

            this.playSound();
        }
    }

    public String currentTime() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        // 获取当前时分秒格式的时间
        return formatter.format(calendar.getTime());
    }

    // 是否是周末
    public Boolean isWeekend() {
        Calendar calendar = Calendar.getInstance();
        int week = calendar.get(Calendar.DAY_OF_WEEK);
        if (week == 1 || week == 7) {
            return true;
        }
        return false;
    }

    public void playSound() {
        if (!this.soundFileSrc.equals("")) {
            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(this.soundFileSrc));

            Clip clip = AudioSystem.getClip();

            clip.open(audioInputStream);

            FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);

            gainControl.setValue(this.soundVolume);

            this.initSoundFileSrc();

            clip.start();
            Thread.sleep(this.continueSeconds * 1000);
            clip.stop();
        }
    }

    public void initSoundFileSrc() {
        this.soundFileSrc = "";
    }
}

3、对switch部分进行抽象。

在看到switch的时候,思考一下,这个功能是在做什么,经过自习阅读,可以发现,这里是为了获取根据当前的时间应该播放哪一个音频,播放多少时间,播放的音量有多大等等。

3.1、复杂的情况的重构:这时候需要考虑,如果把这个播放看做一个播放器对象,用多态的形式给播放器对象下面增加三个子对象也是可以的。但很明显仅仅这三个属性还不足以费劲巴拉的去抽象出来三个对象,所以暂不考虑了。

3.2、简单的情况的重构: 与上面相反,如果switch只是简单的功能,按照简单的方法进行重构。

3.2的实现:

直接看代码,代码中每一步都有注释

这样有一个好处,就是当我们对代码进行增加或删除修改等等操作时,不用再去switch一个一个的去看逻辑,扩展性维护性更强更清晰。

    // 第一步,定义一个结构体,用于存储音频的三个属性
    public class SoundEntity {
        public SoundEntity(String soundFileSrc, int continueSeconds, int soundVolume) {
            this.soundFileSrc = soundFileSrc;
            this.continueSeconds = continueSeconds;
            this.soundVolume = soundVolume;
        }

        public String soundFileSrc;
        public int continueSeconds;
        public int soundVolume;

    }



    // 第二步,把每个时间对应的音频信息塞进一个map中
    Map<String, SoundEntity> map = new HashMap<String, SoundEntity>();
    map.put("07:00:00", new SoundEntity("./wake.mp3", 60 * 60, 3));
    map.put("12:00:00", new SoundEntity("./rest.wav", 60, 80));
    map.put("00:00:00", new SoundEntity("./sleep.mp3", 60 * 30, 15));


    // 第三步,在读秒的while循环中,把时间当做key去map中比对,从而拿到对应的音频信息
    if (!map.containsKey(currentTime)) {
        continue;
    }
    this.soundFileSrc = map.get("currentTime").soundFileSrc;
    this.continueSeconds = map.get("currentTime").continueSeconds;
    this.soundVolume = map.get("currentTime").soundVolume;

再看一下3.2版修改的代码全貌:

package service.alarmClock;

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class AlarmClock {
    /**
     * 闹钟提醒-功能介绍:
     * 每当早上、中午、晚上时,分别有对应类型的播放音乐,分别对应不同的音量、不同的曲子、不同的播放时长
     * 1、如果是周末就不要响
     * 2、如果是早上7点,播放起床闹钟,    持续1小时,音量3
     * 3、如果是中午12点,播放中午休息闹钟,持续1分钟,音量80
     * 4、如果是晚上12点,播放睡觉催眠曲, 持续30分钟,音量15
     */
    private String soundFileSrc;
    private int continueSeconds;
    private int soundVolume;

    public void run() throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException {

        Map<String, SoundEntity> map = new HashMap<String, SoundEntity>();

        map.put("07:00:00", new SoundEntity("./wake.mp3", 60 * 60, 3));
        map.put("12:00:00", new SoundEntity("./rest.wav", 60, 80));
        map.put("00:00:00", new SoundEntity("./sleep.mp3", 60 * 30, 15));



        while (true) {
            // 每秒循环一次
            Thread.sleep(1000);

            String currentTime = this.currentTime();

            System.out.println("当前时间是: " + currentTime);

            if (this.isWeekend()) {
                continue;
            }

            if (!map.containsKey(currentTime)) {
                continue;
            }
            this.soundFileSrc = map.get("currentTime").soundFileSrc;
            this.continueSeconds = map.get("currentTime").continueSeconds;
            this.soundVolume = map.get("currentTime").soundVolume;

            this.playSound();
        }
    }

    public String currentTime() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        // 获取当前时分秒格式的时间
        return formatter.format(calendar.getTime());
    }

    // 是否是周末
    public Boolean isWeekend() {
        Calendar calendar = Calendar.getInstance();
        int week = calendar.get(Calendar.DAY_OF_WEEK);
        if (week == 1 || week == 7) {
            return true;
        }
        return false;
    }

    public void playSound() throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException {
        if (!this.soundFileSrc.equals("")) {
            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(this.soundFileSrc));

            Clip clip = AudioSystem.getClip();

            clip.open(audioInputStream);

            FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);

            gainControl.setValue(this.soundVolume);

            this.initSoundFileSrc();

            clip.start();
            Thread.sleep(this.continueSeconds * 1000);
            clip.stop();
        }
    }

    public void initSoundFileSrc() {
        this.soundFileSrc = "";
    }

    public class SoundEntity {
        public SoundEntity(String soundFileSrc, int continueSeconds, int soundVolume) {
            this.soundFileSrc = soundFileSrc;
            this.continueSeconds = continueSeconds;
            this.soundVolume = soundVolume;
        }

        public String soundFileSrc;
        public int continueSeconds;
        public int soundVolume;

    }
}

本文完。

用一个我自己编造的小案例,来重现了一次重构的思路,要记住好代码和怀代码的区别是是否容易被人类理解,是否容易扩展,是否容易维护等等。

当我们对臃肿的代码进行抽象的时候,一定要以"它的意义"为单位,每一个被抽象出来的方法都应该有它单独的、明确的意义,意义也可以被再细分,方法中抽象方法也是没有问题的。

猜你喜欢

转载自blog.csdn.net/weixin_38256311/article/details/123217834