JavaRush /Java Blog /Random-ID /Rehat kopi #56. Panduan Singkat Praktik Terbaik di Java

Rehat kopi #56. Panduan Singkat Praktik Terbaik di Java

Dipublikasikan di grup Random-ID
Sumber: DZone Panduan ini mencakup praktik dan referensi Java terbaik untuk meningkatkan keterbacaan dan keandalan kode Anda. Pengembang memiliki tanggung jawab besar untuk membuat keputusan yang tepat setiap hari, dan hal terbaik yang dapat membantu mereka membuat keputusan yang tepat adalah pengalaman. Dan meskipun tidak semua dari mereka memiliki pengalaman luas dalam pengembangan perangkat lunak, semua orang dapat memanfaatkan pengalaman orang lain. Saya telah menyiapkan beberapa rekomendasi untuk Anda yang saya peroleh dari pengalaman saya dengan Java. Saya harap mereka membantu Anda meningkatkan keterbacaan dan keandalan kode Java Anda.Rehat kopi #56.  Panduan Singkat Praktik Terbaik di Java - 1

Prinsip Pemrograman

Jangan menulis kode yang hanya berfungsi . Berusahalah untuk menulis kode yang dapat dikelola — tidak hanya oleh Anda, tetapi oleh siapa pun yang mungkin akan mengerjakan perangkat lunak tersebut di masa mendatang. Seorang pengembang menghabiskan 80% waktunya untuk membaca kode, dan 20% menulis dan menguji kode. Jadi, fokuslah pada penulisan kode yang mudah dibaca. Kode Anda tidak memerlukan komentar agar siapa pun dapat memahami fungsinya. Untuk menulis kode yang baik, ada banyak prinsip pemrograman yang bisa kita jadikan pedoman. Di bawah ini saya akan mencantumkan yang paling penting.
  • • KISS – Singkatan dari “Keep It Simple, Stupid.” Anda mungkin memperhatikan bahwa pengembang di awal perjalanannya mencoba menerapkan desain yang rumit dan ambigu.
  • • KERING - “Jangan Mengulangi Diri Sendiri.” Cobalah untuk menghindari duplikat apa pun, alih-alih menempatkannya di satu bagian sistem atau metode.
  • YAGNI - “Kamu Tidak Akan Membutuhkannya.” Jika Anda tiba-tiba bertanya pada diri sendiri, “Bagaimana kalau menambahkan lebih banyak (fitur, kode, dll.)?”, Anda mungkin perlu memikirkan apakah menambahkannya benar-benar layak dilakukan.
  • Bersihkan kode, bukan kode cerdas - Sederhananya, tinggalkan ego Anda dan lupakan menulis kode cerdas. Anda menginginkan kode yang bersih, bukan kode pintar.
  • Hindari Optimasi Dini – Masalah dengan optimasi prematur adalah Anda tidak pernah tahu di mana hambatan dalam program sampai hambatan tersebut muncul.
  • Tanggung jawab tunggal - Setiap kelas atau modul dalam suatu program hanya boleh menyediakan sedikit fungsi tertentu.
  • • Warisan komposisi, bukan pewarisan implementasi - Objek dengan perilaku kompleks harus berisi instance objek dengan perilaku individual, daripada mewarisi kelas dan menambahkan perilaku baru.
  • Senam objek adalah latihan pemrograman yang dirancang sebagai seperangkat 9 aturan .
  • Gagal cepat, berhenti cepat - Prinsip ini berarti menghentikan operasi saat ini ketika terjadi kesalahan yang tidak terduga. Kepatuhan terhadap prinsip ini menghasilkan pengoperasian yang lebih stabil.

Paket

  1. Prioritaskan paket penataan berdasarkan bidang subjek daripada tingkat teknis.
  2. Pilih tata letak yang mendukung enkapsulasi dan penyembunyian informasi untuk melindungi dari penyalahgunaan daripada mengatur kelas karena alasan teknis.
  3. Perlakukan paket seolah-olah mereka memiliki API yang tidak dapat diubah - jangan mengekspos mekanisme internal (kelas) yang dimaksudkan hanya untuk pemrosesan internal.
  4. Jangan mengekspos kelas yang dimaksudkan untuk digunakan hanya di dalam paket.

Kelas

Statis

  1. Jangan izinkan pembuatan kelas statis. Selalu buat konstruktor pribadi.
  2. Kelas statis harus tetap tidak berubah, tidak mengizinkan kelas subkelas atau multi-utas.
  3. Kelas statis harus dilindungi dari perubahan orientasi dan harus disediakan sebagai utilitas seperti pemfilteran daftar.

