Пожалуйста, прекратите писать код реликвии

задняя часть
Пожалуйста, прекратите писать код реликвии

Введение

Привет всем Я считаю, что каждый в большей или меньшей степени подвергался воздействию «кодов предков» в своей повседневной работе.

  1. Тысячи строк для класса, сотни строк для метода, модель серьезной анемии.
  2. Метод внутренней бизнес-логики с толку, то, если / иначе видно везде
  3. Критическая бизнес-логика не аннотирована, везде видны магические значения
  4. Дублирующийся код везде
  5. ...

1.jpeg

В начале года блогеры присоединились к новой продуктовой линейке для разработки нового продукта, а персонал был перетянут из других отделов или набран заново. Требования повторялись около полугода, прежде чем я занялся разработкой. Так как это новый продукт, и каждый является недавно сформированной командой, стиль кодирования у всех очень разный. Каждый раз, когда мне нужно перебрать соответствующий модуль, меня тошнит, когда я смотрю на код. Очевидно, просто прокомментировал строку кода, которая необъяснимым образом привела к нескольким ошибкам, и я не мог бы изменить ее, даже если бы захотел. После этого года я пообщался с руководителями по поводу унификации технической архитектуры и сформулировал некоторые соответствующие рекомендации по НИОКР. Сегодня я поделюсь с вами, как отделить систему и изменить неприятный запах в коде. Если что-то не так, пожалуйста, укажите на это и двигайтесь вперёд~

2. Класс сущности

Как носитель данных класс сущностей обязательно будет контактировать со всеми в их повседневной работе, но правильно ли вы его используете?

Расскажите мне о коде, который я видел в своем предыдущем проекте. Носитель данных, полученный путем запроса данных, носитель данных взаимодействия уровня службы, носитель данных взаимодействия уровня rpc и носитель данных взаимодействия веб-уровня, все сконцентрированы в одном объекте. Этот подход не вызовет серьезных проблем, когда бизнес-сценарий особенно прост, но если это относительно большая система бизнес-приложений. Это вызовет проблемы.

Например

image-20210530145628068.png

Пользовательская таблица в четырех полях, идентификатор пользователя, имя пользователя, номер телефона, пароль пользователя. Теперь нам нужно отобразить пользовательские данные на странице меню управления пользователями. Если дело только в одном субъекте, я проверяю данные из базы данных в четырех полях, передавать пароль на внешний интерфейс, конечно, не целесообразно. Сделайте некоторую десенсибилизацию, пароль установлен на ноль. Но вы все еще можете видеть переднюю часть пакета

{
	"userId":1
  "userName":"admin"
  "mobile":"13888888888"
  "password":null
}

Очевидно, что это неразумно, данные, возвращаемые во внешний интерфейс, должны быть

{
	"userId":1
  "userName":"admin"
  "mobile":"13888888888"
}

Очевидно, что для носителя данных в java особенно важна многоуровневость каждого слоя. Я обычно наношу носитель данных следующим образом

PO: постоянные объекты, атрибуты сущностей и поля таблицы находятся во взаимно однозначном соответствии, генерируются на уровне DAO и используются на уровне службы.

BO: бизнес-объекты, агрегирование данных уровня PO или запросы и агрегирование данных, связанных с несколькими таблицами, а также будут методы обработки бизнес-логики для атрибутов. Создается уровень DAO/Service и используется уровень Service.

DTO: объект передачи данных, общий уровень службы терминов, уровень rpc, уровень контроллера, носитель для передачи массива, без внутренней логики.

VO: Слой дисплея данных используется для слоя контроллера. Здесь я привык к параметрам метода, который используется для соответствия структурной разницы между DTO и слоем VO.

Query: Параметр запроса, входной параметр метода уровня контроллера, получает параметр типа запроса внешнего интерфейса.

Command: Инструктивные параметры, такие как добавленные пользователем, измененные пользователем носители данных.


проиллюстрировать:

