1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        你見(jiàn)過(guò)哪些目瞪口呆的 Java 代碼技巧?

        共 38363字,需瀏覽 77分鐘

         ·

        2022-08-25 18:32

        點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

        作者:java小瓜哥

        來(lái)源:https://juejin.cn/post/6844903954308939784

        自從畢業(yè)后,今年已經(jīng)是我工作的第 8 個(gè)年頭了,我甚至都快忘記了到底是那年畢業(yè)的。從出來(lái)本人一直在做 Java 相關(guān)的工作,現(xiàn)在終于有時(shí)間坐下來(lái),寫(xiě)一篇關(guān)于 Java 寫(xiě)法的一篇文章,來(lái)探討一下如果你真的是一個(gè) Java 程序員,那你真的會(huì)寫(xiě) Java 嗎?

        筆者是一個(gè)務(wù)實(shí)的程序員,故本文絕非扯淡文章,文中內(nèi)容都是干貨,望讀者看后,能有所收獲。

        本文不是一個(gè)吹噓的文章,不會(huì)講很多高深的架構(gòu),相反,會(huì)講解很多基礎(chǔ)的問(wèn)題和寫(xiě)法問(wèn)題,如果讀者自認(rèn)為基礎(chǔ)問(wèn)題和寫(xiě)法問(wèn)題都是不是問(wèn)題,那請(qǐng)忽略這篇文章,節(jié)省出時(shí)間去做一些有意義的事情。

        1開(kāi)發(fā)工具

        不知道有多少”老”程序員還在使用 Eclipse,這些程序員們要不就是因循守舊,要不就是根本就不知道其他好的開(kāi)發(fā)工具的存在,Eclipse 吃內(nèi)存卡頓的現(xiàn)象以及各種偶然莫名異常的出現(xiàn),都告知我們是時(shí)候?qū)ふ倚碌拈_(kāi)發(fā)工具了。

        更換 IDE

        根本就不想多解釋要換什么樣的 IDE,如果你想成為一個(gè)優(yōu)秀的 Java 程序員,請(qǐng)更換 IntelliJ IDEA。使用 IDEA 的好處,請(qǐng)搜索谷歌。

        別告訴我快捷鍵不好用

        更換 IDE 不在我本文的重點(diǎn)內(nèi)容中,所以不想用太多的篇幅去寫(xiě)為什么更換IDE。在這里,我只能告訴你,更換 IDE 只為了更好、更快的寫(xiě)好 Java 代碼。原因略。

        別告訴我快捷鍵不好用,請(qǐng)嘗試新事物。

        bean

        bean 使我們使用最多的模型之一,我將以大篇幅去講解 bean,希望讀者好好體會(huì)。

        domain 包名

        根據(jù)很多 Java 程序員的”經(jīng)驗(yàn)”來(lái)看,一個(gè)數(shù)據(jù)庫(kù)表則對(duì)應(yīng)著一個(gè) domain 對(duì)象,所以很多程序員在寫(xiě)代碼時(shí),包名則使用:com.xxx.domain ,這樣寫(xiě)好像已經(jīng)成為了行業(yè)的一種約束,數(shù)據(jù)庫(kù)映射對(duì)象就應(yīng)該是 domain。但是你錯(cuò)了,domain 是一個(gè)領(lǐng)域?qū)ο?,往往我們?cè)僮鰝鹘y(tǒng) Java 軟件 Web 開(kāi)發(fā)中,這些 domain 都是貧血模型,是沒(méi)有行為的,或是沒(méi)有足夠的領(lǐng)域模型的行為的,所以,以這個(gè)理論來(lái)講,這些 domain 都應(yīng)該是一個(gè)普通的 entity 對(duì)象,并非領(lǐng)域?qū)ο螅哉?qǐng)把包名改為:com.xxx.entity。

        如果你還不理解我說(shuō)的話,請(qǐng)看一下 Vaughn Vernon 出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN DESIGN》(實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))這本書(shū),書(shū)中講解了貧血模型與領(lǐng)域模型的區(qū)別,相信你會(huì)受益匪淺。

        DTO

        數(shù)據(jù)傳輸我們應(yīng)該使用 DTO 對(duì)象作為傳輸對(duì)象,這是我們所約定的,因?yàn)楹荛L(zhǎng)時(shí)間我一直都在做移動(dòng)端 API 設(shè)計(jì)的工作,有很多人告訴我,他們認(rèn)為只有給手機(jī)端傳輸數(shù)據(jù)的時(shí)候(input or output),這些對(duì)象成為 DTO 對(duì)象。請(qǐng)注意!這種理解是錯(cuò)誤的,只要是用于網(wǎng)絡(luò)傳輸?shù)膶?duì)象,我們都認(rèn)為他們可以當(dāng)做是 DTO 對(duì)象,比如電商平臺(tái)中,用戶進(jìn)行下單,下單后的數(shù)據(jù),訂單會(huì)發(fā)到 OMS 或者 ERP 系統(tǒng),這些對(duì)接的返回值以及入?yún)⒁步?DTO 對(duì)象。

        我們約定某對(duì)象如果是 DTO 對(duì)象,就將名稱(chēng)改為 XXDTO,比如訂單下發(fā)OMS:OMSOrderInputDTO。

        DTO 轉(zhuǎn)化

        正如我們所知,DTO 為系統(tǒng)與外界交互的模型對(duì)象,那么肯定會(huì)有一個(gè)步驟是將 DTO 對(duì)象轉(zhuǎn)化為 BO 對(duì)象或者是普通的 entity 對(duì)象,讓 service 層去處理。

        場(chǎng)景

        比如添加會(huì)員操作,由于用于演示,我只考慮用戶的一些簡(jiǎn)單數(shù)據(jù),當(dāng)后臺(tái)管理員點(diǎn)擊添加用戶時(shí),只需要傳過(guò)來(lái)用戶的姓名和年齡就可以了,后端接受到數(shù)據(jù)后,將添加創(chuàng)建時(shí)間和更新時(shí)間和默認(rèn)密碼三個(gè)字段,然后保存數(shù)據(jù)庫(kù)。

        @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);
            }
        }

        我們只關(guān)注一下上述代碼中的轉(zhuǎn)化代碼,其他內(nèi)容請(qǐng)忽略:

        User user = new User();
        user.setUsername(userInputDTO.getUsername());
        user.setAge(userInputDTO.getAge());

        請(qǐng)使用工具

        上邊的代碼,從邏輯上講,是沒(méi)有問(wèn)題的,只是這種寫(xiě)法讓我很厭煩,例子中只有兩個(gè)字段,如果有 20 個(gè)字段,我們要如何做呢? 一個(gè)一個(gè)進(jìn)行 set 數(shù)據(jù)嗎?當(dāng)然,如果你這么做了,肯定不會(huì)有什么問(wèn)題,但是,這肯定不是一個(gè)最優(yōu)的做法。

        網(wǎng)上有很多工具,支持淺拷貝或深拷貝的 Utils。舉個(gè)例子,我們可以使用 org.springframework.beans.BeanUtils#copyProperties 對(duì)代碼進(jìn)行重構(gòu)和優(yōu)化:

        @PostMapping
        public User addUser(UserInputDTO userInputDTO){
            User user = new User();
            BeanUtils.copyProperties(userInputDTO,user);
            return userService.addUser(user);
        }

        BeanUtils.copyProperties 是一個(gè)淺拷貝方法,復(fù)制屬性時(shí),我們只需要把 DTO 對(duì)象和要轉(zhuǎn)化的對(duì)象兩個(gè)的屬性值設(shè)置為一樣的名稱(chēng),并且保證一樣的類(lèi)型就可以了。如果你在做 DTO 轉(zhuǎn)化的時(shí)候一直使用 set 進(jìn)行屬性賦值,那么請(qǐng)嘗試這種方式簡(jiǎn)化代碼,讓代碼更加清晰!

        轉(zhuǎn)化的語(yǔ)義

        上邊的轉(zhuǎn)化過(guò)程,讀者看后肯定覺(jué)得優(yōu)雅很多,但是我們?cè)賹?xiě) Java 代碼時(shí),更多的需要考慮語(yǔ)義的操作,再看上邊的代碼:

        User user = new User();
        BeanUtils.copyProperties(userInputDTO,user);

        雖然這段代碼很好的簡(jiǎn)化和優(yōu)化了代碼,但是他的語(yǔ)義是有問(wèn)題的,我們需要提現(xiàn)一個(gè)轉(zhuǎn)化過(guò)程才好,所以代碼改成如下:

        @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;
         }

        這是一個(gè)更好的語(yǔ)義寫(xiě)法,雖然他麻煩了些,但是可讀性大大增加了,在寫(xiě)代碼時(shí),我們應(yīng)該盡量把語(yǔ)義層次差不多的放到一個(gè)方法中,比如:

        User user = convertFor(userInputDTO);
        return userService.addUser(user);

        這兩段代碼都沒(méi)有暴露實(shí)現(xiàn),都是在講如何在同一個(gè)方法中,做一組相同層次的語(yǔ)義操作,而不是暴露具體的實(shí)現(xiàn)。

        如上所述,是一種重構(gòu)方式,讀者可以參考 Martin Fowler 的《Refactoring Imporving the Design of Existing Code》(重構(gòu) 改善既有代碼的設(shè)計(jì)) 這本書(shū)中的 Extract Method 重構(gòu)方式。

        抽象接口定義

        當(dāng)實(shí)際工作中,完成了幾個(gè) API 的 DTO 轉(zhuǎn)化時(shí),我們會(huì)發(fā)現(xiàn),這樣的操作有很多很多,那么應(yīng)該定義好一個(gè)接口,讓所有這樣的操作都有規(guī)則的進(jìn)行。

        如果接口被定義以后,那么 convertFor 這個(gè)方法的語(yǔ)義將產(chǎn)生變化,它將是一個(gè)實(shí)現(xiàn)類(lèi)。

        看一下抽象后的接口:

        public interface DTOConvert<S,T{
            convert(S s);
        }

        雖然這個(gè)接口很簡(jiǎn)單,但是這里告訴我們一個(gè)事情,要去使用泛型,如果你是一個(gè)優(yōu)秀的 Java 程序員,請(qǐng)為你想做的抽象接口,做好泛型吧。

        我們?cè)賮?lái)看接口實(shí)現(xiàn):

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

        我們這樣重構(gòu)后,我們發(fā)現(xiàn)現(xiàn)在的代碼是如此的簡(jiǎn)潔,并且那么的規(guī)范:

        @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

        如果你是一個(gè)優(yōu)秀的 Java 程序員,我相信你應(yīng)該和我一樣,已經(jīng)數(shù)次重復(fù) review 過(guò)自己的代碼很多次了。

        我們?cè)倏催@個(gè)保存用戶的例子,你將發(fā)現(xiàn),API 中返回值是有些問(wèn)題的,問(wèn)題就在于不應(yīng)該直接返回 User 實(shí)體,因?yàn)槿绻@樣的話,就暴露了太多實(shí)體相關(guān)的信息,這樣的返回值是不安全的,所以我們更應(yīng)該返回一個(gè) DTO 對(duì)象,我們可稱(chēng)它為 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 才更健全。

        不知道在看完這段代碼之后,讀者有是否發(fā)現(xiàn)還有其他問(wèn)題的存在,作為一個(gè)優(yōu)秀的 Java 程序員,請(qǐng)看一下這段我們剛剛抽象完的代碼:

        User user = new UserInputDTOConvert().convert(userInputDTO);

        你會(huì)發(fā)現(xiàn),new 這樣一個(gè) DTO 轉(zhuǎn)化對(duì)象是沒(méi)有必要的,而且每一個(gè)轉(zhuǎn)化對(duì)象都是由在遇到 DTO 轉(zhuǎn)化的時(shí)候才會(huì)出現(xiàn),那我們應(yīng)該考慮一下,是否可以將這個(gè)類(lèi)和 DTO 進(jìn)行聚合呢,看一下我的聚合結(jié)果:

        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 中的轉(zhuǎn)化則由:

        User user = new UserInputDTOConvert().convert(userInputDTO);
        User saveUserResult = userService.addUser(user);

        變成了:

        User user = userInputDTO.convertToUser();
        User saveUserResult = userService.addUser(user);

        我們?cè)?DTO 對(duì)象中添加了轉(zhuǎn)化的行為,我相信這樣的操作可以讓代碼的可讀性變得更強(qiáng),并且是符合語(yǔ)義的。

        再查工具類(lèi)

        再來(lái)看 DTO 內(nèi)部轉(zhuǎn)化的代碼,它實(shí)現(xiàn)了我們自己定義的 DTOConvert 接口,但是這樣真的就沒(méi)有問(wèn)題,不需要再思考了嗎?

        我覺(jué)得并不是,對(duì)于 Convert 這種轉(zhuǎn)化語(yǔ)義來(lái)講,很多工具類(lèi)中都有這樣的定義,這中 Convert 并不是業(yè)務(wù)級(jí)別上的接口定義,它只是用于普通 bean 之間轉(zhuǎn)化屬性值的普通意義上的接口定義,所以我們應(yīng)該更多的去讀其他含有 Convert 轉(zhuǎn)化語(yǔ)義的代碼。

        我仔細(xì)閱讀了一下 GUAVA 的源碼,發(fā)現(xiàn)了 com.google.common.base.Convert 這樣的定義:

        public abstract class Converter<ABimplements Function<AB{
            protected abstract B doForward(A a);
            protected abstract A doBackward(B b);
            //其他略
        }

        從源碼可以了解到,GUAVA 中的 Convert 可以完成正向轉(zhuǎn)化和逆向轉(zhuǎn)化,繼續(xù)修改我們 DTO 中轉(zhuǎn)化的這段代碼:

        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<UserInputDTOUser{
                 @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;
                 }
         }

        看了這部分代碼以后,你可能會(huì)問(wèn),那逆向轉(zhuǎn)化會(huì)有什么用呢?其實(shí)我們有很多小的業(yè)務(wù)需求中,入?yún)⒑统鰠⑹且粯拥?,那么我們變可以輕松的進(jìn)行轉(zhuǎn)化,我將上邊所提到的 UserInputDTO 和 UserOutputDTO 都轉(zhuǎn)成 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<UserDTOUser{
                    @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;
         }

        當(dāng)然,上述只是表明了轉(zhuǎn)化方向的正向或逆向,很多業(yè)務(wù)需求的出參和入?yún)⒌?DTO 對(duì)象是不同的,那么你需要更明顯的告訴程序:逆向是無(wú)法調(diào)用的:

        private static class UserDTOConvert extends Converter<UserDTOUser{
                 @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("不支持逆向轉(zhuǎn)化方法!");
                 }
         }

        看一下 doBackward 方法,直接拋出了一個(gè)斷言異常,而不是業(yè)務(wù)異常,這段代碼告訴代碼的調(diào)用者,這個(gè)方法不是準(zhǔn)你調(diào)用的,如果你調(diào)用,我就”斷言”你調(diào)用錯(cuò)誤了。

        關(guān)于異常處理的更詳細(xì)介紹,可以參考這篇文章:如何優(yōu)雅的設(shè)計(jì) Java 異常(http://dw-z.ink/1Tak-) ,應(yīng)該可以幫你更好的理解異常。

        bean 的驗(yàn)證

        如果你認(rèn)為我上邊寫(xiě)的那個(gè)添加用戶 API 寫(xiě)的已經(jīng)非常完美了,那只能說(shuō)明你還不是一個(gè)優(yōu)秀的程序員。我們應(yīng)該保證任何數(shù)據(jù)的入?yún)⒌椒椒w內(nèi)都是合法的。

        為什么要驗(yàn)證

        很多人會(huì)告訴我,如果這些 API 是提供給前端進(jìn)行調(diào)用的,前端都會(huì)進(jìn)行驗(yàn)證啊,你為什還要驗(yàn)證?

        其實(shí)答案是這樣的,我從不相信任何調(diào)用我 API 或者方法的人,比如前端驗(yàn)證失敗了,或者某些人通過(guò)一些特殊的渠道(比如 Charles 進(jìn)行抓包),直接將數(shù)據(jù)傳入到我的 API,那我仍然進(jìn)行正常的業(yè)務(wù)邏輯處理,那么就有可能產(chǎn)生臟數(shù)據(jù)!

        “對(duì)于臟數(shù)據(jù)的產(chǎn)生一定是致命”,這句話希望大家牢記在心,再小的臟數(shù)據(jù)也有可能讓你找?guī)讉€(gè)通宵!

        jsr 303驗(yàn)證

        hibernate 提供的 jsr 303 實(shí)現(xiàn),我覺(jué)得目前仍然是很優(yōu)秀的,具體如何使用,我不想講,因?yàn)楣雀枭夏憧梢运阉鞒龊芏啻鸢?

        再以上班的 API 實(shí)例進(jìn)行說(shuō)明,我們現(xiàn)在對(duì) DTO 數(shù)據(jù)進(jìn)行檢查:

        public class UserDTO {
            @NotNull
            private String username;
            @NotNull
            private int age;
                //其他代碼略
        }

        API 驗(yàn)證:

        @PostMapping
            public UserDTO addUser(@Valid UserDTO userDTO){
                    User user =  userDTO.convertToUser();
                    User saveResultUser = userService.addUser(user);
                    UserDTO result = userDTO.convertFor(saveResultUser);
                    return result;
            }

        我們需要將驗(yàn)證結(jié)果傳給前端,這種異常應(yīng)該轉(zhuǎn)化為一個(gè) api 異常(帶有錯(cuò)誤碼的異常)。

        @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 帶驗(yàn)證碼的驗(yàn)證錯(cuò)誤異常
             }
        }

        BindingResult 是 Spring MVC 驗(yàn)證 DTO 后的一個(gè)結(jié)果集,可以參考spring 官方文檔(spring.io/)。

        檢查參數(shù)后,可以拋出一個(gè)“帶驗(yàn)證碼的驗(yàn)證錯(cuò)誤異常”

        具體可以參考這篇:很優(yōu)秀的文章

        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 代碼,已經(jīng)讓我看的很累了,我相信讀者也是一樣,看到那么多的 Getter 和 Setter 方法,太煩躁了,那時(shí)候有什么方法可以簡(jiǎn)化這些呢。

        請(qǐng)擁抱 lombok,它會(huì)幫助我們解決一些讓我們很煩躁的問(wèn)題

        去掉 Setter 和 Getter

        其實(shí)這個(gè)標(biāo)題,我不太想說(shuō),因?yàn)榫W(wǎng)上太多,但是因?yàn)楹芏嗳烁嬖V我,他們根本就不知道 lombok 的存在,所以為了讓讀者更好的學(xué)習(xí),我愿意寫(xiě)這樣一個(gè)例子:

        @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<UserDTOUser{
                @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("不支持逆向轉(zhuǎn)化方法!");
                }
            }
        }

        看到了吧,煩人的 Getter 和 Setter 方法已經(jīng)去掉了。

        但是上邊的例子根本不足以體現(xiàn) lombok 的強(qiáng)大。我希望寫(xiě)一些網(wǎng)上很難查到,或者很少人進(jìn)行說(shuō)明的 lombok 的使用以及在使用時(shí)程序語(yǔ)義上的說(shuō)明。

        比如:@Data,@AllArgsConstructor,@NoArgsConstructor..這些我就不進(jìn)行一一說(shuō)明了,請(qǐng)大家自行查詢資料。

        bean 中的鏈?zhǔn)斤L(fēng)格

        什么是鏈?zhǔn)斤L(fēng)格?我來(lái)舉個(gè)例子,看下面這個(gè) 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;
            }
        }

        仔細(xì)看一下 set 方法,這樣的設(shè)置便是 chain 的 style,調(diào)用的時(shí)候,可以這樣使用:

        Student student = new Student()
                .setAge(24)
                .setName("zs");

        相信合理使用這樣的鏈?zhǔn)酱a,會(huì)更多的程序帶來(lái)很好的可讀性,那看一下如果使用 lombok 進(jìn)行改善呢,請(qǐng)使用 @Accessors(chain = true),看如下代碼:

        @Accessors(chain = true)
        @Setter
        @Getter
        public class Student {
            private String name;
            private int age;
        }

        這樣就完成了一個(gè)對(duì)于 bean 來(lái)講很友好的鏈?zhǔn)讲僮鳌?/p>

        靜態(tài)構(gòu)造方法

        靜態(tài)構(gòu)造方法的語(yǔ)義和簡(jiǎn)化程度真的高于直接去 new 一個(gè)對(duì)象。比如 new 一個(gè) List 對(duì)象,過(guò)去的使用是這樣的:

        List<String> list = new ArrayList<>();

        看一下 guava 中的創(chuàng)建方式:

        List<String> list = Lists.newArrayList();

        Lists 命名是一種約定(俗話說(shuō):約定優(yōu)于配置),它是指 Lists 是 List 這個(gè)類(lèi)的一個(gè)工具類(lèi),那么使用 List 的工具類(lèi)去產(chǎn)生 List,這樣的語(yǔ)義是不是要比直接 new 一個(gè)子類(lèi)來(lái)的更直接一些呢,答案是肯定的,再比如如果有一個(gè)工具類(lèi)叫做 Maps,那你是否想到了創(chuàng)建 Map 的方法呢:

        HashMap<String, String> objectObjectHashMap = Maps.newHashMap();

        好了,如果你理解了我說(shuō)的語(yǔ)義,那么,你已經(jīng)向成為 Java 程序員更近了一步了。

        再回過(guò)頭來(lái)看剛剛的 Student,很多時(shí)候,我們?nèi)?xiě) Student 這個(gè) bean 的時(shí)候,他會(huì)有一些必輸字段,比如 Student 中的 name 字段,一般處理的方式是將 name 字段包裝成一個(gè)構(gòu)造方法,只有傳入 name 這樣的構(gòu)造方法,才能創(chuàng)建一個(gè) Student 對(duì)象。

        接上上邊的靜態(tài)構(gòu)造方法和必傳參數(shù)的構(gòu)造方法,使用 lombok 將更改成如下寫(xiě)法(@RequiredArgsConstructor 和 @NonNull):

        @Accessors(chain = true)
        @Setter
        @Getter
        @RequiredArgsConstructor(staticName = "ofName")
        public class Student {
            @NonNull private String name;
            private int age;
        }

        測(cè)試代碼:

        Student student = Student.ofName("zs");

        這樣構(gòu)建出的 bean 語(yǔ)義是否要比直接 new 一個(gè)含參的構(gòu)造方法(包含  name 的構(gòu)造方法)要好很多。

        當(dāng)然,看過(guò)很多源碼以后,我想相信將靜態(tài)構(gòu)造方法 ofName 換成 of 會(huì)先的更加簡(jiǎn)潔:

        @Accessors(chain = true)
        @Setter
        @Getter
        @RequiredArgsConstructor(staticName = "of")
        public class Student {
                @NonNull private String name;
                private int age;
        }

        測(cè)試代碼:

        Student student = Student.of("zs");

        當(dāng)然他仍然是支持鏈?zhǔn)秸{(diào)用的:

        Student student = Student.of("zs").setAge(24);

        這樣來(lái)寫(xiě)代碼,真的很簡(jiǎn)潔,并且可讀性很強(qiáng)。

        使用 builder

        Builder 模式我不想再多解釋了,讀者可以看一下《Head First》(設(shè)計(jì)模式) 的建造者模式。

        今天其實(shí)要說(shuō)的是一種變種的 builder 模式,那就是構(gòu)建 bean 的 builder 模式,其實(shí)主要的思想是帶著大家一起看一下 lombok 給我們帶來(lái)了什么。

        看一下 Student 這個(gè)類(lèi)的原始 builder 狀態(tài):

        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;
                    }
            }
        }

        調(diào)用方式:

        Student student = Student.builder().name("zs").age(24).build();

        這樣的 builder 代碼,讓我是在惡心難受,于是我打算用 lombok 重構(gòu)這段代碼:

        @Builder
        public class Student {
            private String name;
            private int age;
        }

        調(diào)用方式:

        Student student = Student.builder().name("zs").age(24).build();

        代理模式

        正如我們所知的,在程序中調(diào)用 rest 接口是一個(gè)常見(jiàn)的行為動(dòng)作,如果你和我一樣使用過(guò) spring 的 RestTemplate,我相信你會(huì)我和一樣,對(duì)他拋出的非 http 狀態(tài)碼異常深?lèi)和唇^。

        所以我們考慮將 RestTemplate 最為底層包裝器進(jìn)行包裝器模式的設(shè)計(jì):

        public abstract class FilterRestTemplate implements RestOperations {
                protected volatile RestTemplate restTemplate;
                protected FilterRestTemplate(RestTemplate restTemplate){
                        this.restTemplate = restTemplate;
                }
                //實(shí)現(xiàn)RestOperations所有的接口
        }

        然后再由擴(kuò)展類(lèi)對(duì) FilterRestTemplate 進(jìn)行包裝擴(kuò)展:

        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 很完美的更改了異常拋出的行為,讓程序更具有容錯(cuò)性。在這里我們不考慮 ExtractRestTemplate 完成的功能,讓我們把焦點(diǎn)放在 FilterRestTemplate 上,“實(shí)現(xiàn) RestOperations 所有的接口”,這個(gè)操作絕對(duì)不是一時(shí)半會(huì)可以寫(xiě)完的,當(dāng)時(shí)在重構(gòu)之前我?guī)缀鯇?xiě)了半個(gè)小時(shí),如下:

        public abstract class FilterRestTemplate implements RestOperations {
            protected volatile RestTemplate restTemplate;
            protected FilterRestTemplate(RestTemplate restTemplate) {
                    this.restTemplate = restTemplate;
            }

            @Override
            public <T> getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
                    return restTemplate.getForObject(url,responseType,uriVariables);
            }

            @Override
            public <T> getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
                    return restTemplate.getForObject(url,responseType,uriVariables);
            }

            @Override
            public <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);
            }
            //其他實(shí)現(xiàn)代碼略。。。
        }

        我相信你看了以上代碼,你會(huì)和我一樣覺(jué)得惡心反胃,后來(lái)我用 lombok 提供的代理注解優(yōu)化了我的代碼(@Delegate):

        @AllArgsConstructor
        public abstract class FilterRestTemplate implements RestOperations {
            @Delegate
            protected volatile RestTemplate restTemplate;
        }

        這幾行代碼完全替代上述那些冗長(zhǎng)的代碼。

        是不是很簡(jiǎn)潔,做一個(gè)擁抱 lombok 的程序員吧。

        2重構(gòu)

        需求案例

        項(xiàng)目需求

        項(xiàng)目開(kāi)發(fā)階段,有一個(gè)關(guān)于下單發(fā)貨的需求:如果今天下午 3 點(diǎn)前進(jìn)行下單,那么發(fā)貨時(shí)間是明天,如果今天下午 3 點(diǎn)后進(jìn)行下單,那么發(fā)貨時(shí)間是后天,如果被確定的時(shí)間是周日,那么在此時(shí)間上再加 1 天為發(fā)貨時(shí)間。

        思考與重構(gòu)

        我相信這個(gè)需求看似很簡(jiǎn)單,無(wú)論怎么寫(xiě)都可以完成。

        很多人可能看到這個(gè)需求,就動(dòng)手開(kāi)始寫(xiě) Calendar 或 Date 進(jìn)行計(jì)算,從而完成需求。

        而我給的建議是,仔細(xì)考慮如何寫(xiě)代碼,然后再去寫(xiě),不是說(shuō)所有的時(shí)間操作都用 Calendar 或 Date 去解決,一定要看場(chǎng)景。

        對(duì)于時(shí)間的計(jì)算我們要考慮 joda-time 這種類(lèi)似的成熟時(shí)間計(jì)算框架來(lái)寫(xiě)代碼,它會(huì)讓代碼更加簡(jiǎn)潔和易讀。

        請(qǐng)讀者先考慮這個(gè)需求如何用 Java 代碼完成,或先寫(xiě)一個(gè)你覺(jué)得完成這個(gè)代碼的思路,再來(lái)看我下邊的代碼,這樣,你的收獲會(huì)更多一些:

        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() ;
        }

        讀這段代碼的時(shí)候,你會(huì)發(fā)現(xiàn),我將判斷和有可能出現(xiàn)的不同結(jié)果都當(dāng)做一個(gè)變量,最終做一個(gè)三目運(yùn)算符的方式進(jìn)行返回,這樣的優(yōu)雅和可讀性顯而易見(jiàn),當(dāng)然這樣的代碼不是一蹴而就的,我優(yōu)化了 3 遍產(chǎn)生的以上代碼。讀者可根據(jù)自己的代碼和我寫(xiě)的代碼進(jìn)行對(duì)比。

        提高方法

        如果你做了 3 年+的程序員,我相信像如上這樣的需求,你很輕松就能完成,但是如果你想做一個(gè)會(huì)寫(xiě) Java 的程序員,就好好的思考和重構(gòu)代碼吧。

        寫(xiě)代碼就如同寫(xiě)字一樣,同樣的字,大家都會(huì)寫(xiě),但是寫(xiě)出來(lái)是否好看就不一定了。如果想把程序?qū)懞茫鸵粩嗟乃伎己椭貥?gòu),敢于嘗試,敢于創(chuàng)新,不要因循守舊,一定要做一個(gè)優(yōu)秀的 Java 程序員。

        提高代碼水平最好的方法就是有條理的重構(gòu)!(注意:是有條理的重構(gòu))

        設(shè)計(jì)模式

        設(shè)計(jì)模式就是工具,而不是提現(xiàn)你是否是高水平程序員的一個(gè)指標(biāo)。

        我經(jīng)常會(huì)看到某一個(gè)程序員興奮的大喊,哪個(gè)程序哪個(gè)點(diǎn)我用到了設(shè)計(jì)模式,寫(xiě)的多么多么優(yōu)秀,多么多么好。我仔細(xì)去翻閱的時(shí)候,卻發(fā)現(xiàn)有很多是過(guò)度設(shè)計(jì)的。

        業(yè)務(wù)驅(qū)動(dòng)技術(shù) or 技術(shù)驅(qū)動(dòng)業(yè)務(wù)

        業(yè)務(wù)驅(qū)動(dòng)技術(shù) or 技術(shù)驅(qū)動(dòng)業(yè)務(wù) ? 其實(shí)這是一個(gè)一直在爭(zhēng)論的話題,但是很多人不這么認(rèn)為,我覺(jué)得就是大家不愿意承認(rèn)罷了。我來(lái)和大家大概分析一下作為一個(gè) Java 程序員,我們應(yīng)該如何判斷自己所處于的位置.

        業(yè)務(wù)驅(qū)動(dòng)技術(shù):如果你所在的項(xiàng)目是一個(gè)收益很小或者甚至沒(méi)有收益的項(xiàng)目,請(qǐng)不要搞其他創(chuàng)新的東西,不要驅(qū)動(dòng)業(yè)務(wù)要如何如何做,而是要熟知業(yè)務(wù)現(xiàn)在的痛點(diǎn)是什么?如何才能幫助業(yè)務(wù)盈利或者讓項(xiàng)目更好,更順利的進(jìn)行。

        技術(shù)驅(qū)動(dòng)業(yè)務(wù):如果你所在的項(xiàng)目是一個(gè)很牛的項(xiàng)目,比如淘寶這類(lèi)的項(xiàng)目,我可以在滿足業(yè)務(wù)需求的情況下,和業(yè)務(wù)溝通,使用什么樣的技術(shù)能更好的幫助業(yè)務(wù)創(chuàng)造收益,比如說(shuō)下單的時(shí)候要進(jìn)隊(duì)列,可能幾分鐘之后訂單狀態(tài)才能處理完成,但是會(huì)讓用戶有更流暢的體驗(yàn),賺取更多的訪問(wèn)流量,那么我相信業(yè)務(wù)愿意被技術(shù)驅(qū)動(dòng),會(huì)同意訂單的延遲問(wèn)題,這樣便是技術(shù)驅(qū)動(dòng)業(yè)務(wù)。

        我相信大部分人還都處于業(yè)務(wù)驅(qū)動(dòng)技術(shù)的方向吧。

        所以你既然不能驅(qū)動(dòng)業(yè)務(wù),那就請(qǐng)擁抱業(yè)務(wù)變化吧。

        代碼設(shè)計(jì)

        一直在做 Java 后端的項(xiàng)目,經(jīng)常會(huì)有一些變動(dòng),我相信大家也都遇到過(guò)。

        比如當(dāng)我們寫(xiě)一段代碼的時(shí)候,我們考慮將需求映射成代碼的狀態(tài)模式,突然有一天,狀態(tài)模式里邊又添加了很多行為變化的東西,這時(shí)候你就撓頭了,你硬生生的將狀態(tài)模式中添加過(guò)多行為和變化。

        慢慢的你會(huì)發(fā)現(xiàn)這些狀態(tài)模式,其實(shí)更像是一簇算法,應(yīng)該使用策略模式,這時(shí)你應(yīng)該已經(jīng)暈頭轉(zhuǎn)向了。

        說(shuō)了這么多,我的意思是,只要你覺(jué)得合理,就請(qǐng)將狀態(tài)模式改為策略模式吧,所有的模式并不是憑空想象出來(lái)的,都是基于重構(gòu)。

        Java 編程中沒(méi)有銀彈,請(qǐng)擁抱業(yè)務(wù)變化,一直思考重構(gòu),你就有一個(gè)更好的代碼設(shè)計(jì)!

        你真的優(yōu)秀嗎?

        真不好意思,我取了一個(gè)這么無(wú)聊的標(biāo)題。

        國(guó)外流行一種編程方式,叫做結(jié)對(duì)編程,我相信國(guó)內(nèi)很多公司都沒(méi)有這么做,我就不在講述結(jié)對(duì)編程帶來(lái)的好處了,其實(shí)就是一邊 code review,一邊互相提高的一個(gè)過(guò)程。既然做不到這個(gè),那如何讓自己活在自己的世界中不斷提高呢?

        “平時(shí)開(kāi)發(fā)的時(shí)候,做出的代碼總認(rèn)為是正確的,而且寫(xiě)法是完美的?!?,我相信這是大部分人的心聲,還回到剛剛的問(wèn)題,如何在自己的世界中不斷提高呢?

        答案就是:

        1. 多看成熟框架的源碼
        2. 多回頭看自己的代碼
        3. 勤于重構(gòu)

        你真的優(yōu)秀嗎? 如果你每周都完成了學(xué)習(xí)源碼,回頭看自己代碼,然后勤于重構(gòu),我認(rèn)為你就真的很優(yōu)秀了。

        即使也許你只是剛剛?cè)腴T(mén),但是一直堅(jiān)持,你就是一個(gè)真的會(huì)寫(xiě)java代碼的程序員了。

        3技能

        UML

        不想多討論 UML 相關(guān)的知識(shí),但是我覺(jué)得你如果真的會(huì)寫(xiě) Java,請(qǐng)先學(xué)會(huì)表達(dá)自己,UML 就是你說(shuō)話的語(yǔ)言,做一名優(yōu)秀的 Java 程序員,請(qǐng)至少學(xué)會(huì)這兩種 UML 圖:

        1. 類(lèi)圖
        2. 時(shí)序圖

        clean code

        我認(rèn)為保持代碼的簡(jiǎn)潔和可讀性是代碼的最基本保證,如果有一天為了程序的效率而降低了這兩點(diǎn),我認(rèn)為是可以諒解的,除此之外,沒(méi)有任何理由可以讓你任意揮霍你的代碼。

        1. 讀者可以看一下 Robert C. Martin 出版的《Clean Code》(代碼整潔之道) 這本書(shū)
        2. 可以參考美團(tuán)文章聊聊 clean code(http://tech.meituan.com/clean-code.html);
        3. 也可以看一下阿里的 Java 編碼規(guī)范(https://yq.aliyun.com/articles/69327)。

        無(wú)論如何,請(qǐng)保持你的代碼的整潔。

        Linux 基礎(chǔ)命令

        這點(diǎn)其實(shí)和會(huì)寫(xiě) Java 沒(méi)有關(guān)系,但是 Linux 很多時(shí)候確實(shí)承載運(yùn)行 Java 的容器,請(qǐng)學(xué)好 Linux 的基礎(chǔ)命令。

        1. 參考鳥(niǎo)哥的《Linux私房菜》

        4總結(jié)

        Java 是一個(gè)大體系,今天討論并未涉及框架和架構(gòu)相關(guān)知識(shí),只是討論如何寫(xiě)好代碼。

        本文從寫(xiě) Java 程序的小方面一直寫(xiě)到大方面,來(lái)闡述了如何才能寫(xiě)好 Java 程序,并告訴讀者們?nèi)绾尾拍芴岣咦陨淼木幋a水平。

        我希望看到這篇文章的各位都能做一個(gè)優(yōu)秀的 Java 程序員。

            

        1、社區(qū)糾紛不斷:程序員何苦為難程序員?

        2、該死的單元測(cè)試,寫(xiě)起來(lái)到底有多痛?

        3、互聯(lián)網(wǎng)人為什么學(xué)不會(huì)擺爛

        4、為什么國(guó)外JetBrains做 IDE 就可以養(yǎng)活自己,國(guó)內(nèi)不行?區(qū)別在哪?

        5、相比高人氣的Rust、Go,為何 Java、C 在工具層面進(jìn)展緩慢?

        6、讓程序員早點(diǎn)下班的《技術(shù)寫(xiě)作指南》

        點(diǎn)

        點(diǎn)

        點(diǎn)點(diǎn)

        點(diǎn)在看

        瀏覽 25
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            女生扒开尿口让男生舔 | 裸体未来初音被到爽 | 老司机黄色网 | 国产69一区二区三区 | 欧美美女日逼 | 亚洲免费观看视频 | 日韩精品中文字幕在线观看 | 天堂国产 | 无遮挡激情 | 成人精品无码视频A片秀色 |