Warisan

  1. Pilih komposisi daripada warisan.
  2. Jangan setel bidang yang dilindungi . Sebaliknya, tentukan metode akses yang aman .
  3. Jika variabel kelas dapat ditandai sebagai final , lakukanlah.
  4. Jika warisan tidak diharapkan, jadikan kelas tersebut final .
  5. Tandai suatu metode sebagai final jika subkelas tidak diharapkan diizinkan untuk menimpanya.
  6. Jika konstruktor tidak diperlukan, jangan membuat konstruktor default tanpa logika implementasi. Java akan secara otomatis menyediakan konstruktor default jika tidak ditentukan.

Antarmuka

  1. Jangan gunakan antarmuka pola konstanta karena memungkinkan kelas untuk mengimplementasikan dan mencemari API. Gunakan kelas statis sebagai gantinya. Ini memiliki keuntungan tambahan karena memungkinkan Anda melakukan inisialisasi objek yang lebih kompleks dalam blok statis (seperti mengisi koleksi).
  2. Hindari penggunaan antarmuka secara berlebihan .
  3. Memiliki satu dan hanya satu kelas yang mengimplementasikan antarmuka kemungkinan besar akan menyebabkan penggunaan antarmuka secara berlebihan dan lebih banyak merugikan daripada menguntungkan.
  4. "Program untuk antarmuka, bukan implementasi" tidak berarti Anda harus menggabungkan setiap kelas domain Anda dengan antarmuka yang kurang lebih sama, dengan melakukan ini Anda melanggar YAGNI .
  5. Selalu jaga antarmuka tetap kecil dan spesifik sehingga klien hanya mengetahui metode yang mereka minati. Periksa ISP dari SOLID.

Finalisator

  1. Objek #finalize() harus digunakan dengan bijaksana dan hanya sebagai sarana perlindungan terhadap kegagalan saat membersihkan sumber daya (seperti menutup file). Selalu berikan metode pembersihan yang eksplisit (seperti close() ).
  2. Dalam hierarki pewarisan, selalu panggil finalize() induk di blok try . Pembersihan kelas harus dilakukan di blok terakhir .
  3. Jika metode pembersihan eksplisit tidak dipanggil dan finalizer menutup sumber daya, catat kesalahan ini.
  4. Jika logger tidak tersedia, gunakan pengendali pengecualian thread (yang pada akhirnya meneruskan kesalahan standar yang dicatat dalam log).

Aturan umum

Pernyataan

Penegasan, biasanya dalam bentuk pemeriksaan prasyarat, menerapkan kontrak "gagal cepat, berhenti cepat". Mereka harus digunakan secara luas untuk mengidentifikasi kesalahan pemrograman sedekat mungkin dengan penyebabnya. Kondisi objek:
  • • Suatu objek tidak boleh dibuat atau dimasukkan ke dalam keadaan tidak valid.
  • • Dalam konstruktor dan metode, selalu jelaskan dan terapkan kontrak menggunakan pengujian.
  • • Kata kunci Java menegaskan harus dihindari karena dapat dinonaktifkan dan biasanya merupakan konstruksi yang rapuh.
  • • Gunakan kelas utilitas Assertions untuk menghindari kondisi if-else yang panjang lebar untuk pemeriksaan prasyarat.

Generik

Penjelasan lengkap dan sangat mendetail tersedia di FAQ Java Generics . Di bawah ini adalah skenario umum yang harus diperhatikan oleh pengembang.
  1. Kapan pun memungkinkan, lebih baik menggunakan inferensi tipe daripada mengembalikan kelas/antarmuka dasar:

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

  2. Jika jenisnya tidak dapat ditentukan secara otomatis, sebariskan.

    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. Karakter pengganti:

    Gunakan wildcard yang diperluas ketika Anda hanya mendapatkan nilai dari suatu struktur, gunakan wildcard super ketika Anda hanya memasukkan nilai ke dalam suatu struktur, dan jangan gunakan wildcard ketika Anda melakukan keduanya.

    1. Semua orang menyukai PECS ! ( Perluas Produsen, Konsumen-super )
    2. Gunakan Foo untuk produser T.
    3. Gunakan Foo untuk konsumen T.

Lajang

