1. Gambaran keseluruhan
Dalam artikel ini, kami akan menyoroti beberapa peraturan penting yang terdapat dalam alat analisis kod seperti FindBugs, PMD dan CheckStyle.
2. Kerumitan Sikomatik
2.1. Apakah Kerumitan Siklomatik?
Kerumitan kod adalah penting, namun sukar untuk diukur. PMD menawarkan sekumpulan peraturan yang kuat di bawah bahagian Peraturan Ukuran Kodnya, peraturan ini dirancang untuk mengesan pelanggaran mengenai ukuran kaedah dan kerumitan struktur.
CheckStyle terkenal dengan kemampuannya menganalisis kod terhadap standard pengkodan, dan peraturan pemformatan. Walau bagaimanapun, ia juga dapat mengesan masalah dalam reka bentuk kelas / kaedah dengan mengira beberapa metrik kerumitan.
Salah satu pengukuran kerumitan yang paling relevan yang terdapat dalam kedua alat ini adalah CC (Cyclomatic Complexity).
Nilai CC dapat dikira dengan mengukur jumlah jalan pelaksanaan bebas program.
Contohnya, kaedah berikut akan menghasilkan kerumitan siklomatik 3:
public void callInsurance(Vehicle vehicle) { if (vehicle.isValid()) { if (vehicle instanceof Car) { callCarInsurance(); } else { delegateInsurance(); } } }
CC mengambil kira penyataan penyataan bersyarat dan ungkapan boolean pelbagai bahagian.
Secara umum, kod dengan nilai yang lebih tinggi daripada 11 dari segi CC, dianggap sangat kompleks, dan sukar diuji dan dikekalkan.
Beberapa nilai umum yang digunakan oleh alat analisis statik ditunjukkan di bawah:
- 1-4: kerumitan rendah - senang diuji
- 5-7: kerumitan sederhana - boleh diterima
- 8-10: kerumitan tinggi - refactoring harus dipertimbangkan untuk memudahkan ujian
- Kerumitan 11 + yang sangat tinggi - sangat sukar untuk diuji
Tahap kerumitan juga mempengaruhi kebolehpercayaan kod, semakin tinggi CC, semakin tinggi kesukaran untuk melaksanakan ujian yang bersangkutan . Sebenarnya, nilai kerumitan siklomatik menunjukkan dengan tepat bilangan kes ujian yang diperlukan untuk mencapai skor liputan cawangan 100%.
Grafik aliran yang berkaitan dengan kaedah callInsurance () adalah:

