导语前端
自 2013 年毕业后,今年已是我工做的第 4 个年头了,总在作 Java 相关的工做,终于有时间坐下来,写一篇关于 Java 写法的一篇文章,来探讨一下若是你真的是一个 Java 程序员,那你真的会写 Java 吗?java
笔者是一个务实的程序员,故本文绝非扯淡文章,文中内容都是干货,望读者看后,能有所收获。程序员
文章核心算法
其实,本不想把标题写的那么恐怖,只是发现不少人干了几年 Java 之后,都自认为是一个不错的 Java 程序员了,能够拿着上万的工资都处宣扬本身了,写这篇文章的目的并非嘲讽和我同样作 Java 的同行们,只是但愿读者看到此篇文章后,能够和我同样,心平气和的争取作一个优秀的程序员。spring
讲述方向数据库
因为一直从事移动互联网相关工做,Java 开发中常常和移动端打交道或者作一些后端的工做,因此本篇文章更可能涉及和移动端的交互或者与后端的交互方式,笔者但愿以自身的一些学习经验或者开发经验,能够带动认真阅读本篇文章的读者们,让你们对 Java 有一个更好的态度去学习它,它不仅是一个赚钱的工具而已。编程
笔者身边有不少与笔者年龄相仿或年龄更大的朋友或同事,常常有人问我:“你如今还在学习吗?我以为没什么好学的,这些东西都差很少”,我老是回答只要有时间,我就要看一会书,这个时候,你们都会露出一副不屑的眼神或笑容。其实,很是能理解身边朋友或同事的见解,以目前状态来说,大多都是工做至少 5 年的程序员了,对于公司大大小小的业务须要,以目前的知识储备来说,均可以轻松应对,“没有什么好学的”其实这句话没有多大的问题,可是,若是你对编程还有一点点兴趣,只是不知道如何努力或改进,但愿本篇文章能够帮到你。后端
技术点设计模式
本文不是一个吹嘘的文章,不会讲不少高深的架构,相反,会讲解不少基础的问题和写法问题,若是读者自认为基础问题和写法问题都是否是问题,那请忽略这篇文章,节省出时间去作一些有意义的事情。api
开发工具
不知道有多少”老”程序员还在使用 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 方法,直接抛出了一个断言异常,而不是业务异常,这段代码告诉代码的调用者,这个方法不是准你调用的,若是你调用,我就”断言”你调用错误了。
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 官方文档(http://spring.io/)。
检查参数后,能够抛出一个“带验证码的验证错误异常”。
拥抱 lombok
上边的 DTO 代码,已经让我看的很累了,我相信读者也是同样,看到那么多的 Getter 和 Setter 方法,太烦躁了,那时候有什么方法能够简化这些呢。
请拥抱 lombok,它会帮助咱们解决一些让咱们很烦躁的问题
去掉 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 程序员。