1. Я часто смешиваю DTO и VO.Если носитель передачи данных может быть собран и использован только в слое отображения контроллера, его можно напрямую вернуть на внешний интерфейс.Если он не соответствует требованиям внешнего интерфейса, соответствующий класс преобразователя необходимо написать для обработки.Логика преобразования не может быть написана в DTO и VO, они просто носители данных.

2. Команда и DTO/VO. Некоторые онлайн-блогеры будут использовать VO или DTO в качестве входного параметра веб-слоя для добавления, удаления и изменения данных. С точки зрения структуры и определения проблем нет, но к носителю данных с инструкциями это несколько не имеет отношения. Мое понимание DTO и VO заключается в том, что они являются данными результатов, продуктом обработки бизнес-логики. Команда — это инструктивные данные.Благодаря параметру типа команды и бизнес-логике уровня BO данные сопоставляются с уровнем PO для взаимодействия с базой данных.

3. Параметр Query аналогичен параметру Command.Некоторые люди часто используют DTO или VO для передачи данных.По этой же причине бизнес-семантика недостаточно сильна.

3. Модель анемии

Не говоря уже о 90%, но я думаю, что по крайней мере 70% R&D любят агрегировать много логики на сервисном уровне. Таким образом, бизнес-логика всего сервисного уровня недостаточно заметна, а семантика слаба, что затрудняет начало работы вторичным разработчикам. Логика уровня BO в соответствующем сервисном методе пуста.

Модель анемии и модель гиперемии являются общими понятиями в моделировании поля DDD.Эта статья посвящена только преобразованию модели гиперемии модели MVC.Студенты, которые хорошо понимают эту концепцию DDD, могут прочитать:zhuanlan.zhihu.com/p/147879821

Например

image-20210530161819891.png

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

Теперь давайте просто поговорим о стратегии похудения блоггера для вышеуказанного процесса. После отправки команды LoginCommand LoginBO [похож на совокупный корень в DDD, но не полностью согласован] сопоставляет данные и извлекает логику метода минимального узла. Например, метод можно определить, проверив параметры входа. На сервисном уровне вы можете вызывать методы уровня bo один за другим. Вы также можете объединить 4 небольших метода в один метод с четкой логикой. Взгляните на псевдокод.

Модель анемии

//校验1
//校验2
....

一坨代码

//设置token

модель гиперемии

bo层
校验方法1{

}
校验方法2{

}
校验方法3{

}
校验方法4{

}

总校验{
  校验方法1
  校验方法2
  校验方法3
  校验方法4
}

....其余最小处理逻辑



service层

login{
	bo.总校验
	bo.生成token
	返回登录结果
}

Четыре.if/else везде

Я считаю, что каждый, должно быть, сталкивался с множеством логических суждений if/else в методе.Самое неприятное, что он не прокомментировал. Я резюмирую сценарии if/else, которые все часто используют.

  1. Проверка параметров
  2. Внутреннее суждение о бизнес-логике

4.1 Проверка параметров

Проверка параметров метода неизбежна. Результатом проверки параметра error является не что иное, как два вида:

1. Сообщите об ошибке напрямую

Для формы прямого отчета об ошибках рекомендуется прочитать статью блогера о глобальной обработке исключений:Наггетс.Талант/пост/696540…

Если проверка входного параметра не пройдена, код бизнес-ошибки сопоставляется напрямую, а ValidationUtil.isTrue() используется для выражения строгой семантической формы, а код легко читается.

2. вернуть ноль

Сгладьте суждение «если» и четко обозначьте иерархию.

не предлагается

if(true){
	//doSomething
}

предложение

if(false){
	return;
}
//doSomething

4.2 Внутреннее суждение о бизнес-логике

Способ устранения if/else Я уже читал хорошую статью и всем рекомендую:Наггетс.Талант/пост/691387…

Вот класс функционального инструмента, обычно используемый блогером для некоторых простых логических суждений if/else для упрощения кода.

/**
 * java8函数式工具
 *
 * if/else等简单逻辑疯狂缩短代码
 *
 * @author baiyan
 * @date 2021/05/30
 */