Jalan pelaksanaan yang mungkin adalah:
- 0 => 3
- 0 => 1 => 3
- 0 => 2 => 3
Secara matematik, CC dapat dikira menggunakan formula mudah berikut:
CC = E - N + 2P
- E: Jumlah tepi
- N: Jumlah bilangan nod
- P: Jumlah titik keluar
2.2. Bagaimana Mengurangkan Kerumitan Siklomatik?
Untuk menulis kod yang jauh lebih rumit, pembangun cenderung menggunakan pendekatan yang berbeza, bergantung pada keadaan:
- Elakkan menulis pernyataan beralih panjang dengan menggunakan corak reka bentuk, contohnya pembangun dan corak strategi mungkin merupakan calon yang baik untuk menangani masalah ukuran dan kerumitan kod
- Tulis kaedah yang boleh digunakan semula dan diperluas dengan memodulasi struktur kod dan melaksanakan Prinsip Tanggungjawab Tunggal
- Mengikuti peraturan ukuran kod PMD yang lain mungkin mempunyai kesan langsung pada CC , misalnya peraturan panjang kaedah yang berlebihan, terlalu banyak bidang dalam satu kelas, senarai parameter yang berlebihan dalam satu kaedah ... dll
Anda juga boleh mempertimbangkan prinsip dan corak berikut mengenai ukuran dan kerumitan kod, misalnya prinsip KISS (Keep It Simple and Stupid) , dan KERING (Jangan Ulangi Diri Anda).
3. Peraturan Pengendalian Pengecualian
Kecacatan yang berkaitan dengan pengecualian mungkin biasa, tetapi beberapa di antaranya terlalu diremehkan dan harus diperbaiki untuk mengelakkan disfungsi kritikal dalam kod pengeluaran.
PMD dan FindBugs menawarkan kedua-dua sekumpulan peraturan mengenai pengecualian. Inilah pilihan kami mengenai perkara yang mungkin dianggap kritikal dalam program Java ketika menangani pengecualian.
3.1. Jangan Lontarkan Pengecualian Akhirnya
Seperti yang sudah anda ketahui, blok {} akhirnya di Java umumnya digunakan untuk menutup file dan melepaskan sumber, menggunakannya untuk tujuan lain mungkin dianggap sebagai bau kod .
Rutin rawan ralat biasa adalah membuang pengecualian di dalam blok {} akhirnya :
String content = null; try { String lowerCaseString = content.toLowerCase(); } finally { throw new IOException(); }
Kaedah ini seharusnya membuang NullPointerException , tetapi secara mengejutkan ia membuang IOException , yang mungkin menyesatkan kaedah memanggil untuk menangani pengecualian yang salah.
3.2. Kembali di Blok akhirnya
Menggunakan penyata pengembalian di dalam blok {} akhirnya mungkin tidak membingungkan. Sebab mengapa peraturan ini sangat penting, kerana setiap kali kod membuang pengecualian, peraturan tersebut akan dibuang oleh pernyataan pengembalian .
Sebagai contoh, kod berikut berjalan tanpa sebarang kesalahan:
String content = null; try { String lowerCaseString = content.toLowerCase(); } finally { return; }
A NullPointerException tidak ditangkap, namun, masih dibuang oleh kenyataan return di akhirnya blok.
3.3. Gagal Menutup Aliran Pengecualian
Closing streams is one of the main reasons why we use a finally block, but it's not a trivial task as it seems to be.
The following code tries to close two streams in a finally block:
OutputStream outStream = null; OutputStream outStream2 = null; try { outStream = new FileOutputStream("test1.txt"); outStream2 = new FileOutputStream("test2.txt"); outStream.write(bytes); outStream2.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { try { outStream.close(); outStream2.close(); } catch (IOException e) { // Handling IOException } }
If the outStream.close() instruction throws an IOException, the outStream2.close() will be skipped.
A quick fix would be to use a separate try/catch block to close the second stream:
finally { try { outStream.close(); } catch (IOException e) { // Handling IOException } try { outStream2.close(); } catch (IOException e) { // Handling IOException } }
If you want a nice way to avoid consecutive try/catch blocks, check the IOUtils.closeQuiety method from Apache commons, it makes it simple to handle streams closing without throwing an IOException.
5. Bad Practices
5.1. Class Defines compareto() and Uses Object.equals()
Whenever you implement the compareTo() method, don't forget to do the same with the equals() method, otherwise, the results returned by this code may be confusing:
Car car = new Car(); Car car2 = new Car(); if(car.equals(car2)) { logger.info("They're equal"); } else { logger.info("They're not equal"); } if(car.compareTo(car2) == 0) { logger.info("They're equal"); } else { logger.info("They're not equal"); }
Result:
They're not equal They're equal
To clear confusions, it is recommended to make sure that Object.equals() is never called when implementing Comparable, instead, you should try to override it with something like this:
boolean equals(Object o) { return compareTo(o) == 0; }
5.2. Possible Null Pointer Dereference
NullPointerException (NPE) is considered the most encountered Exception in Java programming, and FindBugs complains about Null PointeD dereference to avoid throwing it.
Here's the most basic example of throwing an NPE:
Car car = null; car.doSomething();
The easiest way to avoid NPEs is to perform a null check:
Car car = null; if (car != null) { car.doSomething(); }
Pemeriksaan kosong boleh mengelakkan NPE, tetapi apabila digunakan secara meluas, mereka pasti mempengaruhi kebolehbacaan kod.
Jadi inilah beberapa teknik yang digunakan untuk mengelakkan NPE tanpa pemeriksaan kosong:
- Elakkan kata kunci kosong semasa membuat pengekodan : Peraturan ini mudah, elakkan penggunaan kata kunci kosong semasa memulakan pemboleh ubah, atau mengembalikan nilai
- Penggunaan @NotNull dan @Nullable anotasi
- Gunakan java.util. Pilihan
- Laksanakan Corak Objek Null
6. Kesimpulannya
Dalam artikel ini, kami telah melihat keseluruhan kecacatan kritikal yang dikesan oleh alat analisis statik, dengan garis panduan asas untuk menangani masalah yang dikesan dengan tepat.
Anda boleh melihat set lengkap peraturan untuk masing-masing dengan melayari pautan berikut: FindBugs, PMD.