开发人员在团队中的应用程序上工作的越多,对代码的了解就越多,那么他就越经常参与校对同志的工作。 今天,我将展示由优秀的开发人员编写的代码在一周内可以发现的内容。 削减之下是我们创造力的生动作品的集合(以及我的一些想法)。
比较器
有一个代码:
@Getter class Dto { private Long id; }
有人指出,您可以直接对流进行排序,但是我想引起您对比较器的注意。 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::save
和javax.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); } }
一方面,这有助于简化代码,另一方面,有助于测试。 通常,测试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);
但是,如果该方法返回一个值,则可以使一切变得更简单:
确保 @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;
在这种情况下,您可以节省时间检查自行车的适用性,并获得更多高级功能(请参阅文档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;
乍一看,这似乎是对开发人员的提示,实际上是。 同时,这些注释是比常规标签更强大的工具。
例如,开发环境可以理解它们,如果在添加注释后,我们尝试这样做:
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>
任何外观(但应立即删除)。
简单类型
许多人确信boolean
, int
等与性能有关。 这部分是正确的,但默认情况下,简单类型not null
。 如果实体的整数字段引用表中声明not null
,则使用int
而不是Integer
是有意义的。 这是一种组合-内存消耗较低,并且由于不必要的null
检查而简化了代码。
仅此而已。 请记住,上述所有内容都不是最终的真理,请自己思考并有意义地采用任何建议。