乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      寫出漂亮代碼的45個小技巧

       漢無為 2023-03-16 發(fā)布于湖北

      大家好,我是三友~~

      不知道大家有沒有經(jīng)歷過維護一個已經(jīng)離職的人的代碼的痛苦,一個方法寫老長,還有很多的if else ,根本無法閱讀,更不知道代碼背后的含義,最重要的是沒有人可以問,此時只能心里默默地問候這個留坑的兄弟。。

      其實造成這些原因的很大一部分原因是由于代碼規(guī)范的問題,如果寫的規(guī)范,注釋好,其實很多問題也就解決了。所以本文我就從代碼的編寫規(guī)范,格式的優(yōu)化,設(shè)計原則和一些常見的代碼優(yōu)化的技巧等方面總結(jié)了了45個小技巧分享給大家,如果不足,歡迎指正。

      圖片

      1、規(guī)范命名

      命名是寫代碼中最頻繁的操作,比如類、屬性、方法、參數(shù)等。好的名字應(yīng)當能遵循以下幾點:

      見名知意

      比如需要定義一個變量需要來計數(shù)

      int i = 0;

      名稱 i 沒有任何的實際意義,沒有體現(xiàn)出數(shù)量的意思,所以我們應(yīng)當指明數(shù)量的名稱

      int count = 0;
      能夠讀的出來

      如下代碼:

      private String sfzh;
      private String dhhm;

      這些變量的名稱,根本讀不出來,更別說實際意義了。

      所以我們可以使用正確的可以讀出來的英文來命名

      private String idCardNo;
      private String phone;

      2、規(guī)范代碼格式

      好的代碼格式能夠讓人感覺看起來代碼更加舒適。

      好的代碼格式應(yīng)當遵守以下幾點:

      • 合適的空格
      • 代碼對齊,比如大括號要對齊
      • 及時換行,一行不要寫太多代碼

      好在現(xiàn)在開發(fā)工具支持一鍵格式化,可以幫助美化代碼格式。

      3、寫好代碼注釋

      在《代碼整潔之道》這本書中作者提到了一個觀點,注釋的恰當用法是用來彌補我們在用代碼表達意圖時的失敗。換句話說,當無法通過讀代碼來了解代碼所表達的意思的時候,就需要用注釋來說明。

      作者之所以這么說,是因為作者覺得隨著時間的推移,代碼可能會變動,如果不及時更新注釋,那么注釋就容易產(chǎn)生誤導(dǎo),偏離代碼的實際意義。而不及時更新注釋的原因是,程序員不喜歡寫注釋。(作者很懂?。?/p>

      但是這不意味著可以不寫注釋,當通過代碼如果無法表達意思的時候,就需要注釋,比如如下代碼

      for (Integer id : ids) {
          if (id == 0) {
              continue;
          }
          //做其他事
      }

      為什么 id == 0 需要跳過,代碼是無法看出來了,就需要注釋了。

      好的注釋應(yīng)當滿足一下幾點:

      • 解釋代碼的意圖,說明為什么這么寫,用來做什么
      • 對參數(shù)和返回值注釋,入?yún)⒋硎裁矗鰠⒋硎裁?/section>
      • 有警示作用,比如說入?yún)⒉荒転榭?,或者代碼是不是有坑
      • 當代碼還未完成時可以使用 todo 注釋來注釋

      4、try catch 內(nèi)部代碼抽成一個方法

      try catch代碼有時會干擾我們閱讀核心的代碼邏輯,這時就可以把try catch內(nèi)部主邏輯抽離成一個單獨的方法

      如下圖是Eureka服務(wù)端源碼中服務(wù)下線的實現(xiàn)中的一段代碼

      圖片

      整個方法非常長,try中代碼是真正的服務(wù)下線的代碼實現(xiàn),finally可以保證讀鎖最終一定可以釋放。

      所以這段代碼其實就可以對核心的邏輯進行抽取。

      protected boolean internalCancel(String appName, String id, boolean isReplication) {
          try {
              read.lock();
              doInternalCancel(appName, id, isReplication);
          } finally {
              read.unlock();
          }

          // 剩余代碼
      }

      private boolean doInternalCancel(String appName, String id, boolean isReplication) {
          //真正處理下線的邏輯
      }

      5、方法別太長

      方法別太長就是字面的意思。一旦代碼太長,給人的第一眼感覺就很復(fù)雜,讓人不想讀下去;同時方法太長的代碼可能讀起來容易讓人摸不著頭腦,不知道哪一些代碼是同一個業(yè)務(wù)的功能。

      我曾經(jīng)就遇到過一個方法寫了2000+行,各種if else判斷,我光理清代碼思路就用了很久,最終理清之后,就用策略模式給重構(gòu)了。

      所以一旦方法過長,可以嘗試將相同業(yè)務(wù)功能的代碼單獨抽取一個方法,最后在主方法中調(diào)用即可。

      6、抽取重復(fù)代碼

      當一份代碼重復(fù)出現(xiàn)在程序的多處地方,就會造成程序又臭又長,當這份代碼的結(jié)構(gòu)要修改時,每一處出現(xiàn)這份代碼的地方都得修改,導(dǎo)致程序的擴展性很差。

      所以一般遇到這種情況,可以抽取成一個工具類,還可以抽成一個公共的父類。

      7、多用return

      在有時我們平時寫代碼的情況可能會出現(xiàn)if條件套if的情況,當if條件過多的時候可能會出現(xiàn)如下情況:

      if (條件1) {
          if (條件2) {
              if (條件3) {
                  if (條件4) {
                      if (條件5) {
                          System.out.println('三友的java日記');
                      }
                  }
              }
          }
      }

      面對這種情況,可以換種思路,使用return來優(yōu)化

      if (!條件1) {
          return;
      }
      if (!條件2) {
          return;
      }
      if (!條件3) {
          return;
      }
      if (!條件4) {
          return;
      }
      if (!條件5) {
          return;
      }

      System.out.println('三友的java日記');

      這樣優(yōu)化就感覺看起來更加直觀

      8、if條件表達式不要太復(fù)雜

      比如在如下代碼:

      if (((StringUtils.isBlank(person.getName())
              || '三友的java日記'.equals(person.getName()))
              && (person.getAge() != null && person.getAge() > 10))
              && '漢'.equals(person.getNational())) {
          // 處理邏輯
      }

      這段邏輯,這種條件表達式乍一看不知道是什么,仔細一看還是不知道是什么,這時就可以這么優(yōu)化

      boolean sanyouOrBlank = StringUtils.isBlank(person.getName()) || '三友的java日記'.equals(person.getName());
      boolean ageGreaterThanTen = person.getAge() != null && person.getAge() > 10;
      boolean isHanNational = '漢'.equals(person.getNational());

      if (sanyouOrBlank
          && ageGreaterThanTen
          && isHanNational) {
          // 處理邏輯
      }

      此時就很容易看懂if的邏輯了

      9、優(yōu)雅地參數(shù)校驗

      當前端傳遞給后端參數(shù)的時候,通常需要對參數(shù)進場檢驗,一般可能會這么寫

      @PostMapping
      public void addPerson(@RequestBody AddPersonRequest addPersonRequest) {
          if (StringUtils.isBlank(addPersonRequest.getName())) {
              throw new BizException('人員姓名不能為空');
          }

          if (StringUtils.isBlank(addPersonRequest.getIdCardNo())) {
              throw new BizException('身份證號不能為空');
          }

          // 處理新增邏輯
      }

      這種寫雖然可以,但是當字段的多的時候,光校驗就占據(jù)了很長的代碼,不夠優(yōu)雅。

      針對參數(shù)校驗這個問題,有第三方庫已經(jīng)封裝好了,比如hibernate-validator框架,只需要拿來用即可。

      所以就在實體類上加@NotBlank、@NotNull注解來進行校驗

      @Data
      @ToString
      private class AddPersonRequest {

          @NotBlank(message = '人員姓名不能為空')
          private String name;
          @NotBlank(message = '身份證號不能為空')
          private String idCardNo;
              
          //忽略
      }

      此時Controller接口就需要方法上就需要加上@Valid注解

      @PostMapping
      public void addPerson(@RequestBody @Valid AddPersonRequest addPersonRequest) {
          // 處理新增邏輯
      }

      10、統(tǒng)一返回值

      后端在設(shè)計接口的時候,需要統(tǒng)一返回值

      {  
          'code':0,
          'message':'成功',
          'data':'返回數(shù)據(jù)'
      }

      不僅是給前端參數(shù),也包括提供給第三方的接口等,這樣接口調(diào)用方法可以按照固定的格式解析代碼,不用進行判斷。如果不一樣,相信我,前端半夜都一定會來找你。

      Spring中很多方法可以做到統(tǒng)一返回值,而不用每個方法都返回,比如基于AOP,或者可以自定義HandlerMethodReturnValueHandler來實現(xiàn)統(tǒng)一返回值。

      11、統(tǒng)一異常處理

      當你沒有統(tǒng)一異常處理的時候,那么所有的接口避免不了try catch操作。

      @GetMapping('/{id}')
      public Result<T> selectPerson(@PathVariable('id') Long personId) {
          try {
              PersonVO vo = personService.selectById(personId);
              return Result.success(vo);
          } catch (Exception e) {
              //打印日志
              return Result.error('系統(tǒng)異常');
          }
      }

      每個接口都得這么玩,那不得滿屏的try catch。

      所以可以基于Spring提供的統(tǒng)一異常處理機制來完成。

      12、盡量不傳遞null值

      這個很好理解,不傳null值可以避免方法不支持為null入?yún)r產(chǎn)生的空指針問題。

      當然為了更好的表明該方法是不是可以傳null值,可以通過@NonNull和@Nullable注解來標記。@NonNull就表示不能傳null值,@Nullable就是可以傳null值。

      //示例1
      public void updatePerson(@Nullable Person person) {
          if (person == null) {
              return;
          }
          personService.updateById(person);
      }

      //示例2
      public void updatePerson(@NonNull Person person) {
          personService.updateById(person);
      }

      13、盡量不返回null值

      盡量不返回null值是為了減少調(diào)用者對返回值的為null判斷,如果無法避免返回null值,可以通過返回Optional來代替null值。

      public Optional<Person> getPersonById(Long personId) {
          return Optional.ofNullable(personService.selectById(personId));
      }

      如果不想這么寫,也可以通過@NonNull和@Nullable表示方法會不會返回null值。

      14、日志打印規(guī)范

      好的日志打印能幫助我們快速定位問題

      好的日志應(yīng)該遵循以下幾點:

      • 可搜索性,要有明確的關(guān)鍵字信息
      • 異常日志需要打印出堆棧信息
      • 合適的日志級別,比如異常使用error,正常使用info
      • 日志內(nèi)容太大不打印,比如有時需要將圖片轉(zhuǎn)成Base64,那么這個Base64就可以不用打印

      15、統(tǒng)一類庫

      在一個項目中,可能會由于引入的依賴不同導(dǎo)致引入了很多相似功能的類庫,比如常見的json類庫,又或者是一些常用的工具類,當遇到這種情況下,應(yīng)當規(guī)范在項目中到底應(yīng)該使用什么類庫,而不是一會用Fastjson,一會使用Gson。

      16、盡量使用工具類

      比如在對集合判空的時候,可以這么寫

      public void updatePersons(List<Person> persons) {
          if (persons != null && persons.size() > 0) {
                 
          }
      }

      但是一般不推薦這么寫,可以通過一些判斷的工具類來寫

      public void updatePersons(List<Person> persons) {
          if (!CollectionUtils.isEmpty(persons)) {

          }
      }

      不僅集合,比如字符串的判斷等等,就使用工具類,不要手動判斷。

      17、盡量不要重復(fù)造輪子

      就拿格式化日期來來說,我們一般封裝成一個工具類來調(diào)用,比如如下代碼

      private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss');

      public static String formatDateTime(Date date) {
          return DATE_TIME_FORMAT.format(date);
      }

      這段代碼看似沒啥問題,但是卻忽略了SimpleDateFormat是個線程不安全的類,所以這就會引起坑。

      一般對于這種已經(jīng)有開源的項目并且已經(jīng)做得很好的時候,比如Hutool,就可以把輪子直接拿過來用了。

      18、類和方法單一職責

      單一職責原則是設(shè)計模式的七大設(shè)計原則之一,它的核心意思就是字面的意思,一個類或者一個方法只做單一的功能。

      就拿Nacos來說,在Nacos1.x的版本中,有這么一個接口HttpAgent

      圖片

      這個類只干了一件事,那就是封裝http請求參數(shù),向Nacos服務(wù)端發(fā)送請求,接收響應(yīng),這其實就是單一職責原則的體現(xiàn)。

      當其它的地方需要向Nacos服務(wù)端發(fā)送請求時,只需要通過這個接口的實現(xiàn),傳入?yún)?shù)就可以發(fā)送請求了,而不需要關(guān)心如何攜帶服務(wù)端鑒權(quán)參數(shù)、http請求參數(shù)如何組裝等問題。

      19、盡量使用聚合/組合代替繼承

      繼承的弊端:

      • 靈活性低。java語言是單繼承的,無法同時繼承很多類,并且繼承容易導(dǎo)致代碼層次太深,不易于維護
      • 耦合性高。一旦父類的代碼修改,可能會影響到子類的行為

      所以一般推薦使用聚合/組合代替繼承。

      聚合/組合的意思就是通過成員變量的方式來使用類。

      比如說,OrderService需要使用UserService,可以注入一個UserService而非通過繼承UserService。

      聚合和組合的區(qū)別就是,組合是當對象一創(chuàng)建的時候,就直接給屬性賦值,而聚合的方式可以通過set方式來設(shè)置。

      組合:

      public class OrderService {

          private UserService userService = new UserService();

      }

      聚合:

      public class OrderService {
          
          private UserService userService;

          public void setUserService(UserService userService) {
              this.userService = userService;
          }
      }

      20、使用設(shè)計模式優(yōu)化代碼

      在平時開發(fā)中,使用設(shè)計模式可以增加代碼的擴展性。

      比如說,當你需要做一個可以根據(jù)不同的平臺做不同消息推送的功能時,就可以使用策略模式的方式來優(yōu)化。

      設(shè)計一個接口:

      public interface MessageNotifier {

          /**
           * 是否支持改類型的通知的方式
           *
           * @param type 0:短信 1:app
           * @return
           */

          boolean support(int type);

          /**
           * 通知
           *
           * @param user
           * @param content
           */

          void notify(User user, String content);

      }

      短信通知實現(xiàn):

      @Component
      public class SMSMessageNotifier implements MessageNotifier {
          @Override
          public boolean support(int type) {
              return type == 0;
          }

          @Override
          public void notify(User user, String content) {
              //調(diào)用短信通知的api發(fā)送短信
          }
      }

      app通知實現(xiàn):

      public class AppMessageNotifier implements MessageNotifier {
          @Override
          public boolean support(int type) {
              return type == 1;
          }

          @Override
          public void notify(User user, String content) {
             //調(diào)用通知app通知的api
          }
      }

      最后提供一個方法,當需要進行消息通知時,調(diào)用notifyMessage,傳入相應(yīng)的參數(shù)就行。

      @Resource
      private List<MessageNotifier> messageNotifiers;

      public void notifyMessage(User user, String content, int notifyType) {
          for (MessageNotifier messageNotifier : messageNotifiers) {
              if (messageNotifier.support(notifyType)) {
                  messageNotifier.notify(user, content);
              }
          }
      }

      假設(shè)此時需要支持通過郵件通知,只需要有對應(yīng)實現(xiàn)就行。

      21、不濫用設(shè)計模式

      用好設(shè)計模式可以增加代碼的擴展性,但是濫用設(shè)計模式確是不可取的。

      public void printPerson(Person person) {
          StringBuilder sb = new StringBuilder();
          if (StringUtils.isNotBlank(person.getName())) {
              sb.append('姓名:').append(person.getName());
          }
          if (StringUtils.isNotBlank(person.getIdCardNo())) {
              sb.append('身份證號:').append(person.getIdCardNo());
          }

          // 省略
          System.out.println(sb.toString());
      }

      比如上面打印Person信息的代碼,用if判斷就能夠做到效果,你說我要不用責任鏈或者什么設(shè)計模式來優(yōu)化一下吧,沒必要。

      22、面向接口編程

      在一些可替換的場景中,應(yīng)該引用父類或者抽象,而非實現(xiàn)。

      舉個例子,在實際項目中可能需要對一些圖片進行存儲,但是存儲的方式很多,比如可以選擇阿里云的OSS,又或者是七牛云,存儲服務(wù)器等等。所以對于存儲圖片這個功能來說,這些具體的實現(xiàn)是可以相互替換的。

      所以在項目中,我們不應(yīng)當在代碼中耦合一個具體的實現(xiàn),而是可以提供一個存儲接口

      public interface FileStorage {
          
          String store(String fileName, byte[] bytes);

      }

      如果選擇了阿里云OSS作為存儲服務(wù)器,那么就可以基于OSS實現(xiàn)一個FileStorage,在項目中哪里需要存儲的時候,只要實現(xiàn)注入這個接口就可以了。

      @Autowired
      private FileStorage fileStorage;

      假設(shè)用了一段時間之后,發(fā)現(xiàn)阿里云的OSS比較貴,此時想換成七牛云的,那么此時只需要基于七牛云的接口實現(xiàn)FileStorage接口,然后注入到IOC,那么原有代碼用到FileStorage根本不需要動,實現(xiàn)輕松的替換。

      23、經(jīng)常重構(gòu)舊的代碼

      隨著時間的推移,業(yè)務(wù)的增長,有的代碼可能不再適用,或者有了更好的設(shè)計方式,那么可以及時的重構(gòu)業(yè)務(wù)代碼。

      就拿上面的消息通知為例,在業(yè)務(wù)剛開始的時候可能只支持短信通知,于是在代碼中就直接耦合了短信通知的代碼。但是隨著業(yè)務(wù)的增長,逐漸需要支持app、郵件之類的通知,那么此時就可以重構(gòu)以前的代碼,抽出一個策略接口,進行代碼優(yōu)化。

      24、null值判斷

      空指針是代碼開發(fā)中的一個難題,作為程序員的基本修改,應(yīng)該要防止空指針。

      可能產(chǎn)生空指針的原因:

      • 數(shù)據(jù)返回對象為null
      • 自動拆箱導(dǎo)致空指針
      • rpc調(diào)用返回的對象可能為空格

      所以在需要這些的時候,需要強制判斷是否為null。前面也提到可以使用Optional來優(yōu)雅地進行null值判斷。

      25、pojo類重寫toString方法

      pojo一般內(nèi)部都有很多屬性,重寫toString方法可以方便在打印或者測試的時候查看內(nèi)部的屬性。

      26、魔法值用常量表示

      public void sayHello(String province) {
          if ('廣東省'.equals(province)) {
              System.out.println('靚仔~~');
          } else {
              System.out.println('帥哥~~');
          }
      }

      代碼里,廣東省就是一個魔法值,那么就可以將用一個常量來保存

      private static final String GUANG_DONG_PROVINCE = '廣東省';

      public void sayHello(String province) {
          if (GUANG_DONG_PROVINCE.equals(province)) {
              System.out.println('靚仔~~');
          } else {
              System.out.println('帥哥~~');
          }
      }

      27、資源釋放寫到finally

      比如在使用一個api類鎖或者進行IO操作的時候,需要主動寫代碼需釋放資源,為了能夠保證資源能夠被真正釋放,那么就需要在finally中寫代碼保證資源釋放。

      圖片

      如圖所示,就是CopyOnWriteArrayList的add方法的實現(xiàn),最終是在finally中進行鎖的釋放。

      28、使用線程池代替手動創(chuàng)建線程

      使用線程池還有以下好處:

      • 降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
      • 提高響應(yīng)速度。當任務(wù)到達時,任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
      • 提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng) 的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。

      所以為了達到更好的利用資源,提高響應(yīng)速度,就可以使用線程池的方式來代替手動創(chuàng)建線程。

      如果對線程池不清楚的同學(xué),可以看一下這篇文章:7000字+24張圖帶你徹底弄懂線程池

      29、線程設(shè)置名稱

      在日志打印的時候,日志是可以把線程的名字給打印出來。

      圖片

      如上圖,日志打印出來的就是tom貓的線程。

      所以,設(shè)置線程的名稱可以幫助我們更好的知道代碼是通過哪個線程執(zhí)行的,更容易排查問題。

      30、涉及線程間可見性加volatile

      在RocketMQ源碼中有這么一段代碼

      圖片

      在消費者在從服務(wù)端拉取消息的時候,會單獨開一個線程,執(zhí)行while循環(huán),只要stopped狀態(tài)一直為false,那么就會一直循環(huán)下去,線程就一直會運行下去,拉取消息。

      當消費者客戶端關(guān)閉的時候,就會將stopped狀態(tài)設(shè)置為true,告訴拉取消息的線程需要停止了。但是由于并發(fā)編程中存在可見性的問題,所以雖然客戶端關(guān)閉線程將stopped狀態(tài)設(shè)置為true,但是拉取消息的線程可能看不見,不能及時感知到數(shù)據(jù)的修改,還是認為stopped狀態(tài)設(shè)置為false,那么就還會運行下去。

      針對這種可見性的問題,java提供了一個volatile關(guān)鍵字來保證線程間的可見性。

      圖片

      所以,源碼中就加了volatile關(guān)鍵字。

      加了volatile關(guān)鍵字之后,一旦客戶端的線程將stopped狀態(tài)設(shè)置為true時候,拉取消息的線程就能立馬知道stopped已經(jīng)是false了,那么再次執(zhí)行while條件判斷的時候,就不成立,線程就運行結(jié)束了,然后退出。

      31、考慮線程安全問題

      在平時開發(fā)中,有時需要考慮并發(fā)安全的問題。

      舉個例子來說,一般在調(diào)用第三方接口的時候,可能會有一個鑒權(quán)的機制,一般會攜帶一個請求頭token參數(shù)過去,而token也是調(diào)用第三方接口返回的,一般這種token都會有個過期時間,比如24小時。

      我們一般會將token緩存到Redis中,設(shè)置一個過期時間。向第三方發(fā)送請求時,會直接從緩存中查找,但是當從Redis中獲取不到token的時候,我們都會重新請求token接口,獲取token,然后再設(shè)置到緩存中。

      整個過程看起來是沒什么問題,但是實則隱藏線程安全問題。

      假設(shè)當出現(xiàn)并發(fā)的時候,同時來兩個線程AB從緩存查找,發(fā)現(xiàn)沒有,那么AB此時就會同時調(diào)用token獲取接口。假設(shè)A先獲取到token,B后獲取到token,但是由于CPU調(diào)度問題,線程B雖然后獲取到token,但是先往Redis存數(shù)據(jù),而線程A后存,覆蓋了B請求的token。

      這下就會出現(xiàn)大問題,最新的token被覆蓋了,那么之后一定時間內(nèi)token都是無效的,接口就請求不通。

      針對這種問題,可以使用double check機制來優(yōu)化獲取token的問題。

      所以,在實際中,需要多考慮考慮業(yè)務(wù)是否有線程安全問題,有集合讀寫安全問題,那么就用線程安全的集合,業(yè)務(wù)有安全的問題,那么就可以通過加鎖的手段來解決。

      32、慎用異步

      雖然在使用多線程可以幫助我們提高接口的響應(yīng)速度,但是也會帶來很多問題。

      事務(wù)問題

      一旦使用了異步,就會導(dǎo)致兩個線程不是同一個事務(wù)的,導(dǎo)致異常之后無法正?;貪L數(shù)據(jù)。

      cpu負載過高

      之前有個小伙伴遇到需要同時處理幾萬調(diào)數(shù)據(jù)的需求,每條數(shù)據(jù)都需要調(diào)用很多次接口,為了達到老板期望的時間要求,使用了多線程跑,開了很多線程,此時會發(fā)現(xiàn)系統(tǒng)的cpu會飆升

      意想不到的異常

      還是上面的提到的例子,在測試的時候就發(fā)現(xiàn),由于并發(fā)量激增,在請求第三方接口的時候,返回了很多錯誤信息,導(dǎo)致有的數(shù)據(jù)沒有處理成功。

      雖然說慎用異步,但不代表不用,如果可以保證事務(wù)的問題,或是CPU負載不會高的話,那么還是可以使用的。

      33、減小鎖的范圍

      減小鎖的范圍就是給需要加鎖的代碼加鎖,不需要加鎖的代碼不要加鎖。這樣就能減少加鎖的時間,從而可以較少鎖互斥的時間,提高效率。

      圖片

      比如CopyOnWriteArrayList的addAll方法的實現(xiàn),lock.lock(); 代碼完全可以放到代碼的第一行,但是作者并沒有,因為前面判斷的代碼不會有線程安全的問題,不放到加鎖代碼中可以減少鎖搶占和占有的時間。

      34、有類型區(qū)分時定義好枚舉

      比如在項目中不同的類型的業(yè)務(wù)可能需要上傳各種各樣的附件,此時就可以定義好不同的一個附件的枚舉,來區(qū)分不同業(yè)務(wù)的附件。

      不要在代碼中直接寫死,不定義枚舉,代碼閱讀起來非常困難,直接看到數(shù)字都是懵逼的。。

      35、遠程接口調(diào)用設(shè)置超時時間

      比如在進行微服務(wù)之間進行rpc調(diào)用的時候,又或者在調(diào)用第三方提供的接口的時候,需要設(shè)置超時時間,防止因為各種原因,導(dǎo)致線程”卡死“在那。

      我以前就遇到過線上就遇到過這種問題。當時的業(yè)務(wù)是訂閱kafka的消息,然后向第三方上傳數(shù)據(jù)。在某個周末,突然就接到電話,說數(shù)據(jù)無法上傳了,通過排查線上的服務(wù)器才發(fā)現(xiàn)所有的線程都線程”卡死“了,最后定位到代碼才發(fā)現(xiàn)原來是沒有設(shè)置超時時間。

      36、集合使用應(yīng)當指明初始化大小

      比如在寫代碼的時候,經(jīng)常會用到List、Map來臨時存儲數(shù)據(jù),其中最常用的就是ArrayList和HashMap。但是用不好可能也會導(dǎo)致性能的問題。

      比如說,在ArrayList中,底層是基于數(shù)組來存儲的,數(shù)組是一旦確定大小是無法再改變?nèi)萘康?。但不斷的往ArrayList中存儲數(shù)據(jù)的時候,總有那么一刻會導(dǎo)致數(shù)組的容量滿了,無法再存儲其它元素,此時就需要對數(shù)組擴容。所謂的擴容就是新創(chuàng)建一個容量是原來1.5倍的數(shù)組,將原有的數(shù)據(jù)給拷貝到新的數(shù)組上,然后用新的數(shù)組替代原來的數(shù)組。

      在擴容的過程中,由于涉及到數(shù)組的拷貝,就會導(dǎo)致性能消耗;同時HashMap也會由于擴容的問題,消耗性能。所以在使用這類集合時可以在構(gòu)造的時候指定集合的容量大小。

      37、盡量不要使用BeanUtils來拷貝屬性

      在開發(fā)中經(jīng)常需要對JavaBean進行轉(zhuǎn)換,但是又不想一個一個手動set,比較麻煩,所以一般會使用屬性拷貝的一些工具,比如說Spring提供的BeanUtils來拷貝。不得不說,使用BeanUtils來拷貝屬性是真的舒服,使用一行代碼可以代替幾行甚至十幾行代碼,我也喜歡用。

      但是喜歡歸喜歡,但是會帶來性能問題,因為底層是通過反射來的拷貝屬性的,所以盡量不要用BeanUtils來拷貝屬性。

      比如你可以裝個JavaBean轉(zhuǎn)換的插件,幫你自動生成轉(zhuǎn)換代碼;又或者可以使用性能更高的MapStruct來進行JavaBean轉(zhuǎn)換,MapStruct底層是通過調(diào)用(settter/getter)來實現(xiàn)的,而不是反射來快速執(zhí)行。

      38、使用StringBuilder進行字符串拼接

      如下代碼:

      String str1 = '123';
      String str2 = '456';
      String str3 = '789';
      String str4 = str1 + str2 + str3;

      使用 + 拼接字符串的時候,會創(chuàng)建一個StringBuilder,然后將要拼接的字符串追加到StringBuilder,再toString,這樣如果多次拼接就會執(zhí)行很多次的創(chuàng)建StringBuilder,z執(zhí)行toString的操作。

      所以可以手動通過StringBuilder拼接,這樣只會創(chuàng)建一次StringBuilder,效率更高。

      StringBuilder sb = new StringBuilder();
      String str = sb.append('123').append('456').append('789').toString();

      39、@Transactional應(yīng)指定回滾的異常類型

      平時在寫代碼的時候需要通過rollbackFor顯示指定需要對什么異常回滾,原因在這:

      圖片

      默認是只能回滾RuntimeException和Error異常,所以需要手動指定,比如指定成Expection等。

      40、謹慎方法內(nèi)部調(diào)用動態(tài)代理的方法

      如下事務(wù)代碼

      @Service
      public class PersonService {
          
          public void update(Person person) {
              // 處理
              updatePerson(person);
          }

          @Transactional(rollbackFor = Exception.class)
          public void updatePerson(Person person
      {
              // 處理
          }

      }

      update調(diào)用了加了@Transactional注解的updatePerson方法,那么此時updatePerson的事務(wù)就是失效。

      其實失效的原因不是事務(wù)的鍋,是由AOP機制決定的,因為事務(wù)是基于AOP實現(xiàn)的。AOP是基于對象的代理,當內(nèi)部方法調(diào)用時,走的不是動態(tài)代理對象的方法,而是原有對象的方法調(diào)用,如此就走不到動態(tài)代理的代碼,就會失效了。

      如果實在需要讓動態(tài)代理生效,可以注入自己的代理對象

      @Service
      public class PersonService {

          @Autowired
          private PersonService personService;

          public void update(Person person) {
              // 處理
              personService.updatePerson(person);
          }

          @Transactional(rollbackFor = Exception.class)
          public void updatePerson(Person person
      {
              // 處理
          }

      }

      41、需要什么字段select什么字段

      查詢?nèi)侄斡幸韵聨c壞處:

      增加不必要的字段的網(wǎng)絡(luò)傳輸

      比如有些文本的字段,存儲的數(shù)據(jù)非常長,但是本次業(yè)務(wù)使用不到,但是如果查了就會把這個數(shù)據(jù)返回給客戶端,增加了網(wǎng)絡(luò)傳輸?shù)呢摀?/p>

      會導(dǎo)致無法使用到覆蓋索引

      比如說,現(xiàn)在有身份證號和姓名做了聯(lián)合索引,現(xiàn)在只需要根據(jù)身份證號查詢姓名,如果直接select name 的話,那么在遍歷索引的時候,發(fā)現(xiàn)要查詢的字段在索引中已經(jīng)存在,那么此時就會直接從索引中將name字段的數(shù)據(jù)查出來,返回,而不會繼續(xù)去查找聚簇索引,減少回表的操作。

      所以建議是需要使用什么字段查詢什么字段。比如mp也支持在構(gòu)建查詢條件的時候,查詢某個具體的字段。

       Wrappers.query().select('name');

      42、不循環(huán)調(diào)用數(shù)據(jù)庫

      不要在循環(huán)中訪問數(shù)據(jù)庫,這樣會嚴重影響數(shù)據(jù)庫性能。

      比如需要查詢一批人員的信息,人員的信息存在基本信息表和擴展表中,錯誤的代碼如下:

      public List<PersonVO> selectPersons(List<Long> personIds) {
          List<PersonVO> persons = new ArrayList<>(personIds.size());
          List<Person> personList = personMapper.selectByIds(personIds);
          for (Person person : personList) {
              PersonVO vo = new PersonVO();
              PersonExt personExt = personExtMapper.selectById(person.getId());
              // 組裝數(shù)據(jù)
              persons.add(vo);
          }
          return persons;
      }

      遍歷每個人員的基本信息,去數(shù)據(jù)庫查找。

      正確的方法應(yīng)該先批量查出來,然后轉(zhuǎn)成map:

      public List<PersonVO> selectPersons(List<Long> personIds) {
          List<PersonVO> persons = new ArrayList<>(personIds.size());
          List<Person> personList = personMapper.selectByIds(personIds);
              //批量查詢,轉(zhuǎn)換成Map
          List<PersonExt> personExtList = personExtMapper.selectByIds(person.getId());
          Map<String, PersonExt> personExtMap = personExtList.stream().collect(Collectors.toMap(PersonExt::getPersonId, Function.identity()));
          for (Person person : personList) {
              PersonVO vo = new PersonVO();
              //直接從Map中查找
              PersonExt personExt = personExtMap.get(person.getId());
              // 組裝數(shù)據(jù)
              persons.add(vo);
          }
          return persons;
      }

      43、用業(yè)務(wù)代碼代替多表join

      如上面代碼所示,原本也可以將兩張表根據(jù)人員的id進行關(guān)聯(lián)查詢。但是不推薦這么,阿里也禁止多表join的操作

      圖片

      而之所以會禁用,是因為join的效率比較低。

      MySQL是使用了嵌套循環(huán)的方式來實現(xiàn)關(guān)聯(lián)查詢的,也就是for循環(huán)會套for循環(huán)的意思。用第一張表做外循環(huán),第二張表做內(nèi)循環(huán),外循環(huán)的每一條記錄跟內(nèi)循環(huán)中的記錄作比較,符合條件的就輸出,這種效率肯定低。

      44、裝上阿里代碼檢查插件

      我們平時寫代碼由于各種因為,比如什么領(lǐng)導(dǎo)啊,項目經(jīng)理啊,會一直催進度,導(dǎo)致寫代碼都來不及思考,怎么快怎么來,cv大法上線,雖然有心想寫好代碼,但是手確不聽使喚。所以我建議裝一個阿里的代碼規(guī)范插件,如果有代碼不規(guī)范,會有提醒,這樣就可以知道哪些是可以優(yōu)化的了。

      圖片

      如果你有強迫癥,相信我,裝了這款插件,你的代碼會寫的很漂亮。

      45、及時跟同事溝通

      寫代碼的時候不能閉門造車,及時跟同事溝通,比如剛進入一個新的項目的,對項目工程不熟悉,一些技術(shù)方案不了解,如果上來就直接寫代碼,很有可能就會踩坑。

      參考資料:

      《代碼整潔之道》

      《阿里巴巴Java開發(fā)手冊》

      如何寫出讓人抓狂的代碼?

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多