JavaRush /Blog Java /Random-VI /Nghỉ giải lao #56. Hướng dẫn nhanh về các phương pháp hay...

Nghỉ giải lao #56. Hướng dẫn nhanh về các phương pháp hay nhất trong Java

Xuất bản trong nhóm
Nguồn: DZone Hướng dẫn này bao gồm các tài liệu tham khảo và thực tiễn Java tốt nhất để cải thiện khả năng đọc và độ tin cậy của mã của bạn. Các nhà phát triển có trách nhiệm lớn trong việc đưa ra những quyết định đúng đắn mỗi ngày và điều tốt nhất có thể giúp họ đưa ra những quyết định đúng đắn chính là kinh nghiệm. Và mặc dù không phải tất cả họ đều có nhiều kinh nghiệm trong phát triển phần mềm nhưng mọi người đều có thể sử dụng kinh nghiệm của người khác. Tôi đã chuẩn bị cho bạn một số đề xuất mà tôi đã rút ra được từ trải nghiệm của mình với Java. Tôi hy vọng chúng giúp bạn cải thiện khả năng đọc và độ tin cậy của mã Java.Nghỉ giải lao #56.  Hướng dẫn nhanh về các phương pháp hay nhất trong Java - 1

Nguyên tắc lập trình

Đừng viết mã chỉ hoạt động . Cố gắng viết mã có thể bảo trì được — không chỉ bởi bạn mà còn bởi bất kỳ ai khác có thể sẽ làm việc trên phần mềm trong tương lai. Một nhà phát triển dành 80% thời gian để đọc mã và 20% viết và kiểm tra mã. Vì vậy, hãy tập trung vào việc viết mã có thể đọc được. Mã của bạn không cần bình luận để bất cứ ai hiểu nó làm gì. Để viết mã tốt, có nhiều nguyên tắc lập trình mà chúng ta có thể sử dụng làm hướng dẫn. Dưới đây tôi sẽ liệt kê những cái quan trọng nhất.
  • • KISS – Viết tắt của “Keep It Simple, Stupid.” Bạn có thể nhận thấy rằng khi bắt đầu hành trình, các nhà phát triển sẽ cố gắng triển khai các thiết kế phức tạp, mơ hồ.
  • • DRY - “Đừng lặp lại chính mình.” Cố gắng tránh bất kỳ sự trùng lặp nào, thay vào đó hãy đặt chúng vào một phần duy nhất của hệ thống hoặc phương pháp.
  • YAGNI - “Bạn sẽ không cần nó.” Nếu bạn đột nhiên bắt đầu tự hỏi: “Còn việc bổ sung thêm (tính năng, mã, v.v.) thì sao?”, thì có lẽ bạn cần phải suy nghĩ xem liệu việc bổ sung chúng có thực sự đáng giá hay không.
  • Mã sạch thay vì mã thông minh - Nói một cách đơn giản, hãy bỏ cái tôi của bạn ra ngoài và quên việc viết mã thông minh. Bạn muốn mã sạch chứ không phải mã thông minh.
  • Tránh tối ưu hóa sớm - Vấn đề với việc tối ưu hóa sớm là bạn không bao giờ biết được các điểm thắt cổ chai sẽ ở đâu trong chương trình cho đến khi chúng xuất hiện.
  • Trách nhiệm duy nhất - Mỗi lớp hoặc mô-đun trong một chương trình chỉ nên quan tâm đến việc cung cấp một bit của một chức năng cụ thể.
  • Thành phần thay vì kế thừa triển khai - Các đối tượng có hành vi phức tạp nên chứa các thể hiện của các đối tượng có hành vi riêng lẻ, thay vì kế thừa một lớp và thêm các hành vi mới.
  • Thể dục đối tượng là các bài tập lập trình được thiết kế theo bộ 9 quy tắc .
  • Thất bại nhanh, dừng nhanh - Nguyên tắc này có nghĩa là dừng hoạt động hiện tại khi xảy ra lỗi không mong muốn. Tuân thủ nguyên tắc này dẫn đến hoạt động ổn định hơn.

