Diposting oleh Denis Tsyplakov , Solution Architect, DataArtSelama bertahun-tahun, saya telah menemukan bahwa programmer mengulangi kesalahan yang sama dari waktu ke waktu. Sayangnya, buku-buku tentang aspek teoritis pengembangan tidak membantu menghindarinya: buku biasanya tidak memiliki saran praktis dan konkret. Dan saya bahkan menebak mengapa ...
Rekomendasi pertama yang muncul di pikiran ketika datang ke, misalnya,
logging atau desain kelas sangat sederhana: "Jangan membuat omong kosong langsung." Tetapi pengalaman menunjukkan bahwa itu jelas tidak cukup. Hanya desain kelas dalam kasus ini adalah contoh yang baik - sakit kepala abadi yang muncul dari kenyataan bahwa semua orang melihat masalah ini dengan cara mereka sendiri. Oleh karena itu, saya memutuskan untuk mengumpulkan tip-tip dasar dalam satu artikel, berikut ini Anda akan menghindari sejumlah masalah yang khas, dan yang paling penting, menyelamatkan kolega Anda dari mereka. Jika beberapa prinsip tampak dangkal bagi Anda (karena mereka benar-benar dangkal!) - yah, maka mereka telah menetap di subkorteks Anda, dan tim Anda dapat diberi selamat.
Saya akan melakukan reservasi, pada kenyataannya, kami akan fokus pada kelas semata-mata untuk kesederhanaan. Hampir sama dapat dikatakan tentang fungsi atau blok bangunan lain dari aplikasi.
Jika aplikasi berfungsi dan melakukan tugas, maka desainnya bagus. Atau tidak? Tergantung pada fungsi objektif dari aplikasi; apa yang cukup cocok untuk aplikasi seluler yang perlu ditampilkan sekali di pameran mungkin tidak cocok untuk platform perdagangan yang telah dikembangkan bank mana pun selama bertahun-tahun. Sampai batas tertentu, jawaban atas pertanyaan ini dapat disebut prinsip
SOLID , tetapi terlalu umum - Saya ingin beberapa instruksi yang lebih spesifik yang dapat dirujuk dalam percakapan dengan kolega.
Aplikasi target
Karena tidak ada jawaban universal, saya mengusulkan mempersempit ruang lingkup. Mari kita asumsikan bahwa kita sedang menulis aplikasi bisnis standar yang menerima permintaan melalui HTTP atau antarmuka lain, mengimplementasikan beberapa logika di atasnya, dan kemudian membuat permintaan ke layanan berikutnya dalam rantai atau menyimpan data yang diterima di suatu tempat. Untuk kesederhanaan, mari kita asumsikan bahwa kita menggunakan Spring IoC Framework, karena ini cukup umum sekarang dan sisa kerangka kerja sangat mirip dengannya. Apa yang bisa kita katakan tentang aplikasi seperti itu?
- Waktu yang dihabiskan prosesor untuk memproses satu permintaan penting, tetapi tidak kritis - peningkatan 0,1% dalam cuaca tidak akan.
- Tidak ada memori terabyte yang kami miliki, tetapi jika aplikasi membutuhkan tambahan 50-100 Kbytes, ini tidak akan menjadi bencana.
- Tentu saja, semakin pendek waktu mulai, semakin baik. Tetapi tidak ada perbedaan mendasar antara 6 detik dan 5,9 detik.
Kriteria optimasi
Apa yang penting bagi kami dalam kasus ini?
Kode proyek kemungkinan akan digunakan oleh bisnis selama beberapa, mungkin lebih dari sepuluh tahun.
Kode pada waktu yang berbeda akan dimodifikasi oleh beberapa pengembang yang tidak terbiasa satu sama lain.
Ada kemungkinan bahwa dalam beberapa tahun, pengembang akan ingin menggunakan perpustakaan LibXYZ baru atau kerangka kerja FrABC.
Pada titik tertentu, bagian dari kode atau keseluruhan proyek dapat digabungkan dengan basis kode dari proyek lain.
Di tengah-tengah manajer, secara umum diterima bahwa masalah seperti itu diselesaikan dengan bantuan dokumentasi. Dokumentasinya, tentu saja, baik dan bermanfaat, karena sangat bagus ketika Anda mulai mengerjakan proyek sehingga Anda memiliki lima tiket terbuka yang tergantung pada Anda, manajer proyek bertanya bagaimana Anda telah berkembang, dan Anda perlu membaca (dan mengingat) sekitar 150 halaman-halaman teks yang ditulis jauh dari para penulis yang brilian. Tentu saja, Anda memiliki beberapa hari atau bahkan beberapa minggu untuk dituangkan ke dalam proyek, tetapi, jika Anda menggunakan aritmatika sederhana, di satu sisi 5.000.000 byte kode, di sisi lain, katakanlah, 50 jam kerja. Ternyata rata-rata perlu menyuntikkan kode 100 Kb per jam. Dan di sini semuanya sangat tergantung pada kualitas kode. Jika bersih: mudah dirakit, terstruktur dengan baik, dan dapat diprediksi, maka menuangkan ke dalam proyek tampaknya merupakan proses yang tidak terlalu menyakitkan. Bukan peran terakhir dalam hal ini dimainkan oleh desain kelas. Bukan yang terakhir.
Apa yang kita inginkan dari desain kelas
Dari semua hal di atas, banyak kesimpulan menarik yang dapat ditarik tentang arsitektur umum, tumpukan teknologi, proses pengembangan, dll. Tetapi sejak awal kami memutuskan untuk berbicara tentang desain kelas, mari kita lihat hal-hal berguna apa yang dapat kita pelajari dari apa yang dikatakan sebelumnya mengenai hal itu.
- Saya ingin pengembang yang tidak sepenuhnya akrab dengan kode aplikasi untuk dapat memahami apa yang dilakukan kelas ini ketika melihat kelas. Dan sebaliknya - melihat persyaratan fungsional atau non-fungsional, saya bisa dengan cepat menebak di mana aplikasi berada di kelas yang bertanggung jawab untuk itu. Yah, diharapkan bahwa penerapan persyaratan tidak "tersebar" di seluruh aplikasi, tetapi terkonsentrasi dalam satu kelas atau kelompok kelas yang kompak. Izinkan saya menjelaskan dengan contoh apa jenis antipattern yang saya maksud. Misalkan kita perlu memverifikasi bahwa 10 permintaan dari jenis tertentu hanya dapat dieksekusi oleh pengguna yang memiliki lebih dari 20 poin di akun mereka (tidak peduli apa artinya itu). Cara buruk untuk menerapkan persyaratan seperti itu adalah dengan memasukkan cek di awal setiap permintaan. Kemudian logikanya akan tersebar di 10 metode, di berbagai pengontrol. Cara yang baik adalah membuat filter atau WebRequestInterceptor dan memeriksa semuanya di satu tempat.
- Saya ingin perubahan dalam satu kelas yang tidak mempengaruhi kontrak kelas tidak mempengaruhi, yah, atau (mari kita realistis!) Setidaknya tidak terlalu banyak mempengaruhi kelas lain. Dengan kata lain, saya ingin merangkum implementasi kontrak kelas.
- Saya ingin dimungkinkan, ketika mengubah kontrak kelas, dengan melalui rantai panggilan dan membuat penggunaan menemukan, menemukan kelas yang mempengaruhi perubahan ini. Artinya, saya ingin kelas tidak memiliki dependensi tidak langsung.
- Jika memungkinkan, saya ingin melihat bahwa proses pemrosesan permintaan yang terdiri dari beberapa langkah tingkat tunggal tidak diolesi oleh kode beberapa kelas, tetapi dijelaskan pada tingkat yang sama. Sangat bagus jika kode yang menggambarkan proses pemrosesan demikian cocok pada satu layar di dalam satu metode dengan nama yang jelas. Misalnya, kita perlu menemukan semua kata dalam satu baris, melakukan panggilan ke layanan pihak ketiga untuk setiap kata, mendapatkan deskripsi kata, menerapkan pemformatan ke deskripsi dan menyimpan hasilnya dalam database. Ini adalah satu urutan tindakan dalam 4 langkah. Sangat mudah untuk memahami kode dan mengubah logikanya ketika ada metode di mana langkah-langkah ini berjalan satu demi satu.
- Saya benar-benar ingin hal-hal yang sama dalam kode diimplementasikan dengan cara yang sama. Sebagai contoh, jika kita mengakses database segera dari controller, lebih baik untuk melakukan ini di mana-mana (walaupun saya tidak akan menyebut praktik desain yang baik) Dan jika kita telah memasuki level layanan dan repositori, maka lebih baik untuk tidak menghubungi database secara langsung dari controller.
- Saya ingin jumlah kelas / antarmuka tidak secara langsung bertanggung jawab atas persyaratan fungsional dan non-fungsional menjadi tidak terlalu besar. Bekerja dengan sebuah proyek di mana terdapat dua antarmuka untuk setiap kelas dengan logika, hierarki kompleks warisan dari lima kelas, pabrik kelas dan pabrik kelas abstrak, cukup sulit.
Rekomendasi praktis
Setelah merumuskan keinginan, kami dapat menguraikan langkah-langkah spesifik yang akan memungkinkan kami untuk mencapai tujuan kami.
Metode statis
Sebagai pemanasan, saya akan mulai dengan aturan yang relatif sederhana. Anda seharusnya tidak membuat metode statis kecuali jika diperlukan untuk pengoperasian salah satu perpustakaan yang digunakan (misalnya, Anda perlu membuat serializer untuk tipe data).
Pada prinsipnya, tidak ada yang salah dengan menggunakan metode statis. Jika perilaku suatu metode bergantung sepenuhnya pada parameternya, mengapa tidak membuatnya statis. Tetapi Anda perlu mempertimbangkan fakta bahwa kami menggunakan Spring IoC, yang berfungsi untuk mengikat komponen aplikasi kami. Spring IoC berkaitan dengan konsep Beans dan Lingkup mereka. Pendekatan ini dapat dicampur dengan metode statis yang dikelompokkan ke dalam kelas, tetapi memahami aplikasi ini dan bahkan mengubah sesuatu di dalamnya (jika, misalnya, Anda perlu meneruskan beberapa parameter global ke metode atau kelas) bisa sangat sulit.
Pada saat yang sama, metode statis dibandingkan dengan tempat IoC memberikan keuntungan yang sangat tidak signifikan dalam kecepatan pemanggilan metode. Dan tentang ini, mungkin, keuntungannya berakhir.
Jika Anda tidak membangun fungsi bisnis yang membutuhkan banyak panggilan cepat antara kelas yang berbeda, lebih baik tidak menggunakan metode statis.
Di sini pembaca mungkin bertanya: "Tapi bagaimana dengan kelas StringUtils dan IOUtils?" Memang, sebuah tradisi telah berkembang di dunia Jawa - untuk menempatkan fungsi bantu untuk bekerja dengan string dan input-output stream ke metode statis dan mengumpulkannya di bawah payung kelas SomethingUtils. Tetapi bagi saya tradisi ini agak berlumut. Jika Anda mengikutinya, tentu saja, bahaya besar tidak diharapkan - semua programmer Java terbiasa dengannya. Tetapi tidak ada artinya dalam tindakan ritual semacam itu. Di satu sisi, mengapa tidak membuat kacang StringUtils, di sisi lain, jika Anda tidak membuat kacang dan semua metode tambahan statis, mari kita buat kelas payung statis StockTradingUtils dan BlockChainUtils. Mulai memasukkan logika ke dalam metode statis, menggambar perbatasan dan berhenti itu sulit. Saya menyarankan Anda untuk tidak memulai.
Akhirnya, jangan lupa bahwa di Jawa 11 banyak metode penolong yang telah mengembara pengembang dari proyek ke proyek selama beberapa dekade, baik menjadi bagian dari perpustakaan standar, atau bergabung ke perpustakaan, misalnya, di Google Guava.
Atom, kontrak kelas kompak
Ada aturan sederhana yang berlaku untuk pengembangan sistem perangkat lunak apa pun. Melihat kelas mana saja, Anda harus dapat dengan cepat dan kompak, tanpa menggunakan penggalian yang panjang, menjelaskan apa yang dilakukan kelas ini. Jika tidak mungkin untuk mencocokkan penjelasan dalam satu paragraf (namun tidak perlu, dinyatakan dalam satu kalimat), mungkin ada baiknya memikirkan dan memecah kelas ini menjadi beberapa kelas atom. Misalnya, kelas "Mencari file teks pada disk dan menghitung jumlah huruf Z di masing-masingnya" - kandidat yang bagus untuk dekomposisi "pencarian pada disk" + "menghitung jumlah huruf".
Di sisi lain, jangan membuat kelas terlalu kecil, yang masing-masing dirancang untuk satu aksi. Tapi apa ukuran kelas seharusnya? Aturan dasarnya adalah sebagai berikut:
- Idealnya, ketika kontrak kelas cocok dengan deskripsi fungsi bisnis (atau subfungsi, tergantung pada bagaimana persyaratannya diatur). Ini tidak selalu mungkin: jika upaya untuk mematuhi aturan ini mengarah pada pembuatan kode yang rumit dan tidak jelas, lebih baik memecah kelas menjadi bagian-bagian yang lebih kecil.
- Metrik yang baik untuk menilai kualitas kontrak kelas adalah rasio kompleksitas intrinsiknya dengan kompleksitas kontrak. Misalnya, kontrak kelas yang sangat bagus (meskipun fantastis) mungkin terlihat seperti ini: "Kelas memiliki satu metode yang menerima baris dengan deskripsi subjek dalam bahasa Rusia pada input dan sebagai hasilnya, membuat cerita yang berkualitas atau bahkan sebuah cerita tentang topik tertentu sebagai hasilnya." Di sini, kontraknya sederhana dan dipahami secara umum. Implementasinya sangat kompleks, tetapi kompleksitasnya tersembunyi di dalam kelas.
Mengapa aturan ini penting?
- Pertama, kemampuan untuk menjelaskan dengan jelas kepada diri sendiri apa yang dilakukan setiap kelas selalu bermanfaat. Sayangnya, jauh dari setiap pengembang proyek dapat melakukan ini. Anda dapat sering mendengar sesuatu seperti: “Ya, ini adalah pembungkus kelas Path, yang entah bagaimana kami buat dan kadang-kadang digunakan sebagai ganti Path. Dia juga memiliki metode yang dapat menggandakan semua jalur File.separator - kita perlu metode ini saat menyimpan laporan ke cloud, dan untuk beberapa alasan berakhir di kelas Path. "
- Otak manusia mampu beroperasi secara simultan dengan tidak lebih dari lima hingga sepuluh objek. Kebanyakan orang memiliki tidak lebih dari tujuh. Oleh karena itu, jika pengembang perlu beroperasi dengan lebih dari tujuh objek untuk menyelesaikan masalah, ia akan kehilangan sesuatu atau dipaksa untuk mengemas beberapa objek di bawah satu "payung" logis. Dan jika Anda masih harus mengemasnya, mengapa tidak segera melakukannya, secara sadar, dan berikan payung ini nama yang bermakna dan kontrak yang jelas.
Bagaimana cara memeriksa apakah semuanya cukup terperinci? Minta seorang rekan memberi Anda 5 (lima) menit. Ambil bagian dari aplikasi yang sedang Anda buat saat ini. Untuk setiap kelas, jelaskan kepada seorang rekan apa sebenarnya yang dilakukan kelas ini. Jika Anda tidak muat dalam 5 menit, atau seorang kolega tidak dapat memahami mengapa kelas ini atau itu diperlukan, mungkin Anda harus mengubah sesuatu. Ya, atau tidak mengubah dan melakukan eksperimen lagi, dengan rekan lain.
Ketergantungan kelas
Misalkan kita perlu memilih bagian teks yang ditautkan lebih dari 100 byte untuk file PDF yang dikemas dalam arsip ZIP dan menyimpannya ke database. Antipattern yang populer dalam kasus-kasus seperti ini terlihat seperti ini:
- Ada kelas yang membuka arsip ZIP, mencari file PDF di dalamnya dan mengembalikannya sebagai InputStream.
- Kelas ini memiliki tautan ke kelas yang mencari dalam paragraf teks PDF.
- Kelas yang bekerja dengan PDF, pada gilirannya, memiliki tautan ke kelas yang menyimpan data dalam database.
Di satu sisi, semuanya terlihat logis: data yang diterima, disebut langsung kelas berikutnya dalam rantai. Tetapi pada saat yang sama, kontrak kelas di atas rantai bercampur dalam kontrak dan dependensi semua kelas yang masuk dalam rantai di belakangnya. Adalah jauh lebih tepat untuk membuat kelas-kelas ini menjadi atom dan tidak tergantung satu sama lain, dan membuat kelas lain yang benar-benar mengimplementasikan logika pemrosesan dengan menghubungkan ketiga kelas ini satu sama lain.
Bagaimana tidak melakukannya:
Apa yang salah di sini? Kelas yang bekerja dengan file ZIP meneruskan data ke kelas yang memproses PDF, dan itu, pada gilirannya, meneruskan data ke kelas yang bekerja dengan database. Ini berarti bahwa kelas yang bekerja dengan ZIP, sebagai akibatnya, untuk beberapa alasan tergantung pada kelas yang bekerja dengan database. Selain itu, logika pemrosesan tersebar di tiga kelas, dan untuk memahaminya, kita harus membahas ketiga kelas. Bagaimana jika Anda membutuhkan paragraf teks yang diperoleh dari PDF untuk diteruskan ke layanan pihak ketiga melalui panggilan REST? Anda perlu mengubah kelas yang berfungsi dengan PDF, dan menggambar di dalamnya juga bekerja dengan REST.
Bagaimana cara melakukannya:
Di sini kami memiliki empat kelas:
- Kelas yang hanya berfungsi dengan arsip ZIP dan mengembalikan daftar file PDF (orang dapat berargumen - mengembalikan file itu buruk - mereka besar dan akan merusak aplikasi. Tetapi dalam kasus ini, mari kita baca kata "kembali" dalam arti luas. Misalnya, ia mengembalikan Stream dari InputStream )
- Kelas kedua bertanggung jawab untuk bekerja dengan PDF.
- Kelas ketiga tidak tahu dan tidak bisa melakukan apa pun kecuali menyimpan paragraf dalam database.
- Dan kelas keempat, yang terdiri dari beberapa baris kode, berisi semua logika bisnis yang sesuai pada satu layar.
Saya menekankan sekali lagi bahwa pada tahun 2019 di Jawa setidaknya ada dua yang baik (dan agak kurang
bagus) cara untuk tidak mentransfer file dan daftar lengkap semua paragraf sebagai objek dalam memori. Ini adalah:
- API Streaming Java
- Telepon balik Yaitu, kelas dengan fungsi bisnis tidak mentransfer data secara langsung, tetapi mengatakan ZIP Extractor: inilah panggilan balik untuk Anda, cari file PDF dalam file ZIP, buat InputStream untuk setiap file dan panggil callback yang ditransfer bersamanya.
Perilaku tersirat
Ketika kami tidak mencoba untuk menyelesaikan masalah yang sama sekali baru yang sebelumnya tidak diselesaikan oleh siapa pun, tetapi melakukan sesuatu yang pengembang lain telah lakukan beberapa ratus (atau ratusan ribu) kali sebelumnya, semua anggota tim memiliki beberapa harapan mengenai kompleksitas siklus dan sumber daya intensitas solusi. . Sebagai contoh, jika kita perlu menemukan dalam file semua kata yang dimulai dengan huruf z, ini adalah pembacaan berurutan, satu kali file dalam blok dari disk. Yaitu, jika Anda fokus pada
https://gist.github.com/jboner/2841832 - operasi semacam itu akan membutuhkan beberapa mikrodetik per 1 MB, well, mungkin, tergantung pada lingkungan pemrograman dan beban sistem, beberapa puluh atau bahkan seratus mikrodetik, tetapi tidak sedetik pun. Ini akan membutuhkan beberapa puluh kilobyte memori (kita mengabaikan pertanyaan apa yang kita lakukan dengan hasilnya, ini adalah masalah kelas lain), dan kode kemungkinan besar akan menempati sekitar satu layar. Pada saat yang sama, kami berharap tidak ada sumber daya sistem lain yang akan digunakan. Artinya, kode tidak akan membuat utas, menulis data ke disk, mengirim paket melalui jaringan dan menyimpan data dalam database.
Ini adalah harapan biasa dari pemanggilan metode:
zWordFinder.findZWords(inputStream). ...
Jika kode kelas Anda tidak memenuhi persyaratan ini karena alasan yang masuk akal, misalnya, untuk mengklasifikasikan kata menjadi z dan bukan z, Anda harus memanggil metode REST setiap kali (saya tidak tahu mengapa ini mungkin diperlukan, tetapi mari kita bayangkan ini) perlu untuk menulis dengan sangat hati-hati dalam kontrak kelas, dan itu sangat bagus jika nama metode menunjukkan bahwa metode tersebut berjalan di suatu tempat untuk berkonsultasi.
Jika Anda tidak memiliki alasan yang masuk akal untuk perilaku implisit, tulis ulang kelas.
Bagaimana memahami ekspektasi kompleksitas dan intensitas sumber daya dari metode ini? Anda perlu menggunakan salah satu cara sederhana ini:
- Dengan pengalaman, dapatkan wawasan yang cukup luas.
- Tanya kolega - ini selalu bisa dilakukan.
- Sebelum memulai pengembangan, bicarakan dengan anggota tim tentang rencana implementasi.
- Untuk bertanya pada diri sendiri pertanyaan: "Tapi apakah saya tidak menggunakan terlalu banyak sumber daya yang berlebihan dalam metode ini?" Ini biasanya sudah cukup.
Anda tidak perlu terlibat dalam optimasi juga - menghemat 100 byte saat digunakan oleh kelas 100.000 tidak masuk akal untuk sebagian besar aplikasi.
Aturan ini membuka jendela ke dunia kaya rekayasa ulang, menyembunyikan jawaban untuk pertanyaan seperti "mengapa Anda tidak menghabiskan satu bulan untuk menghemat 10 byte memori dalam aplikasi yang membutuhkan 10 GB untuk bekerja". Tapi saya tidak akan mengembangkan topik ini di sini. Dia layak mendapat artikel terpisah.
Nama Metode Implisit
Dalam pemrograman Java, saat ini ada beberapa konvensi implisit mengenai nama kelas dan perilaku mereka. Tidak banyak dari mereka, tetapi lebih baik tidak melanggarnya. Saya akan mencoba mendaftar yang muncul di pikiran saya:
- Konstruktor - membuat instance kelas, ia dapat membuat beberapa struktur data yang cukup bercabang, tetapi tidak bekerja dengan database, tidak menulis ke disk, tidak mengirim data melalui jaringan (saya katakan, built-in logger dapat melakukan semua ini, tetapi ini adalah cerita yang berbeda dalam dalam hal apapun, itu terletak pada hati nurani dari konfigurator logging).
- Getter - getSomething () - mengembalikan beberapa jenis struktur memori dari kedalaman objek. Sekali lagi, itu tidak menulis ke disk, tidak melakukan perhitungan yang rumit, tidak mengirim data melalui jaringan, tidak bekerja dengan database (kecuali ketika ini adalah bidang ORM yang malas, dan ini hanya salah satu alasan mengapa ladang malas harus digunakan dengan sangat hati-hati) .
- Setter - setSomething (Sesuatu sesuatu) - menetapkan nilai struktur data, tidak melakukan perhitungan yang rumit, tidak mengirim data melalui jaringan, tidak bekerja dengan database. Biasanya, setter tidak diharapkan untuk menyiratkan perilaku atau konsumsi sumber daya komputasi yang signifikan.
- equals () dan hashcode () - tidak ada yang diharapkan sama sekali, kecuali untuk perhitungan dan perbandingan sederhana dalam jumlah yang linear tergantung pada ukuran struktur data. Artinya, jika kita memanggil kode hash untuk objek tiga bidang primitif, diharapkan N * 3 instruksi komputasi sederhana akan dieksekusi.
- toSomething () - itu juga diharapkan menjadi metode yang mengubah satu tipe data ke yang lain, dan untuk konversi itu hanya membutuhkan jumlah memori yang sebanding dengan ukuran struktur, dan waktu prosesor yang secara linear tergantung pada ukuran struktur. Di sini perlu dicatat bahwa konversi jenis tidak selalu dapat dilakukan secara linear, misalnya, mengubah gambar piksel ke format SVG bisa menjadi tindakan yang sangat tidak sepele, tetapi dalam hal ini lebih baik untuk menyebut metode secara berbeda. Sebagai contoh, nama computeAndConvertToSVG () terlihat agak canggung, tetapi langsung menunjukkan bahwa beberapa perhitungan signifikan sedang terjadi di dalam.
Saya akan memberi contoh. Saya baru-baru ini melakukan audit aplikasi. Dengan logika kerja, saya tahu bahwa aplikasi di suatu tempat dalam kode berlangganan ke antrian RabbitMQ. Saya sedang berjalan kodenya - saya tidak dapat menemukan tempat ini. Saya langsung mencari daya tarik untuk kelinci, saya mulai memanjat, saya pergi ke tempat dalam aliran bisnis di mana langganan sebenarnya terjadi - saya mulai bersumpah. Bagaimana tampilannya dalam kode:
- Metode service.getQueueListener (tickerName) disebut - hasil yang dikembalikan diabaikan. Ini mungkin mengingatkan, tetapi potongan kode seperti itu di mana hasil dari metode ini diabaikan bukan satu-satunya dalam aplikasi.
- Di dalam, tickerName diperiksa untuk null dan metode getQueueListenerByName (tickerName) lainnya dipanggil.
- Di dalamnya, sebuah instance dari kelas QueueListener diambil dari hash dengan nama ticker (jika tidak, itu dibuat), dan metode getSubscription () dipanggil.
- Dan sekarang, di dalam metode getSubscription (), berlangganan benar-benar terjadi. Dan itu terjadi di suatu tempat di tengah metode ukuran tiga layar.
Saya akan memberitahu Anda dengan jujur - tanpa menjalankan seluruh rantai dan tanpa membaca selusin layar kode yang penuh perhatian, itu tidak realistis untuk menebak di mana berlangganan. Jika metode ini disebut subscribeToQueueByTicker (tickerName), itu akan menghemat banyak waktu.
Kelas utilitas
Ada buku Pola Desain yang luar biasa: Elemen-elemen Perangkat Lunak Berorientasi Objek yang Dapat Digunakan Kembali (1994), sering disebut GOF (Gang of Four, oleh jumlah penulis). Manfaat buku ini terutama karena memberi para pengembang dari berbagai negara satu bahasa untuk menggambarkan pola desain kelas. Sekarang alih-alih “kelas dijamin hanya ada dalam satu contoh dan memiliki titik akses statis” kita dapat mengatakan “singleton”. Buku yang sama menyebabkan kerusakan nyata pada pikiran yang rapuh. Kerugian ini dijelaskan dengan baik oleh kutipan dari salah satu forum "Kolega, saya perlu membuat toko web, beri tahu saya templat mana yang harus saya mulai." Dengan kata lain, beberapa programmer cenderung menyalahgunakan pola desain, dan dimanapun Anda bisa mengelola dengan satu kelas, kadang-kadang mereka membuat lima atau enam sekaligus - untuk berjaga-jaga, "untuk lebih banyak fleksibilitas".
Bagaimana cara memutuskan apakah Anda memerlukan pabrik kelas abstrak (atau pola lain yang lebih rumit daripada antarmuka) atau tidak? Ada beberapa pertimbangan sederhana:
- Jika Anda menulis aplikasi di Spring, 99% waktunya tidak diperlukan. Musim semi menawarkan Anda blok bangunan tingkat yang lebih tinggi, gunakan. Maksimum yang menurut Anda berguna adalah kelas abstrak.
- Jika poin 1 masih tidak memberikan jawaban yang jelas - ingat bahwa setiap templat +1000 poin ke kompleksitas aplikasi. Menganalisis dengan hati-hati apakah manfaat menggunakan templat lebih besar daripada bahayanya. Beralih ke metafora, ingat bahwa setiap obat tidak hanya menyembuhkan, tetapi juga sedikit membahayakan. Jangan minum semua pil sekaligus.
Contoh yang baik tentang bagaimana Anda tidak perlu melakukannya, Anda bisa lihat di
sini .
Kesimpulan
Sebagai rangkuman, saya ingin mencatat bahwa saya telah mencantumkan rekomendasi paling dasar. Saya bahkan tidak akan membuatnya dalam bentuk artikel - mereka sangat jelas. Tetapi selama setahun terakhir, saya telah menemukan aplikasi terlalu sering di mana banyak dari rekomendasi ini telah dilanggar. Mari kita menulis kode sederhana yang mudah dibaca dan mudah dipelihara.