public final class JavaUtil {

    /**
     * 单例
     */
    private static JavaUtil javaUtil;

    /**
     * 单例工具类获取
     * @return
     */
    public static JavaUtil getJavaUtil() {
        if (javaUtil == null) {
            synchronized (JavaUtil.class) {
                if (javaUtil == null) {
                    javaUtil = new JavaUtil();
                }
            }
        }
        return javaUtil;
    }

    private JavaUtil() {
        super();
    }

    /**
     * 条件成立进行消费
     * @param condition 条件
     * @param value 需要消费的参数
     * @param consumer 消费函数
     * @param <T>
     * @return
     */
    public <T> JavaUtil acceptIfCondition(boolean condition, T value, Consumer<T> consumer) {
        if (condition) {
            consumer.accept(value);
        }
        return this;
    }

    /**
     * 根据条件的成立与否进行参数消费
     * @param condition 条件
     * @param trueValue 条件成立时消费参数
     * @param falseValue 条件不成立时消费参数
     * @param consumer 消费函数
     * @param <T>
     * @return
     */
    public <T> JavaUtil acceptDependCondition(boolean condition, T trueValue, T falseValue, Consumer<T> consumer) {

        consumer.accept(condition ? trueValue : falseValue);

        return this;
    }

    /**
     * 根据条件的成立决定消费函数
     * @param condition 条件
     * @param value 消费的参数
     * @param consumerTrue 条件成立时消费参数
     * @param consumerFalse 条件不成立时消费函数
     * @param <T>
     * @return
     */
    public <T> JavaUtil consumeDependCondition(boolean condition, T value, Consumer<T> consumerTrue, Consumer<T> consumerFalse) {

        if(condition){
            consumerTrue.accept(value);
        }else {
            consumerFalse.accept(value);
        }

        return this;
    }

    /**
     * 条件成立进行消费
     * @param condition 条件
     * @param supplier 提供者函数返回值作为参数
     * @param consumer 消费函数
     * @param <T>
     * @return
     */
    public <T> JavaUtil acceptSupplierIfCondition(boolean condition, Supplier<T> supplier, Consumer<T> consumer) {
        if (condition) {
            consumer.accept(supplier.get());
        }
        return this;
    }

    /**
     * 消费参数如果提供者函数为空
     * @param supplier 提供者函数作为判断入参
     * @param consumer 消费函数
     * @param defValue 消费入参
     * @param <T>
     * @return
     */
    public <T> JavaUtil acceptValueIfNull(Supplier<T> supplier, Consumer<T> consumer, T defValue) {
        return acceptIfCondition(supplier.get() == null, defValue, consumer);
    }

    /**
     * 参数值不为空则进行消费
     *
     * @param value 入参
     * @param consumer 消费函数
     * @param <T>
     * @return
     */
    public <T> JavaUtil acceptIfNotNull(T value, Consumer<T> consumer) {
        if (value != null) {
            consumer.accept(value);
        }
        return this;
    }

    /**
     * 字符串入参不为空则进行消费
     *
     * @param value 入参
     * @param consumer 消费函数
     * @return
     */
    public JavaUtil acceptIfNotEmpty(String value, Consumer<String> consumer) {
        if (StringUtils.hasText(value)) {
            consumer.accept(value);
        }
        return this;
    }

    /**
     * source不为null,则就行映射,并赋值
     * @param source 入参值进行非空判断
     * @param mapFunction 对非空入参进行消费并返回消费参数
     * @param consumer 消费参数
     * @param <T>
     * @param <R>
     * @return
     */
    public <T, R> JavaUtil mapAndAcceptIfNonnull(T source, Function<T, R> mapFunction, Consumer<R> consumer) {
        if (source != null) {
            R apply = mapFunction.apply(source);
            consumer.accept(apply);
        }
        return this;
    }