Gói

  1. Ưu tiên cấu trúc các gói theo lĩnh vực chủ đề hơn là theo trình độ kỹ thuật.
  2. Ưu tiên các bố cục khuyến khích việc đóng gói và ẩn thông tin để bảo vệ khỏi việc sử dụng sai mục đích hơn là tổ chức các lớp học vì lý do kỹ thuật.
  3. Xử lý các gói như thể chúng có API bất biến - không hiển thị các cơ chế (lớp) nội bộ chỉ dành cho xử lý nội bộ.
  4. Không hiển thị các lớp dự định chỉ được sử dụng trong gói.

Các lớp học

Tĩnh

  1. Không cho phép tạo lớp tĩnh. Luôn tạo một hàm tạo riêng.
  2. Các lớp tĩnh phải không thay đổi, không cho phép phân lớp hoặc các lớp đa luồng.
  3. Các lớp tĩnh cần được bảo vệ khỏi những thay đổi về hướng và phải được cung cấp dưới dạng các tiện ích như lọc danh sách.

Di sản

  1. Chọn thành phần thay vì kế thừa.
  2. Không đặt các trường được bảo vệ . Thay vào đó, hãy chỉ định một phương thức truy cập an toàn.
  3. Nếu một biến lớp có thể được đánh dấu là cuối cùng , hãy làm như vậy.
  4. Nếu không mong đợi sự kế thừa, hãy tạo lớp cuối cùng .
  5. Đánh dấu một phương thức là cuối cùng nếu dự kiến ​​các lớp con sẽ không được phép ghi đè lên nó.
  6. Nếu không cần có hàm tạo, thì đừng tạo hàm tạo mặc định mà không có logic triển khai. Java sẽ tự động cung cấp một hàm tạo mặc định nếu nó không được chỉ định.

Giao diện

  1. Không sử dụng giao diện của mẫu hằng vì nó cho phép các lớp triển khai và gây ô nhiễm API. Thay vào đó hãy sử dụng một lớp tĩnh. Điều này có thêm lợi ích là cho phép bạn thực hiện khởi tạo đối tượng phức tạp hơn trong một khối tĩnh (chẳng hạn như điền vào một bộ sưu tập).
  2. Tránh lạm dụng giao diện .
  3. Việc có một và chỉ một lớp triển khai một giao diện có thể sẽ dẫn đến việc lạm dụng giao diện và gây hại nhiều hơn là có lợi.
  4. "Chương trình dành cho giao diện chứ không phải phần triển khai" không có nghĩa là bạn nên gói từng lớp miền của mình với một giao diện ít nhiều giống hệt nhau, bằng cách làm như vậy, bạn đang vi phạm YAGNI .
  5. Luôn giữ giao diện nhỏ gọn và cụ thể để khách hàng chỉ biết về các phương pháp mà họ quan tâm. Kiểm tra ISP từ SOLID.

Người hoàn thiện

  1. Đối tượng #finalize() nên được sử dụng một cách thận trọng và chỉ như một phương tiện bảo vệ khỏi lỗi khi dọn dẹp tài nguyên (chẳng hạn như đóng tệp). Luôn cung cấp phương pháp dọn dẹp rõ ràng (chẳng hạn như close() ).
  2. Trong hệ thống phân cấp kế thừa, luôn gọi phương thức Finalize() của cha mẹ trong khối thử . Việc dọn dẹp lớp phải ở trong khối cuối cùng .
  3. Nếu phương pháp dọn dẹp rõ ràng không được gọi và trình hoàn thiện đã đóng tài nguyên, hãy ghi lại lỗi này.
  4. Nếu không có sẵn trình ghi nhật ký, hãy sử dụng trình xử lý ngoại lệ của luồng (cuối cùng sẽ chuyển một lỗi tiêu chuẩn được ghi lại trong nhật ký).

Quy tắc chung

Các câu lệnh

