我一周的收获

开发人员在团队中的应用程序上工作的越多,对代码的了解就越多,那么他就越经常参与校对同志的工作。 今天,我将展示由优秀的开发人员编写的代码在一周内可以发现的内容。 削减之下是我们创造力的生动作品的集合(以及我的一些想法)。


比较器


有一个代码:


@Getter class Dto { private Long id; } // another class List<Long> readSortedIds(List<Dto> list) { List<Long> ids = list.stream().map(Dto::getId).collect(Collectors.toList()); ids.sort(new Comparator<Long>() { public int compare(Long o1, Long o2) { if (o1 < o2) return -1; if (o1 > o2) return 1; return 0; } }); return ids; } 

有人指出,您可以直接对流进行排序,但是我想引起您对比较器的注意。 Comparator::compare方法的文档用英语用白色表示:


比较其两个参数的顺序。 当第一个参数小于,等于或大于第二个参数时,返回负整数,零或正整数。

这是在我们的代码中实现的行为。 怎么了 事实是,Java的创建者非常有远见,建议许多人需要这样的比较器,并为我们量身定做。 我们可以通过简化代码来使用它:


 List<Long> readSortedIds(List<Dto> list) { List<Long> ids = list.stream().map(Dto::getId).collect(Collectors.toList()); ids.sort(Comparator.naturalOrder()); return ids; } 

同样,此代码


 List<Dto> sortDtosById(List<Dto> list) { list.sort(new Comparator<Dto>() { public int compare(Dto o1, Dto o2) { if (o1.getId() < o2.getId()) return -1; if (o1.getId() > o2.getId()) return 1; return 0; } }); return list; } 

轻弹一下就变成


 List<Dto> sortDtosById(List<Dto> list) { list.sort(Comparator.comparing(Dto::getId)); return list; } 

顺便说一下,在新版本的“想法”中,可以这样进行:


法术


选择性滥用


大概我们每个人都看到这样的事情:


 List<UserEntity> getUsersForGroup(Long groupId) { Optional<Long> optional = Optional.ofNullable(groupId); if (optional.isPresent()) { return userRepository.findUsersByGroup(optional.get()); } return Collections.emptyList(); } 

通常, Optional用于检查值的存在/不存在,尽管不是为此创建的。 领带虐待和写容易:


 List<UserEntity> getUsersForGroup(Long groupId) { if (groupId == null) { return Collections.emptyList(); } return userRepository.findUsersByGroup(groupId); } 

请记住, Optional与方法的参数或字段无关,而与返回值有关。 这就是为什么它在设计时没有序列化支持的原因。


改变参数状态的无效方法


想象这样的方法:


 @Component @RequiredArgsConstructor public class ContractUpdater { private final ContractRepository repository; @Transactional public void updateContractById(Long contractId, Dto contractDto) { Contract contract = repository.findOne(contractId); contract.setValue(contractDto.getValue()); repository.save(contract); } } 

当然,您已经看过很多遍并写过类似的文章。 在这里,我不喜欢该方法更改实体的状态,但是不返回它。 类似的框架方法如何表现? 例如, org.springframework.data.jpa.repository.JpaRepository::savejavax.persistence.EntityManager::merge返回一个值。 假设在更新合同之后,我们需要将其获取到update方法之外。 原来是这样的:


 @Transactional public void anotherMethod(Long contractId, Dto contractDto) { updateService.updateContractById(contractId, contractDto); Contract contract = repositoroty.findOne(contractId); doSmth(contract); } 

是的,我们可以通过更改其签名将实体直接传递给UpdateService::updateContract方法,但是最好这样做:


 @Component @RequiredArgsConstructor public class ContractUpdater { private final ContractRepository repository; @Transactional public Contract updateContractById(Long contractId, Dto contractDto) { Contract contract = repository.findOne(contractId); contract.setValue(contractDto.getValue()); return repository.save(contract); } } // @Transactional public void anotherMethod(Long contractId, Dto contractDto) { Contract contract = updateService.updateContractById(contractId, contractDto); doSmth(contract); } 

一方面,这有助于简化代码,另一方面,有助于测试。 通常,测试void方法是一项非常沉闷的任务,我将使用相同的示例进行演示:


 @RunWith(MockitoJUnitRunner.class) public class ContractUpdaterTest { @Mock private ContractRepository repository; @InjectMocks private ContractUpdater updater; @Test public void updateContractById() { Dto dto = new Dto(); dto.setValue("- "); Long contractId = 1L; when(repository.findOne(contractId)).thenReturn(new Contract()); updater.updateContractById(contractId, contractDto); //void // ,       dto? -  : ArgumentCaptor<Contract> captor = ArgumentCaptor.forClass(Contract.class); verify(repository).save(captor.capture()); Contract updated = captor.getValue(); assertEquals(dto.getValue(), updated.getValue()); } } 

但是,如果该方法返回一个值,则可以使一切变得更简单:


确保
 @RunWith(MockitoJUnitRunner.class) public class ContractUpdaterTest { @Mock private ContractRepository repository; @InjectMocks private ContractUpdater updater; @Test public void updateContractById() { Dto dto = new Dto(); dto.setValue("- "); Long contractId = 1L; when(repository.findOne(contractId)).thenReturn(new Contract()); Contract updated = updater.updateContractById(contractId, contractDto); assertEquals(dto.getValue(), updated.getValue()); } } 

一口气,不仅检查了对ContractRepository::save的调用,而且还检查了保存值的正确性。


自行车构造


为了娱乐,请打开您的项目并寻找以下内容:


 lastIndexOf('.') 

整个表达式很有可能像这样:


 String fileExtension = fileName.subString(fileName.lastIndexOf('.')); 

那是静态分析仪无法警告的,它是关于新发明的自行车的。 先生们,如果您正在解决与文件名/扩展名或路径有关的某个问题,就像读/写/复制一样,那么在十分之九的情况下,摆在您面前的任务已经得到解决。 因此,请结合自行车的结构并采用现成的(已验证的)解决方案:


 import org.apache.commons.io.FilenameUtils; //... String fileExtension = FilenameUtils.getExtension(fileName); 

在这种情况下,您可以节省时间检查自行车的适用性,并获得更多高级功能(请参阅文档FilenameUtils::getExtension )。


或者下面是将一个文件的内容复制到另一个文件的代码:


 try { FileChannel sc = new FileInputStream(src).getChannel(); FileChannel dc = new FileOutputStream(new File(targetName)).getChannel(); sc.transferTo(0, sc.size(), dc); dc.close(); sc.close(); } catch (IOException ex) { log.error("", ex); } 

什么情况可以阻止我们? 成千上万的:


  • 目标可能是一个文件夹,而不是一个文件
  • 源可能是文件夹
  • 源和目标可能是同一文件
  • 无法创建目的地
  • 等。

可悲的是,使用自我录制的内容,我们在复制过程中已经学到了所有东西。
如果我们明智地做


 import org.apache.commons.io.FileUtils; try { FileUtils.copyFile(src, new File(targetName)); } catch (IOException ex) { log.error("", ex); } 

然后将在开始复制之前执行部分检查,并且可能的异常情况将提供更多信息(请参阅FileUtils::copyFile的源FileUtils::copyFile )。


忽略@ Nullable / @ NotNull


假设我们有一个实体:


 @Entity @Getter public class UserEntity { @Id private Long id; @Column private String email; @Column private String petName; } 

在本例中,表中的email列被描述为not null ,这与petName不同。 也就是说,我们可以使用相应的注释标记字段:


 import javax.annotation.Nullable; import javax.annotation.NotNull; //... @Column @NotNull private String email; @Column @Nullable private String petName; 

乍一看,这似乎是对开发人员的提示,实际上是。 同时,这些注释是比常规标签更强大的工具。


例如,开发环境可以理解它们,如果在添加注释后,我们尝试这样做:


 boolean checkIfPetBelongsToUser(UserEnity user, String lostPetName) { return user.getPetName().equals(lostPetName); } 

那么“想法”将通过以下消息警告我们危险:


方法调用“等于”可能会产生“ NullPointerException”

在代码中


 boolean hasEmail(UserEnity user) { boolean hasEmail = user.getEmail() == null; return hasEmail; } 

还会有另一个警告:


条件'user.getEmail()== null'始终为'false'

这有助于内置检查器查找可能的错误,并帮助我们更好地理解代码执行。 出于相同的目的,注释可用于放置在返回值及其参数的方法上。


如果我的论点似乎没有定论,那么请看一下任何严肃的项目的源代码,即“ Spring”-它们像圣诞树一样被标注着。 这不是一时兴起,而是严重的必要。


在我看来,唯一的缺点是需要不断将注释保持在现代状态。 尽管,如果您看一看,这真是福气,因为一遍又一遍地返回代码,我们对它的了解越来越好。


粗心


这段代码没有错误,但是有很多错误:


 Collection<Dto> dtos = getDtos(); Stream.of(1,2,3,4,5) .filter(id -> { List<Integer> ids = dtos.stream().map(Dto::getId).collect(toList()); return ids.contains(id); }) .collect(toList()); 

还不清楚为什么要创建一个新的键列表来执行搜索,如果通过流时该键列表没有变化。 最好只有5个元素,但是如果有100500个元素怎么办? 如果getDtos方法返回100500个对象(在列表中!),那么此代码将具有什么样的性能? 没有,所以最好这样:


 Collection<Dto> dtos = getDtos(); Set<Integer> ids = dtos.stream().map(Dto::getId).collect(toSet()); Stream.of(1,2,3,4,5) .filter(ids::contains) .collect(toList()); 

也在这里:


 static <T, Q extends Query> void setParams( Map<String, Collection<T>> paramMap, Set<String> notReplacedParams, Q query) { notReplacedParams.stream() .filter(param -> paramMap.keySet().contains(param)) .forEach(param -> query.setParameter(param, paramMap.get(param))); } 

显然, inParameterMap.keySet()表达式返回的值是不变的,因此可以将其移动到变量中:


 static <T, Q extends Query> void setParams( Map<String, Collection<T>> paramMap, Set<String> notReplacedParams, Q query) { Set<String> params = paramMap.keySet(); notReplacedParams.stream() .filter(params::contains) .forEach(param -> query.setParameter(param, paramMap.get(param))); } 

顺便说一句,可以使用“循环中的对象分配”检查来计算此类部分。


当静态分析无能为力时


第八届Java早已消亡,但我们都喜欢流。 我们中的一些人非常喜欢它们,以至于我们到处使用它们:


 public Optional<EmailAdresses> getUserEmails() { Stream<UserEntity> users = getUsers().stream(); if (users.count() == 0) { return Optional.empty(); } return users.findAny(); } 

如您所知,流在终止被调用之前是新鲜的,因此重新访问我们代码中的users变量将导致IllegalStateException


静态分析器尚不知道如何报告此类错误,因此,及时捕获它们的责任在于审阅者。


在我看来,使用Stream类型的变量,以及将它们作为参数传递并从方法返回,就像在雷区中行走一样。 也许幸运,也许不是。 因此,有一个简单的规则:应检查代码Stream<T>任何外观(但应立即删除)。


简单类型


许多人确信booleanint等与性能有关。 这部分是正确的,但默认情况下,简单类型not null 。 如果实体的整数字段引用表中声明not null ,则使用int而不是Integer是有意义的。 这是一种组合-内存消耗较低,并且由于不必要的null检查而简化了代码。


仅此而已。 请记住,上述所有内容都不是最终的真理,请自己思考并有意义地采用任何建议。

Source: https://habr.com/ru/post/zh-CN426105/


All Articles