    /**
     * List<对象>转换成 List<对象的某一个属性>,并赋值给别的类
     * @param list        入参
     * @param consumer    消费
     * @param mapFunction 映射
     * @param <T>         原始对象
     * @param <R>         对象里面的某个属性
     * @return
     */
    public <T, R> JavaUtil mapAndAcceptIfNotEmpty(List<T> list, Function<T, R> mapFunction, Consumer<List<R>> consumer) {
        if (CollectionUtils.isNotEmpty(list)) {
            List<R> mapList = list.stream().map(mapFunction).collect(toList());
            consumer.accept(mapList);
        }
        return this;
    }


}

5. Повторяющаяся логика

Всем друзьям, использующим разработку идей, следует использовать подключаемый модуль Ali Development Code. При написании логического кода с большим количеством повторяющихся строк в проекте начало повторяющейся части кода будет выделено волнистой чертой. Все они программисты, и ни у кого нет обсессивно-компульсивного расстройства. Я думал об исключении подсказки, но позже обнаружил, что они похожи только по структуре, но логика установки значений в них разная, поэтому их нельзя разделить на публичные методы.

Например, метод 1 — user.getName(), а метод 2 — employee.getName().

Как это сделать?

Основная особенность jdk8 заключается в том, что функции являются интерфейсами. Используйте функциональные интерфейсы и дженерики для решения.


Например:

Теперь есть метод, который должен записать данные пользователя и сотрудника в файл, имя файла — это имя пользователя и сотрудника, и они не будут повторяться.

Метод обычного письма, должен сообщить о повторении кода

