Saya sering kode dalam Python. Sekarang, untuk alasan pekerjaan, saya kode di Jawa. Proyek yang saya lakukan agak kecil, dan mungkin Python akan bekerja lebih baik, tetapi ada alasan non-teknik yang valid untuk menggunakan Java (saya tidak bisa masuk ke rincian).
Sintaks Java tidak ada masalah; itu hanyalah bahasa lain. Tetapi terlepas dari sintaksisnya, Jawa memiliki budaya, seperangkat metode pengembangan, dan praktik yang dianggap "benar". Dan untuk saat ini saya benar-benar gagal "grok" budaya itu. Jadi saya akan sangat menghargai penjelasan atau petunjuk ke arah yang benar.
Contoh lengkap minimal tersedia dalam pertanyaan Stack Overflow yang saya mulai: https://stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339
Saya punya tugas - parse (dari satu string) dan menangani satu set tiga nilai. Dalam Python itu adalah one-liner (tuple), dalam Pascal atau C a 5-liner record / struct.
Menurut jawaban, setara dengan struct tersedia di sintaksis Java dan triple tersedia di perpustakaan Apache yang banyak digunakan - namun cara "benar" melakukannya sebenarnya dengan membuat kelas terpisah untuk nilai, lengkap dengan getter dan setter. Seseorang sangat baik untuk memberikan contoh yang lengkap. Itu adalah 47 baris kode (yah, beberapa baris ini kosong).
Saya mengerti bahwa komunitas pembangunan besar kemungkinan tidak "salah". Jadi ini masalah dengan pemahaman saya.
Praktik Python mengoptimalkan keterbacaan (yang, dalam filosofi itu, mengarah pada kemampuan pemeliharaan) dan setelah itu, kecepatan pengembangan. Praktik C mengoptimalkan penggunaan sumber daya. Untuk apa praktik Java dioptimalkan? Tebakan terbaik saya adalah skalabilitas (semuanya harus dalam keadaan siap untuk proyek jutaan-LOC), tetapi ini merupakan tebakan yang sangat lemah.
sumber
Jawaban:
Bahasa Jawa
Saya percaya semua jawaban ini tidak ada gunanya dengan mencoba menganggap maksud cara kerja Java. Verbositas Java tidak berasal dari itu menjadi berorientasi objek, seperti Python dan banyak bahasa lainnya juga belum memiliki sintaks terser. Verbositas Java juga tidak datang dari dukungan pengubah akses. Sebaliknya, ini hanyalah bagaimana Java dirancang dan telah berkembang.
Java awalnya dibuat sebagai C yang sedikit ditingkatkan dengan OO. Karena itu Java memiliki sintaks era 70-an. Selain itu, Java sangat konservatif dalam menambahkan fitur untuk mempertahankan kompatibilitas ke belakang dan untuk memungkinkannya bertahan dalam ujian waktu. Seandainya Java menambahkan fitur-fitur trendi seperti XML literals pada 2005 ketika XML adalah hal yang paling disukai, bahasa itu akan dibengkak dengan fitur hantu yang tidak dipedulikan siapa pun dan yang membatasi evolusinya 10 tahun kemudian. Oleh karena itu Java tidak memiliki banyak sintaksis modern untuk mengekspresikan konsep secara singkat.
Namun, tidak ada yang mendasar mencegah Java mengadopsi sintaksis itu. Misalnya, Java 8 menambahkan lambdas dan referensi metode, sangat mengurangi verbositas dalam banyak situasi. Java juga dapat menambahkan dukungan untuk deklarasi tipe data kompak seperti kelas kasus Scala. Tetapi Java tidak melakukannya. Perhatikan bahwa tipe nilai khusus ada di cakrawala dan fitur ini dapat memperkenalkan sintaks baru untuk mendeklarasikannya. Saya kira kita akan melihat.
Budaya Jawa
Sejarah perkembangan perusahaan Java telah membawa kita pada budaya yang kita lihat sekarang. Pada akhir 90-an / awal 00-an, Java menjadi bahasa yang sangat populer untuk aplikasi bisnis sisi server. Dulu aplikasi-aplikasi itu sebagian besar ditulis ad-hoc dan memasukkan banyak masalah kompleks, seperti HTTP API, basis data, dan pemrosesan umpan XML.
Pada tahun 00-an menjadi jelas bahwa banyak dari aplikasi ini memiliki banyak kesamaan dan kerangka kerja untuk mengelola masalah ini, seperti ORM Hibernate, parser XML Xerces, JSP dan servlet API, dan EJB, menjadi populer. Namun, sementara kerangka kerja ini mengurangi upaya untuk bekerja dalam domain tertentu yang mereka atur untuk diotomatisasi, mereka membutuhkan konfigurasi dan koordinasi. Pada saat itu, untuk alasan apa pun, sangat populer untuk menulis kerangka kerja untuk memenuhi kasus penggunaan yang paling kompleks dan karena itu perpustakaan ini rumit untuk diatur dan diintegrasikan. Dan seiring waktu mereka tumbuh semakin kompleks ketika mereka mengumpulkan fitur. Pengembangan perusahaan Java secara bertahap menjadi lebih dan lebih banyak tentang menyatukan perpustakaan pihak ketiga dan lebih sedikit tentang algoritma penulisan.
Akhirnya konfigurasi yang membosankan dan manajemen alat-alat perusahaan menjadi cukup menyakitkan sehingga kerangka kerja, terutama kerangka kerja Spring, datang untuk mengelola manajemen. Anda bisa meletakkan semua konfigurasi Anda di satu tempat, teorinya pergi, dan alat konfigurasi kemudian akan mengkonfigurasi potongan-potongan dan menyatukannya. Sayangnya "kerangka kerja" ini menambahkan lebih banyak abstraksi dan kompleksitas di atas seluruh bola lilin.
Selama beberapa tahun terakhir semakin banyak perpustakaan yang semakin ringan popularitasnya. Meskipun demikian, seluruh generasi programmer Java sudah cukup umur selama pertumbuhan kerangka kerja perusahaan yang berat. Teladan mereka, mereka yang mengembangkan kerangka kerja, menulis pabrik pabrik dan pemuat kacang konfigurasi proxy. Mereka harus mengkonfigurasi dan mengintegrasikan monstrositas ini sehari-hari. Dan sebagai hasilnya budaya masyarakat secara keseluruhan mengikuti contoh kerangka kerja ini dan cenderung terlalu merekayasa.
sumber
for
perulangan ketika amap
akan lebih tepat (dan dapat dibaca). Ada media yang menyenangkan.Saya yakin saya punya jawaban untuk salah satu poin yang Anda ajukan yang belum diajukan oleh orang lain.
Java mengoptimalkan deteksi kesalahan programmer saat kompilasi dengan tidak pernah membuat asumsi
Secara umum, Java cenderung hanya menyimpulkan fakta tentang kode sumber setelah programmer secara eksplisit menyatakan niatnya. Kompiler Java tidak pernah membuat asumsi tentang kode dan hanya akan menggunakan inferensi untuk mengurangi kode yang berlebihan.
Alasan di balik filosofi ini adalah bahwa programmer hanya manusia. Apa yang kita tulis tidak selalu sesuai dengan tujuan program. Bahasa Jawa mencoba untuk mengurangi beberapa masalah tersebut dengan memaksa pengembang untuk selalu secara eksplisit menyatakan tipenya. Itu hanya cara mengecek bahwa kode yang ditulis benar-benar melakukan apa yang dimaksudkan.
Beberapa bahasa lain mendorong logika itu lebih jauh dengan memeriksa pra-kondisi, pasca-kondisi dan invarian (meskipun saya tidak yakin mereka melakukannya pada waktu kompilasi). Ini adalah cara yang bahkan lebih ekstrem bagi programmer untuk memeriksa kompilernya sendiri.
Dalam kasus Anda, ini berarti bahwa agar kompiler menjamin bahwa Anda benar-benar mengembalikan tipe yang Anda pikir Anda kembalikan, Anda perlu memberikan informasi itu kepada kompiler.
Di Jawa ada dua cara untuk melakukan itu:
Gunakan
Triplet<A, B, C>
sebagai tipe kembali (yang benar-benar harus dalamjava.util
, dan saya tidak bisa menjelaskan mengapa hal ini tidak. Terutama karena JDK8 memperkenalkanFunction
,BiFunction
,Consumer
,BiConsumer
, dll ... Sepertinya bahwaPair
danTriplet
setidaknya akan masuk akal. Tapi saya ngelantur )Buat tipe nilai Anda sendiri untuk tujuan itu, di mana setiap bidang diberi nama dan diketik dengan benar.
Dalam kedua kasus tersebut, kompiler dapat menjamin bahwa fungsi Anda mengembalikan jenis yang dinyatakannya, dan bahwa pemanggil menyadari apa jenis setiap bidang yang dikembalikan dan menggunakannya sesuai.
Beberapa bahasa memang menyediakan pemeriksaan tipe statis dan inferensi tipe pada saat yang sama, tetapi hal itu membuat pintu terbuka untuk kelas halus masalah ketidakcocokan tipe. Di mana pengembang berniat mengembalikan nilai dari jenis tertentu, tetapi sebenarnya mengembalikan yang lain dan kompiler MASIH menerima kode karena kebetulan bahwa, secara tidak sengaja fungsi dan pemanggil hanya menggunakan metode yang dapat diterapkan pada kedua yang dimaksud dan tipe yang sebenarnya.
Pertimbangkan sesuatu seperti ini di dalam Skriptrip (atau tipe aliran) di mana inferensi tipe digunakan alih-alih mengetik secara eksplisit.
Ini adalah kasus sepele yang konyol, tentu saja, tetapi dapat menjadi jauh lebih halus dan menyebabkan masalah sulit untuk debug, karena kode terlihat benar. Masalah-masalah semacam ini sepenuhnya dihindari di Jawa, dan itulah sebabnya seluruh bahasa dirancang.
Perhatikan bahwa mengikuti prinsip Berorientasi Objek yang tepat dan pemodelan berbasis domain, kasus parsing durasi dalam pertanyaan terkait juga dapat dikembalikan sebagai
java.time.Duration
objek, yang akan jauh lebih eksplisit daripada kedua kasus di atas.sumber
Java dan Python adalah dua bahasa yang paling sering saya gunakan, tetapi saya datang dari arah lain. Yaitu, saya berada jauh di dunia Jawa sebelum saya mulai menggunakan Python jadi saya mungkin bisa membantu. Saya pikir jawaban untuk pertanyaan yang lebih besar dari "mengapa hal-hal begitu berat" datang ke 2 hal:
yield
.Java 'mengoptimalkan' untuk perangkat lunak bernilai tinggi yang akan dipertahankan selama bertahun-tahun oleh tim besar orang. Saya sudah memiliki pengalaman menulis hal-hal dengan Python dan melihatnya setahun kemudian dan bingung dengan kode saya sendiri. Di Jawa saya bisa melihat potongan kecil kode orang lain dan langsung tahu apa fungsinya. Dengan Python Anda tidak bisa melakukan itu. Bukannya yang lebih baik seperti yang Anda sadari, mereka hanya memiliki biaya yang berbeda.
Dalam kasus spesifik yang Anda sebutkan, tidak ada tupel. Solusi mudahnya adalah membuat kelas dengan nilai publik. Ketika Jawa pertama kali keluar, orang melakukan ini dengan cukup teratur. Masalah pertama dengan melakukan itu adalah sakit kepala pemeliharaan. Jika Anda perlu menambahkan beberapa logika atau keamanan utas atau ingin menggunakan polimorfisme, Anda setidaknya harus menyentuh setiap kelas yang berinteraksi dengan objek 'tuple-esque' itu. Dalam Python ada solusi untuk ini seperti
__getattr__
dll sehingga tidak terlalu mengerikan.Ada beberapa kebiasaan buruk (IMO) di sekitar ini. Dalam hal ini jika Anda menginginkan sebuah tuple, saya mempertanyakan mengapa Anda menjadikannya objek yang dapat diubah. Anda seharusnya hanya memerlukan getter (sebagai catatan, saya benci konvensi get / set tetapi ini adalah apa adanya). Saya pikir kelas telanjang (dapat diubah atau tidak) dapat berguna dalam konteks privat atau paket-privat di Jawa . Yaitu, dengan membatasi referensi dalam proyek ke kelas, Anda dapat refactor nanti sesuai kebutuhan tanpa mengubah antarmuka publik kelas. Berikut adalah contoh bagaimana Anda dapat membuat objek sederhana yang tidak dapat diubah:
Ini adalah semacam pola yang saya gunakan. Jika Anda tidak menggunakan IDE, Anda harus mulai. Ini akan menghasilkan getter (dan setter jika Anda membutuhkannya) untuk Anda sehingga ini tidak terlalu menyakitkan.
Saya akan merasa lalai jika saya tidak menunjukkan bahwa sudah ada jenis yang tampaknya memenuhi sebagian besar kebutuhan Anda di sini . Mengesampingkan itu, pendekatan yang Anda gunakan tidak cocok untuk Java karena bermain untuk kelemahan itu, bukan kekuatannya. Ini peningkatan sederhana:
sumber
Sebelum Anda terlalu marah di Jawa, silakan baca jawaban saya di posting Anda yang lain .
Salah satu keluhan Anda adalah perlunya membuat kelas hanya untuk mengembalikan sejumlah nilai sebagai jawaban. Ini adalah kekhawatiran yang valid yang menurut saya menunjukkan intuisi pemrograman Anda sudah benar! Namun, saya pikir jawaban lain tidak sesuai dengan tetap berpegang pada anti-pola obsesi primitif yang telah Anda komit. Dan Java tidak memiliki kemudahan yang sama untuk bekerja dengan beberapa primitif seperti yang dimiliki Python, di mana Anda dapat mengembalikan beberapa nilai secara asli dan menetapkannya ke beberapa variabel dengan mudah.
Tetapi begitu Anda mulai berpikir tentang apa yang dilakukan
ApproximateDuration
tipe untuk Anda, Anda menyadari bahwa itu tidak dibatasi secara sempit untuk "hanya kelas yang tampaknya tidak perlu untuk mengembalikan tiga nilai". Konsep yang diwakili oleh kelas ini sebenarnya adalah salah satu konsep bisnis domain inti Anda — kebutuhan untuk dapat merepresentasikan waktu dengan cara perkiraan, dan membandingkannya. Ini perlu menjadi bagian dari bahasa inti dari aplikasi Anda, dengan dukungan objek dan domain yang baik, sehingga dapat diuji, modular, dapat digunakan kembali, dan bermanfaat.Apakah kode Anda yang menjumlahkan perkiraan durasi bersama-sama (atau durasi dengan margin kesalahan, namun Anda menyatakannya) sepenuhnya prosedural atau adakah objek-objek untuk itu? Saya akan mengusulkan bahwa desain yang baik sekitar penjumlahan durasi perkiraan bersama akan menentukan melakukannya di luar kode konsumsi, dalam kelas yang dengan sendirinya dapat diuji. Saya pikir menggunakan objek domain semacam ini akan memiliki efek riak positif dalam kode Anda yang membantu menjauhkan Anda dari langkah-langkah prosedural baris demi baris untuk menyelesaikan satu tugas tingkat tinggi (meskipun dengan banyak tanggung jawab), menuju kelas tanggung jawab tunggal yang bebas dari konflik keprihatinan yang berbeda.
Sebagai contoh, katakanlah Anda mempelajari lebih lanjut tentang presisi atau skala apa yang sebenarnya diperlukan untuk penjumlahan dan perbandingan durasi Anda agar berfungsi dengan benar, dan Anda mengetahui bahwa Anda memerlukan bendera perantara untuk menunjukkan "kesalahan sekitar 32 milidetik" (dekat dengan bujur sangkar) root 1000, jadi setengah logaritma antara 1 dan 1000). Jika Anda terikat pada kode yang menggunakan primitif untuk mewakili ini, Anda harus menemukan setiap tempat dalam kode di mana Anda memiliki
is_in_seconds,is_under_1ms
dan mengubahnya menjadiis_in_seconds,is_about_32_ms,is_under_1ms
. Semuanya harus berubah di semua tempat! Membuat kelas yang tanggung jawabnya adalah mencatat margin kesalahan sehingga dapat dikonsumsi di tempat lain membebaskan konsumen Anda dari mengetahui rincian margin kesalahan apa atau apa pun tentang bagaimana mereka digabungkan, dan memungkinkan mereka menentukan margin kesalahan yang relevan saat ini. (Yaitu, tidak ada kode konsumsi yang margin kesalahannya benar dipaksa untuk berubah ketika Anda menambahkan margin level kesalahan baru di kelas, karena semua margin kesalahan lama masih valid).Pernyataan penutup
Keluhan tentang beratnya Java tampaknya kemudian hilang ketika Anda bergerak lebih dekat dengan prinsip-prinsip SOLID dan GRASP, dan rekayasa perangkat lunak yang lebih maju.
Tambahan
Saya akan menambahkan dengan sangat bebas dan tidak adil bahwa sifat otomatis C # dan kemampuan untuk menetapkan properti get-only dalam konstruktor membantu membersihkan lebih jauh lagi kode yang agak berantakan yang akan dibutuhkan "cara Java" (dengan bidang dukungan pribadi yang eksplisit dan fungsi pengambil / penyetel) :
Berikut ini adalah implementasi Java dari hal di atas:
Nah, itu sangat sangat bersih. Perhatikan penggunaan immutabilitas yang sangat penting dan disengaja - ini tampaknya penting untuk jenis kelas penahan nilai ini.
Dalam hal ini, kelas ini juga merupakan kandidat yang layak untuk menjadi
struct
tipe nilai. Beberapa pengujian akan menunjukkan apakah beralih ke struct memiliki manfaat kinerja run-time (bisa).sumber
Baik Python dan Java dioptimalkan untuk perawatan sesuai dengan filosofi desainer mereka, tetapi mereka memiliki ide yang sangat berbeda tentang cara mencapai ini.
Python adalah bahasa multi-paradigma yang mengoptimalkan kejelasan dan kesederhanaan kode (mudah dibaca dan ditulis).
Java (secara tradisional) adalah bahasa OO berbasis paradigma kelas tunggal yang mengoptimalkan untuk kejelasan dan konsistensi - bahkan dengan mengorbankan kode yang lebih bertele-tele.
Tuple Python adalah struktur data dengan sejumlah bidang tetap. Fungsionalitas yang sama dapat dicapai oleh kelas reguler dengan bidang yang dinyatakan secara eksplisit. Dalam Python adalah wajar untuk memberikan tupel sebagai alternatif untuk kelas karena memungkinkan Anda untuk menyederhanakan kode, terutama karena dukungan sintaksis bawaan untuk tupel.
Tetapi ini tidak benar-benar cocok dengan budaya Java untuk menyediakan pintasan seperti itu, karena Anda sudah dapat menggunakan kelas yang dinyatakan secara eksplisit. Tidak perlu memperkenalkan jenis struktur data yang berbeda hanya untuk menyimpan beberapa baris kode dan menghindari beberapa deklarasi.
Java lebih menyukai konsep tunggal (kelas) yang diterapkan secara konsisten dengan minimum gula sintaksis kasus khusus, sementara Python menyediakan beberapa alat dan banyak gula sintaksis untuk memungkinkan Anda memilih yang paling nyaman untuk tujuan tertentu.
sumber
Jangan mencari praktik; itu biasanya ide yang buruk, seperti yang dikatakan dalam Best practices BAD, pola GOOD? . Saya tahu Anda tidak meminta praktik terbaik, tetapi saya masih berpikir Anda akan menemukan beberapa elemen yang relevan di sana.
Mencari solusi untuk masalah Anda lebih baik daripada latihan, dan masalah Anda bukanlah tuple untuk mengembalikan tiga nilai di Java dengan cepat:
Di sini Anda memiliki objek tidak berubah yang hanya berisi data Anda, dan publik sehingga tidak perlu untuk getter. Perhatikan bahwa jika Anda menggunakan beberapa alat serialisasi atau layer persistence seperti ORM, mereka biasanya menggunakan pengambil / penyetel (dan dapat menerima parameter untuk menggunakan bidang alih-alih pengambil / penyetel). Dan inilah mengapa praktik ini banyak digunakan. Jadi jika Anda ingin tahu tentang praktik, lebih baik untuk memahami mengapa mereka ada di sini untuk penggunaan yang lebih baik.
Akhirnya: Saya menggunakan getter karena saya menggunakan banyak alat serialisasi, tetapi saya juga tidak menulisnya; Saya menggunakan lombok: Saya menggunakan pintasan yang disediakan oleh IDE saya.
sumber
Tentang idiom Jawa secara umum:
Ada berbagai alasan mengapa Java memiliki kelas untuk semuanya. Sejauh pengetahuan saya, alasan utamanya adalah:
Java seharusnya mudah dipelajari untuk pemula. Semakin eksplisit, semakin sulit untuk melewatkan detail penting. Lebih sedikit keajaiban terjadi yang akan sulit untuk dipahami oleh pemula.
Adapun contoh spesifik Anda: garis argumen untuk kelas yang terpisah adalah ini: jika ketiga hal tersebut cukup kuat terkait satu sama lain sehingga mereka dikembalikan sebagai satu nilai, nilainya menyebutkan "benda" itu. Dan memperkenalkan nama untuk sekelompok hal yang terstruktur dengan cara yang sama berarti mendefinisikan kelas.
Anda dapat mengurangi pelat dengan alat-alat seperti Lombok:
sumber
Ada banyak hal yang dapat dikatakan tentang budaya Jawa, tetapi saya pikir dalam kasus yang Anda hadapi sekarang, ada beberapa aspek penting:
Kelas "Struct" dengan bidang
Seperti jawaban lain yang disebutkan, Anda bisa menggunakan kelas dengan bidang publik. Jika Anda membuat ini final, maka Anda mendapatkan kelas abadi, dan Anda akan menginisialisasi mereka dengan konstruktor:
Tentu saja, ini berarti Anda terikat pada kelas tertentu, dan apa pun yang pernah perlu untuk menghasilkan atau mengonsumsi hasil parse harus menggunakan kelas ini. Untuk beberapa aplikasi, itu tidak masalah. Bagi yang lain, itu bisa menyebabkan rasa sakit. Banyak kode Java tentang mendefinisikan kontrak, dan itu biasanya akan membawa Anda ke antarmuka.
Perangkap lain adalah bahwa dengan pendekatan berbasis kelas, Anda mengekspos bidang dan semua bidang itu harus memiliki nilai. Misalnya, isSeconds dan millis selalu harus memiliki beberapa nilai, bahkan jika isLessThanOneMilli benar. Apa yang seharusnya penafsiran nilai bidang milis ketika isLessThanOneMilli benar?
"Struktur" sebagai Antarmuka
Dengan metode statis yang diizinkan dalam antarmuka, sebenarnya relatif mudah untuk membuat tipe yang tidak dapat diubah tanpa banyak overhead sintaksis. Misalnya, saya mungkin menerapkan jenis struktur hasil yang Anda bicarakan sebagai sesuatu seperti ini:
Itu masih banyak boilerplate, saya benar-benar setuju, tetapi ada beberapa manfaat juga, dan saya pikir itu mulai menjawab beberapa pertanyaan utama Anda.
Dengan struktur seperti hasil parse ini, kontrak parser Anda didefinisikan dengan sangat jelas. Dalam Python, satu tuple tidak benar-benar berbeda dari tuple lain. Di Java, pengetikan statis tersedia, jadi kami sudah mengesampingkan kelas kesalahan tertentu. Misalnya, jika Anda mengembalikan tupel dengan Python, dan Anda ingin mengembalikan tupel (milid, isSeconds, isLessThanOneMilli), Anda dapat melakukan:
ketika Anda maksud:
Dengan antarmuka Java semacam ini, Anda tidak dapat mengompilasi:
sama sekali. Kamu harus melakukan:
Itulah manfaat dari bahasa yang diketik secara statis pada umumnya.
Pendekatan ini juga mulai memberi Anda kemampuan untuk membatasi nilai apa yang bisa Anda dapatkan. Misalnya, ketika memanggil getMillis (), Anda dapat memeriksa apakah isLessThanOneMilli () benar, dan jika ya, lempar IllegalStateException (misalnya), karena tidak ada nilai milis yang berarti dalam kasus itu.
Menyulitkan Melakukan Hal yang Salah
Dalam contoh antarmuka di atas, Anda masih memiliki masalah yang Anda dapat menukar argumen isSeconds dan isLessThanOneMilli secara tidak sengaja, karena keduanya memiliki tipe yang sama.
Dalam praktiknya, Anda mungkin ingin memanfaatkan TimeUnit dan durasi, sehingga Anda akan mendapatkan hasil seperti:
Itu menjadi lebih banyak kode, tetapi Anda hanya perlu menulisnya sekali, dan (dengan asumsi Anda mendokumentasikan hal-hal dengan benar), orang-orang yang akhirnya menggunakan kode Anda tidak harus menebak apa artinya hasilnya, dan tidak dapat secara tidak sengaja melakukan hal-hal seperti
result[0]
ketika itu berartiresult[1]
. Anda masih dapat membuat instance dengan cukup ringkas, dan mendapatkan data dari situ juga tidak terlalu sulit:Perhatikan bahwa Anda benar-benar dapat melakukan sesuatu seperti ini dengan pendekatan berbasis kelas juga. Cukup tentukan konstruktor untuk kasus yang berbeda. Anda masih memiliki masalah apa yang harus diinisialisasi dengan bidang lain, dan Anda tidak dapat mencegah akses ke sana.
Jawaban lain menyebutkan bahwa sifat Java tipe perusahaan berarti bahwa sebagian besar waktu, Anda membuat perpustakaan lain yang sudah ada, atau menulis perpustakaan untuk digunakan orang lain. API publik Anda seharusnya tidak memerlukan banyak waktu berkonsultasi dengan dokumentasi untuk menguraikan jenis hasil jika dapat dihindari.
Anda hanya menulis struktur ini satu kali, tetapi Anda membuatnya berkali-kali, jadi Anda tetap menginginkan penciptaan yang ringkas (yang Anda dapatkan). Pengetikan statis memastikan bahwa data yang Anda peroleh darinya adalah yang Anda harapkan.
Sekarang, semua yang dikatakan, masih ada tempat-tempat di mana tuple atau daftar sederhana bisa masuk akal. Mungkin ada lebih sedikit overhead dalam mengembalikan array sesuatu, dan jika itu masalahnya (dan overhead itu signifikan, yang akan Anda tentukan dengan profiling), maka menggunakan array nilai sederhana secara internal mungkin membuat banyak akal. API publik Anda mungkin masih memiliki jenis yang jelas.
sumber
originalTimeUnit=DurationTimeUnit.UNDER1MS
, penelepon mencoba membaca nilai milidetik.Masalahnya adalah Anda membandingkan apel dengan jeruk . Anda bertanya bagaimana mensimulasikan pengembalian lebih dari nilai tunggal memberikan contoh python cepat & kotor dengan tuple yang tidak diketik dan Anda benar-benar menerima jawaban praktis satu-baris .
Jawaban yang diterima memberikan solusi bisnis yang benar. Tidak ada solusi sementara cepat yang harus Anda buang dan implementasikan dengan benar saat pertama kali Anda perlu melakukan sesuatu yang praktis dengan nilai yang dikembalikan, tetapi kelas POJO yang kompatibel dengan sejumlah besar perpustakaan, termasuk persistensi, serialisasi / deserialisasi, instrumentasi dan apa pun yang mungkin.
Ini juga tidak lama sama sekali. Satu-satunya hal yang perlu Anda tulis adalah definisi bidang. Setter, getter, kode hash, dan sederajat dapat dihasilkan. Jadi pertanyaan Anda yang sebenarnya seharusnya, mengapa getter dan setter tidak dihasilkan secara otomatis tetapi ini adalah masalah sintaksis (masalah gula sintaksis, menurut beberapa orang) dan bukan masalah budaya.
Dan akhirnya, Anda terlalu banyak berpikir untuk mempercepat sesuatu yang tidak penting sama sekali. Waktu yang dihabiskan untuk menulis kelas DTO tidak signifikan dibandingkan dengan waktu yang dihabiskan untuk memelihara dan men-debug sistem. Oleh karena itu tidak ada yang mengoptimalkan untuk verbositas kurang.
sumber
Tiga faktor berbeda yang berkontribusi pada apa yang Anda amati.
Tuples versus bidang bernama
Mungkin yang paling sepele - dalam bahasa lain Anda menggunakan tuple. Memperdebatkan apakah tuple adalah ide yang bagus bukan intinya - tetapi di Jawa Anda memang menggunakan struktur yang lebih berat sehingga perbandingannya sedikit tidak adil: Anda bisa menggunakan array objek dan beberapa tipe casting.
Sintaks bahasa
Mungkinkah lebih mudah untuk mendeklarasikan kelas? Saya tidak berbicara tentang membuat bidang menjadi publik atau menggunakan peta tetapi sesuatu seperti kelas kasus Scala, yang memberikan semua manfaat dari pengaturan yang Anda uraikan tetapi jauh lebih ringkas:
Kita dapat memiliki itu - tetapi ada biaya: sintaks menjadi lebih rumit. Tentu saja mungkin layak untuk beberapa kasus, atau bahkan untuk sebagian besar kasus, atau bahkan untuk sebagian besar kasus selama 5 tahun ke depan - tetapi perlu dinilai. Ngomong-ngomong, ini merupakan salah satu hal menyenangkan dengan bahasa yang dapat Anda modifikasi sendiri (yaitu lisp) - dan perhatikan bagaimana hal ini menjadi mungkin karena kesederhanaan sintaksis. Bahkan jika Anda tidak benar-benar mengubah bahasa, sintaksis sederhana memungkinkan alat yang lebih kuat; misalnya banyak kali saya kehilangan beberapa opsi refactoring yang tersedia untuk Java tetapi tidak untuk Scala.
Filosofi bahasa
Tetapi faktor yang paling penting adalah bahwa bahasa harus memungkinkan cara berpikir tertentu. Kadang-kadang mungkin terasa menindas (saya sering berharap untuk dukungan untuk fitur tertentu) tetapi menghapus fitur sama pentingnya dengan memilikinya. Bisakah Anda mendukung semuanya? Tentu, tetapi kemudian Anda mungkin juga menulis kompiler yang mengkompilasi setiap bahasa. Dengan kata lain, Anda tidak akan memiliki bahasa - Anda akan memiliki superset bahasa dan setiap proyek pada dasarnya akan mengadopsi subset.
Tentu saja mungkin untuk menulis kode yang bertentangan dengan filosofi bahasa, dan, seperti yang Anda amati, hasilnya seringkali jelek. Memiliki kelas dengan hanya beberapa bidang di Jawa mirip dengan menggunakan var di Scala, menurunkan predikat prolog ke fungsi, melakukan Performa yang tidak aman di haskell dll. Kelas Java tidak dimaksudkan untuk menjadi ringan - mereka tidak ada di sana untuk melewatkan data di sekitar . Ketika sesuatu terasa sulit, sering kali bermanfaat untuk mundur dan melihat apakah ada cara lain. Dalam contoh Anda:
Mengapa durasinya terpisah dari unit? Ada banyak pustaka waktu yang memungkinkan Anda mendeklarasikan durasi - sesuatu seperti Durasi (5, detik) (sintaks akan bervariasi), yang kemudian akan membiarkan Anda melakukan apa pun yang Anda inginkan dengan cara yang jauh lebih kuat. Mungkin Anda ingin mengonversinya - mengapa memeriksa apakah hasil [1] (atau [2]?) Adalah 'jam' dan dikalikan dengan 3600? Dan untuk argumen ketiga - apa tujuannya? Saya kira bahwa pada titik tertentu Anda harus mencetak "kurang dari 1 ms" atau waktu aktual - itu adalah beberapa logika yang secara alami dimiliki oleh data waktu. Yaitu Anda harus memiliki kelas seperti ini:
}
atau apa pun yang sebenarnya ingin Anda lakukan dengan data, maka enkapsulasi logikanya.
Tentu saja, mungkin ada kasus di mana cara ini tidak akan berhasil - Saya tidak mengatakan bahwa ini adalah algoritma ajaib untuk mengubah hasil tuple ke kode Java idiomatik! Dan mungkin ada kasus di mana itu sangat jelek dan buruk dan mungkin Anda harus menggunakan bahasa yang berbeda - itu sebabnya ada begitu banyak!
Tapi pandangan saya tentang mengapa kelas adalah "struktur berat" di Jawa adalah bahwa Anda tidak dimaksudkan untuk menggunakannya sebagai wadah data tetapi sebagai sel logika yang mandiri.
sumber
Menurut pemahaman saya, alasan intinya adalah
Ini memberi Anda "Anda harus menulis kelas untuk kembali untuk hasil yang tidak sepele" yang pada gilirannya agak berat. Jika Anda menggunakan kelas alih-alih antarmuka, Anda bisa memiliki bidang dan menggunakannya secara langsung, tetapi itu mengikat Anda ke implementasi tertentu.
sumber
def f(...): (Int, Int)
adalah fungsif
yang mengembalikan nilai yang kebetulan menjadi tupel bilangan bulat). Saya tidak yakin obat generik di Jawa adalah masalah dengan itu; perhatikan bahwa Haskell juga melakukan penghapusan jenis, misalnya. Saya tidak berpikir ada alasan teknis mengapa Java tidak memiliki tupel.Saya setuju dengan jawaban JacquesB itu
Tetapi kesederhanaan dan konsistensi bukanlah tujuan akhir untuk dioptimalkan. Ketika Anda mengatakan 'python dioptimalkan untuk keterbacaan', Anda segera menyebutkan bahwa tujuan akhirnya adalah 'pemeliharaan' dan 'kecepatan pengembangan'.
Apa yang Anda capai ketika Anda memiliki kesaksian dan konsistensi, dilakukan dengan cara Jawa? Menurut saya itu berkembang sebagai bahasa yang mengklaim untuk memberikan cara yang diprediksi, konsisten, seragam untuk menyelesaikan masalah perangkat lunak.
Dengan kata lain, budaya Java dioptimalkan untuk membuat manajer percaya bahwa mereka memahami pengembangan perangkat lunak.
Atau, seperti yang dikatakan oleh orang bijak sejak dulu ,
sumber
(Jawaban ini bukan penjelasan untuk Java khususnya, tetapi menjawab pertanyaan umum "Apa yang mungkin mengoptimalkan praktik [berat]?")
Pertimbangkan dua prinsip ini:
Mencoba untuk mengoptimalkan salah satu dari tujuan-tujuan ini kadang-kadang dapat menghalangi yang lain (yaitu membuatnya lebih sulit untuk melakukan hal yang salah juga dapat membuat lebih sulit untuk melakukan hal yang benar , atau sebaliknya).
Pengorbanan yang dibuat dalam kasus tertentu tergantung pada aplikasi, keputusan dari programmer atau tim yang bersangkutan, dan budaya (dari organisasi atau komunitas bahasa).
Misalnya, jika bug atau penghentian beberapa jam dalam program Anda dapat mengakibatkan hilangnya nyawa (sistem medis, aeronautika) atau bahkan hanya uang (seperti jutaan dolar di katakan sistem iklan Google), Anda akan membuat pengorbanan yang berbeda (tidak hanya dalam bahasa Anda, tetapi juga dalam aspek lain dari budaya rekayasa) daripada yang Anda lakukan untuk skrip satu kali: kemungkinan condong ke sisi "berat".
Contoh lain yang cenderung membuat sistem Anda lebih "berat":
Ini hanya beberapa contoh, untuk memberi Anda gambaran tentang kasus di mana membuat hal-hal "berat" (dan membuatnya lebih sulit bagi Anda untuk hanya menulis beberapa kode dengan cepat) mungkin benar-benar disengaja. (Orang mungkin bahkan berpendapat bahwa jika menulis kode memerlukan banyak usaha, itu mungkin membuat Anda berpikir lebih hati-hati sebelum menulis kode! Tentu saja argumen ini dengan cepat menjadi konyol.)
Contoh: Sistem Python internal Google cenderung membuat hal-hal “berat” sehingga Anda tidak bisa hanya mengimpor kode orang lain, Anda harus mendeklarasikan ketergantungan pada file BUILD , tim yang kode yang ingin Anda impor perlu perpustakaannya dinyatakan sebagai pustaka mereka. terlihat oleh kode Anda, dll.
Catatan : Semua hal di atas hanya tentang saat segala sesuatu cenderung menjadi "berat". Saya benar-benar tidak mengklaim bahwa Java atau Python (baik bahasa itu sendiri, atau budaya mereka) membuat tradeoff optimal untuk kasus tertentu; itu untuk Anda pikirkan. Dua tautan terkait pada pengorbanan tersebut:
sumber
Budaya Jawa telah berkembang seiring waktu dengan pengaruh besar baik dari sumber terbuka maupun latar belakang perangkat lunak perusahaan - yang merupakan campuran aneh jika Anda benar-benar memikirkannya. Solusi perusahaan menuntut alat berat, dan sumber terbuka menuntut kesederhanaan. Hasil akhirnya adalah bahwa Jawa ada di suatu tempat di tengah.
Bagian dari apa yang mempengaruhi rekomendasi adalah apa yang dianggap dapat dibaca dan dipelihara dengan Python dan Java sangat berbeda.
Saya hanya menyebutkan C # karena perpustakaan standar memiliki satu set kelas Tuple <A, B, C, .. n>, dan ini adalah contoh sempurna tentang seberapa sulit tuple jika bahasa tidak mendukungnya secara langsung. Dalam hampir setiap contoh, kode Anda menjadi lebih mudah dibaca dan dipelihara jika Anda telah memilih kelas dengan baik untuk menangani masalah ini. Dalam contoh spesifik dalam pertanyaan Stack Overflow tertaut Anda, nilai-nilai lain akan dengan mudah dinyatakan sebagai getter yang dihitung pada objek kembali.
Solusi menarik yang dilakukan oleh platform C # yang memberikan jalan tengah yang bahagia adalah gagasan tentang objek anonim (dirilis dalam C # 3.0 ) yang menggaruk gatal ini dengan cukup baik. Sayangnya, Java belum memiliki yang setara.
Sampai fitur bahasa Java diubah, solusi yang paling mudah dibaca dan dipelihara adalah memiliki objek khusus. Itu karena kendala dalam bahasa yang berasal dari awal pada tahun 1995. Para penulis asli memiliki banyak fitur bahasa yang direncanakan yang tidak pernah berhasil, dan kompatibilitas ke belakang adalah salah satu kendala utama seputar evolusi Jawa dari waktu ke waktu.
sumber
Saya pikir salah satu hal inti tentang menggunakan kelas dalam hal ini adalah bahwa apa yang berjalan bersama harus tetap bersama.
Saya telah melakukan diskusi sebaliknya, tentang argumen metode: Pertimbangkan metode sederhana yang menghitung BMI:
Dalam hal ini saya akan menentang gaya ini karena berat dan tinggi terkait. Metode "berkomunikasi" itu adalah dua nilai yang terpisah ketika mereka tidak. Kapan Anda menghitung BMI dengan berat satu orang dan tinggi orang lain? Itu tidak masuk akal.
Lebih masuk akal karena sekarang Anda jelas berkomunikasi bahwa tinggi dan berat berasal dari sumber yang sama.
Hal yang sama berlaku untuk mengembalikan beberapa nilai. Jika mereka terhubung dengan jelas maka kembalikan paket kecil yang rapi dan gunakan objek, jika mereka tidak mengembalikan beberapa nilai.
sumber
Sejujurnya, budaya adalah bahwa programmer Java awalnya cenderung keluar dari universitas di mana prinsip-prinsip Berorientasi Objek dan prinsip-prinsip desain perangkat lunak yang berkelanjutan diajarkan.
Seperti yang dikatakan ErikE dalam lebih banyak kata dalam jawabannya, Anda sepertinya tidak menulis kode berkelanjutan. Apa yang saya lihat dari teladan Anda adalah ada masalah yang sangat canggung.
Dalam budaya Jawa, Anda akan cenderung mengetahui perpustakaan apa yang tersedia, dan itu akan memungkinkan Anda untuk mencapai lebih dari pemrograman Anda. Jadi Anda akan memperdagangkan keanehan Anda untuk pola desain dan gaya yang telah dicoba dan diuji dalam pengaturan industri inti.
Tapi seperti yang Anda katakan, ini bukan tanpa kerugian: hari ini, setelah menggunakan Java selama lebih dari 10 tahun, saya cenderung menggunakan Node / Javascript atau Go untuk proyek baru, karena keduanya memungkinkan pengembangan lebih cepat, dan dengan arsitektur gaya layanan-mikro ini adalah sudah cukup. Dilihat oleh fakta Google pertama kali banyak menggunakan Java, tetapi telah menjadi pencetus Go, saya kira mereka mungkin melakukan hal yang sama. Tetapi meskipun saya menggunakan Go dan Javascript sekarang, saya masih menggunakan banyak keterampilan desain yang saya dapatkan dari bertahun-tahun menggunakan dan memahami Java.
sumber