> Java > java지도 시간 > 본문

Java 코드 작성 기술 예시 분석

PHPz
풀어 주다: 2023-04-18 22:58:03
앞으로
1283명이 탐색했습니다.

개발 도구

얼마나 많은 "오래된" 프로그래머가 여전히 Eclipse를 사용하고 있는지 모르겠습니다. 이러한 프로그래머는 이전 방식에 갇혀 있거나 단순히 Eclipse가 메모리 지연으로 인해 어려움을 겪는 다른 좋은 개발 도구의 존재를 모릅니다. 다양한 우발적이고 설명할 수 없는 이상 현상이 나타나는 것은 물론, 모두 새로운 개발 도구를 찾아야 할 때임을 알려줍니다.

Change IDE

어떤 IDE를 바꿔야 할지 더 이상 설명하고 싶지 않습니다. 훌륭한 Java 프로그래머가 되고 싶다면 IntelliJ IDEA를 바꿔보세요. IDEA 사용의 이점은 Google에서 검색해 보세요.

단축키 사용이 쉽지 않다고 말하지 마세요

IDE 변경은 이 글의 초점이 아니기 때문에 IDE를 변경한 이유를 적는 데 너무 많은 공간을 소비하고 싶지 않습니다. 여기서 제가 말씀드릴 수 있는 것은 IDE를 바꾸는 것은 단지 Java 코드를 더 좋고 더 빠르게 작성하기 위한 것입니다. 이유는 생략합니다.

단축키가 작동하지 않는다고 말하지 말고 새로운 것을 시도해 보세요.

마이크로서비스 아이디어를 바탕으로 실제 프로젝트는 B2C 전자상거래 시나리오로 구축됩니다. 핵심 기술 스택은 Spring Boot + Dubbo입니다. 향후에는 Spring Cloud Alibaba로 개편될 예정입니다.

Bean

빈은 가장 일반적으로 사용되는 모델 중 하나이며 넓은 공간에서 콩을 설명할 예정이니 독자들이 잘 이해할 수 있기를 바랍니다.

도메인 패키지 이름

많은 Java 프로그래머의 "경험"에 따르면 데이터베이스 테이블은 도메인 개체에 해당하므로 많은 프로그래머가 코드를 작성할 때 패키지 이름은 com.xxx.domain을 사용하는 것 같습니다. 업계에서는 제약이 되었으며 데이터베이스 매핑 개체는 도메인이어야 합니다. 하지만 당신은 틀렸습니다. 도메인은 도메인 개체입니다. 전통적인 Java 소프트웨어 웹 개발을 수행할 때 이러한 도메인은 동작이 없거나 도메인 모델 동작이 부족한 빈약한 모델인 경우가 많습니다. 도메인 개체가 아닌 엔터티 개체이므로 패키지 이름을 com.xxx.entity로 변경하세요.

아직도 제가 말하는 내용을 이해하지 못하신다면 Vaughn Vernon이 쓴 "IMPLEMENTING DOMAIN-DRIVEN DESIGN"이라는 책을 읽어보세요. 이 책에서는 빈혈 모델과 도메인 모델의 차이점을 설명하고 있으니 많은 도움이 되실 거라 믿습니다. .

DTO

데이터 전송을 위해서는 DTO 객체를 전송 객체로 사용해야 한다는 점에 동의했습니다. 왜냐하면 저는 오랫동안 모바일 API 설계 작업을 해왔고 많은 사람들이 그렇게 생각한다고 말했습니다. 클라이언트가 데이터(입력 또는 출력)를 전송할 때 이러한 개체는 DTO 개체가 됩니다. 참고하세요! 이러한 이해는 네트워크 전송에 사용되는 개체인 한 DTO 개체로 간주될 수 있다고 생각합니다. 예를 들어 전자 상거래 플랫폼에서 사용자가 주문하면 주문 데이터가 표시됩니다. OMS 또는 ERP 시스템으로 전송되는 경우 이러한 연결의 반환 값과 입력 매개 변수를 DTO 개체라고도 합니다.

객체가 DTO 객체인 경우 이름이 XXDTO로 변경된다는 데 동의합니다(예: 주문 발행 OMS: OMSOrderInputDTO).

DTO 변환

아시다시피 DTO는 시스템이 외부 세계와 상호 작용하기 위한 모델 객체이므로 DTO 객체를 BO 객체 또는 일반 엔터티 객체로 변환하고 서비스를 제공하는 단계가 있어야 합니다. 레이어가 처리합니다.

Scenarios

예를 들어 회원 추가 작업은 데모용이므로 사용자의 간단한 데이터만 고려합니다. 백엔드 관리자가 사용자를 추가하기 위해 클릭하면 사용자의 이름과 나이만 전달하면 됩니다. 백엔드가 수락합니다. 데이터 후에는 생성 시간, 업데이트 시간 및 기본 비밀번호의 세 가지 필드가 추가되고 데이터베이스가 저장됩니다.

@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());
로그인 후 복사

도구를 사용하세요

위 코드는 논리적으로 말하면 문제가 없지만 이런 방식으로 작성하면 매우 기분이 좋아집니다. 피곤합니다. 예에 필드가 2개밖에 없습니다. 20개의 필드가 있다면 어떻게 될까요? 데이터를 하나씩 설정하시겠습니까? 물론 이렇게 하면 분명 문제가 없겠지만, 결코 최적의 접근 방식은 아닙니다.

인터넷에는 얕은 복사 또는 깊은 복사 유틸리티를 지원하는 도구가 많이 있습니다. 예를 들어, 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 프로그래밍에는 만병통치약이 없습니다. 비즈니스 변화를 수용하고 리팩토링에 대해 계속 생각하면 더 나은 코드 디자인을 갖게 될 것입니다!

정말 잘하시나요?

이렇게 지루한 제목을 선택해서 정말 죄송합니다.

해외에서는 페어 프로그래밍(Pair 프로그래밍)이라고 하는데, 국내 기업에서는 이를 안 하는 경우가 많다고 생각하는데, 실제로는 코드 리뷰를 하면서 페어 프로그래밍의 장점을 이야기하지는 않겠습니다. 당신은 이것을 할 수 없는데 어떻게 당신 자신의 세계에서 계속 발전할 수 있습니까?

"나는 개발할 때 항상 내가 만드는 코드가 정확하고 작성 방법이 완벽하다고 생각합니다." 이것이 바로 지금의 질문으로 돌아가서, 어떻게 코딩을 계속할지 생각해 봅시다. 자신의 세계에서 개선은 어떻습니까?

답은 다음과 같습니다.

  • 성숙한 프레임워크의 소스 코드 더 읽기

  • 자신의 코드를 더 자세히 살펴보세요

  • 리팩토링에 부지런히 참여하세요

정말 잘하시나요? 매주 소스코드를 공부하고, 자신의 코드를 되돌아보고, 리팩토링을 열심히 하시면 정말 대단하신 것 같아요.

이제 막 시작하더라도 꾸준히 노력한다면 정말 자바 코드를 작성할 수 있는 프로그래머가 될 것입니다.

Skills

UML

UML 관련 지식을 더 논하고 싶지는 않지만 Java 작성 방법을 정말로 알고 있다면 먼저 자신을 표현하는 방법을 배우십시오. UML은 당신이 말하는 언어입니다. Java 프로그래머는 최소한 다음 두 가지 UML 다이어그램을 배우십시오.

  • 클래스 다이어그램

  • 시퀀스 다이어그램

클린 코드

위 내용은 Java 코드 작성 기술 예시 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