Một xác nhận, thường ở dạng kiểm tra điều kiện tiên quyết, thực thi hợp đồng "thất bại nhanh, dừng nhanh". Chúng nên được sử dụng rộng rãi để xác định lỗi lập trình càng gần nguyên nhân càng tốt. Điều kiện đối tượng:
  • • Không bao giờ được tạo hoặc đặt một đối tượng vào trạng thái không hợp lệ.
  • • Trong hàm tạo và phương thức, luôn mô tả và thực thi hợp đồng bằng cách sử dụng các thử nghiệm.
  • • Nên tránh từ khóa Java khẳng định vì nó có thể bị vô hiệu hóa và thường là một cấu trúc dễ vỡ.
  • • Sử dụng lớp tiện ích Assertions để tránh các điều kiện if-else dài dòng cho việc kiểm tra điều kiện tiên quyết.

Thuốc gốc

Lời giải thích đầy đủ, cực kỳ chi tiết có sẵn trong Câu hỏi thường gặp về Java Generics . Dưới đây là các tình huống phổ biến mà các nhà phát triển nên biết.
  1. Bất cứ khi nào có thể, tốt hơn là sử dụng suy luận kiểu thay vì trả về lớp/giao diện cơ sở:

    // MySpecialObject o = MyObjectFactory.getMyObject();
    public  T getMyObject(int type) {
    return (T) factory.create(type);
    }

  2. Nếu loại không thể được xác định tự động, hãy nội tuyến nó.

    public class MySpecialObject extends MyObject {
     public MySpecialObject() {
      super(Collections.emptyList());   // This is ugly, as we loose type
      super(Collections.EMPTY_LIST();    // This is just dumb
      // But this is beauty
      super(new ArrayList());
      super(Collections.emptyList());
     }
    }

  3. Ký tự đại diện:

    Sử dụng ký tự đại diện mở rộng khi bạn chỉ nhận các giá trị từ một cấu trúc, sử dụng ký tự đại diện siêu khi bạn chỉ đặt các giá trị vào một cấu trúc và không sử dụng ký tự đại diện khi bạn thực hiện cả hai.

    1. Mọi người đều yêu thích PECS ! ( Nhà sản xuất-mở rộng, Người tiêu dùng-siêu )
    2. Sử dụng Foo cho nhà sản xuất T.
    3. Sử dụng Foo cho người tiêu dùng T.

Singletons

Một singleton không bao giờ nên được viết theo kiểu mẫu thiết kế cổ điển , điều này tốt trong C++ nhưng không phù hợp trong Java. Mặc dù nó an toàn theo luồng đúng cách, nhưng đừng bao giờ triển khai những điều sau (nó sẽ gây tắc nghẽn hiệu suất!):
public final class MySingleton {
  private static MySingleton instance;
  private MySingleton() {
    // singleton
  }
  public static synchronized MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }
}
Nếu việc khởi tạo lười biếng thực sự được mong muốn thì sự kết hợp của hai cách tiếp cận này sẽ có hiệu quả.
public final class MySingleton {
  private MySingleton() {
   // singleton
  }
  private static final class MySingletonHolder {
    static final MySingleton instance = new MySingleton();
  }
  public static MySingleton getInstance() {
    return MySingletonHolder.instance;
  }
}
Mùa xuân: Theo mặc định, một Bean được đăng ký với phạm vi singleton, có nghĩa là chỉ một phiên bản sẽ được vùng chứa tạo và kết nối với tất cả người tiêu dùng. Điều này cung cấp ngữ nghĩa giống như một singleton thông thường, không có bất kỳ hạn chế về hiệu suất hoặc ràng buộc nào.

