JavaRush /Java Blog /Random-ID /Panduan Gaya Pemrograman Umum
pandaFromMinsk
Level 39
Минск

Panduan Gaya Pemrograman Umum

Dipublikasikan di grup Random-ID
Artikel ini adalah bagian dari kursus akademis "Java Tingkat Lanjut". Kursus ini dirancang untuk membantu Anda mempelajari cara menggunakan fitur Java secara efektif. Materinya mencakup topik-topik “lanjutan” seperti pembuatan objek, kompetisi, serialisasi, refleksi, dll. Kursus ini akan mengajarkan Anda cara menguasai teknik Java secara efektif. Detailnya di sini .
Isi
1. Pendahuluan 2. Cakupan Variabel 3. Bidang Kelas dan Variabel Lokal 4. Argumen Metode dan Variabel Lokal 5. Boxing dan Unboxing 6. Antarmuka 7. String 8. Konvensi Penamaan 9. Pustaka Standar 10. Kekekalan 11. Pengujian 12. Berikutnya. .. 13. Unduh kode sumber
1. Perkenalan
Pada bagian tutorial ini kita akan melanjutkan pembahasan kita tentang prinsip umum gaya pemrograman yang baik dan desain responsif di Java. Kita telah melihat beberapa prinsip ini di bab panduan sebelumnya, namun banyak tips praktis yang akan diberikan dengan tujuan meningkatkan keterampilan pengembang Java.
2. Ruang lingkup variabel
Di Bagian Tiga ("Cara Mendesain Kelas dan Antarmuka") kita membahas bagaimana visibilitas dan aksesibilitas dapat diterapkan ke anggota kelas dan antarmuka, mengingat batasan cakupan. Namun, kami belum membahas variabel lokal yang digunakan dalam implementasi metode. Dalam bahasa Java, setiap variabel lokal, setelah dideklarasikan, mempunyai cakupan. Variabel ini terlihat dari tempat dideklarasikan hingga titik di mana eksekusi metode (atau blok kode) selesai. Secara umum, satu-satunya aturan yang harus diikuti adalah mendeklarasikan variabel lokal sedekat mungkin dengan tempat penggunaannya. Izinkan saya melihat contoh umum: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } Di kedua fragmen kode, cakupan variabel terbatas pada blok eksekusi tempat variabel ini dideklarasikan. Ketika blok selesai, cakupannya berakhir dan variabel menjadi tidak terlihat. Hal ini tampak lebih jelas, namun dengan dirilisnya Java 8 dan diperkenalkannya lambda, banyak idiom bahasa terkenal yang menggunakan variabel lokal menjadi usang. Izinkan saya memberikan contoh dari contoh sebelumnya menggunakan lambda alih-alih perulangan: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); Dapat dilihat bahwa variabel lokal telah menjadi argumen untuk fungsi tersebut, yang pada gilirannya diteruskan sebagai argumen ke metode forEach .
3. Bidang kelas dan variabel lokal
Setiap metode di Java termasuk dalam kelas tertentu (atau, dalam kasus Java8, antarmuka tempat metode tersebut dideklarasikan sebagai metode default). Di antara variabel lokal yang merupakan bidang kelas atau metode yang digunakan dalam implementasi, terdapat kemungkinan konflik nama. Kompiler Java mengetahui cara memilih variabel yang benar dari variabel yang tersedia, meskipun lebih dari satu pengembang bermaksud menggunakan variabel tersebut. IDE Java modern berfungsi dengan baik dalam memberi tahu pengembang kapan konflik tersebut akan terjadi, melalui peringatan kompiler dan penyorotan variabel. Namun lebih baik memikirkan hal-hal seperti itu saat menulis kode. Saya sarankan untuk melihat sebuah contoh: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } Contohnya terlihat cukup mudah, tetapi ini adalah jebakan. Metode kalkulasiValue memperkenalkan nilai variabel lokal dan, dengan mengoperasikannya, menyembunyikan bidang kelas dengan nama yang sama. Baris tersebut value += value; seharusnya merupakan jumlah dari nilai bidang kelas dan variabel lokal, namun sebaliknya, ada hal lain yang sedang dilakukan. Implementasi yang tepat akan terlihat seperti ini (menggunakan kata kunci this): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } Meskipun contoh ini naif dalam beberapa hal, contoh ini menunjukkan poin penting yang dalam beberapa kasus memerlukan waktu berjam-jam untuk melakukan debug dan memperbaikinya.
4. Argumen metode dan variabel lokal
Jebakan lain yang sering dialami oleh pengembang Java yang tidak berpengalaman adalah penggunaan argumen metode sebagai variabel lokal. Java memungkinkan Anda untuk menetapkan ulang nilai ke argumen non-konstan (namun, ini tidak berpengaruh pada nilai aslinya): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } Cuplikan kode di atas tidak elegan, tetapi berhasil mengungkap masalah dengan baik: argumen str ditetapkan nilai yang berbeda (dan pada dasarnya digunakan sebagai variabel lokal). Dalam semua kasus (tanpa pengecualian apa pun), Anda dapat dan harus melakukannya tanpa contoh ini (misalnya, dengan mendeklarasikan argumen sebagai konstanta). Misalnya: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } Dengan mengikuti aturan sederhana ini, akan lebih mudah untuk melacak kode yang diberikan dan menemukan sumber masalahnya, bahkan saat memasukkan variabel lokal.
5. Pengepakan dan pembongkaran
Boxing dan unboxing adalah nama teknik yang digunakan di Java untuk mengubah tipe primitif ( int, long, double, dll. ) menjadi tipe wrapper yang sesuai ( Integer, Long, Double , dll.). Di Bagian 4 dari tutorial Bagaimana dan Kapan Menggunakan Generik, Anda sudah melihat ini beraksi ketika saya berbicara tentang membungkus tipe primitif sebagai parameter tipe generik. Meskipun kompiler Java mencoba yang terbaik untuk menyembunyikan konversi tersebut dengan melakukan autoboxing, terkadang hal ini kurang dari yang diharapkan dan memberikan hasil yang tidak diharapkan. Mari kita lihat contohnya: public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); Cuplikan kode di atas dapat dikompilasi dengan baik. Namun, itu akan memunculkan NullPointerException pada baris // блок yang mengkonversi antara Long dan long . Saran untuk kasus seperti ini adalah disarankan untuk menggunakan tipe primitif (namun, kita sudah tahu bahwa hal ini tidak selalu memungkinkan).
6. Antarmuka
Pada Bagian 3 dari tutorial, "Cara Mendesain Kelas dan Antarmuka," kita membahas antarmuka dan pemrograman kontrak, menekankan bahwa antarmuka harus lebih disukai daripada kelas konkret bila memungkinkan. Tujuan dari bagian ini adalah untuk mendorong Anda mempertimbangkan antarmuka terlebih dahulu dengan mendemonstrasikannya menggunakan contoh kehidupan nyata. Antarmuka tidak terikat pada implementasi tertentu (kecuali untuk metode default). Itu hanyalah kontrak dan, sebagai contoh, mereka memberikan banyak kebebasan dan fleksibilitas dalam cara kontrak dapat dilaksanakan. Fleksibilitas ini menjadi lebih penting ketika implementasinya melibatkan sistem atau layanan eksternal. Mari kita lihat contoh antarmuka sederhana dan kemungkinan implementasinya: public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } Cuplikan kode di atas menunjukkan pola antarmuka yang umum dan implementasinya. Implementasi ini menggunakan layanan HTTP eksternal ( http://api.geonames.org/ ) untuk mengambil zona waktu lokasi tertentu. Namun karena kontrak bergantung pada antarmuka, sangat mudah untuk memperkenalkan implementasi antarmuka lainnya, misalnya menggunakan database atau bahkan file datar biasa. Dengan mereka, antarmuka sangat membantu dalam merancang kode yang dapat diuji. Misalnya, tidak selalu praktis untuk memanggil layanan eksternal pada setiap pengujian, jadi masuk akal untuk menerapkan implementasi alternatif yang paling sederhana (seperti stub): public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } Implementasi ini dapat digunakan di mana saja di mana antarmuka TimezoneService diperlukan, dengan mengisolasi skrip pengujian dari ketergantungan pada komponen eksternal. Banyak contoh bagus penggunaan antarmuka yang efektif dikemas dalam perpustakaan standar Java. Koleksi, daftar, kumpulan - antarmuka ini memiliki banyak implementasi yang dapat ditukar dengan mulus dan dapat dipertukarkan saat kontrak dimanfaatkan. Misalnya: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. Senar
String adalah salah satu tipe yang paling banyak digunakan baik di Java maupun bahasa pemrograman lainnya. Bahasa Java menyederhanakan banyak manipulasi string rutin dengan mendukung operasi penggabungan dan perbandingan langsung. Selain itu, perpustakaan standar berisi banyak kelas yang membuat operasi string menjadi efisien. Inilah tepatnya yang akan kita bahas di bagian ini. Di Java, string adalah objek yang tidak dapat diubah yang direpresentasikan dalam pengkodean UTF-16. Setiap kali Anda menggabungkan string (atau melakukan operasi apa pun yang mengubah string asli), instance baru dari kelas String dibuat . Oleh karena itu, operasi penggabungan bisa menjadi sangat tidak efisien, menyebabkan banyak instance perantara dari kelas String dibuat (secara umum menimbulkan sampah). Namun perpustakaan standar Java berisi dua kelas yang sangat berguna yang tujuannya adalah untuk membuat manipulasi string menjadi nyaman. Ini adalah StringBuilder dan StringBuffer (satu-satunya perbedaan di antara keduanya adalah bahwa StringBuffer aman untuk thread sedangkan StringBuilder adalah sebaliknya). Mari kita lihat beberapa contoh dari salah satu kelas yang digunakan: final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); Meskipun menggunakan StringBuilder/StringBuffer adalah cara yang disarankan untuk memanipulasi string, ini mungkin terlihat berlebihan dalam skenario paling sederhana yaitu menggabungkan dua atau tiga string, sehingga operator penjumlahan normal ( (“+”), misalnya: String userId = "user:" + new Random().nextInt( 100 ); Seringkali alternatif terbaik untuk menyederhanakan penggabungan adalah dengan menggunakan pemformatan string serta Perpustakaan Standar Java untuk membantu menyediakan metode pembantu String.format statis . Ini mendukung serangkaian penentu format, termasuk angka, simbol, tanggal/waktu, dll. (Lihat dokumentasi referensi untuk rincian lengkap) Metode String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12% String.format menyediakan pendekatan yang bersih dan ringan untuk menghasilkan string dari berbagai tipe data. Perlu dicatat bahwa IDE Java modern dapat mengurai spesifikasi format dari argumen yang diteruskan ke metode String.format dan memperingatkan pengembang jika ada ketidakcocokan yang terdeteksi.
8. Konvensi penamaan
Java adalah bahasa yang tidak memaksa pengembang untuk secara ketat mengikuti konvensi penamaan apa pun, namun komunitas telah mengembangkan serangkaian aturan sederhana yang membuat kode Java terlihat konsisten baik di perpustakaan standar maupun di proyek Java lainnya:
  • nama paket menggunakan huruf kecil: org.junit, com.fasterxml.jackson, javax.json
  • nama kelas, enumerasi, antarmuka, anotasi ditulis dengan huruf kapital: StringBuilder, Runnable, @Override
  • nama bidang atau metode (kecuali static final ) ditentukan dalam notasi unta: isEmpty, format, addAll
  • bidang akhir statis atau nama konstanta enumerasi menggunakan huruf besar, dipisahkan dengan garis bawah ("_"): LOG, MIN_RADIX, INSTANCE.
  • variabel lokal atau argumen metode diketik dalam notasi unta: str, newLength, minimumCapacity
  • nama tipe parameter untuk obat generik diwakili oleh satu huruf dalam huruf besar: T, U, E
Dengan mengikuti konvensi sederhana ini, kode yang Anda tulis akan terlihat ringkas dan gayanya tidak dapat dibedakan dari pustaka atau kerangka kerja lain, dan akan terasa seperti dikembangkan oleh orang yang sama (salah satu saat yang jarang terjadi ketika konvensi benar-benar berfungsi).
9. Perpustakaan standar
Apa pun jenis proyek Java yang sedang Anda kerjakan, perpustakaan standar Java adalah teman terbaik Anda. Ya, sulit untuk tidak setuju bahwa mereka memiliki sisi kasar dan keputusan desain yang aneh, namun, 99% dari kode tersebut berkualitas tinggi yang ditulis oleh para ahli. Ini layak untuk ditelusuri. Setiap rilis Java menghadirkan banyak fitur baru ke perpustakaan yang ada (dengan beberapa kemungkinan masalah dengan fitur lama), dan juga menambahkan banyak perpustakaan baru. Java 5 menghadirkan perpustakaan konkurensi baru sebagai bagian dari paket java.util.concurrent . Java 6 memperkenalkan dukungan skrip (yang kurang dikenal) ( paket javax.script ) dan API kompiler Java (sebagai bagian dari paket javax.tools ). Java 7 membawa banyak perbaikan pada java.util.concurrent , memperkenalkan perpustakaan I/O baru dalam paket java.nio.file dan dukungan untuk bahasa dinamis di java.lang.invoke . Dan terakhir, Java 8 menambahkan tanggal/waktu yang telah lama ditunggu-tunggu di paket java.time . Java sebagai sebuah platform terus berkembang dan sangat penting untuk maju seiring dengan perubahan di atas. Setiap kali Anda mempertimbangkan untuk menyertakan perpustakaan atau kerangka kerja pihak ketiga dalam proyek Anda, pastikan bahwa fungsionalitas yang diperlukan belum terdapat dalam perpustakaan Java standar (tentu saja, ada banyak implementasi algoritma khusus dan berkinerja tinggi yang mendahului algoritma di perpustakaan standar, tetapi dalam banyak kasus sebenarnya tidak diperlukan).
10. Kekekalan
Kekekalan di seluruh panduan ini dan di bagian ini tetap menjadi pengingat: harap ditanggapi dengan serius. Jika kelas yang Anda rancang atau metode yang Anda terapkan dapat memberikan jaminan kekekalan, kelas tersebut dapat digunakan dalam banyak kasus di mana saja tanpa takut dimodifikasi pada saat yang bersamaan. Ini akan membuat hidup Anda sebagai pengembang (dan semoga kehidupan anggota tim Anda) lebih mudah.
11. Pengujian
Praktik pengembangan berbasis pengujian (TDD) sangat populer di komunitas Java, sehingga meningkatkan standar kualitas kode. Dengan semua manfaat yang diberikan TDD, sangat menyedihkan melihat perpustakaan standar Java saat ini tidak menyertakan kerangka pengujian atau alat pendukung apa pun. Namun, pengujian telah menjadi bagian penting dari pengembangan Java modern dan di bagian ini kita akan melihat beberapa teknik dasar menggunakan kerangka JUnit . Di JUnit, pada dasarnya, setiap pengujian adalah serangkaian pernyataan tentang keadaan atau perilaku yang diharapkan dari suatu objek. Rahasia menulis tes yang bagus adalah dengan membuatnya tetap sederhana dan singkat, menguji satu per satu. Sebagai latihan, mari kita tulis serangkaian pengujian untuk memverifikasi bahwa String.format adalah fungsi dari bagian string yang mengembalikan hasil yang diinginkan. package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } Kedua tes terlihat sangat mudah dibaca dan eksekusinya seperti contoh. Saat ini, rata-rata proyek Java berisi ratusan kasus uji, sehingga memberikan masukan cepat kepada pengembang selama proses pengembangan mengenai regresi atau fitur.
12. Selanjutnya
Bagian panduan ini melengkapi rangkaian pembahasan terkait praktik pemrograman di Java dan manual bahasa pemrograman ini. Lain kali kita akan kembali ke fitur bahasa, menjelajahi dunia Java mengenai pengecualian, tipenya, bagaimana dan kapan menggunakannya.
13. Unduh kode sumber
Ini adalah pelajaran tentang prinsip-prinsip pengembangan umum dari kursus Java Tingkat Lanjut. Kode sumber pelajaran dapat diunduh di sini .
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION