老生常谈:你真的会写Java吗?

 文章存在大量代码段,原本为了良好的视觉效果,打算用图片来代替。可是考虑到没法复制,因此仍是用文字了。各位看官见谅。前端

-正文-java

  文章核心linux

  其实,本不想把标题写的那么恐怖,只是发现不少人干了几年java之后,都自认为是一个不错的java程序员了,能够拿着上万的工资都处宣扬本身了,写这篇文章的目的并非嘲讽和我同样作java的同行们,只是但愿读者看到此骗文章后,能够和我同样,心平气和的争取作一个优秀的程序员。程序员

  讲述方向web

  因为一直从事移动互联网相关工做,java开发中常常和移动端打交道或者作一些后端的工做,因此本篇文章更可能涉及和移动端的交互或者与后端的交互方式,笔者但愿以自身的一些学习经验或者开发经验,能够带动认真阅读本篇文章的读者们,让你们对java有一个更好的态度去学习它,它不仅是一个赚钱的工具而已。算法

  笔者身边有不少与笔者年龄相仿或年龄更大的朋友或同事,常常有人问我:“你如今还在学习吗?我以为没什么好学的,这些东西都差很少”,我老是回答只要有时间,我就要看一会书,这个时候,你们都会露出一副不屑的眼神或笑容。spring

  其实,很是能理解身边朋友或同事的见解,以目前状态来说,大多都是工做至少5年的程序员了,对于公司大大小小的业务须要,以目前的知识储备来说,均可以轻松应对,“没有什么好学的”其实这句话没有多大的问题,可是,若是你对编程还有一点点兴趣,只是不知道如何努力或改进,但愿本篇文章能够帮到你。数据库

  技术点编程

  本文不是一个吹嘘的文章,不会讲不少高深的架构,相反,会讲解不少基础的问题和写法问题,若是读者自认为基础问题和写法问题都是否是问题,那请忽略这篇文章,节省出时间去作一些有意义的事情。后端

  开发工具

  不知道有多少”老”程序员还在使用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 { 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 { @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 implements Function { protected abstract B doForward(A a); protected abstract A doBackward(B b); //其余略 }
  从源码能够了解到,GUAVA中的Convert能够完成正向转化和逆向转化,继续修改咱们DTO中转化的这段代码:

private static class UserInputDTOConvert implements DTOConvert { @Override public User convert(UserInputDTO userInputDTO) { User user = new User(); BeanUtils.copyProperties(userInputDTO,user); return user; } }
  修改后:

private static class UserInputDTOConvert extends Converter { @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 { @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 { @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 官方文档

  检查参数后,能够抛出一个“带验证码的验证错误异常”,具体异常设计能够参考如何优雅的设计java异常

  拥抱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 { @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 list = new ArrayList<>();
  看一下guava中的建立方式:

List list = Lists.newArrayList();
  Lists命名是一种约定(俗话说:约定优于配置),它是指Lists是List这个类的一个工具类,那么使用List的工具类去产生List,这样的语义是否是要比直接new一个子类来的更直接一些呢,答案是确定的,再好比若是有一个工具类叫作Maps,那你是否想到了建立Map的方法呢:

HashMap 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 RestResponseDTO postForEntityWithNoException(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException { RestResponseDTO restResponseDTO = new RestResponseDTO(); ResponseEntity 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 getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException { return restTemplate.getForObject(url,responseType,uriVariables); } @Override public T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException { return restTemplate.getForObject(url,responseType,uriVariables); } @Override public T getForObject(URI url, Class responseType) throws RestClientException { return restTemplate.getForObject(url,responseType); } @Override public ResponseEntity getForEntity(String url, Class 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

  我认为保持代码的简洁和可读性是代码的最基本保证,若是有一天为了程序的效率而下降了这两点,我认为是能够谅解的,除此以外,没有任何理由可让你任意挥霍你的代码。

  读者能够看一下Robert C. Martin出版的《Clean Code》(代码整洁之道) 这本书

  能够参考美团文章聊聊clean code

  也能够看一下阿里的Java编码规范

  不管如何,请保持你的代码的整洁。

  linux 基础命令

  这点其实和会写java没有关系,可是linux不少时候确实承载运行java的容器,请学好linux的基础命令。

  参考鸟哥的《Linux私房菜》

  总结

  java是一个大致系,今天讨论并未涉及框架和架构相关知识,只是讨论如何写好代码。

  本文从写java程序的小方面一直写到大方面,来阐述了如何才能写好java程序,并告诉读者们如何才能提升自身的编码水平。

  我但愿看到这篇文章的各位都能作一个优秀的java程序员。