Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.
Поток в Java8 в сочетании с лямбда-выражениями может сделать код короче и красивее и получил широкое распространение. У нас также есть больше возможностей при написании сложного кода.
Код предназначен для того, чтобы в первую очередь его видели люди, а во вторую — чтобы машины выполняли его. То, написан ли код лаконично и ясно, и красиво ли он написан, имеет большое значение для последующего исправления ошибок и расширения функций. Во многих случаях умение писать хороший код не имеет ничего общего с инструментами. Код — это воплощение способностей и достижений инженера.Некоторые люди, даже если они используют поток и лямбду, все равно пишут код как дерьмо.
Если вы мне не верите, давайте посмотрим美妙
код. Молодец, в фильтре действительно есть лихая логика.
public List<FeedItemVo> getFeeds(Query query,Page page){
List<String> orgiList = new ArrayList<>();
List<FeedItemVo> collect = page.getRecords().stream()
.filter(this::addDetail)
.map(FeedItemVo::convertVo)
.filter(vo -> this.addOrgNames(query.getIsSlow(),orgiList,vo))
.collect(Collectors.toList());
//...其他逻辑
return collect;
}
private boolean addDetail(FeedItem feed){
vo.setItemCardConf(service.getById(feed.getId()));
return true;
}
private boolean addOrgNames(boolean isSlow,List<String> orgiList,FeedItemVo vo){
if(isShow && vo.getOrgIds() != null){
orgiList.add(vo.getOrgiName());
}
return true;
}
Если вам не понравится, мы опубликуем небольшой абзац.
if (!CollectionUtils.isEmpty(roleNameStrList) && roleNameStrList.contains(REGULATORY_ROLE)) {
vos = vos.stream().filter(
vo -> !CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
&& vo.getTaskName() != null)
.collect(Collectors.toList());
} else {
vos = vos.stream().filter(vo -> vo.getIsSelect()
&& vo.getTaskName() != null)
.collect(Collectors.toList());
vos = vos.stream().filter(
vo -> !CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
&& vo.getTaskName() != null)
.collect(Collectors.toList());
}
result.addAll(vos.stream().collect(Collectors.toList()));
Код может работать, но лишний. Не отступайте то, что должно быть отступом, не оборачивайте то, что должно оборачиваться, ничто не является хорошим кодом.
как улучшить? Помимо технической проблемы, это еще и проблема осведомленности. Всегда помните, что хороший код сначала читабелен, а затем функционален.
1. Разумные разрывы строк
В Java та же функция, количество написанных строк кода меньше, и ваш код не обязательно хороший. Поскольку Java использует;
В качестве разделения строки кода, если хотите, вы даже можете объединить весь файл Java в одну строку, как в обфусцированном JavaScript.
Конечно, мы знаем, что это неправильно. При написании лямбда есть некоторые подпрограммы, которые могут сделать код более регулярным.
Stream.of("i", "am", "xjjdog").map(toUpperCase()).map(toBase64()).collect(joining(" "));
Приведенный выше код не рекомендуется. Помимо того, что легко создать препятствия при чтении, когда есть проблема с кодом, такая как выдача исключения, также становится трудно найти проблему в стеке исключений. Итак, мы должны обернуть его изящно.
Stream.of("i", "am", "xjjdog")
.map(toUpperCase())
.map(toBase64())
.collect(joining(" "));
Не думайте, что это преобразование бессмысленно, и не принимайте такой разрыв строки как должное. В моем обычном обзоре кода действительно бесчисленное количество кусков кода смешано вместе, и вы не понимаете намерения человека, который написал код.
Разумные разрывы строк — рецепт вечной молодости кода.
2. Стоит функция разделения
Почему функции можно писать все дольше и дольше? Это из-за высокого уровня технологий, которые могут справиться с этим изменением? Ответ из-за лени! Из-за проблемы графика разработки или осведомленности, если встречаются новые требования, ifelse добавляется непосредственно к старому коду, даже если встречаются аналогичные функции, исходный код копируется напрямую. Со временем код перестанет быть кодом.
Сначала поговорим о производительности. В JVM компилятор JIT будет выполнять встраивание методов для кода с большим количеством вызовов и простой логикой, чтобы уменьшить накладные расходы на кадры стека и обеспечить дополнительную оптимизацию. Таким образом, короткие и мощные функции на самом деле удобны для JVM.
С точки зрения удобочитаемости очень важно разделить большой кусок кода на значимые функции, и это также является сутью рефакторинга. В лямбда-выражениях это разделение еще более необходимо.
Я проиллюстрирую это на примере преобразования сущности, которое часто встречается в коде. Следующее преобразование создает анонимную функциюorder->{}
, который очень слаб в смысловом выражении.
public Stream<OrderDto> getOrderByUser(String userId){
return orderRepo.findOrderByUser().stream()
.map(order-> {
OrderDto dto = new OrderDto();
dto.setOrderId(order.getOrderId());
dto.setTitle(order.getTitle().split("#")[0]);
dto.setCreateDate(order.getCreateDate().getTime());
return dto;
});
}
В реальном бизнес-коде такая логика копирования присваивания и преобразования обычно очень длинная, поэтому мы можем попытаться разделить процесс создания dto. Поскольку действие перехода не является основной бизнес-логикой, нам обычно все равно, что в нем происходит.
public Stream<OrderDto> getOrderByUser(String userId){
return orderRepo.findOrderByUser().stream()
.map(this::toOrderDto);
}
public OrderDto toOrderDto(Order order){
OrderDto dto = new OrderDto();
dto.setOrderId(order.getOrderId());
dto.setTitle(order.getTitle().split("#")[0]);
dto.setCreateDate(order.getCreateDate().getTime());
return dto;
}
Такой код преобразования все еще немного уродлив. Но если конструктор OrderDto, параметр Orderpublic OrderDto(Order order)
, тогда мы можем удалить фактическую логику преобразования из основной логики, и весь код может быть очень чистым.
public Stream<OrderDto> getOrderByUser(String userId){
return orderRepo.findOrderByUser().stream()
.map(OrderDto::new);
}
В дополнение к функциям map и flatMap, которые можно использовать для семантики, дополнительные фильтры можно заменить на Predicate. Например:
Predicate<Registar> registarIsCorrect = reg ->
reg.getRegulationId() != null
&& reg.getRegulationId() != 0
&& reg.getType() == 0;
registarIsCorrect, его можно использовать какfilter
параметр.
3. Разумное использование необязательного
В коде Java, поскольку NullPointerException не является обязательным исключением, оно будет скрыто в коде, вызывая множество непредсказуемых ошибок. Поэтому, когда мы получим параметр, мы проверим его легальность и посмотрим, является ли он нулевым, таких кодов полно в коде.
if(null == obj)
if(null == user.getName() || "".equals(user.getName()))
if (order != null) {
Logistics logistics = order.getLogistics();
if(logistics != null){
Address address = logistics.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
Isocode isocode = country.getIsocode();
if (isocode != null) {
return isocode.getNumber();
}
}
}
}
}
Java8 представила класс Optional для решения пресловутой проблемы с нулевыми указателями. Фактически, это класс-оболочка, который предоставляет несколько методов для определения собственной проблемы с нулевым значением.
Приведенный выше пример более сложного кода можно заменить следующим кодом.
String result = Optional.ofNullable(order)
.flatMap(order->order.getLogistics())
.flatMap(logistics -> logistics.getAddress())
.flatMap(address -> address.getCountry())
.map(country -> country.getIsocode())
.orElse(Isocode.CHINA.getNumber());
Когда вы не уверены, является ли то, что вы предоставляете, нулевым, рекомендуется не возвращать нуль, иначе код вызывающего объекта будет полон нулевых суждений. Мы хотим пресечь null в зародыше.
public Optional<String> getUserName() {
return Optional.ofNullable(userName);
}
Кроме того, мы хотим как можно меньше использовать необязательный метод get, он также делает код некрасивым. Например:
Optional<String> userName = "xjjdog";
String defaultEmail = userName.get() == null ? "":userName.get() + "@xjjdog.cn";
Вместо этого его следует изменить следующим образом:
Optional<String> userName = "xjjdog";
String defaultEmail = userName
.map(e -> e + "@xjjdog.cn")
.orElse("");
Так почему же наш код по-прежнему полон различных нулевых суждений? Даже в очень специализированном и популярном коде? Очень важная причина заключается в том, что использование Optional должно быть последовательным. Когда в одной из ссылок есть пробел, большинство кодеров будут писать некоторый код в имитации, чтобы сохранить стиль исходного кода согласованным.
Если вы хотите популяризировать использование варианта «Необязательно» в проекте, разработчикам скаффолда или рецензентам нужно больше работать.
4. Вернуть поток или вернуть список?
Многие люди сталкиваются с дилеммой при разработке интерфейса. Данные, которые я возвращаю, напрямую возвращают поток или список?
Если вы возвращаете список, такой как ArrayList, то изменение списка напрямую повлияет на значения в нем, если только вы не обернете его неизменяемым образом. Аналогично, массивы имеют ту же проблему.
Но для Stream это неизменяемо, это не влияет на исходную коллекцию. Для этого сценария мы рекомендуем возвращать поток Stream напрямую, а не коллекцию. Преимущество этого метода также заключается в том, что пользователям API настоятельно рекомендуется часто использовать функции, связанные с Stream, чтобы унифицировать стиль кода.
public Stream<User> getAuthUsers(){
...
return Stream.of(users);
}
Нематериальные коллекции являются сильными требованиями для предотвращения непредвиденных модификаций этих коллекций внешними функциями. В Гуаве есть большое количествоImmutable
Классы поддерживают эту упаковку. В качестве другого примера, перечисление Java, егоvalues()
Метод, чтобы предотвратить изменение перечисления внешним API, можно копировать только одну копию данных.
Однако, если ваш апи ориентирован на конечного пользователя и его не нужно модифицировать, то лучше возвращать List напрямую, например, функция находится в Controller.
5. Используйте меньше или совсем не используйте параллельные потоки
Есть много проблем с параллельными потоками в Java, и на эти проблемы часто натыкаются люди, не знакомые с параллельным программированием. Дело не в том, что параллельные потоки — это плохо, но если вы обнаружите, что ваша команда постоянно на это попадается, то вы, не задумываясь, уменьшите частоту рекомендаций.
Общей проблемой параллельных потоков является безопасность потоков. В процессе итерации, если вы используете потоконебезопасные классы, это может привести к проблемам. Например, следующий код в большинстве случаев неверен.
List transform(List source){
List dst = new ArrayList<>();
if(CollectionUtils.isEmpty()){
return dst;
}
source.stream.
.parallel()
.map(..)
.filter(..)
.foreach(dst::add);
return dst;
}
Вы можете сказать, что я могу просто заменить foreach на collect. Но обратите внимание, что многие разработчики не знают об этом. Поскольку API предоставляет такую функцию, это имеет логический смысл, поэтому вы не можете запретить другим использовать ее таким образом.
Еще одна проблема, связанная со злоупотреблением параллельными потоками, — выполнение очень длинных задач ввода-вывода в итерациях. У вас есть вопрос перед использованием параллельных потоков? Поскольку он параллельный, как настроен его пул потоков?
К сожалению, все параллельные потоки совместно используют ForkJoinPool. его размер, по умолчаниюCPU个数-1
, в большинстве случаев недостаточно.
Если кто-то ведет трудоемкий бизнес ввода-вывода в параллельном потоке, вам нужно стоять в очереди, даже если вы выполняете простую математическую операцию. Дело в том, что вы не можете запретить другим учащимся проекта использовать параллельные потоки, и вы не можете знать, что он делает.
Что делать тогда? Мой подход — универсальный и прямой запрет. Хотя это немного жестоко, это позволяет избежать проблемы.
Суммировать
Функция Stream, добавленная Java8, очень хороша, нам больше не нужно завидовать другим языкам, и код писать проще. Хотя он выглядит очень мощным, это всего лишь синтаксический сахар, и не надейтесь, что с его помощью вы обретете сверхспособности.
С ростом популярности Stream таких кодов в нашем коде становится все больше. Но сейчас очень много кода, после использования Stream и Lambda, код становится все хуже и хуже, вонючий и длинный так, что не читается. Никакой другой причины, злоупотребляли!
В целом при использовании Stream и Lambda необходимо следить за тем, чтобы основной процесс был простым и понятным, стиль должен быть единым, разрыв строки должен быть разумным, функция должна быть добавлена, а необязательные функции должны использоваться правильно. , а логику кода не следует добавлять к таким функциям, как filter. При написании кода осознанно следуйте этим советам, простота и элегантность — залог продуктивности.
Если вам кажется, что функций, предоставляемых Java, недостаточно, у нас также есть библиотека классов с открытым исходным кодом.vavr
, который предоставляет больше возможностей и может быть объединен с Stream и Lambda для расширения возможностей функционального программирования.
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.3</version>
</dependency>
Но независимо от того, насколько мощные API и методы программирования предоставляются, он не может противостоять злоупотреблениям со стороны мелких партнеров. Эти коды логически логичны, но выглядят неуклюже и трудоемки в обслуживании.
Написание кучи лямбда-кода — лучший способ оскорбить коллег, а также лучший способ его похоронить.
Писать код — это как говорить и болтать. Все занимаются одной и той же работой.Некоторые люди хороши собой и хороши собой, и каждому нравится с ним поболтать;
Код, помимо смысла работы, — это еще один способ для нас выразить свои идеи в мире. Как написать хороший код — это не только техническая проблема, но и проблема сознания.
Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.