//用户文件生成
public static void createExportFiles(String tempExportPath, List<User> datas)  {
    if (CollectionUtil.isEmpty(datas)){
        return;
    }
    datas.forEach(data -> {
        String fileName = user.getName() + ".json";
        FileWriter writer = new FileWriter(tempExportPath + File.separator + fileName);
        try {
            writer.write(GsonUtil.gsonToString(data));
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    });
}

//用户文件生成
public static void createExportFiles(String tempExportPath, List<Employee> datas)  {
    if (CollectionUtil.isEmpty(datas)){
        return;
    }
    datas.forEach(data -> {
        String fileName = data.getName() + ".json";
        FileWriter writer = new FileWriter(tempExportPath + File.separator + fileName);
        try {
            writer.write(GsonUtil.gsonToString(data));
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    });
}

Общая функциональная связывание

public static <T,R> void createExportFiles(String tempExportPath, List<T> datas, Function<T, R> function)  {
    if (CollectionUtil.isEmpty(datas)){
        return;
    }
    datas.forEach(data -> {
        String fileName = function.apply(data) + ".json";
        FileWriter writer = new FileWriter(tempExportPath + File.separator + fileName);
        try {
            writer.write(GsonUtil.gsonToString(data));
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    });
}

调用是传参
user  :    createExportFiles("/root",List<User> users,User::getName)       
employee : createExportFiles("/root",List<Employee> employees,Employee::getName)       

6. Магическое значение/константа/перечисление

Я полагаю, что вы будете часто сталкиваться с кодом здесь

if(state==1){
	//doSomething
}else if(state==2){
	//doSomething
}else{
	//doSomething
}

Что означает магическое значение 1, 2 и 3?

Немного лучше дать вам замечание и лучший способ определить для вас константы. Но правильный путь должен состоять в том, чтобы определить перечисление, которое четко определяет роль 1, 2 и 3.

Например, определите перечисление пола

public enum SexEnum {

    MAN(1,"男","man"),
    WOMAN(0,"女","woman"),
    ;

    @Getter
    private Integer key;
    @Getter
    private String value;
    @Getter
    private String name;

    SexEnum(Integer key, String value,String name) {
        this.key = key;
        this.value = value;
        this.name = name;
    }

    public static SexEnum getByKey(String key) {
        for (SexEnum e : values()) {
            if (Objects.equals(key, e.key)) {
                return e;
            }
        }
        throw new RuntimeException("未找到对应的性别枚举:" + key);
    }
}

При вынесении гендерных суждений

不推荐
if(Objects.equals(sex,1)){

}

推荐,语义清晰
if(Objects.equals(sex,SexEnum.MAN.getKey())){

}

7. Путаница с классами инструментов

Если вы будете искать в StringUtil старый проект, то наверняка найдете множество пользовательских StringUtils. Большинство методов внутри одинаковые, что является совершенно ненужным дублированием кода для проекта.

Рекомендуется вытащить базовый проект отдельно и поместить его в общий класс инструментов, который не имеет никакого отношения к бизнесу, здесь поддерживаются публичные методы. Базовый модуль определяется внутри бизнеса.При наличии необходимого бизнес-расширения для класса инструментов в базовом проекте класс инструментов самого бизнеса может быть реализован путем наследования класса инструментов в базовом проекте. Оценка по типу обеспечивается классом инструмента базового проекта.

8. Путаница в управлении логикой данных

8.1. Путаница с данными уровня Dao

Если вы думаете о монолитном приложении как об огромном распределенном приложении. Тогда каждый внутренний сервисный уровень является соответствующим микросервисом. Уровень DAO, соответствующий каждому микросервису, представляет собой базу данных, соответствующую микросервису. Различные микросервисы не могут вызывать разные библиотеки. Следовательно, никогда не разрешается добавлять, удалять или изменять данные, которых нет в этом сервисном уровне, в соответствующем уровне Dao этого сервисного уровня.

8.2 Модификация данных и путаница в запросах, не относящиеся к основному бизнес-процессу

Обрабатывайте данные о ежедневных операциях, данные, которые мы, вероятно, возразим в версии 1, взяты из mysql, mysql, а вторая версия стала полимеризацией Redis, третья версия — полимеризацией ES, mysql и Redis.

image-20210530174140778.pngС развитием версии источник данных постоянно модифицируется, и каждая модификация изменяет основную бизнес-логику. Это приведет к большому количеству действий по агрегации данных в коде по мере развития бизнеса, но в существенном смысле. Например, если я хочу запросить информацию о пользователе, мне все равно, из скольких источников данных собрана ваша информация о пользователе, меня волнуют только мои пользовательские данные. Вот концепция хранилища в DDD. Думайте о уровне DAO как о мощном хранилище. Вы несете ответственность только за то, чтобы сообщить ему, какие данные вам нужны. Что касается источника и агрегации данных, оставьте это на уровне хранилища. Это немного похоже на логику объединения операций с данными уровня dao и сервисного уровня в небольшой сервис.

image-20210530174245135.png

Таким образом, основная логика очень ясна.

9. Слишком много логики сериализации

В логике определено слишком много логики, не сильно связанной с этим бизнесом.

Например, мы определяем систему разрешений как: Пользователь 1: n Роль 1: n Разрешение

Сейчас разрабатываем интерфейс для удаления пользователей. Соответственно, отношения разрешений между пользователем и ролью также необходимо удалить.

Обычно так, как мы кодируем

public void delete(Long userId){
	删除用户
	删除用户与角色关联关系
	删除。。。
}

Обнаружили ли вы проблему? Я явно удаляю пользователей, но я делаю вещи, которые выходят за границы моего бизнеса, связанные с удалением пользователей. И если в будущем будет больше функций, связанных с пользователями, нужно ли мне вызывать их по одной? Это явно неразумно, код трудно читать и его нелегко поддерживать. Здесь необходимо выполнить развязку.В сценарии микросервиса важным знанием, которое можно учитывать при развязке, является MQ. Разве это не механизм мониторинга событий для бизнес-приложений?

Изменить код

public void delete(Long userId){
	删除用户
	发布用户删除事件
}

//角色关联函数
public void listener(Event event){
	删除角色与用户关联关系
}

//通知其他微服务
public void listener(Event event){
	发送mq消息,告知其他微服务用户删除
}

10. Свяжитесь со мной

Если вы считаете, что статья хорошо написана, вы можете поставить лайк и прокомментировать + подписаться на Санлиана, хорошо~

Диндин: louyanfeng25

WeChat: baiyan_lou