Jelajahi mengapa Scala dirancang untuk menyatukan ide fungsional dan berorientasi-objek di JVM, apa yang dilakukannya dengan baik, dan trade-off praktis yang harus diketahui tim.

Java membuat JVM berhasil, tetapi juga menetapkan ekspektasi yang lama-kelamaan menimbulkan masalah bagi banyak tim: banyak boilerplate, penekanan berat pada state yang bisa diubah, dan pola yang sering memerlukan framework atau kode-generasi agar tetap terkontrol. Pengembang menyukai kecepatan JVM, tooling, dan cerita deployment—tetapi mereka menginginkan bahasa yang memungkinkan mengekspresikan gagasan lebih langsung.
Pada awal 2000-an, pekerjaan sehari-hari di JVM melibatkan hierarki kelas yang verbose, upacara getter/setter, dan bug terkait null yang masuk ke produksi. Menulis program konkuren mungkin, tetapi shared mutable state membuat kondisi balapan halus mudah muncul. Bahkan ketika tim mengikuti desain berorientasi-objek yang baik, kode sehari-hari masih membawa banyak kompleksitas kebetulan.
Taruhan Scala adalah bahwa bahasa yang lebih baik dapat mengurangi gesekan itu tanpa meninggalkan JVM: mempertahankan performa “cukup baik” dengan mengompilasi ke bytecode, tapi memberi pengembang fitur yang membantu memodelkan domain secara bersih dan membangun sistem yang lebih mudah diubah.
Kebanyakan tim JVM tidak memilih antara gaya “murni fungsional” dan “murni berorientasi objek”—mereka mencoba mengirim perangkat lunak sesuai tenggat. Scala bertujuan membiarkan Anda menggunakan OO di tempat yang cocok (enkapsulasi, API modular, batas layanan) sambil mengandalkan ide fungsional (imutabilitas, kode berorientasi ekspresi, transformasi yang dapat dikomposisi) untuk membuat program lebih aman dan lebih mudah dipahami.
Campuran itu mencerminkan bagaimana sistem nyata sering dibangun: batas berorientasi objek di sekitar modul dan layanan, dengan teknik fungsional di dalam modul tersebut untuk mengurangi bug dan menyederhanakan pengujian.
Scala bertujuan menyediakan tipe statis yang lebih kuat, komposisi dan reuse yang lebih baik, serta alat level bahasa yang mengurangi boilerplate—semua sambil tetap kompatibel dengan pustaka dan operasi JVM.
Martin Odersky merancang Scala setelah bekerja pada generics di Java dan melihat kekuatan di bahasa seperti ML, Haskell, dan Smalltalk. Komunitas yang terbentuk di sekitar Scala—akademia, tim enterprise JVM, dan kemudian data engineering—membantunya berkembang menjadi bahasa yang mencoba menyeimbangkan teori dengan kebutuhan produksi.
Scala mengambil frasa “semuanya adalah objek” secara serius. Nilai yang mungkin Anda anggap “primitif” di bahasa JVM lain—seperti 1, true, atau 'a'—berperilaku seperti objek normal dengan metode. Itu berarti Anda bisa menulis kode seperti 1.toString atau 'a'.isLetter tanpa berganti mode mental antara “operasi primitif” dan “operasi objek”.
Jika Anda terbiasa dengan pemodelan gaya Java, permukaan berorientasi-objek Scala langsung dikenali: Anda mendefinisikan kelas, membuat instans, memanggil metode, dan mengelompokkan perilaku dengan tipe mirip-antarmuka.
Anda bisa memodelkan domain dengan cara yang langsung:
class User(val name: String) {
def greet(): String = s"Hi, $name"
}
val u = new User("Sam")
println(u.greet())
Kefamiliaran itu penting di JVM: tim dapat mengadopsi Scala tanpa melepaskan cara berpikir dasar “objek dengan metode”.
Model objek Scala lebih seragam dan fleksibel daripada Java:
object Config { ... }), yang sering menggantikan pola static di Java.val/var, mengurangi boilerplate.Pewarisan tetap ada dan sering dipakai, tetapi biasanya lebih ringan:
class Admin(name: String) extends User(name) {
override def greet(): String = s"Welcome, $name"
}
Dalam kerja sehari-hari, ini berarti Scala mendukung blok bangunan OO yang sama—kelas, enkapsulasi, overriding—sambil melicinkan beberapa kekakuan era JVM (seperti penggunaan static yang berat dan getter/setter yang verbose).
Sisi fungsional Scala bukan mode terpisah—ia muncul dalam kebiasaan default yang didorong bahasa. Dua ide yang menggerakkannya: lebih memilih data imutable, dan memperlakukan kode Anda sebagai ekspresi yang menghasilkan nilai.
Di Scala, Anda mendeklarasikan nilai dengan val dan variabel dengan var. Keduanya ada, tetapi kebiasaan budaya adalah val.
Saat memakai val, Anda mengatakan: “referensi ini tidak akan di-reassign.” Pilihan kecil itu mengurangi jumlah state tersembunyi dalam program. Lebih sedikit state berarti lebih sedikit kejutan saat kode tumbuh, terutama dalam alur kerja bisnis multi-langkah di mana nilai sering ditransformasikan.
var masih berguna—kode glue UI, penghitung, atau bagian yang kritis performa—tetapi penggunaannya sebaiknya disengaja, bukan otomatis.
Scala mendorong menulis kode sebagai ekspresi yang dievaluasi menjadi hasil, daripada rangkaian pernyataan yang sebagian besar memutasi state.
Sering terlihat sebagai membangun hasil dari hasil-hasil kecil:
val discounted =
if (isVip) price * 0.9
else price
Di sini, if adalah ekspresi, jadi ia mengembalikan nilai. Gaya ini mempermudah menelusuri “apa nilai ini?” tanpa mengikuti jejak assignment.
Alih-alih loop yang memodifikasi koleksi, kode Scala biasanya mentransformasikan data:
val emails = users
.filter(_.isActive)
.map(_.email)
filter dan map adalah fungsi tingkat-tinggi: mereka menerima fungsi lain sebagai input. Manfaatnya bukan sekadar akademis—itu kejelasan. Anda dapat membaca pipeline sebagai cerita kecil: pilih pengguna aktif, lalu ambil email.
Fungsi murni bergantung hanya pada inputnya dan tidak memiliki efek samping (tidak menulis tersembunyi, tidak I/O). Ketika lebih banyak kode Anda murni, pengujian menjadi sederhana: masukkan input, asser output. Penalaran juga lebih mudah, karena Anda tidak perlu menebak apa lagi yang berubah di bagian lain sistem.
Jawaban Scala untuk “bagaimana berbagi perilaku tanpa membangun pohon kelas raksasa?” adalah trait. Trait mirip interface, tetapi juga bisa membawa implementasi nyata—metode, field, dan logika pembantu kecil.
Trait membiarkan Anda menggambarkan kemampuan (“bisa logging”, “bisa validasi”, “bisa caching”) dan kemudian menempelkan kemampuan itu ke banyak kelas. Ini mendorong blok bangunan kecil dan fokus alih-alih beberapa kelas dasar besar yang semua orang harus warisi.
Berbeda dari pewarisan single-inheritance, trait dirancang untuk multiple inheritance of behavior dengan cara yang terkontrol. Anda bisa menambahkan lebih dari satu trait ke kelas, dan Scala mendefinisikan linearization order yang jelas untuk resolusi metode.
Saat Anda “mix in” trait, Anda menyusun perilaku di boundary kelas daripada menggali lebih dalam ke pewarisan. Itu sering lebih mudah dipelihara:
Contoh sederhana:
trait Timestamped { def now(): Long = System.currentTimeMillis() }
trait ConsoleLogging { def log(msg: String): Unit = println(msg) }
class Service extends Timestamped with ConsoleLogging {
def handle(): Unit = log(s"Handled at ${now()}")
}
Gunakan trait ketika:
Gunakan abstract class ketika:
Keuntungan nyata adalah Scala membuat reuse terasa seperti merakit bagian, bukan mewarisi takdir.
Pattern matching Scala adalah salah satu fitur yang membuat bahasa terasa sangat “fungsional,” meski tetap mendukung desain objek klasik. Alih-alih memaksa logika ke jaringan metode virtual, Anda bisa memeriksa sebuah nilai dan memilih perilaku berdasarkan bentuknya.
Sederhananya, pattern matching adalah switch yang lebih kuat: bisa mencocokkan konstanta, tipe, struktur bersarang, dan bahkan mengikat bagian nilai ke nama. Karena itu adalah ekspresi, ia secara alami menghasilkan hasil—sering kali menghasilkan kode yang ringkas dan mudah dibaca.
sealed trait Payment
case class Card(last4: String) extends Payment
case object Cash extends Payment
def describe(p: Payment): String = p match {
case Card(last4) => s"Card ending $last4"
case Cash => "Cash"
}
Contoh itu juga menunjukkan Algebraic Data Type (ADT) bergaya Scala:
sealed trait mendefinisikan himpunan kemungkinan yang tertutup.case class dan case object mendefinisikan varian konkret.“Kata kunci sealed” penting: kompiler mengetahui semua subtype yang valid (dalam berkas yang sama), yang membuka jalan untuk pattern matching yang lebih aman.
ADT mendorong Anda memodelkan keadaan nyata domain. Alih-alih menggunakan null, string ajaib, atau boolean yang dapat dikombinasikan menjadi keadaan mustahil, Anda mendefinisikan kasus yang diizinkan secara eksplisit. Itu membuat banyak kesalahan tidak mungkin diekspresikan dalam kode—jadi tidak bisa lolos ke produksi.
Pattern matching bersinar ketika Anda:
Namun bisa berlebihan jika tiap perilaku diungkapkan sebagai blok match raksasa yang tersebar di seluruh basis kode. Jika match tumbuh besar atau muncul di mana-mana, itu sering tanda Anda perlu pemfaktoran yang lebih baik (fungsi pembantu) atau memindahkan beberapa perilaku lebih dekat ke tipe datanya.
Sistem tipe Scala adalah salah satu alasan terbesar tim memilihnya—dan juga salah satu alasan beberapa tim mundur. Pada kondisi terbaik, ia membiarkan Anda menulis kode ringkas yang tetap mendapat pemeriksaan kuat waktu-compile. Pada kondisi terburuk, terasa seperti harus men-debug kompiler.
Type inference berarti Anda biasanya tidak perlu menuliskan tipe di mana-mana. Kompiler sering bisa menebaknya dari konteks.
Itu mengurangi boilerplate: Anda bisa fokus pada apa yang direpresentasikan sebuah nilai daripada terus-menerus memberi anotasi tipenya. Saat Anda menambahkan anotasi tipe, biasanya untuk memperjelas maksud di boundary (API publik, generic rumit) bukan untuk tiap variabel lokal.
Generics memungkinkan menulis kontainer dan utilitas yang bekerja untuk banyak tipe (seperti List[Int] dan List[String]). Variance berkaitan dengan apakah tipe generic bisa disubstitusi saat parameter tipenya berubah.
+A) kira-kira berarti “list kucing bisa digunakan di tempat list hewan diharapkan.”-A) kira-kira berarti “handler hewan bisa digunakan di tempat handler kucing diharapkan.”Ini kuat untuk desain pustaka, tetapi bisa membingungkan saat pertama kali bertemu.
Scala memopulerkan pola di mana Anda bisa “menambahkan perilaku” ke tipe tanpa memodifikasinya, dengan melewatkan kapabilitas secara implisit. Misalnya, Anda bisa mendefinisikan bagaimana membandingkan atau mencetak sebuah tipe dan logika itu dipilih otomatis.
Di Scala 2 ini menggunakan implicit; di Scala 3 ini diekspresikan lebih langsung dengan given/using. Idenya sama: memperluas perilaku secara komposabel.
Trade-off-nya adalah kompleksitas. Trik tingkat-tipe dapat menghasilkan pesan kesalahan panjang, dan kode yang terlalu abstrak bisa sulit dibaca bagi pendatang baru. Banyak tim mengadopsi aturan praktis: gunakan sistem tipe untuk menyederhanakan API dan mencegah kesalahan, tetapi hindari desain yang mengharuskan semua orang ‘berpikir seperti kompiler’ untuk membuat perubahan.
Scala memiliki beberapa “jalur” untuk menulis kode konkuren. Itu berguna—karena tidak setiap masalah membutuhkan level mesin yang sama—tetapi juga berarti tim harus sengaja dalam memilih apa yang diadopsi.
Untuk banyak aplikasi JVM, Future adalah cara paling sederhana menjalankan pekerjaan secara konkuren dan mengompos hasil. Anda memulai pekerjaan, lalu pakai map/flatMap untuk membangun workflow async tanpa memblokir thread.
Model mental yang baik: Future cocok untuk tugas independen (panggilan API, query database, perhitungan latar) di mana Anda ingin menggabungkan hasil dan menangani kegagalan di satu tempat.
Scala memungkinkan Anda mengekspresikan rantai Future dengan gaya yang lebih linear (melalui for-comprehensions). Ini tidak menambah primitif konkruensi baru, tetapi membuat maksud lebih jelas dan mengurangi “callback nesting.”
Trade-off: masih mudah tanpa sengaja memblokir (mis. menunggu Future) atau membebani execution context jika Anda tidak memisahkan pekerjaan CPU-bound dan IO-bound.
Untuk pipeline jangka panjang—event, log, pemrosesan data—pustaka streaming (seperti Akka/Pekko Streams, FS2, atau sejenis) fokus pada kontrol aliran. Fitur kunci adalah backpressure: produsen melambat saat konsumen tidak bisa mengejar.
Model ini sering mengungguli “cuma spawn lebih banyak Future” karena memperlakukan throughput dan memori sebagai perhatian utama.
Pustaka actor (Akka/Pekko) memodelkan konkruensi sebagai komponen independen yang berkomunikasi lewat pesan. Ini dapat menyederhanakan penalaran tentang state, karena tiap actor menangani satu pesan dalam satu waktu.
Actor bersinar ketika Anda butuh proses berstatus panjang (device, sesi, koordinator). Mereka bisa berlebihan untuk aplikasi request/response sederhana.
Struktur data immutable mengurangi shared mutable state—sumber banyak race condition. Bahkan saat memakai thread, Futures, atau actor, mengoper nilai immutable membuat bug konkruensi lebih jarang dan debugging lebih mudah.
Mulai dengan Futures untuk pekerjaan paralel yang jelas. Pindah ke streaming ketika Anda butuh kontrol throughput, dan pertimbangkan actors ketika state dan koordinasi mendominasi desain.
Keuntungan praktis terbesar Scala adalah hidup di JVM dan dapat menggunakan ekosistem Java langsung. Anda bisa menginstansiasi kelas Java, mengimplementasikan interface Java, dan memanggil metode Java dengan sedikit ceremony—sering terasa seperti menggunakan pustaka Scala lain.
Sebagian besar interop “jalur bahagia” mudah:
Di balik layar, Scala dikompilasi ke bytecode JVM. Secara operasional, berjalan seperti bahasa JVM lain: dikelola oleh runtime yang sama, menggunakan GC yang sama, dan diprofil/ dimonitor dengan alat yang familiar.
Gesekan muncul saat default Scala tidak cocok dengan Java:
Null. Banyak API Java mengembalikan null; kode Scala lebih suka Option. Anda sering membungkus hasil Java secara defensif untuk menghindari NullPointerException mengejutkan.
Checked exceptions. Scala tidak memaksa Anda mendeklarasikan atau menangkap checked exception, tetapi pustaka Java mungkin melemparkannya. Ini bisa membuat penanganan error terasa tidak konsisten kecuali Anda menstandarisasi cara menerjemahkan exception.
Mutabilitas. Koleksi Java dan API yang banyak setter mendorong mutasi. Di Scala, mencampur gaya mutable dan immutable bisa menghasilkan kode yang membingungkan, terutama di boundary API.
Perlakukan boundary sebagai lapisan terjemahan:
Option segera, dan konversi Option kembali ke null hanya di tepi.Jika dilakukan dengan baik, interop membuat tim Scala lebih cepat dengan menggunakan kembali pustaka JVM yang sudah teruji sambil menjaga kode Scala ekspresif dan lebih aman di dalam layanan.
Pitch Scala menarik: Anda bisa menulis kode fungsional yang elegan, mempertahankan struktur OO di tempat yang membantu, dan tetap di JVM. Dalam praktik, tim tidak sekadar “mendapatkan Scala”—mereka merasakan serangkaian trade-off sehari-hari yang muncul dalam onboarding, build, dan code review.
Scala memberi banyak kekuatan ekspresif: banyak cara memodelkan data, banyak cara mengabstraksi perilaku, banyak cara menyusun API. Fleksibilitas itu produktif setelah Anda berbagi model mental—tetapi awalnya bisa memperlambat tim.
Pendatang baru mungkin kurang bergulat dengan sintaks dan lebih dengan pilihan: “Haruskah ini case class, kelas biasa, atau ADT?” “Apakah kita memakai pewarisan, trait, type class, atau sekadar fungsi?” Bagian sulitnya bukan Scala tidak mungkin—melainkan menyepakati apa yang tim Anda anggap “Scala normal.”
Kompilasi Scala cenderung lebih berat daripada yang banyak tim perkirakan, terutama saat proyek tumbuh atau bergantung pada pustaka yang banyak macro (lebih umum di Scala 2). Build incremental bisa membantu, tetapi waktu kompilasi tetap jadi perhatian praktis: CI lebih lambat, feedback loop lebih panjang, dan lebih banyak tekanan untuk menjaga modul kecil dan dependency rapi.
Tooling build menambahkan lapisan lain. Baik menggunakan sbt atau sistem build lain, Anda perlu memperhatikan caching, paralelisme, dan bagaimana proyek dibagi ke submodule. Ini bukan isu akademis—mereka memengaruhi kebahagiaan pengembang dan seberapa cepat bug diperbaiki.
Tooling Scala sudah banyak meningkat, tetapi tetap layak diuji dengan stack Anda. Sebelum standarisasi, tim harus mengevaluasi:
Jika IDE kesulitan, ekspresivitas bahasa bisa menjadi bumerang: kode yang “benar” tapi sulit dieksplor menjadi mahal untuk dipelihara.
Karena Scala mendukung FP dan OO (plus banyak hibrida), tim bisa berakhir dengan basis kode yang terasa seperti beberapa bahasa sekaligus. Di situlah frustrasi biasanya muncul: bukan karena Scala, tapi karena konvensi yang tidak konsisten.
Konvensi dan linter penting karena mereka mengurangi perdebatan. Putuskan sejak awal apa itu “Scala yang baik” untuk tim Anda—bagaimana menangani imutabilitas, penanganan error, penamaan, dan kapan menggunakan pola tingkat-tipe canggih. Konsistensi mempermudah onboarding dan membuat review berfokus pada perilaku, bukan estetika.
Scala 3 (sering disebut “Dotty” saat pengembangan) bukan re-write identitas Scala—melainkan upaya mempertahankan campuran FP/OOP sambil menghaluskan tepi tajam yang ditemui tim di Scala 2.
Scala 3 mempertahankan dasar yang familiar, tetapi mendorong kode ke struktur yang lebih jelas.
Anda akan melihat optional braces dengan indentation signifikan, yang membuat kode sehari-hari dibaca lebih seperti bahasa modern dan kurang seperti DSL padat. Ini juga menstandarkan pola yang dulu “mungkin tapi berantakan” di Scala 2—mis. menambahkan metode lewat extension daripada sekumpulan trik implicit.
Secara filosofis, Scala 3 berusaha membuat fitur kuat terasa lebih eksplisit, sehingga pembaca dapat memahami apa yang terjadi tanpa menghafal lusinan konvensi.
Implicits Scala 2 sangat fleksibel: hebat untuk typeclass dan dependency injection, tapi juga sumber pesan kompilasi membingungkan dan “aksi dari jauh.”
Scala 3 menggantinya dengan given/using. Kapabilitas serupa, tapi maksudnya lebih jelas: “ini instance yang disediakan” (given) dan “metode ini membutuhkan satu” (using). Itu meningkatkan keterbacaan dan membuat pola typeclass gaya FP lebih mudah diikuti.
Enum juga penting. Banyak tim Scala 2 memakai sealed trait + case object/class untuk memodelkan ADT. enum di Scala 3 memberi sintaks khusus yang rapi—lebih sedikit boilerplate, tetap sama kekuatan pemodelan.
Sebagian besar proyek nyata melakukan cross-building (mempublikasikan artefak Scala 2 dan Scala 3) dan memigrasi modul demi modul.
Alat bantu ada, tetapi tetap ada pekerjaan: inkompatibilitas sumber (terutama seputar implicits), pustaka yang bergantung pada macro, dan tooling build bisa memperlambat. Kabar baik: kode bisnis biasa biasanya lebih mudah dipindahkan daripada kode yang sangat mengandalkan sihir kompiler.
Dalam kode sehari-hari, Scala 3 cenderung membuat pola FP terasa lebih “kelas satu”: wiring typeclass lebih jelas, ADT lebih rapi dengan enum, dan alat tipe yang lebih kuat (seperti union/intersection types) hadir tanpa banyak upacara.
Pada saat yang sama, ia tidak meninggalkan OO—trait, kelas, dan komposisi mixin tetap sentral. Bedanya, Scala 3 membuat batas antara “struktur OO” dan “abstraksi FP” lebih mudah dilihat, yang biasanya membantu tim menjaga konsistensi kode seiring waktu.
Scala bisa menjadi bahasa “alat kuat” di JVM—tetapi bukan default universal. Keuntungan terbesar muncul ketika masalah mendapatkan manfaat dari pemodelan yang lebih kuat dan komposisi yang lebih aman, dan ketika tim siap menggunakan bahasa secara disengaja.
Sistem dan pipeline berat data. Jika Anda mentransformasikan, memvalidasi, dan memperkaya banyak data (stream, ETL, event processing), gaya fungsional dan tipe yang kuat membantu menjaga transformasi eksplisit dan lebih sedikit rentan terhadap kesalahan.
Pemodelan domain yang kompleks. Ketika aturan bisnis bernuansa—pricing, risiko, kelayakan, izin—kemampuan Scala mengekspresikan batasan di tipe dan membangun potongan kecil yang dapat dikomposisi dapat mengurangi “spaghetti if-else” dan membuat state tidak valid lebih sulit direpresentasikan.
Organisasi yang sudah berinvestasi di JVM. Jika dunia Anda sudah bergantung pada pustaka Java, tooling JVM, dan praktik operasional, Scala dapat menghadirkan ergonomi bergaya FP tanpa meninggalkan ekosistem itu.
Scala memberi imbalan pada konsistensi. Tim biasanya berhasil ketika mereka memiliki:
Tanpa ini, basis kode bisa menyimpang ke campuran gaya yang sulit diikuti pendatang baru.
Tim kecil yang butuh onboarding cepat. Jika Anda mengharapkan serah terima sering, banyak kontributor junior, atau perubahan staf cepat, kurva belajar dan variasi idiom bisa memperlambat.
Aplikasi CRUD sederhana. Untuk layanan “request in / record out” yang sederhana dengan kompleksitas domain minimal, keuntungan Scala mungkin tidak sebanding dengan biaya tooling build, waktu kompilasi, dan keputusan gaya.
Tanyakan:
Jika Anda menjawab “ya” untuk sebagian besar, Scala sering cocok. Jika tidak, bahasa JVM yang lebih sederhana mungkin memberikan hasil lebih cepat.
Satu tip praktis saat mengevaluasi bahasa: jaga loop prototipe singkat. Misalnya, tim kadang menggunakan platform vibe-coding seperti Koder.ai untuk memutar aplikasi referensi kecil (API + database + UI) dari spesifikasi berbasis chat, iterasi dalam mode perencanaan, dan memakai snapshot/rollback untuk mengeksplorasi alternatif dengan cepat. Bahkan bila target produksi Anda Scala, memiliki prototipe cepat yang bisa diekspor sebagai kode sumber dan dibandingkan dengan implementasi JVM lain dapat membuat percakapan “apakah kita harus memilih Scala?” lebih konkret—berdasarkan workflow, deployment, dan maintainability, bukan hanya fitur bahasa.
Scala dirancang untuk mengurangi masalah umum di JVM—boilerplate berlebihan, bug terkait null, dan desain hirarki warisan yang rapuh—sambil mempertahankan performa, tooling, dan akses ke pustaka JVM. Tujuannya adalah mengekspresikan logika domain lebih langsung tanpa meninggalkan ekosistem Java.
Gunakan OO untuk mendefinisikan batas modul yang jelas (API, enkapsulasi, antarmuka layanan), dan gunakan teknik FP di dalam batas-batas itu (imutabilitas, gaya ekspresi, fungsi yang mirip murni) untuk mengurangi state tersembunyi serta membuat perilaku lebih mudah diuji dan diubah.
Utamakan val secara default untuk menghindari reassignment yang tak disengaja dan mengurangi state tersembunyi. Gunakan var secara sengaja pada tempat yang kecil dan terlokalisir (mis. loop performa ketat atau glue UI), dan usahakan agar mutasi tidak masuk ke logika bisnis inti bila memungkinkan.
Traits adalah kemampuan yang dapat digunakan ulang yang bisa Anda mix ke banyak kelas, sering menghindari hirarki yang dalam dan rapuh.
Modelkan himpunan keadaan tertutup dengan sealed trait plus case class/case object, lalu gunakan match untuk menangani tiap kasus.
Ini membuat keadaan yang tidak valid lebih sulit direpresentasikan dan memungkinkan refaktor yang lebih aman karena kompiler dapat memperingatkan ketika ada kasus baru yang belum ditangani.
Type inference menghilangkan anotasi yang berulang sehingga kode tetap ringkas, namun tetap diperiksa oleh kompiler.
Praktik umum: tambahkan tipe eksplisit di boundary (metode publik, API modul, generic yang kompleks) untuk meningkatkan keterbacaan dan menstabilkan pesan kesalahan tanpa harus memberi anotasi pada setiap nilai lokal.
Variance menjelaskan bagaimana subtyping bekerja untuk tipe generic.
+A): sebuah kontainer bisa “diperlebar” (mis. dapat dianggap ).Mekanisme itu mendukung desain bergaya type-class: Anda menyediakan perilaku “dari luar” tanpa mengubah tipe aslinya.
implicitgiven / usingScala 3 membuat maksud lebih jelas (apa yang disediakan vs apa yang dibutuhkan), sehingga biasanya meningkatkan keterbacaan dan mengurangi “aksi dari jauh.”
Mulailah sederhana dan naik tingkat hanya bila diperlukan:
Dalam semua kasus, mengoper nilai immutable membantu menghindari race condition.
Perlakukan batas Java/Scala sebagai lapisan terjemahan:
null dari Java ke Option segera (dan konversi kembali ke null hanya di tepi sistem).List[Cat]List[Animal]-A): sebuah konsumen/handler dapat diperlebar (mis. Handler[Animal] dipakai di tempat Handler[Cat] diperlukan).Anda akan merasakan ini terutama saat merancang pustaka atau API yang menerima/mengembalikan tipe generic.
Ini menjaga interop yang dapat diprediksi dan mencegah kebiasaan default Java (null, mutasi) bocor ke seluruh kodebase.