共计 18385 个字符,预计需要花费 46 分钟才能阅读完成。
起源:https://juejin.cn/post/684490…
导语
自从毕业后,往年曾经是我工作的第 8 个年头了,我甚至都快遗记了到底是哪年毕业的。
从进去,自己始终在做 Java 相干的工作,当初终于有工夫坐下来,写一篇对于 Java 写法的一篇文章,来探讨一下如果你真的是一个 Java 程序员,那你真的会写 Java 吗?
笔者是一个求实的程序员,故本文绝非扯淡文章,文中内容都是干货,望读者看后,能有所播种。
本文不是一个吹牛的文章,不会讲很多浅近的架构,相同,会解说很多根底的问题和写法问题,如果读者自认为根底问题和写法问题都是不是问题,那请疏忽这篇文章,节俭出工夫去做一些有意义的事件。
开发工具
不晓得有多少“老”程序员还在应用 Eclipse,这些程序员们要不就是旧调重弹,要不就是基本就不晓得其余好的开发工具的存在,Eclipse 吃内存卡顿的景象以及各种偶尔莫名异样的呈现,都告知咱们是时候寻找新的开发工具了。
| 更换 IDE
基本就不想多解释要换什么样的 IDE,如果你想成为一个优良的 Java 程序员,请更换 IntelliJ IDEA。应用 IDEA 的益处,请搜寻谷歌。
| 别通知我快捷键不好用
更换 IDE 不在我本文的重点内容中,所以不想用太多的篇幅去写为什么更换 IDE。在这里,我只能通知你,更换 IDE 只为了更好、更快的写好 Java 代码。起因略。
别通知我快捷键不好用,请尝试新事物。
| bean
bean 使咱们应用最多的模型之一,我将以大篇幅去解说 bean,心愿读者好好领会。
| domain 包名
依据很多 Java 程序员的“教训”来看,一个数据库表则对应着一个 domain 对象,所以很多程序员在写代码时,包名则应用:com.xxx.domain,这样写如同曾经成为了行业的一种束缚,数据库映射对象就应该是 domain。
然而你错了,domain 是一个畛域对象,往往咱们再做传统 Java 软件 Web 开发中,这些 domain 都是贫血模型,是没有行为的,或是没有足够的畛域模型的行为的。
所以,以这个实践来讲,这些 domain 都应该是一个一般的 entity 对象,并非畛域对象,所以请把包名改为:com.xxx.entity。
如果你还不了解我说的话,请看一下 Vaughn Vernon 出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN DESIGN》(实现畛域驱动设计)这本书,书中解说了贫血模型与畛域模型的区别,置信你会受益匪浅。
| DTO
数据传输咱们应该应用 DTO 对象作为传输对象,这是咱们所约定的,因为很长时间我始终都在做挪动端 API 设计的工作,有很多人通知我,他们认为只有给手机端传输数据的时候(input or output),这些对象成为 DTO 对象。
请留神!这种了解是谬误的,只有是用于网络传输的对象,咱们都认为他们能够当做是 DTO 对象,比方电商平台中,用户进行下单,下单后的数据,订单会发到 OMS 或者 ERP 零碎,这些对接的返回值以及入参也叫 DTO 对象。
咱们约定某对象如果是 DTO 对象,就将名称改为 XXDTO,比方订单下发 OMS:OMSOrderInputDTO。
| DTO 转化
正如咱们所知,DTO 为零碎与外界交互的模型对象,那么必定会有一个步骤是将 DTO 对象转化为 BO 对象或者是一般的 entity 对象,让 service 层去解决。
| 场景
比方增加会员操作,因为用于演示,我只思考用户的一些简略数据,当后盾管理员点击增加用户时,只须要传过来用户的姓名和年龄就能够了,后端承受到数据后,将增加创立工夫和更新工夫和默认明码三个字段,而后保留数据库。
@RequestMapping("/v1/api/user")
@RestController
public class UserApi {
@Autowired
private UserService userService;
@PostMapping
public User addUser(UserInputDTO userInputDTO){User user = new User();
user.setUsername(userInputDTO.getUsername());
user.setAge(userInputDTO.getAge());
return userService.addUser(user);
}
}
咱们只关注一下上述代码中的转化代码,其余内容请疏忽:
User user = new User();
user.setUsername(userInputDTO.getUsername());
user.setAge(userInputDTO.getAge());
| 请应用工具
上边的代码,从逻辑上讲,是没有问题的,只是这种写法让我很腻烦,例子中只有两个字段,如果有 20 个字段,咱们要如何做呢?一个一个进行 set 数据吗?
当然,如果你这么做了,必定不会有什么问题,然而,这必定不是一个最优的做法。网上有很多工具,反对浅拷贝或深拷贝的 Utils。
举个例子,咱们能够应用 org.springframework.beans.BeanUtils#copyProperties 对代码进行重构和优化:
@PostMapping
public User addUser(UserInputDTO userInputDTO){User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return userService.addUser(user);
}
BeanUtils.copyProperties 是一个浅拷贝办法,复制属性时,咱们只须要把 DTO 对象和要转化的对象两个的属性值设置为一样的名称,并且保障一样的类型就能够了。
如果你在做 DTO 转化的时候始终应用 set 进行属性赋值,那么请尝试这种形式简化代码,让代码更加清晰!
| 转化的语义
上边的转化过程,读者看后必定感觉优雅很多,然而咱们再写 Java 代码时,更多的须要思考语义的操作,再看上边的代码:
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
尽管这段代码很好的简化和优化了代码,然而他的语义是有问题的,咱们须要提现一个转化过程才好,所以代码改成如下:
@PostMapping
public User addUser(UserInputDTO userInputDTO){User user = convertFor(userInputDTO);
return userService.addUser(user);
}
private User convertFor(UserInputDTO userInputDTO){User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
这是一个更好的语义写法,尽管他麻烦了些,然而可读性大大增加了,在写代码时,咱们应该尽量把语义档次差不多的放到一个办法中,比方:
User user = convertFor(userInputDTO);
return userService.addUser(user);
这两段代码都没有裸露实现,都是在讲如何在同一个办法中,做一组雷同档次的语义操作,而不是裸露具体的实现。
如上所述,是一种重构形式,读者能够参考 Martin Fowler 的《Refactoring Imporving the Design of Existing Code》(重构改善既有代码的设计)这本书中的 Extract Method 重构形式。
| 形象接口定义
当理论工作中,实现了几个 API 的 DTO 转化时,咱们会发现,这样的操作有很多很多,那么应该定义好一个接口,让所有这样的操作都有规定的进行。
如果接口被定义当前,那么 convertFor 这个办法的语义将产生变动,它将是一个实现类。
看一下形象后的接口:
public interface DTOConvert<S,T> {T convert(S s);
}
尽管这个接口很简略,然而这里通知咱们一个事件,要去应用泛型,如果你是一个优良的 Java 程序员,请为你想做的形象接口,做好泛型吧。
咱们再来看接口实现:
public class UserInputDTOConvert implements DTOConvert {
@Override
public User convert(UserInputDTO userInputDTO) {User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
咱们这样重构后,咱们发现当初的代码是如此的简洁,并且那么的标准:
@RequestMapping("/v1/api/user")
@RestController
public class UserApi {
@Autowired
private UserService userService;
@PostMapping
public User addUser(UserInputDTO userInputDTO){User user = new UserInputDTOConvert().convert(userInputDTO);
return userService.addUser(user);
}
}
| review code
如果你是一个优良的 Java 程序员,我置信你应该和我一样,曾经数次反复 review 过本人的代码很屡次了。
咱们再看这个保留用户的例子,你将发现,API 中返回值是有些问题的,问题就在于不应该间接返回 User 实体,因为如果这样的话,就裸露了太多实体相干的信息,这样的返回值是不平安的。
所以咱们更应该返回一个 DTO 对象,咱们可称它为 UserOutputDTO:
@PostMapping
public UserOutputDTO addUser(UserInputDTO userInputDTO){User user = new UserInputDTOConvert().convert(userInputDTO);
User saveUserResult = userService.addUser(user);
UserOutputDTO result = new UserOutDTOConvert().convertToUser(saveUserResult);
return result;
}
这样你的 API 才更健全。
不晓得在看完这段代码之后,读者有是否发现还有其余问题的存在,作为一个优良的 Java 程序员,请看一下这段咱们刚刚形象完的代码:
User user = new UserInputDTOConvert().convert(userInputDTO);
你会发现,new 这样一个 DTO 转化对象是没有必要的,而且每一个转化对象都是由在遇到 DTO 转化的时候才会呈现,那咱们应该考虑一下,是否能够将这个类和 DTO 进行聚合呢?
看一下我的聚合后果:
public class UserInputDTO {
private String username;
private int age;
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public User convertToUser(){UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert();
User convert = userInputDTOConvert.convert(this);
return convert;
}
private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
@Override
public User convert(UserInputDTO userInputDTO) {User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
}
而后 API 中的转化则由:
User user = new UserInputDTOConvert().convert(userInputDTO);
User saveUserResult = userService.addUser(user);
变成了:
User user = userInputDTO.convertToUser();
User saveUserResult = userService.addUser(user);
咱们再 DTO 对象中增加了转化的行为,我置信这样的操作能够让代码的可读性变得更强,并且是合乎语义的。
| 再查工具类
再来看 DTO 外部转化的代码,它实现了咱们本人定义的 DTOConvert 接口,然而这样真的就没有问题,不须要再思考了吗?
我感觉并不是,对于 Convert 这种转化语义来讲,很多工具类中都有这样的定义,这中 Convert 并不是业务级别上的接口定义,它只是用于一般 bean 之间转化属性值的一般意义上的接口定义,所以咱们应该更多的去读其余含有 Convert 转化语义的代码。
我仔细阅读了一下 GUAVA 的源码,发现了 com.google.common.base.Convert 这样的定义:
public abstract class Converter<A, B> implements Function<A, B> {protected abstract B doForward(A a);
protected abstract A doBackward(B b);
// 其余略
}
从源码能够理解到,GUAVA 中的 Convert 能够实现正向转化和逆向转化,持续批改咱们 DTO 中转化的这段代码:
private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
@Override
public User convert(UserInputDTO userInputDTO) {User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
批改后:
private static class UserInputDTOConvert extends Converter<UserInputDTO, User> {
@Override
protected User doForward(UserInputDTO userInputDTO) {User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
@Override
protected UserInputDTO doBackward(User user) {UserInputDTO userInputDTO = new UserInputDTO();
BeanUtils.copyProperties(user,userInputDTO);
return userInputDTO;
}
}
看了这部分代码当前,你可能会问,那逆向转化会有什么用呢?其实咱们有很多小的业务需要中,入参和出参是一样的,那么咱们变能够轻松的进行转化,我将上边所提到的 UserInputDTO 和 UserOutputDTO 都转成 UserDTO 展现给大家。
DTO:
public class UserDTO {
private String username;
private int age;
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public User convertToUser(){UserDTOConvert userDTOConvert = new UserDTOConvert();
User convert = userDTOConvert.convert(this);
return convert;
}
public UserDTO convertFor(User user){UserDTOConvert userDTOConvert = new UserDTOConvert();
UserDTO convert = userDTOConvert.reverse().convert(user);
return convert;
}
private static class UserDTOConvert extends Converter<UserDTO, User> {
@Override
protected User doForward(UserDTO userDTO) {User user = new User();
BeanUtils.copyProperties(userDTO,user);
return user;
}
@Override
protected UserDTO doBackward(User user) {UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user,userDTO);
return userDTO;
}
}
}
API:
@PostMapping
public UserDTO addUser(UserDTO userDTO){User user = userDTO.convertToUser();
User saveResultUser = userService.addUser(user);
UserDTO result = userDTO.convertFor(saveResultUser);
return result;
}
当然,上述只是表明了转化方向的正向或逆向,很多业务需要的出参和入参的 DTO 对象是不同的,那么你须要更显著的通知程序:逆向是无奈调用的。
private static class UserDTOConvert extends Converter<UserDTO, User> {
@Override
protected User doForward(UserDTO userDTO) {User user = new User();
BeanUtils.copyProperties(userDTO,user);
return user;
}
@Override
protected UserDTO doBackward(User user) {throw new AssertionError("不反对逆向转化办法!");
}
}
看一下 doBackward 办法,间接抛出了一个断言异样,而不是业务异样,这段代码通知代码的调用者,这个办法不是准你调用的,如果你调用,我就“断言”你调用谬误了。
对于异样解决的更具体介绍,能够参考这篇文章:如何优雅的设计 Java 异样,应该能够帮你更好的了解异样。
| bean 的验证
如果你认为我上边写的那个增加用户 API 写的曾经十分完满了,那只能阐明你还不是一个优良的程序员。咱们应该保障任何数据的入参到办法体内都是非法的。
| 为什么要验证
很多人会通知我,如果这些 API 是提供给前端进行调用的,前端都会进行验证啊,你为什还要验证?
其实答案是这样的,我从不置信任何调用我 API 或者办法的人,比方前端验证失败了,或者某些人通过一些非凡的渠道 (比方 Charles 进行抓包),间接将数据传入到我的 API,那我依然进行失常的业务逻辑解决,那么就有可能产生脏数据!
“对于脏数据的产生肯定是致命”,这句话心愿大家牢记在心,再小的脏数据也有可能让你找几个通宵!
| jsr 303 验证
hibernate 提供的 jsr 303 实现,我感觉目前依然是很优良的,具体如何应用,我不想讲,因为谷歌上你能够搜寻出很多答案!
再以下班的 API 实例进行阐明,咱们当初对 DTO 数据进行查看:
public class UserDTO {
@NotNull
private String username;
@NotNull
private int age;
// 其余代码略
}
API 验证:
@PostMapping
public UserDTO addUser(@Valid UserDTO userDTO){User user = userDTO.convertToUser();
User saveResultUser = userService.addUser(user);
UserDTO result = userDTO.convertFor(saveResultUser);
return result;
}
咱们须要将验证后果传给前端,这种异样应该转化为一个 api 异样(带有错误码的异样)。
@PostMapping
public UserDTO addUser(@Valid UserDTO userDTO, BindingResult bindingResult){checkDTOParams(bindingResult);
User user = userDTO.convertToUser();
User saveResultUser = userService.addUser(user);
UserDTO result = userDTO.convertFor(saveResultUser);
return result;
}
private void checkDTOParams(BindingResult bindingResult){if(bindingResult.hasErrors()){//throw new 带验证码的验证谬误异样}
}
BindingResult 是 Spring MVC 验证 DTO 后的一个后果集,能够参考 spring 官网文档:
https://spring.io/
查看参数后,能够抛出一个“带验证码的验证谬误异样”。
拥抱 lombok
上边的 DTO 代码,曾经让我看的很累了,我置信读者也是一样,看到那么多的 Getter 和 Setter 办法,太焦躁了,那时候有什么办法能够简化这些呢。
请拥抱 lombok,它会帮忙咱们解决一些让咱们很焦躁的问题。
具体看以看这篇教程:
https://www.javastack.cn/arti…
去掉 Setter 和 Getter
其实这个题目,我不太想说,因为网上太多,然而因为很多人通知我,他们基本就不晓得 lombok 的存在,所以为了让读者更好的学习,我违心写这样一个例子:
@Setter
@Getter
public class UserDTO {
@NotNull
private String username;
@NotNull
private int age;
public User convertToUser(){UserDTOConvert userDTOConvert = new UserDTOConvert();
User convert = userDTOConvert.convert(this);
return convert;
}
public UserDTO convertFor(User user){UserDTOConvert userDTOConvert = new UserDTOConvert();
UserDTO convert = userDTOConvert.reverse().convert(user);
return convert;
}
private static class UserDTOConvert extends Converter<UserDTO, User> {
@Override
protected User doForward(UserDTO userDTO) {User user = new User();
BeanUtils.copyProperties(userDTO,user);
return user;
}
@Override
protected UserDTO doBackward(User user) {throw new AssertionError("不反对逆向转化办法!");
}
}
}
看到了吧,烦人的 Getter 和 Setter 办法曾经去掉了。
然而上边的例子基本不足以体现 lombok 的弱小。我心愿写一些网上很难查到,或者很少人进行阐明的 lombok 的应用以及在应用时程序语义上的阐明。
比方:@Data,@AllArgsConstructor,@NoArgsConstructor.. 这些我就不进行一一阐明了,请大家自行查问材料。
bean 中的链式格调
什么是链式格调?我来举个例子,看上面这个 Student 的 bean:
public class Student {
private String name;
private int age;
public String getName() {return name;}
public Student setName(String name) {
this.name = name;
return this;
}
public int getAge() {return age;}
public Student setAge(int age) {return this;}
}
认真看一下 set 办法,这样的设置便是 chain 的 style,调用的时候,能够这样应用:
Student student = new Student()
.setAge(24)
.setName("zs");
置信正当应用这样的链式代码,会更多的程序带来很好的可读性,那看一下如果应用 lombok 进行改善呢,请应用 @Accessors(chain = true),看如下代码:
@Accessors(chain = true)
@Setter
@Getter
public class Student {
private String name;
private int age;
}
这样就实现了一个对于 bean 来讲很敌对的链式操作。
| 动态构造方法
动态构造方法的语义和简化水平真的高于间接去 new 一个对象。比方 new 一个 List 对象,过来的应用是这样的:
List<String> list = new ArrayList<>();
看一下 guava 中的创立形式:
List<String> list = Lists.newArrayList();
Lists 命名是一种约定(俗话说:约定优于配置),它是指 Lists 是 List 这个类的一个工具类,那么应用 List 的工具类去产生 List,这样的语义是不是要比间接 new 一个子类来的更间接一些呢,答案是必定的。
再比方如果有一个工具类叫做 Maps,那你是否想到了创立 Map 的办法呢:
HashMap<String, String> objectObjectHashMap = Maps.newHashMap();
好了,如果你了解了我说的语义,那么,你曾经向成为 Java 程序员更近了一步了。
再回过头来看刚刚的 Student,很多时候,咱们去写 Student 这个 bean 的时候,他会有一些必输字段。
比方 Student 中的 name 字段,个别解决的形式是将 name 字段包装成一个构造方法,只有传入 name 这样的构造方法,能力创立一个 Student 对象。
接上上边的动态构造方法和必传参数的构造方法,应用 lombok 将更改成如下写法(@RequiredArgsConstructor 和 @NonNull):
@Accessors(chain = true)
@Setter
@Getter
@RequiredArgsConstructor(staticName = "ofName")
public class Student {
@NonNull private String name;
private int age;
}
测试代码:
Student student = Student.ofName("zs");
这样构建出的 bean 语义是否要比间接 new 一个含参的构造方法(蕴含 name 的构造方法)要好很多。
当然,看过很多源码当前,我想置信将动态构造方法 ofName 换成 of 会先的更加简洁:
@Accessors(chain = true)
@Setter
@Getter
@RequiredArgsConstructor(staticName = "of")
public class Student {
@NonNull private String name;
private int age;
}
测试代码:
Student student = Student.of("zs");
当然他依然是反对链式调用的:
Student student = Student.of("zs").setAge(24);
这样来写代码,真的很简洁,并且可读性很强。
| 应用 builder
Builder 模式我不想再多解释了,读者能够看一下《Head First》(设计模式) 的建造者模式。
明天其实要说的是一种变种的 builder 模式,那就是构建 bean 的 builder 模式,其实次要的思维是带着大家一起看一下 lombok 给咱们带来了什么。
看一下 Student 这个类的原始 builder 状态:
public class Student {
private String name;
private int age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public static Builder builder(){return new Builder();
}
public static class Builder{
private String name;
private int age;
public Builder name(String name){
this.name = name;
return this;
}
public Builder age(int age){
this.age = age;
return this;
}
public Student build(){Student student = new Student();
student.setAge(age);
student.setName(name);
return student;
}
}
}
调用形式:
Student student = Student.builder().name("zs").age(24).build();
这样的 builder 代码,让我是在恶心好受,于是我打算用 lombok 重构这段代码:
@Builder
public class Student {
private String name;
private int age;
}
调用形式:
Student student = Student.builder().name("zs").age(24).build();
代理模式
正如咱们所知的,在程序中调用 rest 接口是一个常见的行为动作,如果你和我一样应用过 spring 的 RestTemplate,我置信你会我和一样,对他抛出的非 http 状态码异样疾恶如仇。
所以咱们思考将 RestTemplate 最为底层包装器进行包装器模式的设计:
public abstract class FilterRestTemplate implements RestOperations {
protected volatile RestTemplate restTemplate;
protected FilterRestTemplate(RestTemplate restTemplate){this.restTemplate = restTemplate;}
// 实现 RestOperations 所有的接口
}
而后再由扩大类对 FilterRestTemplate 进行包装扩大:
public class ExtractRestTemplate extends FilterRestTemplate {
private RestTemplate restTemplate;
public ExtractRestTemplate(RestTemplate restTemplate) {super(restTemplate);
this.restTemplate = restTemplate;
}
public <T> RestResponseDTO<T> postForEntityWithNoException(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {RestResponseDTO<T> restResponseDTO = new RestResponseDTO<T>();
ResponseEntity<T> tResponseEntity;
try {tResponseEntity = restTemplate.postForEntity(url, request, responseType, uriVariables);
restResponseDTO.setData(tResponseEntity.getBody());
restResponseDTO.setMessage(tResponseEntity.getStatusCode().name());
restResponseDTO.setStatusCode(tResponseEntity.getStatusCodeValue());
}catch (Exception e){restResponseDTO.setStatusCode(RestResponseDTO.UNKNOWN_ERROR);
restResponseDTO.setMessage(e.getMessage());
restResponseDTO.setData(null);
}
return restResponseDTO;
}
}
包装器 ExtractRestTemplate 很完满的更改了异样抛出的行为,让程序更具备容错性。
在这里咱们不思考 ExtractRestTemplate 实现的性能,让咱们把焦点放在 FilterRestTemplate 上,“实现 RestOperations 所有的接口”。
这个操作相对不是一时半会能够写完的,过后在重构之前我简直写了半个小时,如下:
public abstract class FilterRestTemplate implements RestOperations {
protected volatile RestTemplate restTemplate;
protected FilterRestTemplate(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {return restTemplate.getForObject(url,responseType,uriVariables);
}
@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {return restTemplate.getForObject(url,responseType,uriVariables);
}
@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {return restTemplate.getForObject(url,responseType);
}
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {return restTemplate.getForEntity(url,responseType,uriVariables);
}
// 其余实现代码略。。。}
我置信你看了以上代码,你会和我一样感觉恶心反胃,起初我用 lombok 提供的代理注解优化了我的代码(@Delegate):
@AllArgsConstructor
public abstract class FilterRestTemplate implements RestOperations {
@Delegate
protected volatile RestTemplate restTemplate;
}
这几行代码齐全代替上述那些简短的代码。是不是很简洁,做一个拥抱 lombok 的程序员吧。
重构
| 我的项目需要
我的项目开发阶段,有一个对于下单发货的需要:如果今天下午 3 点前进行下单,那么发货工夫是今天,如果今天下午 3 点后进行下单,那么发货工夫是先天,如果被确定的工夫是周日,那么在此工夫上再加 1 天为发货工夫。
| 思考与重构
我置信这个需要看似很简略,无论怎么写都能够实现。
很多人可能看到这个需要,就入手开始写 Calendar 或 Date 进行计算,从而实现需要。
而我给的倡议是,认真思考如何写代码,而后再去写,不是说所有的工夫操作都用 Calendar 或 Date 去解决,肯定要看场景。
对于工夫的计算咱们要思考 joda-time 这种相似的成熟工夫计算框架来写代码,它会让代码更加简洁和易读。
请读者先思考这个需要如何用 Java 代码实现,或先写一个你感觉实现这个代码的思路,再来看我下边的代码,这样,你的播种会更多一些:
final DateTime DISTRIBUTION_TIME_SPLIT_TIME = new DateTime().withTime(15,0,0,0);
private Date calculateDistributionTimeByOrderCreateTime(Date orderCreateTime){DateTime orderCreateDateTime = new DateTime(orderCreateTime);
Date tomorrow = orderCreateDateTime.plusDays(1).toDate();
Date theDayAfterTomorrow = orderCreateDateTime.plusDays(2).toDate();
return orderCreateDateTime.isAfter(DISTRIBUTION_TIME_SPLIT_TIME) ? wrapDistributionTime(theDayAfterTomorrow) : wrapDistributionTime(tomorrow);
}
private Date wrapDistributionTime(Date distributionTime){DateTime currentDistributionDateTime = new DateTime(distributionTime);
DateTime plusOneDay = currentDistributionDateTime.plusDays(1);
boolean isSunday = (DateTimeConstants.SUNDAY == currentDistributionDateTime.getDayOfWeek());
return isSunday ? plusOneDay.toDate() : currentDistributionDateTime.toDate() ;
}
读这段代码的时候,你会发现,我将判断和有可能呈现的不同后果都当做一个变量,最终做一个三目运算符的形式进行返回。
这样的优雅和可读性不言而喻,当然这样的代码不是欲速不达的,我优化了 3 遍产生的以上代码。读者可依据本人的代码和我写的代码进行比照。
| 进步办法
如果你做了 3 年 + 的程序员,我置信像如上这样的需要,你很轻松就能实现,然而如果你想做一个会写 Java 的程序员,就好好的思考和重构代码吧。
写代码就如同写字一样,同样的字,大家都会写,然而写进去是否难看就不肯定了。如果想把程序写好,就要一直的思考和重构,敢于尝试,敢于创新,不要旧调重弹,肯定要做一个优良的 Java 程序员。
进步代码程度最好的办法就是有条理的重构!(留神:是有条理的重构)
| 设计模式
设计模式就是工具,而不是提现你是否是高水平程序员的一个指标。
我常常会看到某一个程序员兴奋的大喊,哪个程序哪个点我用到了设计模式,写的如许如许优良,如许如许好。我认真去翻阅的时候,却发现有很多是适度设计的。
| 业务驱动技术 or 技术驱动业务
业务驱动技术 or 技术驱动业务?其实这是一个始终在争执的话题,然而很多人不这么认为,我感觉就是大家不违心抵赖罢了。我来和大家大略剖析一下作为一个 Java 程序员,咱们应该如何判断本人所处于的地位。
业务驱动技术: 如果你所在的我的项目是一个收益很小或者甚至没有收益的我的项目,请不要搞其余翻新的货色,不要驱动业务要如何如何做,而是要熟知业务当初的痛点是什么?如何能力帮忙业务盈利或者让我的项目更好,更顺利的进行。
技术驱动业务: 如果你所在的我的项目是一个很牛的我的项目,比方淘宝这类的我的项目,我能够在满足业务需要的状况下,和业务沟通,应用什么样的技术能更好的帮忙业务发明收益。
比如说下单的时候要进队列,可能几分钟之后订单状态能力解决实现,然而会让用户有更晦涩的体验,赚取更多的拜访流量,那么我置信业务违心被技术驱动,会批准订单的提早问题,这样便是技术驱动业务。
我置信大部分人还都处于业务驱动技术的方向吧。所以你既然不能驱动业务,那就请拥抱业务变动吧。
| 代码设计
始终在做 Java 后端的我的项目,常常会有一些变动,我置信大家也都遇到过。
比方当咱们写一段代码的时候,咱们思考将需要映射成代码的状态模式,忽然有一天,状态模式里边又增加了很多行为变动的货色,这时候你就挠头了,你硬生生的将状态模式中增加过多行为和变动。
缓缓的你会发现这些状态模式,其实更像是一簇算法,应该应用策略模式,这时你应该曾经昏头昏脑了。
说了这么多,我的意思是,只有你感觉正当,就请将状态模式改为策略模式吧,所有的模式并不是凭空想象进去的,都是基于重构。
Java 编程中没有银弹,请拥抱业务变动,始终思考重构,你就有一个更好的代码设计!
| 你真的优良吗?
真不好意思,我取了一个这么无聊的题目。
国外风行一种编程形式,叫做结对编程,我置信国内很多公司都没有这么做,我就不在讲述结对编程带来的益处了,其实就是一边 code review,一边相互进步的一个过程。既然做不到这个,那如何让本人活在本人的世界中一直进步呢?
“平时开发的时候,做出的代码总认为是正确的,而且写法是完满的。”,我置信这是大部分人的心声,还回到刚刚的问题,如何在本人的世界中一直进步呢?
答案就是:
- 多看成熟框架的源码
- 多回头看本人的代码
- 勤于重构
你真的优良吗?如果你每周都实现了学习源码,回头看本人代码,而后勤于重构,我认为你就真的很优良了。
即便兴许你只是刚刚入门,然而始终保持,你就是一个真的会写 Java 代码的程序员了。
技能
| UML
不想多探讨 UML 相干的常识,然而我感觉你如果真的会写 Java,请先学会表白本人,UML 就是你谈话的语言。
做一名优良的 Java 程序员,请至多学会这两种 UML 图:
- 类图
- 时序图
| clean code
我认为放弃代码的简洁和可读性是代码的最根本保障,如果有一天为了程序的效率而升高了这两点,我认为是能够体谅的,除此之外,没有任何理由能够让你任意挥霍你的代码。
无论如何,请放弃你的代码的整洁。
| Linux 根底命令
这点其实和会写 Java 没有关系,然而 Linux 很多时候的确承载运行 Java 的容器,请学好 Linux 的根底命令。
总结
Java 是一个大体系,明天探讨并未波及框架和架构相干常识,只是探讨如何写好代码。
本文从写 Java 程序的小方面始终写到大方面,来论述了如何能力写好 Java 程序,并通知读者们如何能力进步本身的编码程度。
我心愿看到这篇文章的各位都能做一个优良的 Java 程序员。
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿 (2022 最新版)
2. 劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!