Ngoại lệ

  1. Sử dụng các ngoại lệ đã kiểm tra cho các điều kiện có thể sửa được và các ngoại lệ thời gian chạy cho các lỗi lập trình. Ví dụ: lấy số nguyên từ một chuỗi.

    Xấu: NumberFormatException mở rộng RuntimeException, vì vậy nó nhằm mục đích chỉ ra lỗi lập trình.

  2. Đừng làm những điều sau:

    // String str = input string
    Integer value = null;
    try {
       value = Integer.valueOf(str);
    } catch (NumberFormatException e) {
    // non-numeric string
    }
    if (value == null) {
    // handle bad string
    } else {
    // business logic
    }

    Sử dụng đúng:

    // String str = input string
    // Numeric string with at least one digit and optional leading negative sign
    if ( (str != null) && str.matches("-?\\d++") ) {
       Integer value = Integer.valueOf(str);
      // business logic
    } else {
      // handle bad string
    }
  3. Bạn phải xử lý các trường hợp ngoại lệ ở đúng nơi, đúng chỗ ở cấp tên miền.

    CÁCH SAI - Lớp đối tượng dữ liệu không biết phải làm gì khi xảy ra ngoại lệ cơ sở dữ liệu.

    class UserDAO{
        public List getUsers(){
            try{
                ps = conn.prepareStatement("SELECT * from users");
                rs = ps.executeQuery();
                //return result
            }catch(Exception e){
                log.error("exception")
                return null
            }finally{
                //release resources
            }
        }}
    

    CÁCH ĐƯỢC KHUYẾN NGHỊ - Lớp dữ liệu chỉ cần lấy lại ngoại lệ và chuyển trách nhiệm xử lý ngoại lệ hoặc không cho lớp chính xác.

    === RECOMMENDED WAY ===
    Data layer should just retrow the exception and transfer the responsability to handle the exception or not to the right layer.
    class UserDAO{
       public List getUsers(){
          try{
             ps = conn.prepareStatement("SELECT * from users");
             rs = ps.executeQuery();
             //return result
          }catch(Exception e){
           throw new DataLayerException(e);
          }finally{
             //release resources
          }
      }
    }

  4. Các ngoại lệ nói chung KHÔNG nên được ghi lại vào thời điểm chúng được ban hành mà nên ghi vào thời điểm chúng thực sự được xử lý. Việc ghi nhật ký các ngoại lệ, khi chúng được ném đi hoặc ném lại, có xu hướng làm cho các tệp nhật ký bị nhiễu. Cũng lưu ý rằng dấu vết ngăn xếp ngoại lệ vẫn ghi lại vị trí ngoại lệ được ném ra.

  5. Hỗ trợ việc sử dụng các ngoại lệ tiêu chuẩn.

  6. Sử dụng ngoại lệ thay vì trả lại mã.

Bằng và HashCode

Có một số vấn đề cần xem xét khi viết các phương thức tương đương đối tượng và mã băm thích hợp. Để dễ sử dụng hơn, hãy sử dụng bằnghàm băm của java.util.Objects .
public final class User {
 private final String firstName;
 private final String lastName;
 private final int age;
 ...
 public boolean equals(Object o) {
   if (this == o) {
     return true;
   } else if (!(o instanceof User)) {
     return false;
   }
   User user = (User) o;
   return Objects.equals(getFirstName(), user.getFirstName()) &&
    Objects.equals(getLastName(),user.getLastName()) &&
    Objects.equals(getAge(), user.getAge());
 }
 public int hashCode() {
   return Objects.hash(getFirstName(),getLastName(),getAge());
 }
}

Quản lý nguồn tài nguyên

Các cách giải phóng tài nguyên một cách an toàn: Câu lệnh try-with-resource đảm bảo rằng mỗi tài nguyên đều được đóng ở cuối câu lệnh. Bất kỳ đối tượng nào triển khai java.lang.AutoCloseable, bao gồm tất cả các đối tượng triển khai java.io.Closeable , đều có thể được sử dụng làm tài nguyên.
private doSomething() {
try (BufferedReader br = new BufferedReader(new FileReader(path)))
 try {
   // business logic
 }
}

Sử dụng móc tắt máy

