首頁  >  文章  >  Java  >  Java程式碼編寫技巧實例分析

Java程式碼編寫技巧實例分析

PHPz
PHPz轉載
2023-04-18 22:58:031232瀏覽

開發工具

不知道有多少」老」程式設計師還在使用Eclipse,這些程式設計師們要不就是因循守舊,要不就是根本就不知道其他好的開發工具的存在,Eclipse吃內存卡頓的現像以及各種偶然莫名異常的出現,都告知我們是時候尋找新的開發工具了。

更換 IDE

根本就不想多解釋要換什麼樣的 IDE,如果你想成為一個優秀的 Java 程式設計師,請更換 IntelliJ IDEA。使用 IDEA 的好處,請搜尋Google。

別告訴我快捷鍵不好用

更換 IDE 不在我本文的重點內容中,所以不想用太多的篇幅去寫為什麼要更換IDE。在這裡,我只能告訴你,更換 IDE 只為了更好、更快的寫好 Java 程式碼。原因略。

別告訴我快捷鍵不好用,請嘗試新事物。

基於微服務的思想,建構在 B2C 電商場景下的專案實戰。核心技術堆疊,是 Spring Boot Dubbo 。未來,會重構成 Spring Cloud Alibaba 。

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 异常(http://lrwinx.github.io/2016/04/28/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%BE%E8%AE%A1java%E5%BC%82%E5%B8%B8/) ,应该可以帮你更好的理解异常。

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 后的一个结果集

检查参数后,可以抛出一个“带验证码的验证错误异常”,具体异常设计可以参考如何优雅的设计 Java 异常(http://lrwinx.github.io/2016/04/28/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%BE%E8%AE%A1java%E5%BC%82%E5%B8%B8/)。

拥抱 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 模式,那就是构建 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

#

以上是Java程式碼編寫技巧實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除