Singleton tidak boleh ditulis dalam gaya pola desain klasik , yang bagus di C++ tetapi tidak sesuai di Java. Meskipun thread-safe sudah benar, jangan pernah menerapkan hal berikut (ini akan menjadi penghambat kinerja!):
public final class MySingleton {
  private static MySingleton instance;
  private MySingleton() {
    // singleton
  }
  public static synchronized MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }
}
Jika inisialisasi lambat benar-benar diinginkan, maka kombinasi kedua pendekatan ini akan berhasil.
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;
  }
}
Spring: Secara default, bean didaftarkan dengan cakupan tunggal, yang berarti hanya satu instance yang akan dibuat oleh container dan terhubung ke semua konsumen. Ini memberikan semantik yang sama seperti singleton biasa, tanpa batasan kinerja atau pengikatan apa pun.

Pengecualian

  1. Gunakan pengecualian yang dicentang untuk kondisi yang dapat diperbaiki dan pengecualian waktu proses untuk kesalahan pemrograman. Contoh: mendapatkan bilangan bulat dari sebuah string.

    Buruk: NumberFormatException memperluas RuntimeException, sehingga dimaksudkan untuk menunjukkan kesalahan pemrograman.

  2. Jangan lakukan hal berikut:

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

    Penggunaan yang Benar:

    // 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. Anda harus menangani pengecualian di tempat yang tepat, di tempat yang tepat di tingkat domain.

    CARA YANG SALAH - Lapisan objek data tidak tahu apa yang harus dilakukan ketika terjadi pengecualian database.

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

    CARA YANG DIREKOMENDASIKAN - Lapisan data sebaiknya menampilkan kembali pengecualian dan menyerahkan tanggung jawab untuk menangani pengecualian atau tidak ke lapisan yang benar.

    === 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. Pengecualian pada umumnya TIDAK boleh dicatat pada saat dikeluarkan, namun pada saat pengecualian tersebut benar-benar diproses. Pengecualian logging, ketika dilempar atau dilempar kembali, cenderung mengisi file log dengan noise. Perhatikan juga bahwa pelacakan tumpukan pengecualian masih mencatat di mana pengecualian tersebut dilemparkan.

  5. Mendukung penggunaan pengecualian standar.

  6. Gunakan pengecualian daripada kode pengembalian.

Sama dengan dan HashCode

Ada sejumlah masalah yang perlu dipertimbangkan ketika menulis metode kesetaraan objek dan kode hash yang tepat. Agar lebih mudah digunakan, gunakan equal dan hash 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());
 }
}

Pengelolaan sumber daya

Cara melepaskan sumber daya dengan aman: Pernyataan coba-dengan-sumber daya memastikan bahwa setiap sumber daya ditutup pada akhir pernyataan. Objek apa pun yang mengimplementasikan java.lang.AutoCloseable, yang mencakup semua objek yang mengimplementasikan java.io.Closeable , dapat digunakan sebagai sumber daya.
private doSomething() {
try (BufferedReader br = new BufferedReader(new FileReader(path)))
 try {
   // business logic
 }
}

Gunakan Kait Shutdown

Gunakan hook shutdown yang dipanggil saat JVM dimatikan dengan baik. (Tetapi metode ini tidak akan mampu menangani gangguan mendadak, seperti pemadaman listrik) Ini adalah alternatif yang direkomendasikan daripada mendeklarasikan metode finalize() yang hanya akan berjalan jika System.runFinalizersOnExit() bernilai true (defaultnya salah) .
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();
   }
 }
}
Biarkan sumber daya menjadi lengkap (dan terbarukan) dengan mendistribusikannya antar server. (Ini akan memungkinkan pemulihan dari gangguan mendadak seperti pemadaman listrik.) Lihat contoh kode di atas yang menggunakan ExpiringGeneralLock (kunci yang umum untuk semua sistem).

Tanggal Waktu

Java 8 memperkenalkan API Tanggal-Waktu baru dalam paket java.time. Java 8 memperkenalkan API Tanggal-Waktu baru untuk mengatasi kekurangan API Tanggal-Waktu lama berikut: non-threading, desain yang buruk, penanganan zona waktu yang rumit, dll.

Paralelisme

Aturan umum

  1. Waspadalah terhadap perpustakaan berikut, yang tidak aman untuk thread. Selalu sinkronkan dengan objek jika digunakan oleh banyak thread.
  2. Tanggal ( tidak dapat diubah ) - Gunakan API Tanggal-Waktu baru, yang aman untuk thread.
  3. SimpleDateFormat - Gunakan API Tanggal-Waktu baru, yang aman untuk thread.
  4. Lebih suka menggunakan kelas java.util.concurrent.atomic daripada membuat variabel mudah menguap .
  5. Perilaku kelas atom lebih jelas bagi pengembang rata-rata, sedangkan volatil memerlukan pemahaman tentang model memori Java.
  6. Kelas atom membungkus variabel yang mudah menguap ke dalam antarmuka yang lebih nyaman.
  7. Pahami kasus penggunaan yang cocok untuk volatil . (lihat artikel )
  8. Gunakan Dapat Dipanggil ketika pengecualian yang dicentang diperlukan tetapi tidak ada tipe pengembalian. Karena Void tidak dapat dipakai, ia mengomunikasikan maksudnya dan dapat mengembalikan null dengan aman .

Aliran

  1. java.lang.Thread seharusnya tidak digunakan lagi. Meskipun hal ini tidak terjadi secara resmi, di hampir semua kasus, paket java.util.concurrent memberikan solusi yang lebih jelas untuk masalah tersebut.
  2. Memperluas java.lang.Thread dianggap praktik buruk - implementasikan Runnable sebagai gantinya dan buat thread baru dengan instance di konstruktor (aturan komposisi atas warisan).
  3. Lebih memilih eksekutor dan thread ketika pemrosesan paralel diperlukan.
  4. Selalu disarankan untuk menentukan pabrik thread kustom Anda sendiri untuk mengelola konfigurasi thread yang dibuat ( detail lebih lanjut di sini ).
  5. Gunakan DaemonThreadFactory di Executors untuk thread yang tidak kritis sehingga kumpulan thread dapat segera dimatikan ketika server dimatikan ( detail lebih lanjut di sini ).
this.executor = Executors.newCachedThreadPool((Runnable runnable) -> {
   Thread thread = Executors.defaultThreadFactory().newThread(runnable);
   thread.setDaemon(true);
   return thread;
});
  1. Sinkronisasi Java tidak lagi lambat (55–110 ns). Jangan menghindarinya dengan menggunakan trik seperti penguncian ganda .
  2. Lebih suka sinkronisasi dengan objek internal daripada kelas, karena pengguna dapat melakukan sinkronisasi dengan kelas/instance Anda.
  3. Selalu sinkronkan beberapa objek dalam urutan yang sama untuk menghindari kebuntuan.
  4. Sinkronisasi dengan suatu kelas tidak secara inheren memblokir akses ke objek internalnya. Selalu gunakan kunci yang sama saat mengakses sumber daya.
  5. Ingatlah bahwa kata kunci yang disinkronkan tidak dianggap sebagai bagian dari tanda tangan metode dan oleh karena itu tidak akan diwariskan.
  6. Hindari sinkronisasi yang berlebihan, hal ini dapat menyebabkan kinerja buruk dan kebuntuan. Gunakan kata kunci tersinkronisasi hanya untuk bagian kode yang memerlukan sinkronisasi.

Koleksi

  1. Gunakan koleksi paralel Java-5 dalam kode multi-utas bila memungkinkan. Mereka aman dan memiliki karakteristik yang sangat baik.
  2. Jika perlu, gunakan CopyOnWriteArrayList alih-alih disinkronkanList.
  3. Gunakan Collections.unmodified list(...) atau salin koleksi saat menerimanya sebagai parameter ke new ArrayList(list) . Hindari memodifikasi koleksi lokal dari luar kelas Anda.
  4. Selalu kembalikan salinan koleksi Anda, hindari mengubah daftar Anda secara eksternal dengan new ArrayList (list) .
  5. Setiap koleksi harus dibungkus dalam kelas terpisah, jadi sekarang perilaku yang terkait dengan koleksi tersebut memiliki rumahnya (misalnya metode pemfilteran, menerapkan aturan pada setiap elemen).

Aneka ragam

  1. Pilih lambda daripada kelas anonim.
  2. Pilih referensi metode daripada lambda.
  3. Gunakan enum alih-alih konstanta int.
  4. Hindari penggunaan float dan double jika diperlukan jawaban yang tepat, melainkan gunakan BigDecimal seperti Uang.
  5. Pilih tipe primitif daripada primitif kotak.
  6. Anda harus menghindari penggunaan angka ajaib dalam kode Anda. Gunakan konstanta.
  7. Jangan kembalikan Null. Berkomunikasi dengan klien metode Anda menggunakan `Opsional`. Sama untuk koleksi - kembalikan array atau koleksi kosong, bukan nol.
  8. Hindari membuat objek yang tidak diperlukan, menggunakan kembali objek, dan menghindari pembersihan GC yang tidak diperlukan.

Inisialisasi malas

Inisialisasi lambat adalah pengoptimalan kinerja. Ini digunakan ketika data dianggap “mahal” karena alasan tertentu. Di Java 8 kita harus menggunakan antarmuka penyedia fungsional untuk ini.
== 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 + ")");
Itu saja untuk saat ini, saya harap ini bermanfaat!
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION