Di waktu luangnya dari pekerjaan utama, penulis materi berkonsultasi pada Go dan mengurai kode. Tentu saja, dalam kegiatan seperti itu, ia membaca banyak kode yang ditulis oleh orang lain. Baru-baru ini, penulis artikel ini memiliki kesan (ya, kesan, tidak ada statistik) bahwa programmer lebih mungkin untuk bekerja dengan antarmuka dalam "gaya Java".
Posting ini berisi rekomendasi dari penulis tentang penggunaan optimal antarmuka di Go, berdasarkan pengalamannya dalam menulis kode.Pada contoh postingan ini kita akan menggunakan dua paket
animal
dan
circus
. Banyak hal dalam posting ini menggambarkan bekerja dengan kode yang berbatasan dengan penggunaan reguler paket.
Bagaimana tidak
Fenomena yang sangat umum yang saya amati:
package animals type Animal interface { Speaks() string }
package circus import "animals" func Perform(a animal.Animal) string { return a.Speaks() }
Ini adalah apa yang disebut penggunaan antarmuka gaya Java. Ini dapat ditandai dengan langkah-langkah berikut:
- Tentukan antarmuka.
- Tentukan satu jenis yang memenuhi perilaku antarmuka.
- Tetapkan metode yang memenuhi implementasi antarmuka.
Singkatnya, kita berurusan dengan "tipe penulisan yang memenuhi antarmuka." Kode ini memiliki
bau yang berbeda, menyarankan pemikiran berikut:
- Hanya satu jenis yang memenuhi antarmuka, tanpa ada niat untuk memperluasnya lebih jauh.
- Fungsi biasanya mengambil tipe konkret alih-alih jenis antarmuka.
Bagaimana cara melakukannya
Antarmuka di Go mendorong pendekatan malas, yang bagus. Alih-alih menulis jenis yang memenuhi antarmuka, Anda harus menulis antarmuka yang memenuhi persyaratan praktis nyata.
Yang berarti: alih-alih mendefinisikan
Animal
dalam paket
animals
, tentukan pada titik penggunaan, yaitu paket
circus
* .
package animals type Dog struct{} func (a Dog) Speaks() string { return "woof" }
package circus type Speaker interface { Speaks() string } func Perform(a Speaker) string { return a.Speaks() }
Cara yang lebih alami untuk melakukan ini adalah seperti ini:
- Tentukan Jenis
- Tentukan antarmuka pada titik penggunaan.
Pendekatan ini mengurangi ketergantungan pada komponen paket
animals
. Mengurangi ketergantungan adalah cara yang tepat untuk membangun perangkat lunak yang tahan terhadap kesalahan.
Hukum Tempat Tidur
Ada satu prinsip yang baik untuk menulis perangkat lunak yang baik. Ini adalah
hukum Postel , yang sering dirumuskan sebagai berikut:
“Jadilah konservatif tentang apa yang Anda rujuk dan liberal tentang apa yang Anda terima”
Dalam hal Go, hukumnya adalah:
“Terima antarmuka, kembalikan struktur”
Secara keseluruhan, ini adalah aturan yang sangat baik untuk merancang hal-hal yang toleran dan stabil
* . Unit utama kode dalam Go adalah fungsi. Saat mendesain fungsi dan metode, penting untuk mematuhi pola berikut:
func funcName(a INTERFACETYPE) CONCRETETYPE
Di sini kami menerima semua yang mengimplementasikan antarmuka yang bisa apa saja, termasuk yang kosong. Nilai tipe tertentu disarankan. Tentu saja, membatasi apa yang bisa masuk akal. Seperti kata pepatah Go:
"Antarmuka kosong tidak mengatakan apa-apa," Rob Pike
Oleh karena itu, sangat disarankan untuk mencegah fungsi menerima
interface{}
.
Contoh Aplikasi: Imitasi
Contoh mencolok tentang manfaat penerapan hukum Postel adalah kasus uji. Katakanlah Anda memiliki fungsi yang terlihat seperti ini:
func Takes(db Database) error
Jika
Database
adalah antarmuka, maka dalam kode uji Anda dapat dengan mudah memberikan imitasi implementasi
Database
tanpa harus melewati objek DB nyata.
Ketika definisi antarmuka terlebih dahulu dapat diterima
Sejujurnya, pemrograman adalah cara yang cukup gratis untuk mengekspresikan ide. Tidak ada aturan yang tak tergoyahkan. Tentu saja, Anda selalu dapat mendefinisikan antarmuka terlebih dahulu, tanpa takut ditangkap oleh polisi kode. Dalam konteks banyak paket, jika Anda mengetahui fungsi Anda dan berniat untuk menerima antarmuka spesifik dalam paket, maka lakukanlah.
Mendefinisikan antarmuka biasanya bernada over-engineering, tetapi ada situasi di mana Anda harus melakukan hal itu. Secara khusus, contoh-contoh berikut muncul dalam pikiran:
- Antarmuka tertutup
- Tipe data abstrak
- Antarmuka Rekursif
Selanjutnya, kami secara singkat mempertimbangkan masing-masing dari mereka.
Antarmuka tertutup
Antarmuka tertutup hanya dapat didiskusikan dalam konteks beberapa paket. Antarmuka yang disegel adalah antarmuka dengan metode yang tidak diekspor. Ini berarti bahwa pengguna di luar paket ini tidak dapat membuat jenis yang memenuhi antarmuka ini. Ini berguna untuk mengemulasi tipe varian agar dapat mencari tipe yang memuaskan antarmuka secara mendalam.
Jika Anda mendefinisikan sesuatu seperti ini:
type Fooer interface { Foo() sealed() }
Hanya paket yang ditentukan oleh
Fooer
dapat menggunakannya dan membuat sesuatu yang bernilai darinya. Ini memungkinkan Anda membuat operator switch brute-force untuk tipe.
Antarmuka yang disegel juga memungkinkan alat analisis untuk dengan mudah mengambil kecocokan pola non-tabrakan. Paket sumtypes BurntSushi bertujuan untuk mengatasi masalah ini.
Tipe data abstrak
Kasus lain mendefinisikan antarmuka sebelumnya melibatkan pembuatan tipe data abstrak. Mereka dapat disegel atau tidak disegel.
Contoh yang baik dari ini adalah paket
sort
, yang merupakan bagian dari pustaka standar. Ini mendefinisikan koleksi yang bisa diurutkan sebagai berikut
type Interface interface {
Sepotong kode ini mengecewakan banyak orang, karena jika Anda ingin menggunakan paket
sort
Anda harus menerapkan metode untuk antarmuka. Banyak yang tidak suka harus menambahkan tiga baris kode tambahan.
Namun, saya menemukan ini sebagai bentuk generik yang sangat elegan di Go. Penggunaannya harus lebih sering didorong.
Pilihan desain alternatif dan pada saat yang sama akan membutuhkan jenis pesanan yang lebih tinggi. Dalam posting ini kami tidak akan mempertimbangkannya.
Antarmuka Rekursif
Ini mungkin contoh lain dari kode dengan stok, tetapi ada kalanya tidak mungkin untuk menghindari menggunakannya. Manipulasi sederhana memungkinkan Anda untuk mendapatkan sesuatu seperti
type Fooer interface { Foo() Fooer }
Pola antarmuka rekursif jelas akan memerlukan definisi di muka. Rekomendasi definisi antarmuka titik penggunaan tidak berlaku di sini.
Pola ini berguna untuk membuat konteks dengan pekerjaan selanjutnya di dalamnya. Kode yang dimuat oleh konteks biasanya membungkus dirinya di dalam paket dengan hanya mengekspor konteks (ala paket
tensor ), oleh karena itu, dalam praktiknya, saya tidak sering menemukan kasus ini. Saya dapat memberi tahu Anda hal lain tentang pola kontekstual, tetapi tinggalkan untuk posting lain.
Kesimpulan
Terlepas dari kenyataan bahwa salah satu judul pos berbunyi "Bagaimana tidak melakukannya," saya sama sekali tidak mencoba untuk melarang apa pun. Sebaliknya, saya ingin membuat pembaca lebih sering berpikir tentang kondisi perbatasan, karena dalam kasus seperti itulah berbagai situasi darurat muncul.
Saya menemukan prinsip deklarasi penggunaan titik sangat berguna. Sebagai hasil penerapannya dalam praktik, saya tidak menemui masalah yang muncul jika saya mengabaikannya.
Namun, saya juga sesekali menulis antarmuka gaya Java. Biasanya, ini terjadi jika, tak lama sebelumnya, saya menulis banyak kode dalam Java atau Python. Keinginan untuk terlalu rumit dan "menyajikan segala sesuatu dalam bentuk kelas" kadang-kadang memanifestasikan dirinya dengan sangat kuat, terutama jika Anda menulis Go-code setelah menulis banyak kode berorientasi objek.
Dengan demikian, posting ini juga berfungsi sebagai pengingat untuk diri sendiri tentang bagaimana jalan untuk menulis kode yang nantinya tidak akan menyebabkan sakit kepala. Menunggu komentar Anda!
