Как провести рефакторинг вашего «пустого ада» с минимальными затратами

Java

В некоторых старых проектах мы должны часто сталкиваться со следующими сценариями (если мы не можем столкнуться с ними, нам не нужно продолжать чтение): Некоторые тела методов некоторых классов имеют логику для оценки одной и той же переменной. Если в теле этих методов не так много методов, которые нужно обнулить, хорошо сказать, что если большинство методов нужно обнулить для переменной, то эти коды часто сложно поддерживать.

Например:

Этот метод суждения о большом количестве одной и той же переменной принесет следующие недостатки (тщательный вкус недостатков является важной основой для понимания последующих улучшений):

  • Повторение этой логики оценки null в нескольких местах приведет к созданию большого количества повторяющегося кода, который нелегко поддерживать.
  • Если большая часть кода в классе относится к этому типу кода, коллеги обычно тратят больше времени на его понимание и дольше думают над его расширением.
  • Эта нулевая логика не может защитить недавно введенные новые методы от нулевых.Если новый метод написан, но нулевая логика забыта, могут возникнуть нулевые ошибки.

Рассмотрим конкретный пример: в проектах электронной коммерции у нас всегда есть единый вход и выход для обработки платежей, существует много видов платежного поведения, например, распространенный — использовать купоны и не использовать купоны.

public class PayProcess {


    private CouponInfo couponInfo;

    public void setCouponInfo(CouponInfo couponInfo) {
        this.couponInfo = couponInfo;
    }

    //检查支付的合法性
    public boolean checkLegitimate() {
        if (null != couponInfo) {
            //其实这里主要就是检查一下券有没有过期
            return couponInfo.checkLeg();
        }
        //如果没有券 就意味着合法
        return true;
    }

    //获取实际支付金额
    public int getPayValue(int totalValue) {
        if (null != couponInfo) {
            //支付总金额 减去 券的金额 自然就是需要支付的金额
            return totalValue - couponInfo.getCouponValue();
        }
        return totalValue;
    }

    //获取优惠券的类型
    public int getCouponType() {
        if (null != couponInfo) {
            return couponInfo.getCouponType();
        }
        //如果压根就没有优惠券  这里就返回0 0代表没有优惠券, 实际业务中 我们不能写这种魔法数字
        //一定要定义成常量,这里为了演示方便 我就偷懒了
        return 0;
    }


}

class CouponInfo {


    public boolean checkLeg() {
        //实际中 我们会校验券的时间 等等,现在为了演示方便 我就直接返回一个false了
        //大家知道意思就好
        return false;
    }

    public int getCouponValue() {
        //返回券的实际价值,这里也是为了演示方便 我直接返回一个固定值
        return 3;
    }

    public int getCouponType() {
        //返回券的种类,看看是无敌券?还是限定品类的券 等等
        //为了演示方便 我直接写一个int值,实际写的时候 一定要写成常量
        return 2;
    }
}

Когда мы используем эту платежную систему, определенно будет несколько сценариев использования, в некоторых сценариях используются купоны, а в некоторых нет. Например:

public static void main(String[] args) {
        //这里是用券的
        PayProcess p1 = new PayProcess();
        p1.setCouponInfo(new CouponInfo());

        //这里是没有用券的
        PayProcess p2 = new PayProcess();
        p1.setCouponInfo(null);

    }

Невооруженным глазом видно, что нашему PayProcess приходится писать много пустого кода. Защитное программирование никогда не помешает. Это также важный момент, упомянутый в Руководстве по разработке Java для Ali.Если массив должен быть оценен как пустой, он должен быть оценен как пустой, и если массив должен быть оценен как выходящий за пределы, массив должен быть вне границ. границы. Идея правильная, но такой код легко может попасть в ад пустых суждений. О каких недостатках говорилось в начале нашей статьи.

Как рефакторить эту часть старого кода? Сделать его таким плохим?

Добавляем новый класс (на самом деле основная цель здесь — единообразно обрабатывать нулевые случаи):

//这里面的逻辑 注意看 其实和PayProcess 里面当券为null的时候逻辑一样的
public class NullCouponInfo extends CouponInfo {
    public boolean checkLeg() {
        return true;
    }

    //没有券  那券的价值就为0
    public int getCouponValue() {
        return 0;
    }

    // 没有券 自然type为0
    public int getCouponType() {
        return 0;
    }
}

Тогда наш класс оплаты можно значительно обновить:

public class PayProcess {


    private CouponInfo couponInfo;

    public void setCouponInfo(CouponInfo couponInfo) {
        this.couponInfo = couponInfo;
    }

    //检查支付的合法性
    public boolean checkLegitimate() {
        //其实这里主要就是检查一下券有没有过期
        return couponInfo.checkLeg();
    }

    //获取实际支付金额
    public int getPayValue(int totalValue) {
        //支付总金额 减去 券的金额 自然就是需要支付的金额
        return totalValue - couponInfo.getCouponValue();
    }

    //获取优惠券的类型
    public int getCouponType() {
        return couponInfo.getCouponType();
    }


}

При последнем звонке, когда купон пуст, не передавать null в качестве параметра

   PayProcess p3 = new PayProcess();
   p1.setCouponInfo(new NullCouponInfo());

Вы можете видеть, что после этого изменения вся логика стала намного понятнее, а читабельность тоже очень хорошая. Также не так много дублированного кода. Конечно, здесь есть скрытая опасность: Когда нам нужно добавить какие-то новые методы в купон, нам нужно изменить NullCouponInfo в дополнение к изменению CouponInfo.Если мы изменим его, это оставит скрытые опасности в классе PayProcess. Хотя не будет исключения нулевого указателя, но часто мы не получим желаемых результатов.

Фактически, для этого сценария нам нужно только абстрагировать интерфейс. Пусть наши CouponInfo и NullCouponInfo наследуют интерфейс: IКупон (Не делайте CouponInfo родительским классом NullCouponInfo.), так что все новые методы нужно добавлять только в интерфейс, чтобы при компиляции нам предлагалось реализовать в обоих подклассах. Чтобы избежать вышеупомянутых скрытых опасностей. (Код здесь относительно прост и не будет демонстрироваться)

Суммировать:Заменить всю нулевую логику нулевым объектом, вы сможете решить задачу, поставленную в начале нашей статьи. Вот некоторые ключевые моменты:

  • Если бизнес-логика проста, введение режима нулевых объектов вместо этого увеличит объем кода. Поэтому пользователям необходимо иметь определенное суждение о сложности всего бизнеса.
  • При использовании нулевого объекта нужно писать комментарии, особенно в процессе рефакторинга, вызывающий должен быть уведомлен об окончании рефакторинга, потому что если вы введете этот паттерн и ваши коллеги не узнают об этом, они могут не написать логика для нулевого случая. Конечно, использование интерфейса может обойти эту ситуацию.
  • Даже с интерфейсами общая сложность кода немного возрастает.
  • Это не нулевой сценарий со строгой проверкой, и слепая имитация нулевого объекта усложнит дизайн.
  • Не приговорите, чтобы опустошить этот сценарий, мы думаем об этом очень часто определить, пусто ли список, вы также можете воспользоваться таким подходом.