Sử dụng móc tắt máy được gọi khi JVM tắt một cách nhẹ nhàng. (Nhưng nó sẽ không thể xử lý các gián đoạn đột ngột, chẳng hạn như do mất điện) Đây là một giải pháp thay thế được đề xuất thay vì khai báo phương thức Finalize() sẽ chỉ chạy nếu System.runFinalizersOnExit() là đúng (mặc định là sai) .
public final class SomeObject {
 var distributedLock = new ExpiringGeneralLock ("SomeObject", "shared");
 public SomeObject() {
   Runtime
     .getRuntime()
     .addShutdownHook(new Thread(new LockShutdown(distributedLock)));
 }
 /** Code may have acquired lock across servers */
 ...
 /** Safely releases the distributed lock. */
 private static final class LockShutdown implements Runnable {
   private final ExpiringGeneralLock distributedLock;
   public LockShutdown(ExpiringGeneralLock distributedLock) {
     if (distributedLock == null) {
       throw new IllegalArgumentException("ExpiringGeneralLock is null");
     }
     this.distributedLock = distributedLock;
   }
   public void run() {
     if (isLockAlive()) {
       distributedLock.release();
     }
   }
   /** @return True if the lock is acquired and has not expired yet. */
   private boolean isLockAlive() {
     return distributedLock.getExpirationTimeMillis() > System.currentTimeMillis();
   }
 }
}
Cho phép tài nguyên trở nên hoàn chỉnh (cũng như có thể tái tạo) bằng cách phân phối chúng giữa các máy chủ. (Điều này sẽ cho phép khôi phục sau khi bị gián đoạn đột ngột chẳng hạn như mất điện.) Xem mã ví dụ ở trên sử dụng ExpiringGeneralLock (một khóa chung cho tất cả các hệ thống).

Ngày giờ

Java 8 giới thiệu API ngày giờ mới trong gói java.time. Java 8 giới thiệu API ngày giờ mới để giải quyết các thiếu sót sau của API ngày giờ cũ: không phân luồng, thiết kế kém, xử lý múi giờ phức tạp, v.v.

Sự song song

Quy tắc chung

  1. Hãy cẩn thận với các thư viện không an toàn cho luồng sau đây. Luôn đồng bộ hóa với các đối tượng nếu chúng được sử dụng bởi nhiều luồng.
  2. Ngày ( không phải bất biến ) - Sử dụng API ngày giờ mới, an toàn cho luồng.
  3. SimpleDateFormat - Sử dụng API ngày giờ mới, an toàn cho luồng.
  4. Thích sử dụng các lớp java.util.concurrent.atomic hơn là tạo các biến dễ bay hơi .
  5. Hành vi của các lớp nguyên tử rõ ràng hơn đối với nhà phát triển trung bình, trong khi tính dễ bay hơi đòi hỏi sự hiểu biết về mô hình bộ nhớ Java.
  6. Các lớp nguyên tử bọc các biến dễ bay hơi vào một giao diện thuận tiện hơn.
  7. Hiểu các trường hợp sử dụng ở những nơi dễ biến động là thích hợp . (xem bài viết )
  8. Sử dụng có thể gọi được khi cần có ngoại lệ được kiểm tra nhưng không có kiểu trả về. Vì Void không thể được khởi tạo nên nó truyền đạt ý định và có thể trả về null một cách an toàn .

Dòng

  1. java.lang.Thread không được dùng nữa. Mặc dù đây không phải là trường hợp chính thức, nhưng trong hầu hết các trường hợp, gói java.util.concurrent cung cấp giải pháp rõ ràng hơn cho vấn đề.
  2. Việc mở rộng java.lang.Thread được coi là phương pháp không tốt - thay vào đó hãy triển khai Runnable và tạo một luồng mới với một phiên bản trong hàm tạo (quy tắc tổng hợp về kế thừa).
  3. Ưu tiên người thực thi và luồng khi cần xử lý song song.
  4. Bạn nên chỉ định nhà máy sản xuất luồng tùy chỉnh của riêng mình để quản lý cấu hình của các luồng đã tạo ( chi tiết hơn tại đây ).
  5. Sử dụng DaemonThreadFactory trong Executors cho các luồng không quan trọng để nhóm luồng có thể bị tắt ngay lập tức khi máy chủ tắt (xem thêm chi tiết tại đây ).
this.executor = Executors.newCachedThreadPool((Runnable runnable) -> {
   Thread thread = Executors.defaultThreadFactory().newThread(runnable);
   thread.setDaemon(true);
   return thread;
});
  1. Đồng bộ hóa Java không còn quá chậm nữa (55–110 ns). Đừng tránh nó bằng cách sử dụng các thủ thuật như khóa kiểm tra hai lần .
  2. Ưu tiên đồng bộ hóa với đối tượng bên trong hơn là lớp vì người dùng có thể đồng bộ hóa với lớp/phiên bản của bạn.
  3. Luôn đồng bộ nhiều đối tượng theo cùng một thứ tự để tránh tình trạng bế tắc.
  4. Đồng bộ hóa với một lớp vốn không chặn quyền truy cập vào các đối tượng bên trong của nó. Luôn sử dụng các khóa giống nhau khi truy cập tài nguyên.
  5. Hãy nhớ rằng từ khóa được đồng bộ hóa không được coi là một phần của chữ ký phương thức và do đó sẽ không được kế thừa.
  6. Tránh đồng bộ hóa quá mức, điều này có thể dẫn đến hiệu suất kém và bế tắc. Sử dụng nghiêm ngặt từ khóa được đồng bộ hóa cho phần mã yêu cầu đồng bộ hóa.

Bộ sưu tập

  1. Sử dụng các bộ sưu tập song song Java-5 trong mã đa luồng bất cứ khi nào có thể. Chúng an toàn và có những đặc tính tuyệt vời.
  2. Nếu cần, hãy sử dụng CopyOnWriteArrayList thay vì được đồng bộ hóa.
  3. Sử dụng Collections.unmodibility list(...) hoặc sao chép bộ sưu tập khi nhận nó dưới dạng tham số sang new ArrayList(list) . Tránh sửa đổi các bộ sưu tập cục bộ từ bên ngoài lớp học của bạn.
  4. Luôn trả lại một bản sao bộ sưu tập của bạn, tránh sửa đổi danh sách của bạn từ bên ngoài bằng new ArrayList (list) .
  5. Mỗi bộ sưu tập phải được bao bọc trong một lớp riêng biệt, vì vậy bây giờ hành vi được liên kết với bộ sưu tập có một nhà (ví dụ: phương pháp lọc, áp dụng quy tắc cho từng thành phần).

Điều khoản khác

  1. Chọn lambdas thay vì các lớp ẩn danh.
  2. Chọn tài liệu tham khảo phương pháp thay vì lambdas.
  3. Sử dụng enum thay vì hằng số int.
  4. Tránh sử dụng float và double nếu cần câu trả lời chính xác, thay vào đó hãy sử dụng BigDecimal chẳng hạn như Money.
  5. Chọn kiểu nguyên thủy thay vì kiểu nguyên thủy đóng hộp.
  6. Bạn nên tránh sử dụng số ma thuật trong mã của mình. Sử dụng hằng số.
  7. Đừng trả lại Null. Giao tiếp với ứng dụng khách phương thức của bạn bằng cách sử dụng `Tùy chọn`. Tương tự đối với các bộ sưu tập - trả về các mảng hoặc bộ sưu tập trống, không phải giá trị rỗng.
  8. Tránh tạo các đối tượng không cần thiết, sử dụng lại các đối tượng và tránh việc dọn dẹp GC không cần thiết.

Khởi tạo lười biếng

Khởi tạo lười biếng là tối ưu hóa hiệu suất. Nó được sử dụng khi dữ liệu được coi là “đắt” vì một lý do nào đó. Trong Java 8, chúng ta phải sử dụng giao diện nhà cung cấp chức năng cho việc này.
== Thread safe Lazy initialization ===
public final class Lazy {
   private volatile T value;
   public T getOrCompute(Supplier supplier) {
       final T result = value; // Just one volatile read
       return result == null ? maybeCompute(supplier) : result;
   }
   private synchronized T maybeCompute(Supplier supplier) {
       if (value == null) {
           value = supplier.get();
       }
       return value;
   }
}
Lazy lazyToString= new Lazy<>()
return lazyToString.getOrCompute( () -> "(" + x + ", " + y + ")");
Đó là tất cả bây giờ, tôi hy vọng điều này hữu ích!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION