Tips Buruk untuk Programer Go
Di bagian pertama publikasi, saya menjelaskan cara menjadi programmer Go "jahat". Kejahatan datang dalam banyak bentuk, tetapi dalam pemrograman itu terletak pada kesulitan yang disengaja untuk memahami dan memelihara kode. Program jahat mengabaikan sarana dasar bahasa yang mendukung teknik yang memberikan manfaat jangka pendek dengan imbalan masalah jangka panjang. Sebagai pengingat singkat, โpraktikโ jahat Go termasuk:
- Paket tidak bernama dan terorganisir
- Antarmuka tidak terorganisir dengan benar
- Melewati pointer ke variabel dalam fungsi untuk mengisi nilainya
- Menggunakan panik bukannya kesalahan
- Menggunakan fungsi init dan mengosongkan impor untuk mengkonfigurasi dependensi
- Unduh file konfigurasi menggunakan fungsi init
- Menggunakan kerangka kerja alih-alih perpustakaan
Bola kejahatan yang besar
Apa yang terjadi jika kita menggabungkan semua praktik kejahatan kita? Kami akan memiliki kerangka kerja yang akan menggunakan banyak file konfigurasi, mengisi bidang struktur menggunakan pointer, mendefinisikan antarmuka untuk menggambarkan jenis yang dipublikasikan, mengandalkan kode "ajaib" dan panik setiap kali terjadi masalah.
Dan saya berhasil. Jika Anda membuka
https://github.com/evil-go , Anda akan melihat
Fall , kerangka kerja DI yang memungkinkan Anda untuk menerapkan praktik "jahat" apa pun yang Anda inginkan. Saya telah menyolder Fall dengan kerangka kerja Outboy kecil yang mengikuti prinsip yang sama.
Anda mungkin bertanya betapa jahatnya mereka? Ayo lihat. Saya sarankan pergi untuk program Go sederhana (ditulis menggunakan praktik terbaik) yang menyediakan titik akhir http. Dan kemudian menulis ulang menggunakan Fall and Outboy.
Praktik terbaik
Program kami terletak pada satu paket yang disebut sapa, yang menggunakan semua fungsi dasar untuk mengimplementasikan titik akhir kami. Karena ini adalah contoh, kami menggunakan kerja dalam memori DAO, dengan tiga bidang untuk nilai yang akan kami kembalikan. Kami juga akan memiliki metode yang, tergantung pada input, menggantikan panggilan ke database kami dan mengembalikan salam yang diinginkan.
package greet type Dao struct { DefaultMessage string BobMessage string JuliaMessage string } func (sdi Dao) GreetingForName(name string) (string, error) { switch name { case "Bob": return sdi.BobMessage, nil case "Julia": return sdi.JuliaMessage, nil default: return sdi.DefaultMessage, nil } }
Berikutnya adalah logika bisnis. Untuk mengimplementasikannya, kami menetapkan struktur untuk menyimpan data output, antarmuka GreetingFinder untuk menggambarkan apa yang dicari logika bisnis di tingkat pencarian data, dan struktur untuk menyimpan logika bisnis itu sendiri dengan bidang untuk GreetingFinder. Logikanya sebenarnya sederhana - hanya memanggil GreetingFinder dan menangani kesalahan yang mungkin terjadi.
type Response struct { Message string } type GreetingFinder interface { GreetingForName(name string) (string, error) } type Service struct { GreetingFinder GreetingFinder } func (ssi Service) Greeting(name string)(Response, error) { msg, err := ssi.GreetingFinder.GreetingForName(name) if err != nil { return Response{}, err } return Response{Message: msg}, nil }
Kemudian datang lapisan web, dan untuk bagian ini kita mendefinisikan antarmuka Greeter, yang menyediakan semua logika bisnis yang kita butuhkan, serta struktur yang berisi http handler yang dikonfigurasi dengan Greeter. Kemudian kami membuat metode untuk mengimplementasikan antarmuka http.Handler, yang membagi permintaan http, memanggil greeter-a (ucapan), memproses kesalahan, dan mengembalikan hasilnya.
type Greeter interface { Greeting(name string) (Response, error) } type Controller struct { Greeter Greeter } func (mc Controller) ServeHTTP(rw http.ResponseWriter, req *http.Request) { result, err := mc.Greeter.Greeting( req.URL.Query().Get("name")) if err != nil { rw.WriteHeader(http.StatusInternalServerError) rw.Write([]byte(err.Error())) return } rw.Write([]byte(result.Message)) }
Ini adalah akhir dari paket salam. Selanjutnya, kita akan melihat bagaimana pengembang Go yang "baik" akan menulis utama untuk menggunakan paket ini. Dalam paket utama, kita mendefinisikan struktur yang disebut Config, yang berisi properti yang perlu kita jalankan. Fungsi utama kemudian melakukan 3 hal.
- Pertama, ia memanggil fungsi loadProperties, yang menggunakan pustaka sederhana ( https://github.com/evil-go/good-sample/blob/master/config/config.go ) untuk memuat properti dari file konfigurasi dan menempatkannya dalam salinan konfigurasi kami. Jika unduhan konfigurasi gagal, fungsi utama melaporkan kesalahan dan keluar.
- Kedua, fungsi utama mengikat komponen-komponen dalam paket ucapan, secara eksplisit menetapkan nilai-nilai dari konfigurasi dan mengatur dependensi.
- Ketiga, ia memanggil perpustakaan server kecil ( https://github.com/evil-go/good-sample/blob/master/server/server.go ) dan meneruskan alamat, metode HTTP, dan http.Handler ke endpoint untuk pemrosesan permintaan. Panggilan perpustakaan meluncurkan layanan web. Dan ini adalah seluruh aplikasi kita.
package main type Config struct { DefaultMessage string BobMessage string JuliaMessage string Path string } func main() { c, err := loadProperties() if err != nil { fmt.Println(err) os.Exit(1) } dao := greet.Dao{ DefaultMessage: c.DefaultMessage, BobMessage: c.BobMessage, JuliaMessage: c.JuliaMessage, } svc := greet.Service{GreetingFinder: dao} controller := greet.Controller{Greeter: svc} err = server.Start(server.Endpoint{c.Path, http.MethodGet, controller}) if err != nil { fmt.Println(err) os.Exit(1) } }
Contohnya cukup singkat, tetapi ini menunjukkan betapa kerennya Go ditulis; beberapa hal ambigu, tetapi secara umum jelas apa yang terjadi. Kami merekatkan perpustakaan kecil yang secara khusus diatur untuk bekerja bersama. Tidak ada yang disembunyikan; siapa pun dapat mengambil kode ini, memahami bagaimana bagian-bagiannya terhubung bersama, dan jika perlu ulang ke yang baru.
Bintik hitam
Sekarang kita akan mempertimbangkan versi Fall and Outboy. Hal pertama yang akan kita lakukan adalah memecah paket ucapan menjadi beberapa paket, yang masing-masing berisi satu lapisan aplikasi. Ini adalah paket DAO. Impor Fall, kerangka DI kami, dan karena kita "jahat" dan mendefinisikan hubungan dengan antarmuka sebaliknya, kita akan mendefinisikan antarmuka yang disebut GreetDao. Harap dicatat - kami telah menghapus semua tautan ke kesalahan; jika ada yang salah, kami hanya panik. Pada titik ini, kami sudah memiliki kemasan yang buruk, antarmuka yang buruk, dan bug yang buruk. Awal yang bagus!
Kami sedikit mengubah nama struktur kami dari contoh yang baik. Fields sekarang memiliki tag struct; mereka digunakan untuk membuat Fall menetapkan nilai terdaftar di bidang. Kami juga memiliki fungsi init untuk paket kami, yang dengannya kami mengumpulkan "kekuatan jahat". Dalam fungsi init paket, kami memanggil Fall dua kali:
- Setelah mendaftarkan file konfigurasi yang memberikan nilai untuk tag struktur.
- Dan yang lainnya, untuk mendaftarkan pointer ke instance dari struktur. Fall akan dapat mengisi kolom ini untuk kami dan membuat DAO tersedia untuk digunakan oleh kode lain.
package dao import ( "github.com/evil-go/fall" ) type GreetDao interface { GreetingForName(name string) string } type greetDaoImpl struct { DefaultMessage string `value:"message.default"` BobMessage string `value:"message.bob"` JuliaMessage string `value:"message.julia"` } func (gdi greetDaoImpl) GreetingForName(name string) string { switch name { case "Bob": return gdi.BobMessage case "Julia": return gdi.JuliaMessage default: return gdi.DefaultMessage } } func init() { fall.RegisterPropertiesFile("dao.properties") fall.Register(&greetDaoImpl{}) }
Mari kita lihat paket layanannya. Ia mengimpor paket DAO karena perlu akses ke antarmuka yang ditentukan di sana. Paket layanan juga mengimpor paket model, yang belum kami pertimbangkan - kami akan menyimpan tipe data kami di sana. Dan kami mengimpor Fall, karena, seperti semua kerangka kerja "baik", ia menembus ke mana-mana. Kami juga mendefinisikan antarmuka untuk layanan untuk memberikan akses ke lapisan web. Sekali lagi, tanpa penanganan kesalahan.
Implementasi layanan kami sekarang memiliki tag struktural dengan kawat. Bidang bertanda kawat secara otomatis menghubungkan ketergantungannya ketika struktur terdaftar di musim gugur. Dalam contoh kecil kami, jelas apa yang akan ditugaskan ke bidang ini. Namun dalam program yang lebih besar, Anda hanya akan tahu bahwa di suatu tempat antarmuka GreetDao ini diimplementasikan, dan terdaftar di Musim Gugur. Anda tidak dapat mengontrol perilaku ketergantungan.
Berikutnya adalah metode layanan kami, yang telah sedikit dimodifikasi untuk mendapatkan struktur GreetResponse dari paket model, dan yang menghilangkan penanganan kesalahan. Akhirnya, kami memiliki fungsi init dalam paket yang mendaftarkan instance layanan di Musim Gugur.
package service import ( "github.com/evil-go/fall" "github.com/evil-go/evil-sample/dao" "github.com/evil-go/evil-sample/model" ) type GreetService interface { Greeting(string) model.GreetResponse } type greetServiceImpl struct { Dao dao.GreetDao `wire:""` } func (ssi greetServiceImpl) Greeting(name string) model.GreetResponse { return model.GreetResponse{Message: ssi.Dao.GreetingForName(name)} } func init() { fall.Register(&greetServiceImpl{}) }
Sekarang mari kita lihat paket model. Tidak ada yang perlu dilihat. Dapat dilihat bahwa model dipisahkan dari kode yang membuatnya, hanya untuk membagi kode menjadi beberapa lapisan.
package model type GreetResponse struct { Message string }
Dalam paket web kami memiliki antarmuka web. Di sini kami mengimpor Fall dan Outboy, dan kami juga mengimpor paket layanan yang menjadi dasar paket web. Karena framework hanya bekerja dengan baik bersama ketika mereka terintegrasi di belakang panggung, Fall memiliki kode khusus untuk memastikan bahwa itu dan Outboy bekerja bersama. Kami juga mengubah struktur sehingga menjadi pengontrol untuk aplikasi web kami. Dia memiliki dua bidang:
- Yang pertama terhubung melalui Fall ke implementasi antarmuka GreetService dari paket layanan.
- Yang kedua adalah jalur untuk satu-satunya titik akhir web kami. Ini diberikan nilai dari file konfigurasi yang terdaftar dalam fungsi init dari paket ini.
Handler http kami telah berganti nama menjadi GetHello dan sekarang bebas dari penanganan kesalahan. Kami juga memiliki metode Init (dengan huruf kapital), yang tidak boleh dikacaukan dengan fungsi init. Init adalah metode ajaib yang disebut untuk struktur yang terdaftar di Musim Gugur setelah mengisi semua bidang. Di Init, kami memanggil Outboy untuk mendaftarkan controller dan titik akhir di jalur yang ditetapkan menggunakan Fall. Melihat kode, Anda akan melihat path dan handler, tetapi metode HTTP tidak ditentukan. Di Outboy, nama metode digunakan untuk menentukan metode HTTP mana yang ditanggapi oleh pawang. Karena metode kami disebut GetHello, ia merespons permintaan GET. Jika Anda tidak tahu aturan ini, Anda tidak akan dapat memahami permintaan apa yang dia jawab. Benar, ini sangat jahat?
Akhirnya, kita memanggil fungsi init untuk mendaftarkan file konfigurasi dan pengontrol di Musim Gugur.
package web import ( "github.com/evil-go/fall" "github.com/evil-go/outboy" "github.com/evil-go/evil-sample/service" "net/http" ) type GreetController struct { Service service.GreetService `wire:""` Path string `value:"controller.path.hello"` } func (mc GreetController) GetHello(rw http.ResponseWriter, req *http.Request) { result := mc.Service.Greeting(req.URL.Query().Get("name")) rw.Write([]byte(result.Message)) } func (mc GreetController) Init() { outboy.Register(mc, map[string]string{ "GetHello": mc.Path, }) } func init() { fall.RegisterPropertiesFile("web.properties") fall.Register(&GreetController{}) }
Tetap hanya untuk menunjukkan bagaimana kita menjalankan program. Dalam paket utama, kami menggunakan impor kosong untuk mendaftar Outboy dan paket web. Dan fungsi utama memanggil fall.Start () untuk meluncurkan seluruh aplikasi.
package main import ( _ "github.com/evil-go/evil-sample/web" "github.com/evil-go/fall" _ "github.com/evil-go/outboy" ) func main() { fall.Start() }
Gangguan pada integumen
Dan ini dia, program lengkap yang ditulis menggunakan semua alat Go jahat kita. Ini mimpi buruk. Dia secara ajaib menyembunyikan bagaimana bagian-bagian dari program tersebut cocok bersama, dan membuatnya sangat sulit untuk memahami pekerjaannya.
Namun, Anda harus mengakui bahwa ada sesuatu yang menarik tentang penulisan kode dengan Fall and Outboy. Untuk program kecil, Anda bahkan bisa mengatakan itu adalah peningkatan. Lihat betapa mudahnya mengkonfigurasi! Saya dapat menghubungkan dependensi dengan hampir tanpa kode! Saya mendaftarkan pawang untuk metode ini, hanya menggunakan namanya! Dan tanpa penanganan kesalahan, semuanya terlihat sangat bersih!
Begitulah cara kejahatan bekerja. Sekilas, ini sangat menarik. Tetapi ketika program Anda berubah dan tumbuh, semua keajaiban ini hanya mulai mengganggu, mempersulit pemahaman tentang apa yang terjadi. Hanya ketika Anda benar-benar terobsesi dengan kejahatan barulah Anda menoleh ke belakang dan menyadari bahwa Anda terjebak.
Untuk pengembang Java, ini mungkin terasa akrab. Teknik-teknik ini dapat ditemukan di banyak kerangka kerja Java populer. Seperti yang saya sebutkan sebelumnya, saya telah bekerja dengan Jawa selama lebih dari 20 tahun, mulai dari 1.0.2 pada tahun 1996. Dalam banyak kasus, pengembang Java adalah yang pertama kali menemukan masalah dalam menulis perangkat lunak perusahaan berskala besar di era Internet. Saya ingat saat ketika servlets, EJB, Spring, dan Hibernate baru saja muncul. Keputusan yang dibuat pengembang Java saat itu masuk akal. Tetapi selama bertahun-tahun, teknik ini menunjukkan usia mereka. Bahasa yang lebih baru, seperti Go, dirancang untuk menghilangkan titik nyeri yang ditemukan saat menggunakan teknik yang lebih lama. Namun, ketika pengembang Java mulai mempelajari Go dan menulis kode dengannya, mereka harus ingat bahwa mencoba mereproduksi pola dari Java akan menghasilkan hasil yang buruk.
Go dirancang untuk pemrograman serius - untuk proyek yang menjangkau ratusan pengembang dan puluhan tim. Tetapi untuk Go untuk melakukan ini, Anda harus menggunakannya dengan cara yang terbaik. Kita dapat memilih untuk menjadi jahat atau baik. Jika kita memilih yang jahat, kita dapat mendorong pengembang Go muda untuk mengubah gaya dan teknik mereka sebelum mereka memahami Go. Atau kita bisa memilih yang baik. Bagian dari pekerjaan kami sebagai pengembang Go adalah untuk mendidik Gophers muda (Gophers), untuk membantu mereka memahami prinsip-prinsip yang mendasari praktik terbaik kami.
Satu-satunya kelemahan untuk mengikuti jalan kebaikan adalah bahwa Anda harus mencari cara lain untuk mengekspresikan kejahatan batin Anda.
Mungkin mencoba mengemudi dengan kecepatan 30 km / jam di jalan raya federal?