From 67475bce52969d357b7bbbcf5eb4e3b8a22ac2d2 Mon Sep 17 00:00:00 2001 From: Noval Agung Prayogo Date: Thu, 25 Apr 2024 19:04:52 +0700 Subject: [PATCH] feat: Writing improvement section A (#265) * feat: update A.32. Buffered Channel * feat: update A.34. Channel - Range dan Close * feat: update A.34. Channel - Range dan Close * feat: update A.55. Simple Client HTTP Request * feat: A.33. Channel - Select * feat: # A.35. Channel - Timeout * feat: A.55. Simple Client HTTP Request * feat: A.48. Arguments & Flag * feat: A.62. Concurrency Pattern: Pipeline * feat: A.43. Konversi Antar Tipe Data * feat: update A.31. Channel * feat: update A.50. File * feat: semantic markdown update * feat: semantic markdown update * feat: update A.21. Fungsi Closure * feat: update A.19. Fungsi Multiple Return * feat: A.22. Fungsi Sebagai parameter * feat: writing improvement * feat: update A.20. Fungsi Variadic * feat: update exec content * feat: update defer exit content * feat: update encoding content * feat: update error panic recover content * feat: update A.18. Fungsi * feat: update A.6. Command * feat: update A.65. Go Generics * feat: update A.4. GOPATH Dan Workspace * feat: update A.30. Goroutine * feat: update A.61. Go Vendoring * feat: update A.47. Hash SHA1 * feat: update A.7. Program Pertama: Hello World * feat: update A.5. Instalasi Editor * feat: update images * feat: update submodule * feat: update A.28. Any / interface{} / Interface Kosong * feat: update go version * feat: update A.27. Interface * feat: update A.53. JSON Data * feat: update A.8. Komentar * feat: update A.11. Konstanta * feat: update map chapter * feat: update A.25. Method * feat: update A.26. Properti Public dan Private (Exported vs Unexported) * feat: update A.4. GOPATH dan Workspace * feat: A.39. Random * feat: A.29. Reflect * feat: update A.45. Regexp * feat: update A.13. Seleksi Kondisi * feat: update A.3. Go Modules * feat: update A.63. Concurrency Pattern: Simplified Fan-out Fan-in Pipeline * feat: update A.16. Slice * feat: update A.38. Layout Format String * feat: update A.44. Fungsi String * feat: update A.42. Time Duration * feat: update wording * feat: update A.56. SQL * feat: update A.10. Tipe Data * feat: update A.52. URL Parsing * feat: update A.59. WaitGroup * feat: update A.41. Timer, Ticker, & Scheduler * feat: update A.58. Unit Test * feat: update A.9. Variabel * feat: A.54. Web Service API Server * feat: A.51. Web Server * feat: update # A.40. Time, Parsing Time, & Format Time * feat: update A.24. Struct --- content/2-instalasi-golang.md | 8 +- content/A-array.md | 50 ++++---- content/A-buffered-channel.md | 12 +- content/A-channel-range-close.md | 33 ++--- content/A-channel-select.md | 8 +- content/A-channel-timeout.md | 12 +- content/A-channel.md | 39 +++--- content/A-client-http-request-simple.md | 36 +++--- content/A-command-line-args-flag.md | 22 ++-- content/A-concurrency-pipeline.md | 52 ++++---- content/A-data-type-conversion.md | 30 ++--- content/A-defer-exit.md | 10 +- content/A-encoding-base64.md | 6 +- content/A-error-panic-recover.md | 30 +++-- content/A-exec.md | 18 +-- content/A-file.md | 26 ++-- content/A-fungsi-closure.md | 42 ++++--- content/A-fungsi-multiple-return.md | 26 ++-- content/A-fungsi-sebagai-parameter.md | 20 ++-- content/A-fungsi-variadic.md | 38 +++--- content/A-fungsi.md | 51 ++++---- content/A-go-command.md | 43 +++---- content/A-go-vendoring.md | 25 ++-- content/A-golang-generics.md | 54 +++++---- content/A-gopath-dan-workspace.md | 12 +- content/A-goroutine.md | 18 +-- content/A-hash-sha1.md | 5 +- content/A-hello-world.md | 10 +- content/A-instalasi-editor.md | 4 +- content/A-interface-kosong.md | 18 +-- content/A-interface.md | 34 +++--- content/A-json.md | 28 ++--- content/A-komentar.md | 4 +- content/A-konstanta.md | 20 ++-- content/A-map.md | 48 ++++---- content/A-method.md | 57 +++++---- content/A-pipeline-context-cancellation.md | 26 ++-- content/A-properti-public-dan-private.md | 113 ++++++++++-------- content/A-random.md | 22 ++-- content/A-reflect.md | 36 +++--- content/A-regex.md | 16 +-- content/A-seleksi-kondisi.md | 27 ++--- .../A-setup-go-project-dengan-go-modules.md | 29 ++--- .../A-simplified-fan-in-fan-out-pipeline.md | 44 +++---- content/A-slice.md | 10 +- content/A-sql.md | 34 +++--- content/A-string-format.md | 2 +- content/A-strings.md | 2 +- content/A-struct.md | 40 +++---- content/A-time-duration.md | 13 +- content/A-time-parsing-format.md | 12 +- content/A-timer-ticker-scheduler.md | 8 +- content/A-tipe-data.md | 14 ++- content/A-unit-test.md | 26 ++-- content/A-url-parsing.md | 4 +- content/A-variabel.md | 28 ++--- content/A-waitgroup.md | 16 +-- content/A-web-server.md | 10 +- content/A-web-service-api.md | 11 +- content/B-golang-web-hello-world.md | 6 +- content/B-http-basic-auth.md | 4 +- content/B-simple-configuration.md | 10 +- content/B-template-functions.md | 6 +- .../C-best-practice-configuration-env-var.md | 6 +- content/C-cors-preflight-request.md | 12 +- content/C-dockerize-golang.md | 34 +++--- content/C-echo-routing.md | 16 +-- content/C-golang-aws-s3.md | 4 +- content/C-golang-grpc-protobuf.md | 14 +-- content/C-golang-jwt.md | 16 +-- content/C-golang-ldap-authentication.md | 8 +- content/C-golang-protobuf-implementation.md | 10 +- content/C-golang-sso-saml-sp.md | 4 +- content/C-https-tls.md | 8 +- .../C-parsing-http-request-payload-echo.md | 8 +- content/C-project-layout-structure.md | 2 +- content/C-secure-middleware.md | 10 +- content/C-write-pdf-file.md | 8 +- content/CONTRIBUTING.md | 2 +- ...ert-1mil-csv-record-into-db-in-a-minute.md | 20 ++-- content/SUMMARY.md | 6 +- content/images/A_generics_1.png | Bin 12552 -> 14472 bytes content/images/A_generics_2.png | Bin 15259 -> 15787 bytes examples | 2 +- 84 files changed, 880 insertions(+), 828 deletions(-) diff --git a/content/2-instalasi-golang.md b/content/2-instalasi-golang.md index 0e4feb93a..a6d02da7a 100644 --- a/content/2-instalasi-golang.md +++ b/content/2-instalasi-golang.md @@ -4,13 +4,13 @@ Hal pertama yang perlu dilakukan sebelum bisa menggunakan Go adalah meng-*instal Di sini penulis mencoba meringkas petunjuk instalasi pada *link* di atas, agar lebih mudah untuk diikuti terutama untuk pembaca yang baru belajar. -> Go yang digunakan adalah versi **1.20**, direkomendasikan menggunakan versi tersebut. +> Go yang digunakan adalah versi **1.22**, direkomendasikan menggunakan versi tersebut. URL untuk mengunduh *installer* Go: https://golang.org/dl/. Silakan langsung unduh dari *link* tersebut lalu lakukan proses instalasi, atau bisa mengikuti petunjuk pada chapter ini. ## A.2.1. Instalasi Go *Stable* -#### • Instalasi Go di Windows +#### ◉ Instalasi Go di Windows 1. Download terlebih dahulu *installer*-nya di [https://golang.org/dl/](https://golang.org/dl/). Pilih *installer* untuk sistem operasi Windows sesuai jenis bit yang digunakan. @@ -26,7 +26,7 @@ URL untuk mengunduh *installer* Go: https://golang.org/dl/. Silakan langsung und > Sering terjadi, command `go version` tidak bisa dijalankan meskipun instalasi sukses. Solusinya bisa dengan restart CMD (tutup CMD, kemudian buka lagi). Setelah itu coba jalankan ulang command di atas. -#### • Instalasi Go di MacOS +#### ◉ Instalasi Go di MacOS Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh/). @@ -57,7 +57,7 @@ Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh 5. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil. -#### • Instalasi Go di Linux +#### ◉ Instalasi Go di Linux 1. Unduh arsip *installer* dari [https://golang.org/dl/](https://golang.org/dl/), pilih installer untuk Linux yang sesuai dengan jenis bit komputer anda. Proses download bisa dilakukan lewat CLI, menggunakan `wget` atau `curl`. diff --git a/content/A-array.md b/content/A-array.md index 10af138d1..2d4e75f32 100644 --- a/content/A-array.md +++ b/content/A-array.md @@ -1,6 +1,8 @@ # A.15. Array -Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan. Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0. +Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan. + +Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0. Contoh penerapan array: @@ -14,13 +16,13 @@ names[3] = "law" fmt.Println(names[0], names[1], names[2], names[3]) ``` -Variabel `names` dideklarasikan sebagai `array string` dengan alokasi elemen `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya. +Variabel `names` dideklarasikan sebagai `array string` dengan alokasi kapasitas elemen adalah `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya. ![Menampilkan elemen array](images/A_array_0_array.png) ## A.15.1. Pengisian Elemen Array yang Melebihi Alokasi Awal -Pengisian elemen array pada indeks yang tidak sesuai dengan alokasi menghasilkan error. Contoh sederhana, jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid. +Pengisian elemen array pada indeks yang tidak sesuai dengan jumlah alokasi menghasilkan error. Contoh: jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid. ```go var names [4]string @@ -31,7 +33,7 @@ names[3] = "law" names[4] = "ez" // baris kode ini menghasilkan error ``` -Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang nantinya pada chapter selanjutnya ([A.16. Slice](/A-slice.html)) akan kita bahas. +Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang pembahasannya ada pada chapter selanjutnya, ([A.16. Slice](/A-slice.html)). ## A.15.2. Inisialisasi Nilai Awal Array @@ -44,11 +46,11 @@ fmt.Println("Jumlah element \t\t", len(fruits)) fmt.Println("Isi semua element \t", fruits) ``` -Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, akan menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini biasa digunakan untuk *debugging* data array. +Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini umum digunakan untuk keperluan *debugging* data array. ![Menghitung jumlah elemen dan menampilkan isi array](images/A_array_1_array_initialization_and_len.png) -Fungsi `len()` dipakai untuk menghitung jumlah elemen sebuah array. +Fungsi `len()` berfungsi untuk menghitung jumlah elemen sebuah array. ## A.15.3. Inisialisasi Nilai Array Dengan Gaya Vertikal @@ -69,11 +71,11 @@ fruits = [4]string{ } ``` -Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah elemen, termasuk elemen terakhir. Jika tidak, maka akan muncul error. +Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah setiap elemen (termasuk elemen terakhir), agar tidak memunculkan syntax error. ## A.15.4. Inisialisasi Nilai Awal Array Tanpa Jumlah Elemen -Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Jumlah elemen akan di kalkulasi secara otomatis menyesuaikan data elemen yang diisikan. +Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Metode penulisan ini membuat kapasitas array otomatis dihitung dari jumlah elemen array yang ditulis. ```go var numbers = [...]int{2, 3, 2, 4, 3} @@ -82,17 +84,19 @@ fmt.Println("data array \t:", numbers) fmt.Println("jumlah elemen \t:", len(numbers)) ``` -Variabel `numbers` akan secara otomatis memiliki jumlah elemen `5`, karena pada saat deklarasi disiapkan 5 buah elemen. +Variabel `numbers` secara otomatis kapasitas elemennya adalah `5`. ![Deklarasi array menggunakan tanda 3 titik](images/A_array_1_1_array_dots.png) ## A.15.5. Array Multidimensi -Array multidimensi adalah array yang tiap elemennya juga berupa array (dan bisa seterusnya, tergantung ke dalaman dimensinya). +Array multidimensi adalah array yang tiap elemennya juga berupa array. + +> Level kedalaman array multidimensi adalah tidak terbatas, bisa saja suatu array berisi elemen array yang setiap elemennya juga adalah nilai array, dst. -Cara deklarasi array multidimensi secara umum sama dengan cara deklarasi array biasa, dengan cara menuliskan data array dimensi selanjutnya sebagai elemen array dimensi sebelumnya. +Cara deklarasi array multidimensi secara umum sama dengan array biasa, bedanya adalah pada array biasa, setiap elemen berisi satu nilai, sedangkan pada array multidimensi setiap elemen berisi array. -Khusus untuk array yang merupakan sub dimensi atau elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut. +Khusus penulisan array yang merupakan subdimensi/elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut. ```go var numbers1 = [2][3]int{[3]int{3, 2, 3}, [3]int{3, 4, 5}} @@ -102,13 +106,13 @@ fmt.Println("numbers1", numbers1) fmt.Println("numbers2", numbers2) ``` -Kedua array di atas memiliki elemen yang sama. +Kedua array di atas memiliki jumlah dan isi elemen yang sama. ![Array multidimensi](images/A_array_2_array_multidimension.png) ## A.15.6. Perulangan Elemen Array Menggunakan Keyword `for` -Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan menggunakan keyword ini, elemen-elemen dalam array bisa didapat. +Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan/looping menggunakan keyword ini, elemen-elemen dalam array bisa didapat. Ada beberapa cara yang bisa digunakan untuk me-looping data array, yg pertama adalah dengan memanfaatkan variabel iterasi perulangan untuk mengakses elemen berdasarkan indeks-nya. Contoh: @@ -126,7 +130,7 @@ Perulangan di atas dijalankan sebanyak jumlah elemen array `fruits` (bisa diketa ## A.15.7. Perulangan Elemen Array Menggunakan Keyword `for` - `range` -Ada cara yang lebih sederhana me-looping data array, dengan menggunakan keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut. +Ada cara lain yang lebih sederhana untuk operasi perulangan array, yaitu menggunakan kombinasi keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut. ```go var fruits = [4]string{"apple", "grape", "banana", "melon"} @@ -138,13 +142,11 @@ for i, fruit := range fruits { Array `fruits` diambil elemen-nya secara berurutan. Nilai tiap elemen ditampung variabel oleh `fruit` (tanpa huruf s), sedangkan indeks nya ditampung variabel `i`. -Output program di atas, sama dengan output program sebelumnya, hanya cara yang digunakan berbeda. +Output program di atas, sama persis dengan output program sebelumnya, hanya saja cara yang diterapkan berbeda. ## A.15.8. Penggunaan Variabel Underscore `_` Dalam `for` - `range` -Kadang kala ketika *looping* menggunakan `for` - `range`, ada kemungkinan di mana data yang dibutuhkan adalah elemen-nya saja, indeks-nya tidak. Sedangkan kode di atas, `range` mengembalikan 2 data, yaitu indeks dan elemen. - -Seperti yang sudah diketahui, bahwa di Go tidak memperbolehkan adanya variabel yang menganggur atau tidak dipakai. Jika dipaksakan, error akan muncul, contohnya seperti kode berikut. +Terkadang, dalam penerapan *looping* menggunakan `for` - `range`, ada kebutuhan di mana yang dibutuhkan dari perulangan adlah adalah elemen-nya saja, sedangkan indeks-nya tidak, contoh: ```go var fruits = [4]string{"apple", "grape", "banana", "melon"} @@ -154,11 +156,11 @@ for i, fruit := range fruits { } ``` -Hasil dari kode program di atas: +Hasil dari kode program di atas adalah error, karena Go tidak memperbolehkan adanya variabel yang menganggur atau tidak dipakai. ![Error karena ada variabel yang tidak digunakan](images/A_array_4_for_range_error.png) -Di sinilah salah satu kegunaan variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore. +Di sinilah salah satu kegunaan dari variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore. ```go var fruits = [4]string{"apple", "grape", "banana", "melon"} @@ -172,7 +174,7 @@ Pada kode di atas, yang sebelumnya adalah variabel `i` diganti dengan `_`, karen ![For range tanpa indeks](images/A_array_5_for_range_underscore.png) -Jika yang dibutuhkan hanya indeks elemen-nya saja, bisa gunakan 1 buah variabel setelah keyword `for`. +Bagaiamana jika sebaliknya? Misal, yang dibutuhkan hanya indeks-nya saja, nilainya tidak penting. Maka cukup tulis satu variabel saja setelah keyword `for`, yaitu variabel penampung nilai indeks. ```go for i, _ := range fruits { } @@ -182,7 +184,7 @@ for i := range fruits { } ## A.15.9. Alokasi Elemen Array Menggunakan Keyword `make` -Deklarasi sekaligus alokasi data array juga bisa dilakukan lewat keyword `make`. +Deklarasi sekaligus alokasi kapasitas array juga bisa dilakukan lewat keyword `make`. ```go var fruits = make([]string, 2) @@ -192,7 +194,7 @@ fruits[1] = "manggo" fmt.Println(fruits) // [apple manggo] ``` -Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan alokasi 2 slot. +Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan kapasitas alokasi 2 slot. --- diff --git a/content/A-buffered-channel.md b/content/A-buffered-channel.md index 52a3cd2f8..b5e16bf82 100644 --- a/content/A-buffered-channel.md +++ b/content/A-buffered-channel.md @@ -1,12 +1,12 @@ # A.32. Buffered Channel -Proses transfer data pada channel secara default dilakukan dengan cara **un-buffered**, atau tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan penerimaan data tidak akan di proses sebelum proses serah-terima itu sendiri selesai. +Proses transfer data pada channel secara default dilakukan dengan metode **un-buffered** atau tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan juga penerimaan data tidak akan diproses sebelum proses serah-terima itu sendiri selesai. Buffered channel sedikit berbeda. Pada channel jenis ini, ditentukan angka jumlah buffer-nya. Angka tersebut menjadi penentu jumlah data yang bisa dikirimkan bersamaan. Selama jumlah data yang dikirim tidak melebihi jumlah buffer, maka pengiriman akan berjalan **asynchronous** (tidak blocking). Ketika jumlah data yang dikirim sudah melewati batas buffer, maka pengiriman data hanya bisa dilakukan ketika salah satu data yang sudah terkirim adalah sudah diambil dari channel di goroutine penerima, sehingga ada slot channel yang kosong. -Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous*. +Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous* atau blocking. ![Analogi buffered channel](images/A_buffered_channel_1_anatomy.png) @@ -49,21 +49,21 @@ func main() { Pada kode di atas, parameter kedua fungsi `make()` adalah representasi jumlah buffer. Perlu diperhatikan bahwa nilai buffered channel dimulai dari `0`. Ketika nilainya adalah **3** berarti jumlah buffer maksimal ada **4**. -Bisa dilihat terdapat IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial. +Terdapat juga IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial. ![Implementasi buffered channel](images/A_buffered_channel_2_buffered_channel.png) -Bisa dilihat output di atas, pada proses pengiriman data ke-4, diikuti dengan proses penerimaan data; yang kedua proses tersebut berlangsung secara blocking. +Terlihat di output, proses pengiriman data indeks ke-4 adalah diikuti dengan proses penerimaan data yang proses transfernya sendiri dilakukan *syncrhonous* atau blocking. Pengiriman data indeks ke 0, 1, 2 dan 3 akan berjalan secara asynchronous, hal ini karena channel ditentukan nilai buffer-nya sebanyak 3 (ingat, jika nilai buffer adalah 3, maka 4 data yang akan di-buffer). Pengiriman selanjutnya (indeks 5) hanya akan terjadi jika ada salah satu data dari ke-empat data yang sebelumnya telah dikirimkan sudah diterima (dengan serah terima data yang bersifat blocking). Setelahnya, pengiriman data kembali dilakukan secara asynchronous (karena sudah ada slot buffer ada yang kosong). Karena pengiriman dan penerimaan data via buffered channel terjadi tidak selalu synchronous (tergantung jumlah buffer-nya), maka ada kemungkinan dimana eksekusi program selesai namun tidak semua data diterima via channel `messages`. Karena alasan ini pada bagian akhir ditambahkan statement `time.Sleep(1 * time.Second)` agar ada jeda 1 detik sebelum program selesai. -#### • Fungsi `time.Sleep()` +#### ◉ Fungsi `time.Sleep()` Fungsi ini digunakan untuk menambahkan delay sebelum statement berikutnya dieksekusi. Durasi delay ditentukan oleh parameter, misal `1 * time.Second` maka durasi delay adalah 1 detik. -Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada chapter terpisah, yaitu [Time Duration](https://dasarpemrogramangolang.novalagung.com/A-time-duration.html). +Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada chapter terpisah, yaitu [Time Duration](/A-time-duration.html). --- diff --git a/content/A-channel-range-close.md b/content/A-channel-range-close.md index 6f6b3357e..9ce0ee6a7 100644 --- a/content/A-channel-range-close.md +++ b/content/A-channel-range-close.md @@ -1,16 +1,17 @@ # A.34. Channel - Range dan Close -Proses *retrieving* data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`. +Proses penerimaan/*retrieving* data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`. Penerapannnya cukup mudah, yaitu dengan menuliskan keyword `for` - `range` pada variabel channel. -`for` - `range` jika diterapkan pada channel berfungsi untuk handle penerimaan data. Setiap kali ada pengiriman data via channel, maka akan men-trigger perulangan `for` - `range`. Perulangan akan berlangsung terus-menerus seiring pengiriman data ke channel yang dipergunakan. Dan perulangan hanya akan berhenti jika channel yang digunakan tersebut di **close** atau di non-aktifkan. Fungsi `close` digunakan utuk me-non-aktifkan channel. +Cara kerjanya: -Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data, itulah mengapa perulangan `for` - `range` juga berhenti. +- Transaksi data via channel men-trigger perulangan `for` - `range`. Perulangan akan berlangsung seiring terjadinya pengiriman data ke channel yang di-iterasi. +- Perulangan tersebut hanya akan berhenti jika channel di-**close** atau di non-aktifkan via fungsi `close()`. Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data. -## A.34.1. Penerapan `for` - `range` - `close` Pada Channel +## A.34.1. Penerapan `for` - `range` - `close` -Berikut adalah contoh program menggunakan `for` - `range` untuk menerima data dari channel. +Berikut adalah contoh program pengaplikasian `for`, `range`, dan `close` untuk penerimaan data dari channel. -Ok, pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Di dalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim. +Pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Di dalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim. ```go func sendMessage(ch chan<- string) { @@ -21,7 +22,7 @@ func sendMessage(ch chan<- string) { } ``` -Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Di dalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di tiap looping, data yang diterima dari channel ditampilkan. +Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Di dalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di setiap iterasi, data yang diterima dari channel ditampilkan. ```go func printMessage(ch <-chan string) { @@ -31,7 +32,7 @@ func printMessage(ch <-chan string) { } ``` -Buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine (dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru). Tak lupa jalankan juga fungsi `printMessage()`. +Selanjutnya, buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine. Dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru. Tak lupa jalankan juga fungsi `printMessage()`. ```go func main() { @@ -43,25 +44,25 @@ func main() { } ``` -Setelah 20 data sukses dikirim dan diterima, channel `ch` di-non-aktifkan (`close(ch)`). Membuat perulangan data channel dalam `printMessage()` juga akan berhenti. +Setelah 20 data yang dikirim sukses diterima, channel `ch` di-non-aktifkan dengan adanya statement `close(ch)`. Statement tersebut menghentikan perulangan channel dalam `printMessage()`. ![Penerapan for-range-close pada channel](images/A_channel_range_close_1_for_range_close.png) ---- +## A.34.2. Penjelasan tambahan -Berikut adalah penjelasan tambahan mengenai channel. +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: -## A.34.1.1. Channel Direction +#### ◉ Channel Direction -Ada yang unik dengan fitur parameter channel yang disediakan oleh Go. Level akses channel bisa ditentukan, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**. +Go mendesain API channel untuk mendukung level akses channel, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**. Cara pemberian level akses adalah dengan menambahkan tanda `<-` sebelum atau setelah keyword `chan`. Untuk lebih jelasnya bisa dilihat di list berikut. | Sintaks | Penjelasan | | :------- | :--------- | -| `ch chan string` | Parameter `ch` bisa digunakan untuk **mengirim** dan **menerima** data | -| `ch chan<- string` | Parameter `ch` hanya bisa digunakan untuk **mengirim** data | -| `ch <-chan string` | Parameter `ch` hanya bisa digunakan untuk **menerima** data | +| `ch chan string` | Parameter `ch` untuk **mengirim** dan **menerima** data | +| `ch chan<- string` | Parameter `ch` hanya untuk **mengirim** data | +| `ch <-chan string` | Parameter `ch` hanya untuk **menerima** data | Pada kode di atas bisa dilihat bahwa secara default channel akan memiliki kemampuan untuk mengirim dan menerima data. Untuk mengubah channel tersebut agar hanya bisa mengirim atau menerima saja, dengan memanfaatkan simbol `<-`. diff --git a/content/A-channel-select.md b/content/A-channel-select.md index 3f45edaf0..07b9603bd 100644 --- a/content/A-channel-select.md +++ b/content/A-channel-select.md @@ -1,16 +1,16 @@ # A.33. Channel - Select -Disediakannya channel membuat engineer menjadi mudah dalam me-manage goroutine. Namun perlu di-ingat, meskipun lewat channel manajemen goroutine menjadi mudah, fungsi utama channel bukan untuk kontrol, melainkan untuk sharing data antar goroutine. +Channel membuat manajemen goroutine menjadi sangat mudah di Go. Namun perlu di-ingat, fungsi utama channel adalah bukan untuk kontrol eksekusi goroutine, melainkan untuk sharing data atau komunikasi goroutine. -> Nantinya pada chapter [A.59. sync.WaitGroup](/A-waitgroup.html) akan dibahas secara komprehensif bagaimana cara optimal mengontrol goroutine. +> Pada chapter [A.59. sync.WaitGroup](/A-waitgroup.html) akan dibahas secara komprehensif tentang cara yang lebih optimal untuk kontrol eksekusi goroutine. -Ada kalanya kita butuh tak hanya satu channel saja untuk melakukan komunikasi data antar goroutine. Tergantung jenis kasusnya, sangat mungkin lebih dari satu channel dibutuhkan. Nah, di situlah kegunaan dari `select`. Select ini mempermudah kontrol komunikasi data lewat satu ataupun banyak channel. +Tergantung jenis kasusnya, ada kalanya kita butuh lebih dari satu channel untuk komunikasi data antar goroutine. Penerimaan data pada banyak goroutine penerapannya masih sama, yaitu dengan menambahkan karakter `<-` pada statement. Selain itu, ada juga cara lain yaitu menggunakan keyword `select`. Keyword ini mempermudah kontrol penerimaan data via satu atau lebih dari satu channel. Cara penggunaan `select` untuk kontrol channel sama seperti penggunaan `switch` untuk seleksi kondisi. ## A.33.1. Penerapan Keyword `select` -Program berikut merupakan contoh sederhana penerapan select dalam channel. Dipersiapkan 2 buah goroutine, satu untuk pencarian rata-rata, dan satu untuk nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`. +Program berikut merupakan contoh sederhana penerapan keyword `select`. Di sini disiapkan 2 buah goroutine, satu untuk menghitung rata-rata dari data array numerik, dan satu lagi untuk pencarian nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`. Ok, langsung saja kita praktek. Pertama, siapkan 2 fungsi yang sudah dibahas di atas. Fungsi pertama digunakan untuk mencari rata-rata, dan fungsi kedua untuk penentuan nilai tertinggi dari sebuah slice. diff --git a/content/A-channel-timeout.md b/content/A-channel-timeout.md index aaa154359..a76d38bae 100644 --- a/content/A-channel-timeout.md +++ b/content/A-channel-timeout.md @@ -1,12 +1,12 @@ # A.35. Channel - Timeout -Teknik channel timeout digunakan untuk mengontrol penerimaan data dari channel mengacu ke kapan waktu diterimanya data, dengan durasi timeout bisa kita tentukan sendiri. +Teknik channel timeout digunakan untuk kontrol waktu penerimaan data pada channel, berapa lama channel tersebut harus menunggu hingga akhirnya suatu penerimaan data dianggap timeout. -Ketika tidak ada aktivitas penerimaan data dalam durasi yang sudah ditentukan, maka blok timeout dieksekusi. +Durasi penerimaan kita tentukan sendiri. Ketika tidak ada aktivitas penerimaan data dalam durasi tersebut, blok timeout dijalankan. ## A.35.1. Penerapan Channel Timeout -Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine baru dijalankan dengan tugas mengirimkan data setiap interval tertentu, dengan durasi interval-nya sendiri adalah acak/random. +Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine dijalankan dengan tugas adalah mengirimkan data secara berulang dalam interval tertentu, dengan durasi interval-nya sendiri adalah acak/random. ```go package main @@ -45,8 +45,8 @@ func retreiveData(ch <-chan int) { Ada 2 blok kondisi pada `select` tersebut. - - Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`. - - Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout. +- Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`. +- Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout. Terakhir, kedua fungsi tersebut dipanggil di `main()`. @@ -61,7 +61,7 @@ func main() { } ``` -Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika tidak ada aktivitas penerimaan dari channel dalam durasi 5 detik, perulangan pengecekkan channel diberhentikan. +Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika dalam durasi 5 detik tidak ada aktivitas penerimaan sama sekali, maka dianggap timeout dan perulangan pengecekkan channel dihentikan. ![Channel timeout](images/A_channel_timeout_1_channel_delay.png) diff --git a/content/A-channel.md b/content/A-channel.md index 1a65b1e28..583ada27a 100644 --- a/content/A-channel.md +++ b/content/A-channel.md @@ -1,16 +1,18 @@ # A.31. Channel -**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain. Mekanisme yang dilakukan adalah serah-terima data lewat channel tersebut. Dalam komunikasinya, sebuah channel difungsikan sebagai pengirim di sebuah goroutine, dan juga sebagai penerima di goroutine lainnya. Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**. +**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain dengan mekanisme serah terima data, jadi harus ada data yang dikirim dari goroutine A untuk kemudian diterima di goroutine B. -![Analogi channel](images/A_channel_1_analogy.png) +Peran channel adalah sebagai media perantara bagi pengirim data dan juga penerima data. Jadi channel adalah *thread safe*, aman digunakan di banyak goroutine. + +Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**. Artinya, statement di-bawah syntax pengiriman dan penerimaan data via channel hanya akan dieksekusi setelah proses serah terima berlangsung dan selesai. -Pada chapter ini kita akan belajar mengenai pemanfaatan channel. +![Analogi channel](images/A_channel_1_analogy.png) ## A.31.1. Penerapan Channel -Channel merupakan sebuah variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`. Variabel channel memiliki satu tugas, menjadi pengirim, atau penerima data. +Channel berbentuk variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`. -Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Data tersebut akan diterima 3 kali di goroutine utama `main`. +Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Kesemua data tersebut nantinya diterima oleh di goroutine utama yaitu proses yang dijalankan di dalam blok fungsi `main()`. ```go package main @@ -43,7 +45,7 @@ func main() { } ``` -Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Cara pembuatan channel yaitu dengan menuliskan keyword `make` dengan isi keyword `chan` diikuti dengan tipe data channel yang diinginkan. +Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Contoh cara pembuatan channel bisa dilihat di situ, yaitu dengan memanggil fungsi `make()` dengan isi adalah keyword `chan` diikuti dengan tipe data channel yang diinginkan. ```go var messages = make(chan string) @@ -75,21 +77,24 @@ var message1 = <-messages fmt.Println(message1) ``` -Penerimaan channel bersifat blocking. Artinya statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel. +Penerimaan channel bersifat blocking. Artinya: -Ke semua data yang dikirim dari tiga goroutine berbeda tersebut datanya akan diterima secara berurutan oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan. +- Statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel. +- Berlaku juga dengan statement `messages <- data`. Statement dibawahnya tidak akan dieksekusi hingga data yang dikirim ke channel `messages` benar-benar diterima oleh penerima, yaitu variabel `message1`. + +Ke semua data yang dikirim dari tiga goroutine berbeda tersebut nantinya diterima oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan. ![Implementasi channel](images/A_channel_2_channel.png) Dari screenshot output di atas bisa dilihat bahwa text yang dikembalikan oleh `sayHelloTo` tidak selalu berurutan, meskipun penerimaan datanya adalah berurutan. Hal ini dikarenakan, pengiriman data adalah dari 3 goroutine yang berbeda, yang kita tidak tau mana yang prosesnya selesai lebih dulu. Goroutine yang dieksekusi lebih awal belum tentu selesai lebih awal, yang jelas proses yang selesai lebih awal datanya akan diterima lebih awal. -Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi sejenis `fmt.Scanln()` atau lainnya, untuk mengantisipasi goroutine utama `main` selesai sebelum ketiga goroutine di atas selesai. +Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi seperti `fmt.Scanln()` (atau lainnya) untuk mengantisipasi goroutine utama (yaitu `main`) selesai sebelum ketiga goroutine di atas selesai. ## A.31.2. Channel Sebagai Tipe Data Parameter -Variabel channel bisa di-pass ke fungsi lain sebagai parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan. +Variabel channel bisa di-pass ke fungsi lain via parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan. -Siapkan fungsi `printMessage` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan. +Siapkan fungsi `printMessage()` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan. ```go func printMessage(what chan string) { @@ -97,7 +102,7 @@ func printMessage(what chan string) { } ``` -Setelah itu ubah implementasi di fungsi `main`. +Setelah itu ubah implementasi di fungsi `main()`. ```go func main() { @@ -120,17 +125,17 @@ func main() { Output program di atas sama dengan program sebelumnya. -Parameter `what` fungsi `printMessage` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main`. +Parameter `what` pada fungsi `printMessage()` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main()`. Passing data bertipe channel lewat parameter sifatnya **pass by reference**, yang di transferkan adalah pointer datanya, bukan nilai datanya. ![Parameter channel](images/A_channel_3_channel_param.png) ---- +## A.32.3. Penjelasan tambahan -Berikut merupakan penjelasan tambahan untuk kode di atas. +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: -#### • Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi +#### ◉ Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi Data slice yang baru di inisialisasi bisa langsung di-iterasi, caranya mudah dengan menuliskannya langsung setelah keyword `range`. @@ -140,7 +145,7 @@ for _, each := range []string{"wick", "hunt", "bourne"} { } ``` -#### • Eksekusi Goroutine Pada IIFE +#### ◉ Eksekusi Goroutine Pada IIFE Eksekusi goroutine tidak harus pada fungsi atau closure yang sudah terdefinisi. Sebuah IIFE juga bisa dijalankan sebagai goroutine baru. Caranya dengan langsung menambahkan keyword `go` pada waktu deklarasi-eksekusi IIFE-nya. diff --git a/content/A-client-http-request-simple.md b/content/A-client-http-request-simple.md index d175f667b..91b737d3f 100644 --- a/content/A-client-http-request-simple.md +++ b/content/A-client-http-request-simple.md @@ -1,6 +1,6 @@ # A.55. Simple Client HTTP Request -Pada chapter sebelumnya telah dibahas bagaimana membuat Web Service API yang mem-provide data JSON, pada chapter ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut. +Pada chapter sebelumnya telah dibahas bagaimana cara membuat Web Service API yang response data-nya berbentuk JSON. Pada chapter ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut. Pastikan anda sudah mempraktekkan apa-apa yang ada pada chapter sebelumnya ([A.54. Web Service API Server](/A-web-service-api.html)), karena web service yang telah dibuat di situ juga dipergunakan pada chapter ini. @@ -8,9 +8,9 @@ Pastikan anda sudah mempraktekkan apa-apa yang ada pada chapter sebelumnya ([A.5 ## A.55.1. Penggunaan HTTP Request -Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini. +Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini. Untuk menggunakannya pastikan import package-nya terlebih dahulu. -Sebelumnya, import package yang dibutuhkan. Dan siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada chapter ([A.54. Web Service API Server](/A-web-service-api.html)). +Kemudian siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada chapter ([A.54. Web Service API Server](/A-web-service-api.html)). ```go package main @@ -60,19 +60,19 @@ Statement `&http.Client{}` menghasilkan instance `http.Client`. Objek ini nantin Fungsi `http.NewRequest()` digunakan untuk membuat request baru. Fungsi tersebut memiliki 3 parameter yang wajib diisi. - 1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya - 2. Parameter kedua, adalah URL tujuan request - 3. Parameter ketiga, form data request (jika ada) +1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya +2. Parameter kedua, adalah URL tujuan request +3. Parameter ketiga, form data request (jika ada) -Fungsi tersebut menghasilkan instance bertipe `http.Request`. Objek tersebut nantinya disisipkan pada saat eksekusi request. +Fungsi tersebut menghasilkan instance bertipe `http.Request` yang nantinya digunakan saat eksekusi request. -Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada instance `http.Client` yang sudah dibuat, dengan parameter adalah instance request-nya. Contohnya seperti pada `client.Do(request)`. +Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada variabel `client` yang sudah dibuat. Fungsi `Do()` dipanggil dengan disisipkan argument fungsi yaitu object `request`. Penulisannya: `client.Do(request)`. -Method tersebut mengembalikan instance bertipe `http.Response`, yang di dalamnya berisikan informasi yang dikembalikan dari web API. +Method tersebut mengembalikan instance bertipe `http.Response` yang di contoh ditampung oleh variabel `response`. Dari data response tersebut kita bisa mengakses informasi yang berhubungan dengan HTTP response, termasuk response body. -Data response bisa diambil lewat property `Body` dalam bentuk string. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`. Setelah itu barulah kita bisa menampilkannya. +Data response body tersedia via property `Body` dalam tipe `[]byte`. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`. -Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya seperti pada kode `defer response.Body.Close()`. +Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya dengan memanggil method `Close()` milik property `Body` yang dalam penerapannya umumnya di-defer. Contohnya: `defer response.Body.Close()`. Selanjutnya, eksekusi fungsi `fetchUsers()` dalam fungsi `main()`. @@ -90,20 +90,20 @@ func main() { } ``` -Ok, terakhir sebelum memulai testing, pastikan telah run aplikasi pada chapter sebelumya ([A.54. Web Service API Server](/A-web-service-api.html)). Kemudian start prompt cmd/terminal baru dan jalankan program yang barusan kita buat pada chapter ini. +Ok, terakhir sebelum memulai testing, pastikan telah run aplikasi pada chapter sebelumya ([A.54. Web Service API Server](/A-web-service-api.html)). Setelah itu start prompt cmd/terminal baru dan jalankan program yang telah dibuat di chapter ini. ![HTTP Request](images/A_http_request_1_http_request.png) ## A.55.2. HTTP Request Dengan Form Data -Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Yang pertama, import beberapa package lagi, `bytes` dan `net/url`. +Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Pertama, import package `bytes` dan `net/url`. ```go import "bytes" import "net/url" ``` -Buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`. +Kemudian buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`. ```go func fetchUser(ID string) (student, error) { @@ -136,13 +136,13 @@ func fetchUser(ID string) (student, error) { } ``` -Isi fungsi di atas bisa dilihat memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya. +Isi fungsi `fetchUser()` memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya. Statement `url.Values{}` akan menghasilkan objek yang nantinya digunakan sebagai form data request. Pada objek tersebut perlu di set data apa saja yang ingin dikirimkan menggunakan fungsi `Set()` seperti pada `param.Set("id", ID)`. -Statement `bytes.NewBufferString(param.Encode())` maksudnya, objek form data di-encode lalu diubah menjadi bentuk `bytes.Buffer`, yang nantinya disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`. +Statement `bytes.NewBufferString(param.Encode())` melakukan proses encoding pada data param untuk kemudian diubah menjadi bentuk `bytes.Buffer`. Nantinya data buffer tersebut disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`. -Karena data yang akan dikirim di-encode, maka pada header perlu di set tipe konten request-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` artinya tipe konten request di set sebagai `application/x-www-form-urlencoded`. +Karena data yang akan dikirim adalah *encoded*, maka pada header perlu dituliskan juga tipe encoding-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` menandai bahwa HTTP request berisi body yang ter-encode sesuai spesifikasi `application/x-www-form-urlencoded`. > Pada konteks HTML, HTTP Request yang di trigger dari tag `
` secara default tipe konten-nya sudah di set `application/x-www-form-urlencoded`. Lebih detailnya bisa merujuk ke spesifikasi HTML form [http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1) @@ -168,7 +168,7 @@ Untuk keperluan testing, kita hardcode `ID` nilainya `"E001"`. Jalankan program ## A.55.3. Secure & Insecure HTTP Request -Kita telah mempelajari bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada chapter [C.27. Secure & Insecure Client HTTP Request](/C-secure-insecure-client-http-request.html) kita akan belajar cara membuat http client request yang lebih *njlimet* untuk kasus yang lebih advance, tapi sabar dulu hehe. +Sampai sini kita telah belajar bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada chapter [C.27. Secure & Insecure Client HTTP Request](/C-secure-insecure-client-http-request.html) pembelajaran topik HTTP request dilanjutkan kembali, kita akan bahas tentang aspek keamanan/security suatu HTTP request. --- diff --git a/content/A-command-line-args-flag.md b/content/A-command-line-args-flag.md index 1fea223a4..d79470285 100644 --- a/content/A-command-line-args-flag.md +++ b/content/A-command-line-args-flag.md @@ -1,14 +1,12 @@ # A.48. Arguments & Flag -**Arguments** adalah data opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur. +**Arguments** adalah data argument opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur. -Pada chapter ini kita akan belajar tentang penggunaan arguments dan flag. +Pada chapter ini kita akan belajar tentang penerapan arguments dan flag. ## A.48.1. Penggunaan Arguments -Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array dengan pemisah adalah tanda spasi. - -Berikut merupakan contoh penggunaannya. +Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array. Setiap data argument yang disisipkan saat pemanggilan program, datanya dipecah menggunakan karakter spasi lalu di-map ke bentuk array. Contoh penerapan: ```go package main @@ -27,9 +25,7 @@ func main() { } ``` -Pada saat eksekusi program disisipkan juga argument-nya. Sebagai contoh disisipkan 3 buah data sebagai argumen, yaitu: `banana`, `potato`, dan `ice cream`. - -Untuk eksekusinya sendiri bisa menggunakan `go run` ataupun dengan cara build-execute. +Argument disisipkan saat eksekusi program. Sebagai contoh, kita ingin menyisipkan 3 buah argumen berikut: `banana`, `potato`, dan `ice cream`. Maka penulisan saat pemanggilan program-nya seperti ini: - Menggunakan `go run` @@ -44,11 +40,13 @@ Untuk eksekusinya sendiri bisa menggunakan `go run` ataupun dengan cara build-ex $ ./bab45 banana potato "ice cream" ``` -Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Gunakan `os.Args[1:]` untuk mengambil slice arguments-nya saja. +Output program: ![Pemanfaatan arguments](images/A_cli_flag_arg_1_argument.png) -Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi nya ( ), maka harus diapit tanda petik (`"`), agar tidak dideteksi sebagai 2 argumen. +Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi-nya ( ) harus dituliskan dengan diapit tanda petik (`"`) agar tidak dideteksi sebagai 2 argumen. + +Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Maka disini penting untuk hanya mengambil element index ke 1 hingga seterusnya saja via statement `os.Args[1:]`. ## A.48.2. Penggunaan Flag @@ -85,7 +83,7 @@ fmt.Println(*dataName) Kode tersebut maksudnya adalah, disiapkan flag bertipe `string`, dengan key adalah `name`, dengan nilai default `"anonymous"`, dan keterangan `"type your name"`. Nilai flag nya sendiri akan disimpan ke dalam variabel `dataName`. -Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu agar bisa mendapatkan nilai aslinya (`*dataName`). +Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu untuk mengakses nilai aslinya (`*dataName`). ![Contoh penggunaan flag](images/A_cli_flag_arg_2_flag.png) @@ -110,7 +108,7 @@ Sebenarnya ada 2 cara deklarasi flag yang bisa digunakan, dan cara di atas merup Cara kedua mirip dengan cara pertama, perbedannya adalah kalau di cara pertama nilai pointer flag dikembalikan lalu ditampung variabel. Sedangkan pada cara kedua, nilainya diambil lewat parameter pointer. -Agar lebih jelas perhatikan contoh berikut. +Agar lebih jelas perhatikan contoh berikut: ```go // cara ke-1 diff --git a/content/A-concurrency-pipeline.md b/content/A-concurrency-pipeline.md index 4b335c40e..d842a7649 100644 --- a/content/A-concurrency-pipeline.md +++ b/content/A-concurrency-pipeline.md @@ -1,14 +1,14 @@ # A.62. Concurrency Pattern: Pipeline -Kita sudah membahas beberapa kali tentang *concurrency* atau konkurensi di Go programming. Pada chapter ini kita akan belajar salah satu best practice konkurensi dalam Go, yaitu *pipeline*, yang merupakan satu di antara banyak *concurrency pattern* yang ada di Go. +Kita sudah membahas beberapa kali tentang topik *concurrency* atau konkurensi di Go programming. Pada chapter ini kita akan belajar salah satu best practice konkurensi dalam Go, yaitu teknik *pipeline*, yang merupakan satu di antara banyak *concurrency pattern* yang ada di Go. -Go memiliki beberapa API untuk keperluan konkurensi, di antara *goroutine* dan *channel*. Dengan memanfaatkan APIs yang ada kita bisa membentuk sebuah *streaming data pipeline* yang benefitnya adalah efisiensi penggunaan I/O dan efisiensi penggunaan banyak CPU. +Go memiliki beberapa API untuk keperluan konkurensi, dua diantaranya adalah *goroutine* dan *channel*. Dengan memanfaatkan APIs yang ada kita bisa membentuk sebuah *streaming data pipeline* yang benefitnya adalah efisiensi penggunaan I/O dan efisiensi penggunaan banyak CPU. ## A.62.1. Konsep *Pipeline* Definisi *pipeline* yang paling mudah versi penulis adalah **beberapa/banyak proses yang berjalan secara konkuren yang masing-masing proses merupakan bagian dari serangkaian tahapan proses yang berhubungan satu sama lain**. -Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang di mana database yang di backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut: +Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang di mana database server yang perlu di-backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut: 1. Kita perlu data *list* dari semua database yang harus di-backup, beserta alamat akses dan kredensial-nya. 2. Kita jalankan proses backup, bisa secara sekuensial (setelah `db1` selesai, lanjut `db2`, lanjut `db3`, dst), atau secara paralel (proses backup `db1`, `db2`, `db3`, dan lainnya dijalankan secara bersamaan). @@ -18,7 +18,7 @@ Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database * B. File-file hasil dump kemudian di-*archive* ke bentuk `.zip` atau `.tar.gz` (misalnya). * C. File archive di-kirim ke server backup, sebagai contoh AWS S3. -Kalau diperhatikan pada kasus di atas, mungkin akan lebih bagus dari segi performansi kalau proses backup banyak database tersebut dilakukan secara parallel. Dan untuk ini penulis setuju. +Kalau diperhatikan pada kasus di atas, mungkin akan lebih bagus dari segi performansi kalau proses backup banyak database tersebut dilakukan secara parallel. Dan akan lebih bagus lagi, jika di masing-masing proses backup database tersebut, proses A, B, dan C dijalankan secara konkuren. Dengan menjadikan ketiga proses tersebut (A, B, C) sebagai proses konkuren, maka I/O akan lebih efisien. Nantinya antara proses A, B, dan C eksekusinya akan tetap berurutan (karena memang harus berjalan secara urut. Tidak boleh kalau misal B lebih dulu dieksekusi kemudian A); akan tetapi, ketika goroutine yang bertanggung jawab untuk eksekusi proses A selesai, kita bisa lanjut dengan eksekusi proses B (yang memang *next stage*-nya proses A) plus eksekusi proses A lainnya (database lain) secara paralel. Jadi goroutine yang handle A ini ga sampai menganggur. @@ -47,14 +47,14 @@ Untuk mempermudah memahami tabel di atas silakan ikuti penjelasan beruntun berik Pada contoh ini kita asumsikan pipeline A adalah hanya satu goroutine, pipeline B juga satu goroutine, demikian juga pipeline C. Tapi sebenarnya dalam implementasi *real world* bisa saja ada banyak goroutine untuk masing-masing pipeline (banyak goroutine untuk pipeline A, banyak goroutine untuk pipeline B, banyak goroutine untuk pipeline C). -Semoga cukup jelas ya. Gpp kalau bingung, nanti kita sambil praktek juga jadi bisa saja temen-temen mulai benar-benar pahamnya setelah praktek. +Semoga cukup jelas ya. Tapi jika masih bingung, juga tidak apa. Kita sambil praktek juga, dan bisa saja pembaca mulai benar-benar pahamnya saat praktek. + +> Penulis sarankan untuk benar-benar memahami setiap bagian praktek ini, karena topik ini merupakan pembahasan yang cukup berat untuk pemula, tapi masih dalam klasifikasi fundamental kalau di Go programming. Bingung tidak apa, nanti bisa di-ulang-ulang, yang penting tidak sekadar *copy-paste*. ## A.62.2. Skenario Praktek Ok, penjabaran teori sepanjang sungai `nil` tidak akan banyak membawa penjelasan yang real kalau tidak diiringi dengan praktek. So, mari kita mulai praktek. -Penulis sarankan untuk benar-benar memahami setiap bagian praktek ini, karena topik ini merupakan pembahasan yang cukup berat untuk pemula, tapi masih dalam klasifikasi fundamental kalau di Go programming. Bingung tidak apa, nanti bisa di-ulang-ulang, yang penting tidak sekadar *copy-paste*. - Untuk skenario praktek kita tidak menggunakan analogi backup database di atas ya, karena untuk setup environment-nya butuh banyak *effort*. Skenario praktek yang kita pakai adalah mencari [md5 sum](https://en.wikipedia.org/wiki/Md5sum) dari banyak file, kemudian menggunakan hash dari content-nya sebagai nama file. Jadi file yang lama akan di-rename dengan nama baru yaitu hash dari konten file tersebut. Agar skenario ini bisa kita eksekusi, kita perlu siapkan dulu sebuah program untuk *generate dummy files*. @@ -63,7 +63,7 @@ Agar skenario ini bisa kita eksekusi, kita perlu siapkan dulu sebuah program unt Buat project baru dengan nama bebas loss gak reweellll beserta satu buah file bernama `1-dummy-file-generator.go`. -Dalam file tersebut import beberapa hal dan definisikan, yaitu: +Dalam file tersebut import dan definisikan beberapa hal, diantaranya: 1. Konstanta `totalFile` yang isinya jumlah file yang ingin di-generate. 1. Variabel `contentLength` yang isinya panjang karakter random yang merupakan isi dari masing-masing *generated* file. @@ -87,7 +87,7 @@ const contentLength = 5000 var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.59-pipeline-temp") ``` -Kemudian siapkan fungsi `main()` yang isinya statement pemanggilan fungsi `generate()`. Selain itu juga ada beberapa statement untuk keperluan *benchmark* performa dari sisi *execution time*. +Kemudian siapkan fungsi `main()` yang isinya statement pemanggilan fungsi `generate()`, dan beberapa hal lainnya untuk keperluan *benchmark* performa dari sisi *execution time*. ```go func main() { @@ -117,7 +117,7 @@ func randomString(length int) string { } ``` -Lalu siapkan fungsi `generateFiles()`-nya. isinya kurang lebih adalah generate banyak file sejumlah `totalFile`. Lalu di tiap-tiap file di-isi dengan *random string* dengan lebar sepanjang `contentLength`. Untuk nama file-nya sendiri, formatnya adalah `file-.txt`. +Siapkan fungsi `generateFiles()`-nya, isinya kurang lebih adalah generate banyak file sejumlah `totalFile`. Lalu di tiap-tiap file di-isi dengan *random string* dengan lebar sepanjang `contentLength`. Untuk nama file-nya sendiri, formatnya adalah `file-.txt`. ```go func generateFiles() { @@ -151,9 +151,9 @@ Bisa dilihat sebanyak 3000 dummy file di-generate pada folder temporary os, di s ## A.62.4. Program 2: Baca Semua Files, Cari MD5 Hash-nya, Lalu Gunakan Hash Untuk Rename File -Sesuai judul sub bagian, kita akan buat satu file program lagi, yang isinya kurang lebih adalah melakukan pembacaan terhadap semua dummy file yang sudah di-generate untuk kemudian dicari *hash*-nya, lalu menggunakan value hash tersebut sebagai nama file baru masing-masing file. +Sesuai judul sub bagian, kita akan buat satu file program lagi, yang isinya adalah melakukan operasi baca terhadap semua dummy file yang sudah di-generate, untuk kemudian dicari *hash*-nya lalu menggunakan nilai hash tersebut sebagai nama untuk file-file baru yang akan dibuat. -Pada bagian ini kita belum masuk ke aspek konkurensi-nya ya. Sabar dulu. Saya akan coba sampaikan dengan penjabaran yang bisa diterima oleh banyak pembaca termasuk yang masih junior. +Pada bagian ini kita belum masuk ke aspek konkurensi-nya ya. Sabar dulu. Saya akan coba sampaikan dengan penjabaran yang bisa diterima oleh banyak pembaca (termasuk yang masih awam banget). Siapkan file `2-find-md5-sum-of-file-then-rename-it.go`, import beberapa *packages* dan siapkan definisi variabel `tempPath`. @@ -233,7 +233,7 @@ func proceed() { } ``` -Cukup panjang isi fungsi ini, tetapi isinya cukup *straightforward* kok. +Cukup panjang isi fungsi ini, tetapi isinya cukup *straight forward* kok. * Pertama kita siapkan `counterTotal` sebagai counter jumlah file yang ditemukan dalam `$TEMP/chapter-A.59-pipeline-temp`. Idealnya jumlahnya adalah sama dengan isi variabel `totalFile` pada program pertama, kecuali ada error. * Kedua, kita siapkan `counterRenamed` sebagai counter jumlah file yang berhasil di-rename. Untuk ini juga idealnya sama dengan nilai pada `counterTotal`, kecuali ada error @@ -250,7 +250,7 @@ Selesai dalam waktu **1,17 detik**, lumayan untuk eksekusi proses sekuensial. Ok, aplikasi sudah siap. Selanjutnya kita akan refactor aplikasi tersebut ke bentuk konkuren menggunakan metode *pipeline*. -## A.62.5. Program 3: Lakukan Proses Secara Concurrent Menggunakan Pipeline +## A.62.5. Program 3: Lakukan Proses Secara Concurrent Menggunakan Teknik Pipeline Pada bagian ini kita akan re-write ulang program 2, isinya masih sama persis kalau dilihat dari perspektif bisnis logic, tapi metode yang kita terapkan dari sisi engineering berbeda. Di sini kita akan terapkan *pipeline*. Bisnis logic akan dipecah menjadi 3 dan seluruhnya dieksekusi secara konkuren, yaitu: @@ -258,9 +258,9 @@ Pada bagian ini kita akan re-write ulang program 2, isinya masih sama persis kal * Proses perhitungan md5 hash sum * Proses rename file -Kenapa kita pecah, karena ketiga proses tersebut bisa dijalankan bersama secara konkuren, dalam artian misalnya ketika file1 sudah selesai dibaca, perhitungan md5 sum nya bisa dijalankan secara bersama dengan pembacaan file2. Begitu juga untuk proses rename-nya, misalnya, proses rename file24 bisa dijalnkan secara konkuren bersamaan dengan proses hitung md5 sum file22 dan bersamaan dengan proses baca file28. +Kenapa kita pecah, karena ketiga proses tersebut bisa dijalankan bersama secara konkuren, dalam artian misalnya ketika `file1` sudah selesai dibaca, perhitungan md5sum-nya bisa dijalankan secara bersama dengan pembacaan `file2`. Begitu juga untuk proses rename-nya, misalnya, proses rename `file24` bisa dijalnkan secara konkuren bersamaan dengan proses hitung md5sum `file22` dan bersamaan dengan proses baca `file28`. -#### • Basis Kode Program +#### ◉ Basis Kode Program Mungkin agar lebih terlihat perbandingannya nanti di akhir, kita siapkan file terpisah saja untuk program ini. Siapkan file baru bernama `3-find-md5-sum-of-file-then-rename-it-concurrently.go`. @@ -291,7 +291,7 @@ type FileInfo struct { Kurang lebih sama seperti sebelumnya, hanya saja ada beberapa packages lain yg di-import dan ada struct `FileInfo`. Struct ini digunakan sebagai metadata tiap file. Karena nantinya proses read file, md5sum, dan rename file akan dipecah menjadi 3 goroutine berbeda, maka perlu ada metadata untuk mempermudah tracking file, agar nanti ketika dapat md5 sum nya tidak salah simpan, dan ketika rename tidak salah file. -#### • Pipeline 1: Baca File +#### ◉ Pipeline 1: Baca File Siapkan fungsi main, lalu panggil fungsi `readFiles()`. @@ -349,17 +349,17 @@ func readFiles() <-chan FileInfo { } ``` -Bisa dilihat isi fungsi `readFiles()`. Di fungsi tersebut ada sebuah channel bernama `chanOut` tipenya channel `FileInfo`, yang variabel channel ini langsung dijadikan nilai balik dari fungsi `readFiles()`. +Bisa dilihat isi fungsi `readFiles()`. Di fungsi tersebut ada sebuah channel bernama `chanOut` tipenya channel `FileInfo`, variabel channel ini dijadikan nilai balik dari fungsi `readFiles()`. Di dalam fungsi `readFiles()` juga ada proses lain yang berjalan secara *asynchronous* dan *concurrent* yaitu goroutine yang isinya pembacaan file. Dalam blok kode baca file, informasi `path` dan konten file dibungkus dalam objek baru dengan tipe `FileInfo` kemudian dikirim ke channel `chanOut`. -Karena proses utama dalam fungsi `readFiles` berada dalam goroutine, maka di `main()`, ketika statement `chanFileContent := readFiles()` selesai dieksekusi, bukan berarti proses pembacaan file selesai, malah mungkin baru saja dimulai. Ini karena proses baca file dijalankan dalam goroutine di dalam fungsi `readFiles()` tersebut. +Karena proses utama dalam fungsi `readFiles()` berada dalam goroutine, maka di `main()`, ketika statement `chanFileContent := readFiles()` selesai dieksekusi, bukan berarti proses pembacaan file selesai, malah mungkin baru saja dimulai. Ini karena proses baca file dijalankan dalam goroutine di dalam fungsi `readFiles()` tersebut. Mengenai channel `chanOut` sendiri, akan di-close ketika dipastikan **semua file sudah dikirim datanya ke channel tersebut** (silakan lihat statement `close(chanOut)` di akhir goroutine). Ok lanjut, karena di sini ada channel yang digunakan sebagai media pengiriman data (`FileInfo`), maka juga harus ada penerima data channel-nya dong. Yups. -#### • Pipeline 2: MD5 Hash Konten File +#### ◉ Pipeline 2: MD5 Hash Konten File Tepat di bawah pipeline 1, tambahkan pemanggilan fungsi `getSum()` sebanyak 3x, bisa lebih banyak sih sebenarnya, bebas. Kemudian jadikan nilai balik pemanggilan fungsi tersebut sebagai variadic argument pemanggilan fungsi `mergeChanFileInfo()`. @@ -390,7 +390,7 @@ Jadi TL;DR nya: * Fungsi Fan-out digunakan untuk pembuatan worker, untuk distribusi job, yang proses distribusinya sendiri akan berhenti ketika channel inputan di-close. * Fungsi Fan-in digunakan untuk *multiplexing* atau menggabung banyak worker ke satu channel saja, yang di mana channel baru ini juga otomatis di-close ketika channel input adalah closed. -Lanjut buat fungsi `getSum()`-nya. +Sekarang lanjut buat fungsi `getSum()`. ```go func getSum(chanIn <-chan FileInfo) <-chan FileInfo { @@ -447,7 +447,7 @@ Secara garis besar, pada fungsi ini terjadi beberapa proses: * Channel `chanOut` ini dijadikan sebagai nilai balik fungsi. * Di situ kita gunakan `sync.WaitGroup` untuk kontrol goroutine. Kita akan tunggu hingga semua channel input adalah closed, setelah itu barulah kita close channel `chanOut` ini. -#### • Pipeline 3: Rename file +#### ◉ Pipeline 3: Rename file Tambahkan statement pipeline ketiga, yaitu pemanggilan fungsi Fan-out `rename()`, lalu panggil fungsi Fan-in `mergeChanFileInfo()` untuk multiplex channel kembalian fungsi `rename()`. @@ -493,7 +493,7 @@ Bisa dilihat di atas kita rename file asli yang informasi path-nya ada di `FileI Setelah semua file berhasil di-rename, maka channel `chanOut` di-close. -#### • Pipeline 4 / Output +#### ◉ Pipeline 4 / Output Serangkaian proses yang sudah kita setup punya ketergantungan tinggi satu sama lain, dan eksekusinya harus berurutan meskipun *concurrently*. Ini secara langsung juga mempermudah kita dalam mengolah output hasil pipeline. Kita cukup fokus ke channel hasil Fan-in yang paling terakhir, yaitu channel `chanRename`. @@ -528,9 +528,11 @@ Bisa dilihat bedanya, untuk rename 3000 file menggunakan cara sekuensial membutu ## A.62.6. Kesimpulan -Pipeline concurrency pattern sangat bagus untuk diterapkan pada kasus yang proses-nya bisa di-klasifikasi menjadi sub-proses kecil-kecil yang secara I/O tidak saling tunggu (tapi secara flow harus berurutan). +Pipeline concurrency pattern sangat bagus untuk diterapkan pada case yang proses-nya bisa diklasifikasi menjadi sub-proses kecil-kecil yang secara I/O tidak saling tunggu (tapi secara flow harus berurutan). + +Untuk banyak kasus, metode pipeline ini sangat tepat guna. Kita bisa dengan mudah mengontrol penggunaan resource seperti **CPU** dengan cara menentukan angka ideal jumlah worker untuk masing-masing pipeline, tapi untuk bagian ini butuh *test and try* juga, karena tidak selalu banyak worker itu menghasilkan proses yang lebih cepat, dan misalpun bisa, perlu dicek juga konsumsi resource-nya berlebihan atau tidak. Bisa jadi karena terlalu banyak worker malah lebih lambat karena ada constraint I/O. -Untuk banyak kasus, metode pipeline ini sangat tepat guna. Kita bisa dengan mudah mengontrol penggunaan resource seperti **CPU** dengan cara menentukan angka ideal jumlah worker untuk masing-masing pipeline, tapi untuk bagian ini butuh *test and try*, karena tidak selalu banyak worker itu menghasilkan proses yang lebih cepat. Bisa jadi karena terlalu banyak worker malah lebih lambat. Jadi silakan lakukan testing saja, sesuaikan dengan spesifikasi CPU laptop/komputer/server yang digunakan. +Intinya butuh banyak percobaan dan testing, sesuaikan dengan spesifikasi hardware laptop/komputer/server yang digunakan. Ok sekian untuk chapter panjang ini. diff --git a/content/A-data-type-conversion.md b/content/A-data-type-conversion.md index 370ec902f..135d4f3ba 100644 --- a/content/A-data-type-conversion.md +++ b/content/A-data-type-conversion.md @@ -1,12 +1,12 @@ # A.43. Konversi Antar Tipe Data -Pada chapter sebelum-sebelumnyanya kita sudah mengaplikasikan beberapa cara konversi data, contohnya seperti konversi `string` ↔ `int` menggunakan `strconv`, dan `time.Time` ↔ `string`. Pada chapter ini kita akan belajar lebih banyak. +Di beberapa chapter sebelum ini kita telah menerapkan beberapa cara konversi data, contohnya seperti konversi `string` ↔ `int` menggunakan `strconv`, dan `time.Time` ↔ `string`. Pada chapter ini kita akan mempelajarinya lebih detail. ## A.43.1. Konversi Menggunakan `strconv` Package `strconv` berisi banyak fungsi yang sangat membantu kita untuk melakukan konversi. Berikut merupakan beberapa fungsi yang dalam package tersebut. -#### • Fungsi `strconv.Atoi()` +#### ◉ Fungsi `strconv.Atoi()` Fungsi ini digunakan untuk konversi data dari tipe `string` ke `int`. `strconv.Atoi()` menghasilkan 2 buah nilai kembalian, yaitu hasil konversi dan `error` (jika konversi sukses, maka `error` berisi `nil`). @@ -26,7 +26,7 @@ func main() { } ``` -#### • Fungsi `strconv.Itoa()` +#### ◉ Fungsi `strconv.Itoa()` Merupakan kebalikan dari `strconv.Atoi`, berguna untuk konversi `int` ke `string`. @@ -37,7 +37,7 @@ var str = strconv.Itoa(num) fmt.Println(str) // "124" ``` -#### • Fungsi `strconv.ParseInt()` +#### ◉ Fungsi `strconv.ParseInt()` Digunakan untuk konversi `string` berbentuk numerik dengan basis tertentu ke tipe numerik non-desimal dengan lebar data bisa ditentukan. @@ -63,7 +63,7 @@ if err == nil { } ``` -#### • Fungsi `strconv.FormatInt()` +#### ◉ Fungsi `strconv.FormatInt()` Berguna untuk konversi data numerik `int64` ke `string` dengan basis numerik bisa ditentukan sendiri. @@ -74,7 +74,7 @@ var str = strconv.FormatInt(num, 8) fmt.Println(str) // 30 ``` -#### • Fungsi `strconv.ParseFloat()` +#### ◉ Fungsi `strconv.ParseFloat()` Digunakan untuk konversi `string` ke numerik desimal dengan lebar data bisa ditentukan. @@ -89,7 +89,7 @@ if err == nil { Pada contoh di atas, string `"24.12"` dikonversi ke float dengan lebar tipe data `float32`. Hasil konversi `strconv.ParseFloat` adalah sesuai dengan standar [IEEE Standard for Floating-Point Arithmetic](https://en.wikipedia.org/wiki/IEEE_floating_point). -#### • Fungsi `strconv.FormatFloat()` +#### ◉ Fungsi `strconv.FormatFloat()` Berguna untuk konversi data bertipe `float64` ke `string` dengan format eksponen, lebar digit desimal, dan lebar tipe data bisa ditentukan. @@ -113,7 +113,7 @@ Ada beberapa format eksponen yang bisa digunakan. Detailnya bisa dilihat di tabe | `g` | Akan menggunakan format eksponen `e` untuk eksponen besar dan `f` untuk selainnya | | `G` | Akan menggunakan format eksponen `E` untuk eksponen besar dan `f` untuk selainnya | -#### • Fungsi `strconv.ParseBool()` +#### ◉ Fungsi `strconv.ParseBool()` Digunakan untuk konversi `string` ke `bool`. @@ -126,7 +126,7 @@ if err == nil { } ``` -#### • Fungsi `strconv.FormatBool()` +#### ◉ Fungsi `strconv.FormatBool()` Digunakan untuk konversi `bool` ke `string`. @@ -139,12 +139,14 @@ fmt.Println(str) // true ## A.43.2. Konversi Data Menggunakan Teknik Casting -Keyword tipe data bisa digunakan untuk casting, atau konversi antar tipe data. Cara penggunaannya adalah dengan menuliskan tipe data tujuan casting sebagai fungsi, lalu menyisipkan data yang akan dikonversi sebagai parameter fungsi tersebut. +Cara penerapannya adalah dengan menggunakan keyword tipe data sebagai nama fungsi, kemudiaan argument pemanggilannya diisi dengan data yang ingin dikonversi tipenya. ```go +// konversi nilai 24 bertipe int ke float64 var a float64 = float64(24) fmt.Println(a) // 24 +// konversi nilai 24.00 bertipe float32 ke int32 var b int32 = int32(24.00) fmt.Println(b) // 24 ``` @@ -175,7 +177,7 @@ fmt.Printf("%s \n", s) // halo ``` -Pada contoh di-atas, beberapa kode byte dituliskan dalam bentuk slice, ditampung variabel `byte1`. Lalu, nilai variabel tersebut di-cast ke `string`, untuk kemudian ditampilkan. +Di contoh ke-2 di-atas, beberapa kode byte dituliskan dalam bentuk slice, ditampung variabel `byte1`. Lalu, nilai variabel tersebut di-cast ke `string`, untuk kemudian ditampilkan. Selain itu, tiap karakter string juga bisa di-casting ke bentuk `int`, hasilnya adalah sama yaitu data byte dalam bentuk numerik basis desimal, dengan ketentuan literal string yang digunakan adalah tanda petik satu ('). @@ -189,9 +191,9 @@ var d string = string(104) fmt.Println(d) // h ``` -## A.43.4. Type Assertions Pada Interface Kosong (`interface{}`) +## A.43.4. Type Assertions Pada Tipe `any` atau Interface Kosong (`interface{}`) -**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}`. Jadi bisa disimpulkan bahwa teknik type assertions hanya bisa dilakukan pada data bertipe `interface{}`. Lebih jelasnya silakan cek contoh berikut. +**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}` atau `any`. Lebih jelasnya silakan cek contoh berikut. Variabel `data` disiapkan bertipe `map[string]interface{}`, map tersebut berisikan beberapa item dengan tipe data value-nya berbeda satu sama lain, sementara tipe data untuk key-nya sama yaitu `string`. @@ -215,7 +217,7 @@ Statement `data["nama"].(string)` maksudnya adalah, nilai `data["nama"]` yang be Pada kode di atas, tidak akan terjadi panic error, karena semua operasi type assertion adalah dilakukan menggunakan tipe data yang sudah sesuai dengan tipe data nilai aslinya. Seperti `data["nama"]` yang merupakan `string` pasti bisa di-asertasi ke tipe `string`. -Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, pasti akan men-trigger panic error. +Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, hasilnya adalah panic error. Nah, dari penjelasan di atas, terlihat bahwa kita harus tau terlebih dahulu apa tipe data asli dari data yang tersimpan dalam interface. Jika misal tidak tau, maka bisa gunakan teknik di bawah ini untuk pengecekan sukses tidaknya proses asertasi. diff --git a/content/A-defer-exit.md b/content/A-defer-exit.md index 0f891f4e9..bb719c471 100644 --- a/content/A-defer-exit.md +++ b/content/A-defer-exit.md @@ -4,7 +4,7 @@ ## A.36.1. Penerapan keyword `defer` -Seperti yang sudah dijelaskan secara singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan. +Seperti yang sudah dijelaskan singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan. Defer bisa ditempatkan di mana saja, awal maupun akhir blok. Tetapi tidak mempengaruhi kapan waktu dieksekusinya, akan selalu dieksekusi di akhir. @@ -19,7 +19,7 @@ func main() { } ``` -Output: +Output program: ![Penerapan `defer`](images/A_defer_exit_1_defer.png) @@ -45,7 +45,7 @@ func orderSomeFood(menu string) { } ``` -Output: +Output program: ![Penerapan `defer` dengan `return`](images/A_defer_exit_2_defer_return.png) @@ -68,7 +68,7 @@ func main() { } ``` -Output: +Output program: ``` halo 1 @@ -95,7 +95,7 @@ func main() { } ``` -Output: +Output program: ``` halo 1 diff --git a/content/A-encoding-base64.md b/content/A-encoding-base64.md index 08a092cd0..5d4bbfd1d 100644 --- a/content/A-encoding-base64.md +++ b/content/A-encoding-base64.md @@ -1,8 +1,8 @@ # A.46. Encode - Decode Base64 -Go memiliki package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, perlu dilakukan casting untuk data-data yang belum sesuai tipenya. +Go menyediakan package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke bentuk base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, maka perlu dilakukan casting untuk data-data yang tipenya belum `[]byte`. -Ada beberapa cara yang bisa digunakan untuk encode dan decode data, dan pada chapter ini kita akan mempelajarinya. +Proses encoding dan decoding bisa dilakukan via beberapa cara yang pada chapter ini kita akan pelajari. ## A.46.1. Penerapan Fungsi `EncodeToString()` & `DecodeString()` @@ -59,7 +59,7 @@ Fungsi `base64.StdEncoding.EncodedLen(len(data))` menghasilkan informasi lebar v Fungsi `base64.StdEncoding.DecodedLen()` memiliki kegunaan sama dengan `EncodedLen()`, hanya saja digunakan untuk keperluan decoding. -Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut. +Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` ini memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut. Pada pemanggilan fungsi encode/decode, variabel `encoded` dan `decoded` tidak disisipkan nilai pointer-nya, cukup di-pass dengan cara biasa, tipe datanya sudah dalam bentuk `[]byte`. diff --git a/content/A-error-panic-recover.md b/content/A-error-panic-recover.md index 6a2291e4a..dca1c4ff4 100644 --- a/content/A-error-panic-recover.md +++ b/content/A-error-panic-recover.md @@ -1,14 +1,18 @@ # A.37. Error, Panic, dan Recover -Error merupakan topik yang sangat penting dalam pemrograman Go. Di bagian ini kita akan belajar mengenai pemanfaatan error dan cara membuat custom error sendiri. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya. +Error merupakan topik yang sangat penting dalam pemrograman Go, salah satu alasannya karena Go tidak mengadopsi konsep exception. + +Pada chapter ini kita akan belajar tentang pemanfaatan error dan cara membuat custom error. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya. ## A.37.1. Pemanfaatan Error -`error` merupakan sebuah tipe. Error memiliki 1 buah property berupa method `Error()`, method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`. +Di go, `error` merupakan sebuah tipe data. Error memiliki beberapa property yang salah satunya adalah method `Error()`. Method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`. + +Pada praktik pemrograman Go, pembaca akan menemui banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu, yang biasanya salah satunya adalah bertipe `error`. -Di Go, banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu. Biasanya, salah satu kembalian adalah bertipe `error`. Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan. +Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan. -Di bawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Dari sini kita akan belajar mengenai pemanfaatan error. +Di bawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Pada kode tersebut kita akan belajar mengenai pemanfaatan error. ```go package main @@ -93,17 +97,21 @@ Fungsi `validate()` mengembalikan 2 data. Data pertama adalah nilai `bool` yang Fungsi `strings.TrimSpace()` digunakan untuk menghilangkan karakter spasi sebelum dan sesudah string. Ini dibutuhkan karena user bisa saja menginputkan spasi lalu enter. -Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`. Selain itu objek error juga bisa dibuat lewat fungsi `fmt.Errorf()`. +Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`. ![Custom error](images/A_error_panic_recover_2_custom_error.png) +Selain menggunakan `errors.New()`, objek error bisa dibuat via fungsi `fmt.Errorf()`. Pengaplikasiannya mirip, perbedaannya fungsi `fmt.Errorf()` mendukung format string. + ## A.37.3. Penggunaan `panic` -Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine (karena `main()` juga merupakan goroutine, maka behaviour yang sama juga berlaku). Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error). +Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine. Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error). + +> Perlu diingat bahwa `main()` juga merupakan goroutine, maka behaviour yang sama adalah berlaku. -Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, jadi sangat mendetail dan heboh. +Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, isinya sangat detail dan heboh. -Kembali ke koding, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya. +Kembali ke praktek, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya. ```go func main() { @@ -126,7 +134,7 @@ Jalankan program lalu langsung tekan enter, maka panic error muncul dan baris ko ## A.37.4. Penggunaan `recover` -Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic (pesan panic tidak akan muncul). +Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic dan efek sampingnya pesan panic tidak muncul dan eksekusi program adalah tidak error. Ok, mari kita modifikasi sedikit fungsi di-atas untuk mempraktekkan bagaimana cara penggunaan recover. Tambahkan fungsi `catch()`, dalam fungsi ini terdapat statement `recover()` yang dia akan mengembalikan pesan panic error yang seharusnya muncul. @@ -157,7 +165,7 @@ func main() { } ``` -Output: +Output program: ![Handle panic menggunakan recover](images/A_error_panic_recover_4_recover.png) @@ -207,7 +215,7 @@ func main() { } ``` -Pada kode di atas, bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi. +Bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi. --- diff --git a/content/A-exec.md b/content/A-exec.md index d472f8544..7c05f887e 100644 --- a/content/A-exec.md +++ b/content/A-exec.md @@ -1,12 +1,12 @@ # A.49. Exec -**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di terminal (atau CMD untuk pengguna Windows). +**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di command line sesuai sistem operasinya (Linux-distros, Windows, MacOS, dan lainnya). ## A.49.1. Penggunaan Exec -Go menyediakan package `exec` berisikan banyak fungsi untuk keperluan eksekusi perintah CLI. +Go menyediakan package `exec` isinya banyak sekali API atau fungsi untuk keperluan eksekusi perintah command line. -Cara untuk eksekusi command cukup mudah, yaitu dengan menuliskan command dalam bentuk string, diikuti arguments-nya (jika ada) sebagai parameter variadic pada fungsi `exec.Command()`. +Cara eksekusi command adalah menggunakan fungsi `exec.Command()` dengan argument pemanggilan fungsi diisi command CLI yang diinginkan. Contoh: ```go package main @@ -26,15 +26,17 @@ func main() { } ``` -Fungsi `exec.Command()` digunakan untuk menjalankan command. Fungsi tersebut bisa langsung di-chain dengan method `Output()`, jika ingin mendapatkan outputnya. Output yang dihasilkan berbentuk `[]byte`, gunakan cast ke string untuk mengambil bentuk string-nya. +Fungsi `exec.Command()` menjalankan command yang dituliskan pada argument pemanggilan fungsi. + +Untuk mendapatkan outputnya, chain saja langsung dengan method `Output()`. Output yang dihasilkan berbentuk `[]byte`, maka pastikan cast ke string terlebih dahulu untuk membaca isi outputnya. ![Ekeskusi command menggunakan exec](images/A_exec_1_exec.png) ## A.49.2. Rekomendasi Penggunaan Exec -Kadang kala, pada saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya) kita menemui error yang mengatakan command not found. Hal itu terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `dir.exe` dan lainnya. Di OS non-windows-pun juga demikian. +Ada kalanya saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya), error muncul menginformasikan bahwa command tidak ditemukan (command not found). Hal ini biasanya terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `cmd` atau `cmd.exe`, di Linux tidak ditentukan apakah memakai `bash` atau `shell`, dan lainnya -Untuk mengatasi masalah ini, tambahkan `bash -c` pada linux/nix command atau `cmd /C` untuk windows. +Untuk mengatasi masalah ini, tambahkan `bash -c` pada sistem operasi berbasi Linux, MacOS, Unix, atau `cmd /C` untuk OS Windows. ```go if runtime.GOOS == "windows" { @@ -44,11 +46,11 @@ if runtime.GOOS == "windows" { } ``` -Statement `runtime.GOOS` mengembalikan informasi sistem operasi dalam string. +Statement `runtime.GOOS` penggunaannya mengembalikan informasi sistem operasi dalam bentuk string. Manfaatkan seleksi kondisi untuk memastikan command yang ingin dieksekusi sudah sesuai dengan OS atau belum. ## A.49.3. Method Exec Lainnya -Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Detailnya silakan langsung merujuk ke dokumentasi [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/) +Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Lebih detailnya silakan langsung melihat dokumentasi package tersebut di [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/) --- diff --git a/content/A-file.md b/content/A-file.md index 2ab152a7b..58b386141 100644 --- a/content/A-file.md +++ b/content/A-file.md @@ -1,12 +1,12 @@ # A.50. File -Ada beberapa cara yang bisa digunakan untuk operasi file di Go. Pada chapter ini kita akan mempelajari teknik yang paling dasar, yaitu dengan memanfaatkan `os.File`. +Pada chapter ini kita akan belajar beberapa teknik operasi file yang paling dasar. ## A.50.1. Membuat File Baru -Pembuatan file di Go sangatlah mudah, cukup dengan memanggil fungsi `os.Create()` lalu memasukkan path file yang ingin dibuat sebagai parameter. Jika ternyata file yang akan dibuat sudah ada, maka akan ditimpa. Bisa memanfaatkan `os.IsNotExist()` untuk mendeteksi apakah file sudah dibuat atau belum. +Pembuatan file di Go sangat mudah, dilakukan dengan memanfaatkan fungsi `os.Create()` disertai path file sebagai argument pemanggilan fungsi. Jika ternyata file yang akan dibuat sudah ada duluan, maka operasi `os.Create()` akan menimpa file yang sudah ada dengan file baru. Untuk menghindari penimpaan file, gunakan fungsi `os.IsNotExist()` untuk mendeteksi apakah file yang ingin dibuat sudah ada atau belum. -Berikut merupakan contoh pembuatan file. +Contoh program operasi pembuatan file: ```go package main @@ -43,17 +43,17 @@ func main() { } ``` -Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai parameter fungsi `os.IsNotExist()`, untuk mendeteksi apakah file yang akan dibuat sudah ada. Jika belum ada, maka fungsi tersebut akan mengembalikan nilai `true`. +Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai argument pemanggilan fungsi `os.IsNotExist()`, untuk mengetahui apakah file yang akan dibuat sudah ada. Jika rupanya file belum ada ada, maka fungsi tersebut akan mengembalikan nilai `true`. -Fungsi `os.Create()` digunakan untuk membuat file pada path tertentu. Fungsi ini mengembalikan objek `*os.File` dari file yang bersangkutan. File yang baru terbuat statusnya adalah otomatis **open**, maka dari itu perlu untuk di-**close** menggunakan method `file.Close()` setelah file tidak digunakan lagi. +Fungsi `os.Create()` ini mengembalikan objek bertipe `*os.File`. File yang baru dibuat, statusnya adalah otomatis **open**. Setelah operasi file selesai, file harus di-**close** menggunakan method `file.Close()`. -Membiarkan file terbuka ketika sudah tak lagi digunakan bukan hal yang baik, karena efeknya ke memory dan akses ke file itu sendiri, file akan di-lock sehingga tidak bisa digunakan oleh proses lain selama status file masih open atau belum di-close. +Membiarkan file terbuka ketika sudah tak lagi digunakan adalah tidak baik, karena ada efek ke memory dan akses ke file itu sendiri, file menjadi terkunci/locked, membuatnya tidak bisa diakses oleh proses lain selama status file statusnya masih **open** dan belum di-close. ![Membuat file baru](images/A_file_1_create.png) ## A.50.2. Mengedit Isi File -Untuk mengedit file, yang perlu dilakukan pertama adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk pengisian data. Terakhir panggil method `Sync()` untuk menyimpan perubahan. +Untuk mengedit file, yang pertama perlu dilakukan adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk penulisan data. Di akhir, panggil method `Sync()` untuk menyimpan perubahan. ```go func writeFile() { @@ -80,13 +80,13 @@ func main() { } ``` -Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan ke dalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file akan disimpan dengan dipanggilnya `Sync()`. +Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan ke dalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file menjadi tersimpan dengan adanya pemanggilan method `Sync()`. ![Mengedit file](images/A_file_2_write.png) ## A.50.3. Membaca Isi File -File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Setelah itu, gunakan method `Read()` dengan parameter adalah variabel, yang di mana hasil proses baca akan disimpan ke variabel tersebut. +File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Dari object file kembalian fungsi tersebut, gunakan method `Read()` dengan disertai argument berupa variabel yang akan menampung data hasil operasi baca. ```go // tambahkan di bagian import package io @@ -120,21 +120,21 @@ func main() { } ``` -Pada kode di atas `os.OpenFile()` digunakan untuk membuka file. Fungsi tersebut memiliki beberapa parameter. +Fungsi `os.OpenFile()` dalam pemanggilannya memerlukan beberapa argument parameter untuk di-isi: 1. Parameter pertama adalah path file yang akan dibuka. 2. Parameter kedua adalah level akses. `os.O_RDONLY` maksudnya adalah **read only**. 3. Parameter ketiga adalah permission file-nya. -Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen 1024. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file akan dilakukan terus menerus, berurutan dari baris pertama hingga akhir. +Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen `1024`. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file dilakukan terus menerus, berurutan dari baris pertama hingga akhir. -Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error tersebut adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**. +Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**. ![Membaca isi file](images/A_file_3_read.png) ## A.50.4. Menghapus File -Cara menghapus file sangatlah mudah, cukup panggil fungsi `os.Remove()`, masukan path file yang ingin dihapus sebagai parameter. +Operasi menghapus file dilakukan via fungsi `os.Remove()`. Panggil fungsi tersebut, kemudian isi path dari file yang ingin dihapus sebagai argument fungsi. ```go func deleteFile() { diff --git a/content/A-fungsi-closure.md b/content/A-fungsi-closure.md index 4c60405ec..b8e6ab54b 100644 --- a/content/A-fungsi-closure.md +++ b/content/A-fungsi-closure.md @@ -1,12 +1,12 @@ # A.21. Fungsi Closure -Definisi **Closure** adalah sebuah fungsi yang bisa disimpan dalam variabel. Dengan menerapkan konsep tersebut, kita bisa membuat fungsi di dalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi. - -Closure merupakan *anonymous function* atau fungsi tanpa nama. Biasa dimanfaatkan untuk membungkus suatu proses yang hanya dipakai sekali atau dipakai pada blok tertentu saja. +Definisi **Closure** adalah suatu *anonymous function* (atau fungsi tanpa nama) yang disimpan dalam variabel. Dengan adanya closure, kita bisa mendesain beberapa hal diantaranya seperti: membuat fungsi di dalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi. Closure biasa dimanfaatkan untuk membungkus suatu proses yang hanya dijalankan sekali saja atau hanya dipakai pada blok tertentu saja. ## A.21.1. Closure Disimpan Sebagai Variabel -Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel yang menyimpan closure memiliki sifat seperti fungsi yang disimpannya. Di bawah ini adalah contoh program sederhana untuk mencari nilai terendah dan tertinggi dari suatu array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`. +Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel closure memiliki sifat seperti fungsi yang disimpannya. + +Di bawah ini adalah contoh program sederhana yang menerapkan closure untuk pencarian nilai terendah dan tertinggi dari data array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`. ```go package main @@ -35,8 +35,7 @@ func main() { } ``` - -Bisa dilihat pada kode di atas bagaimana sebuah closure dibuat dan dipanggil. Sedikit berbeda memang dibanding pembuatan fungsi biasa. Fungsi ditulis tanpa nama, lalu ditampung dalam variabel. +Bisa dilihat pada kode di atas bagaimana cara deklarasi closure dan cara pemanggilannya. Sedikit berbeda memang dibanding pembuatan fungsi biasa, pada closure fungsi ditulis tanpa memiliki nama lalu ditampung ke variabel. ```go var getMinMax = func(n []int) (int, int) { @@ -44,7 +43,7 @@ var getMinMax = func(n []int) (int, int) { } ``` -Cara pemanggilannya, dengan menuliskan nama variabel tersebut sebagai fungsi, seperti pemanggilan fungsi biasa. +Cara pemanggilan closure adalah dengan memperlakukan variabel closure seperti fungsi, dituliskan seperti pemanggilan fungsi. ```go var min, max = getMinMax(numbers) @@ -54,25 +53,25 @@ Output program: ![Penerapan closure](images/A_fungsi_closure_1_closure.png) ---- +## A.21.2. Penjelasan tambahan -Berikut adalah penjelasan tambahan mengenai kode di atas +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: -## A.21.1.1. Penggunaan Template String `%v` +#### ◉ Penggunaan Template String `%v` -Template `%v` digunakan untuk menampilkan segala jenis data. Bisa array, int, float, bool, dan lainnya. +Template `%v` digunakan untuk menampilkan data tanpa melihat tipe datanya. Jadi bisa digunakan untuk menampilkan data array, int, float, bool, dan lainnya. Bisa dilihat di contoh statement, data bertipe array dan numerik ditampilkan menggunakan `%v`. ```go fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max) ``` -Bisa dilihat pada statement di atas, data bertipe array dan numerik ditampilkan menggunakan `%v`. Template ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Sangat tepat jika digunakan pada data bertipe `interface{}` yang nantinya akan di bahas pada chapter [A.27. Interface](/A-interface.html). +Template `%v` ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Biasa digunakan untuk keperluan debugging, misalnya untuk menampilkan data bertipe `any` atau `interface{}`. ---- +> Pembahasan mengenai tipe data `any` atau `interface{}` ada di chapter [A.27. Interface](/A-interface.html) -## A.21.2. Immediately-Invoked Function Expression (IIFE) +#### ◉ Immediately-Invoked Function Expression (IIFE) -Closure jenis ini dieksekusi langsung pada saat deklarasinya. Biasa digunakan untuk membungkus proses yang hanya dilakukan sekali, bisa mengembalikan nilai, bisa juga tidak. +Closure jenis IIFE ini eksekusinya adalah langsung saat deklarasi. Teknik ini biasa diterapkan untuk membungkus proses yang hanya dilakukan sekali. IIFE bisa memiliki nilai balik atau bisa juga tidak. Di bawah ini merupakan contoh sederhana penerapan metode IIFE untuk filtering data array. @@ -104,7 +103,7 @@ Output program: ![Penerapan IIFE](images/A_fungsi_closure_2_iife.png) -Ciri khas IIFE adalah adanya kurung parameter tepat setelah deklarasi closure berakhir. Jika ada parameter, bisa juga dituliskan dalam kurung parameternya. +Ciri khas dari penulisan IIFE adalah adanya tanda kurung parameter yang ditulis di akhir deklarasi closure. Jika IIFE memiliki parameter, maka argument-nya juga ditulis. Contoh: ```go var newNumbers = func(min int) []int { @@ -112,13 +111,13 @@ var newNumbers = func(min int) []int { }(3) ``` -Pada contoh di atas IIFE menghasilkan nilai balik yang kemudian ditampung `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**. +Di contoh sederhana di atas, IIFE menghasilkan nilai balik yang ditampung variabel `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**-nya. > Closure bisa juga dengan gaya manifest typing, caranya dengan menuliskan skema closure-nya sebagai tipe data. Contoh:
var closure (func (string, int, []string) int)
closure = func (a string, b int, c []string) int {
    // ..
} ## A.21.3. Closure Sebagai Nilai Kembalian -Salah satu keunikan closure lainnya adalah bisa dijadikan sebagai nilai balik fungsi, cukup aneh memang, tapi pada suatu kondisi teknik ini sangat membantu. Di bawah ini disediakan sebuah fungsi bernama `findMax()`, fungsi ini salah satu nilai kembaliannya berupa closure. +Salah satu keunikan lain dari closure adalah: closure bisa dijadikan sebagai nilai balik fungsi. Cukup aneh, tapi pada kondisi tertentu teknik ini sangat berguna. Sebagai contoh, di bawah ini dideklarasikan sebuah fungsi bernama `findMax()` yang salah satu nilai kembaliannya adalah berupa closure. ```go package main @@ -148,7 +147,12 @@ return len(res), func() []int { > Fungsi tanpa nama yang akan dikembalikan boleh disimpan pada variabel terlebih dahulu. Contohnya:
var getNumbers = func() []int {
    return res
}
return len(res), getNumbers -Sedikit tentang fungsi `findMax()`, fungsi ini digunakan untuk mencari banyaknya angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Nilai kembalian pertama adalah jumlah angkanya. Nilai kembalian kedua berupa closure yang mengembalikan angka-angka yang dicari. Berikut merupakan contoh implementasi fungsi tersebut. +Tentang fungsi `findMax()` sendiri, fungsi ini dibuat untuk mempermudah pencarian angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Fungsi ini mengembalikan dua buah nilai balik: + +- Nilai balik pertama adalah jumlah angkanya. +- Nilai balik kedua berupa closure yang mengembalikan angka-angka yang dicari. + +Berikut merupakan contoh implementasi fungsi tersebut: ```go func main() { diff --git a/content/A-fungsi-multiple-return.md b/content/A-fungsi-multiple-return.md index 796983568..2ac5893f0 100644 --- a/content/A-fungsi-multiple-return.md +++ b/content/A-fungsi-multiple-return.md @@ -1,12 +1,10 @@ # A.19. Fungsi Multiple Return -Umumnya fungsi hanya memiliki satu buah nilai balik saja. Jika ada kebutuhan di mana data yang dikembalikan harus banyak, biasanya digunakanlah tipe seperti `map`, slice, atau `struct` sebagai nilai balik. +Di Go, suatu fungsi bisa saja mengembalikan nilai belik lebih dari 1 buah. Teknik ini bisa menjadi alternatif selain menggunakan tipe data kolektif seperti `map`, slice, atau `struct` sebagai nilai balik. Pada chapter ini kita akan belajar penerapannya. -Go menyediakan kapabilitas bagi programmer untuk membuat fungsi memiliki banyak nilai balik. Pada chapter ini akan dibahas bagaimana penerapannya. +## A.19.1. Penerapan Fungsi Multiple Return -## A.19.1 Penerapan Fungsi Multiple Return - -Cara membuat fungsi yang memiliki banyak nilai balik tidaklah sulit. Tinggal tulis saja pada saat deklarasi fungsi semua tipe data nilai yang dikembalikan, dan pada keyword `return` tulis semua data yang ingin dikembalikan. Contoh bisa dilihat pada berikut. +Cara membuat fungsi agar memiliki banyak nilai balik tidaklah sulit, caranya pada saat deklarasi fungsi, tulis semua tipe data nilai balik yang ingin dikembalikan. Kemudian dalam body fungsi, pada penggunaan keyword `return`, tulis semua data yang ingin dikembalikan. Contoh: ```go package main @@ -25,7 +23,7 @@ func calculate(d float64) (float64, float64) { } ``` -Fungsi `calculate()` di atas menerima satu buah parameter (`diameter`) yang digunakan dalam proses perhitungan. Di dalam fungsi tersebut ada 2 hal yang dihitung, yaitu nilai **luas** dan **keliling**. Kedua nilai tersebut kemudian dijadikan sebagai return value fungsi. +Fungsi `calculate()` di atas memiliki satu buah parameter yaitu `d` (diameter). Di dalam fungsi terdapat operasi perhitungan nilai **luas** dan **keliling** dari nilai `d`. Kedua hasilnya kemudian dijadikan sebagai return value. Cara pendefinisian banyak nilai balik bisa dilihat pada kode di atas, langsung tulis tipe data semua nilai balik dipisah tanda koma, lalu ditambahkan kurung di antaranya. @@ -39,7 +37,7 @@ Tak lupa di bagian penulisan keyword `return` harus dituliskan juga semua data y return area, circumference ``` -Implementasi dari fungsi `calculate()` di atas, bisa dilihat pada kode berikut. +Sekarang, coba panggil fungsi `calculate()` yang sudah dibuat untuk mencari nilai luas dan keliling dari suatu diameter. ```go func main() { @@ -55,13 +53,13 @@ Output program: ![Penerapan teknik multiple return](images/A_fungsi_multiple_return_1_multiple_return.png) -Karena fungsi tersebut memiliki banyak nilai balik, maka pada pemanggilannya harus disiapkan juga banyak variabel untuk menampung nilai kembalian yang ada (sesuai jumlah nilai balik fungsi). +Fungsi `calculate()` memiliki banyak nilai balik, maka dalam pemanggilannya harus disiapkan juga sejumlah variabel untuk menampung nilai balik fungsi (sesuai dengan jumlah nilai balik yang dideklarasikan). ```go var area, circumference = calculate(diameter) ``` -## A.19.2 Fungsi Dengan Predefined Return Value +## A.19.2. Fungsi Dengan Predefined Return Value Keunikan lainnya yang jarang ditemui di bahasa lain adalah, di Go variabel yang digunakan sebagai nilai balik bisa didefinisikan di awal. @@ -84,15 +82,15 @@ Fungsi dideklarasikan memiliki 2 buah tipe data, dan variabel yang nantinya dija Karena variabel nilai balik sudah ditentukan di awal, untuk mengembalikan nilai cukup dengan memanggil `return` tanpa perlu diikuti variabel apapun. Nilai terakhir `area` dan `circumference` sebelum pemanggilan keyword `return` adalah hasil dari fungsi di atas. ---- +## A.19.3. Penjelasan tambahan -Ada beberapa hal baru dari kode di atas yang perlu dibahas, seperti `math.Pow()` dan `math.Pi`. Berikut adalah penjelasannya. +Ada beberapa hal baru dari kode di atas yang perlu dibahas, diantaranya `math.Pow()` dan `math.Pi`. -#### • Penggunaan Fungsi `math.Pow()` +#### ◉ Penggunaan Fungsi `math.Pow()` -Fungsi `math.Pow()` digunakan untuk memangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`. +Fungsi `math.Pow()` digunakan untuk operasi pangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`. -#### • Penggunaan Konstanta `math.Pi` +#### ◉ Penggunaan Konstanta `math.Pi` `math.Pi` adalah konstanta bawaan `package math` yang merepresentasikan **Pi** atau **22/7**. diff --git a/content/A-fungsi-sebagai-parameter.md b/content/A-fungsi-sebagai-parameter.md index 8eacd0a12..0e27fde4d 100644 --- a/content/A-fungsi-sebagai-parameter.md +++ b/content/A-fungsi-sebagai-parameter.md @@ -1,8 +1,8 @@ # A.22. Fungsi Sebagai parameter -Setelah pada chapter sebelumnya kita belajar mengenai fungsi yang mengembalikan nilai balik berupa fungsi, kali ini topiknya tidak kalah unik, yaitu fungsi yang digunakan sebagai parameter. +Pada chapter sebelumnya kita telah belajar tentang fungsi yang mengembalikan nilai balik berupa fungsi. Kali ini topiknya tidak kalah unik, yaitu tentang fungsi yang memiliki parameter sebuah fungsi. -Di Go, fungsi bisa dijadikan sebagai tipe data variabel. Dari situ sangat memungkinkan untuk menjadikannya sebagai parameter juga. +Di Go, fungsi bisa dijadikan sebagai tipe data variabel, maka sangat memungkinkan untuk menjadikannya sebagai parameter. ## A.22.1. Penerapan Fungsi Sebagai Parameter @@ -50,7 +50,7 @@ func main() { } ``` -Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di atas. Berikut merupakan penjelasannya. +Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di atas. Berikut adalah penjelasannya: 1. Data array (yang didapat dari parameter pertama) akan di-looping. 2. Di tiap perulangannya, closure `callback` dipanggil, dengan disisipkan data tiap elemen perulangan sebagai parameter. @@ -62,15 +62,17 @@ Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di Pada `dataContainsO`, parameter kedua fungsi `filter()` berisikan statement untuk deteksi apakah terdapat substring `"o"` di dalam nilai variabel `each` (yang merupakan data tiap elemen), jika iya, maka kondisi filter bernilai `true`, dan sebaliknya. -pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter. +Pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter. -Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat membantu proses pembuatan aplikasi. +Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat berguna. ## A.22.2. Alias Skema Closure -Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Pada fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik. +Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Di fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik. -Pada fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama. Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh: +Untuk fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias dalam pendefinisiannya, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama, maka kita tidak perlu menuliskan skema panjang fungsi tersebut berulang-ulang. + +Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh: ```go type FilterCallback func(string) bool @@ -82,11 +84,11 @@ func filter(data []string, callback FilterCallback) []string { Skema `func(string) bool` diubah menjadi tipe dengan nama `FilterCallback`. Tipe tersebut kemudian digunakan sebagai tipe data parameter `callback`. ---- +## A.22.3. Penjelasan tambahan Di bawah ini merupakan penjelasan tambahan mengenai fungsi `strings.Contains()`. -## A.22.2.1. Penggunaan Fungsi `string.Contains()` +#### ◉ Penggunaan Fungsi `string.Contains()` Inti dari fungsi ini adalah untuk deteksi apakah sebuah substring adalah bagian dari string, jika iya maka akan bernilai `true`, dan sebaliknya. Contoh penggunaannya: diff --git a/content/A-fungsi-variadic.md b/content/A-fungsi-variadic.md index 75e54f00a..7263e9926 100644 --- a/content/A-fungsi-variadic.md +++ b/content/A-fungsi-variadic.md @@ -1,16 +1,16 @@ # A.20. Fungsi Variadic -Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter sejenis yang tak terbatas. Maksud **tak terbatas** di sini adalah jumlah parameter yang disisipkan ketika pemanggilan fungsi bisa berapa saja. +Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter bisa menampung nilai sejenis yang tidak terbatas jumlahnya. -Parameter variadic memiliki sifat yang mirip dengan slice. Nilai dari parameter-parameter yang disisipkan bertipe data sama, dan ditampung oleh sebuah variabel saja. Cara pengaksesan tiap datanya juga sama, dengan menggunakan index. +Parameter variadic memiliki sifat yang mirip dengan slice, yaitu nilai dari parameter-parameter yang disisipkan bertipe data sama, dan kesemuanya cukup ditampung oleh satu variabel saja. Cara pengaksesan tiap nilai juga mirip, yaitu dengan menggunakan index. Pada chapter ini kita akan belajar mengenai cara penerapan fungsi variadic. ## A.20.1. Penerapan Fungsi Variadic -Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel (sebelum tipe data). Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut. +Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel, sebelum tipe data. Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut. -Berikut merupakan contoh penerepannya. +Contoh program: ```go package main @@ -56,19 +56,21 @@ Nilai tiap parameter bisa diakses seperti cara pengaksesan tiap elemen slice. Pa for _, number := range numbers { ``` ---- +## A.20.2. Penjelasan tambahan -Berikut merupakan penjelasan tambahan dari kode yang telah kita tulis. +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: -#### • Penggunaan Fungsi `fmt.Sprintf()` +#### ◉ Penggunaan Fungsi `fmt.Sprintf()` -Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada kasus di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`. +Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada case di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`. Selain `fmt.Sprintf()`, ada juga `fmt.Sprint()` dan `fmt.Sprintln()`. -#### • Penggunaan Fungsi `float64()` +#### ◉ Penggunaan Fungsi `float64()` + +Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) menandakan bahwa digunakan untuk keperluan **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Sebagian besar tipe data dasar yang telah dipelajari pada chapter [A.9. Variabel](/A-variabel.html) bisa di-casting. -Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) berguna untuk **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Sebagian besar tipe data dasar yang telah dipelajari pada chapter [A.9. Variabel](/A-variabel.html) bisa di-cast. Dan cara penerapannya juga sama, cukup panggil sebagai fungsi, lalu masukan data yang ingin dikonversi sebagai parameter. +Cara penerapan casting: panggil saja tipe data yang diingunkan seperti pemanggilan fungsi, lalu masukan data yang ingin dikonversi sebagai argument pemanggilan fungsi tersebut. Pada contoh di atas, variabel `total` yang tipenya adalah `int`, dikonversi menjadi `float64`, begitu juga `len(numbers)` yang menghasilkan `int` dikonversi ke `float64`. @@ -76,11 +78,9 @@ Variabel `avg` perlu dijadikan `float64` karena penghitungan rata-rata lebih ser Operasi bilangan (perkalian, pembagian, dan lainnya) di Go hanya bisa dilakukan jika tipe datanya sejenis. Maka dari itulah perlu adanya casting ke tipe `float64` pada tiap operand. ---- - -## A.20.2. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice +## A.20.3. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice -Slice bisa digunakan sebagai parameter variadic. Caranya dengan menambahkan tanda titik tiga kali, tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut. +Slice bisa digunakan sebagai argument pada fungsi variadic. Caranya penerapannya: tulis saja nama variabel tapi disertai dengan tanda titik tiga kali, dituliskan tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut: ```go var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3} @@ -90,9 +90,9 @@ var msg = fmt.Sprintf("Rata-rata : %.2f", avg) fmt.Println(msg) ``` -Pada kode di atas, variabel `numbers` yang merupakan slice int, disisipkan ke fungsi `calculate()` sebagai parameter variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna ketika sebuah data slice ingin difungsikan sebagai parameter variadic. +Pada kode di atas, variabel `numbers` bertipe data slice int, disisipkan pada pemanggilan fungsi `calculate()` sebagai argument parameter fungsi variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna pada case dimana sebuah data slice perlu untuk digunakan sebagai argument parameter variadic. -Perhatikan juga kode berikut ini. Intinya adalah sama, hanya caranya yang berbeda. +Agar lebih jelas, perhatikan 2 kode berikut. Intinya sama, hanya cara penulisannya yang berbeda. ```go var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3} @@ -105,7 +105,7 @@ var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3) Pada deklarasi parameter fungsi variadic, tanda 3 titik (`...`) dituliskan sebelum tipe data parameter. Sedangkan pada pemanggilan fungsi dengan menyisipkan parameter array, tanda tersebut dituliskan di belakang variabelnya. -## A.20.3. Fungsi Dengan Parameter Biasa & Variadic +## A.20.4. Fungsi Dengan Parameter Biasa & Variadic Parameter variadic bisa dikombinasikan dengan parameter biasa, dengan syarat parameter variadic-nya harus diposisikan di akhir. Contohnya bisa dilihat pada kode berikut. @@ -123,7 +123,7 @@ func yourHobbies(name string, hobbies ...string) { Nilai parameter pertama fungsi `yourHobbies()` akan ditampung oleh `name`, sedangkan nilai parameter kedua dan seterusnya akan ditampung oleh `hobbies` sebagai slice. -Cara pemanggilannya masih sama seperi pada fungsi biasa. +Cara pemanggilannya masih sama seperi pada fungsi biasa, contoh: ```go func main() { @@ -131,7 +131,7 @@ func main() { } ``` -Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali. +Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali seperti ini: ```go func main() { diff --git a/content/A-fungsi.md b/content/A-fungsi.md index 3efec2df9..dc748eec4 100644 --- a/content/A-fungsi.md +++ b/content/A-fungsi.md @@ -1,18 +1,20 @@ # A.18. Fungsi -Fungsi merupakan aspek penting dalam pemrograman. Definisi fungsi sendiri adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (singkatan dari *don't repeat yourself*), tak perlu menuliskan banyak kode yang kegunaannya berkali-kali, cukup sekali saja lalu panggil sesuai kebutuhan. +Dalam konteks pemrograman, fungsi adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (singkatan dari *don't repeat yourself*) yang artinya kita tidak perlu menuliskan banyak kode untuk kegunaan yang sama berulang kali. Cukup deklarasikan sekali saja blok kode sebagai suatu fungsi, lalu panggil sesuai kebutuhan. -Pada chapter ini kita akan belajar tentang penggunaan fungsi di Go. +Pada chapter ini kita akan belajar tentang penerapannya di Go. ## A.18.1. Penerapan Fungsi -Sebenarnya tanpa sadar, kita sudah menerapkan fungsi pada pembahasan-pembahasan sebelum ini, yaitu pada fungsi `main`. Fungsi `main` merupakan fungsi yang paling utama pada program Go. +Mungkin pembaca sadar, bahwa sebenarnya kita sudah mengimplementasikan fungsi pada banyak praktek sebelumnya, yaitu fungsi `main()`. Fungsi `main()` sendiri merupakan fungsi utama pada program Go, yang akan dieksekusi ketika program dijalankan. -Cara membuat fungsi cukup mudah, yaitu dengan menuliskan keyword `func`, diikuti setelahnya nama fungsi, kurung yang berisikan parameter, dan kurung kurawal untuk membungkus blok kode. +Selain fungsi `main()`, kita juga bisa membuat fungsi lainnya. Dan caranya cukup mudah, yaitu dengan menuliskan keyword `func` kemudian diikuti nama fungsi, lalu kurung `()` (yang bisa diisi parameter), dan diakhiri dengan kurung kurawal untuk membungkus blok kode. -Parameter sendiri adalah variabel yang disisipkan pada saat pemanggilan fungsi. +Parameter merupakan variabel yang menempel di fungsi yang nilainya ditentukan saat pemanggilan fungsi tersebut. Parameter sifatnya opsional, suatu fungsi bisa tidak memiliki parameter, atau bisa saja memeliki satu atau banyak parameter (tergantung kebutuhan). -Silakan lihat dan praktekan kode tentang implementasi fungsi berikut. +> Data yang digunakan sebagai value parameter saat pemanggilan fungsi biasa disebut dengan argument parameter (atau argument). + +Agar lebih jelas, silakan lihat dan praktekan kode contoh implementasi fungsi berikut ini: ```go package main @@ -31,17 +33,22 @@ func printMessage(message string, arr []string) { } ``` -Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`. +Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage()` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`. + +Fungsi tersebut dipanggil dalam `main()`, dalam pemanggilannya disisipkan dua buah argument parameter. -Fungsi tersebut dipanggil dalam `main`, dengan disisipkan 2 buah data sebagai parameter, data pertama adalah string `"halo"` yang ditampung parameter `message`, dan parameter ke 2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`. +1. Argument parameter pertama adalah string `"halo"` yang ditampung parameter `message` +2. Argument parameter ke-2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr` -Di dalam `printMessage`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`). +Di dalam `printMessage()`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`). ![Contoh penggunaan fungsi](images/A_fungsi_1_function.png) ## A.18.2. Fungsi Dengan Return Value / Nilai Balik -Sebuah fungsi bisa dirancang tidak mengembalikan nilai balik (*void*), atau bisa mengembalikan suatu nilai. Fungsi yang memiliki nilai kembalian, harus ditentukan tipe data nilai baliknya pada saat deklarasi. +Selain parameter, fungsi bisa memiliki attribute **return value** atau nilai balik. Fungsi yang memiliki return value, saat deklarasinya harus ditentukan terlebih dahulu tipe data dari nilai baliknya. + +> Fungsi yang tidak mengembalikan nilai apapun (contohnya seperti fungsi `main()` dan `printMessage()`) biasa disebut dengan **void function** Program berikut merupakan contoh penerapan fungsi yang memiliki return value. @@ -61,8 +68,10 @@ func main() { randomValue = randomWithRange(2, 10) fmt.Println("random number:", randomValue) + randomValue = randomWithRange(2, 10) fmt.Println("random number:", randomValue) + randomValue = randomWithRange(2, 10) fmt.Println("random number:", randomValue) } @@ -73,41 +82,39 @@ func randomWithRange(min, max int) int { } ``` -Fungsi `randomWithRange` bertugas untuk *generate* angka acak sesuai dengan range yang ditentukan, yang kemudian angka tersebut dijadikan nilai kembalian fungsi. +Fungsi `randomWithRange()` didesain untuk *generate* angka acak sesuai dengan range yang ditentukan lewat parameter, yang kemudian angka tersebut dijadikan nilai balik fungsi. ![Fungsi dengan nilai balik](images/A_fungsi_2_function_return_type.png) -Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange`. +Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange()`. ```go func randomWithRange(min, max int) int ``` -Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang ingin dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi. +Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi. Eksekusi keyword `return` akan menjadikan proses dalam blok fungsi berhenti pada saat itu juga. Semua statement setelah keyword tersebut tidak akan dieksekusi. ---- - Dari kode di atas mungkin ada beberapa hal yang belum pernah kita lakukan pada pembahasan-pembahasan sebelumnya, kita akan bahas satu-persatu. ## A.18.3. Penggunaan Fungsi `rand.New()` -Fungsi ini digunakan untuk membuat object randomizer, yang dari object tersebut nilai random/acak bisa di-generate. Dalam penerapannya, fungsi `rand.New()` membutuhkan argument yaitu random source seed, yang bisa kita buat lewat statement `rand.NewSource(time.Now().Unix())`. +Fungsi `rand.New()` digunakan untuk membuat object randomizer, yang dari object tersebut kita bisa mendapatkan nilai random/acak hasil generator. Dalam penerapannya, fungsi `rand.New()` membutuhkan argument yaitu random source seed, yang bisa kita buat lewat statement `rand.NewSource(time.Now().Unix())`. ```go var randomizer = rand.New(rand.NewSource(time.Now().Unix())) ``` -> Dalam penggunaan fungsi `rand.NewSource`, argument bisa diisi dengan nilai apapun, salah satunya adalah `time.Now().Unix()`. +> Dalam penggunaan fungsi `rand.NewSource()`, argument bisa diisi dengan nilai apapun, salah satunya adalah `time.Now().Unix()`. > -> Lebih detailnya mengenai random dibahas pada chapter [A.39. Random](A-random.html). +> Lebih detailnya mengenai random dan apa peran seed dibahas pada chapter [A.39. Random](A-random.html). -Fungsi `rand.New()` berada dalam package `math/rand`, yang harus di-import terlebih dahulu sebelum bisa dimanfaatkan. Package `time` juga perlu di-import karena kita menggunakan fungsi `(time.Now().Unix())` di situ. +Fungsi `rand.New()` berada dalam package `math/rand`. Package tersebut harus di-import terlebih dahulu sebelum bisa menggunakan fungsi-fungsi yang ada didalamnya. Package `time` juga perlu di-import karena di contoh ini fungsi `(time.Now().Unix())` digunakan. ## A.18.4. Import Banyak Package -Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas. +Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas silakan pilih sesuai selera. ```go import "fmt" @@ -137,7 +144,7 @@ func randomWithRange(min, max int) int ## A.18.6. Penggunaan Keyword `return` Untuk Menghentikan Proses Dalam Fungsi -Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi di mana ia dipakai. Contohnya bisa dilihat pada kode berikut. +Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi di mana ia ditulis. Contohnya bisa dilihat pada kode berikut. ```go package main @@ -161,7 +168,7 @@ func divideNumber(m, n int) { } ``` -Fungsi `divideNumber` dirancang tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya. +Fungsi `divideNumber()` dirancang tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya. Di dalamnya terdapat proses validasi nilai variabel pembagi, jika nilainya adalah 0, maka akan ditampilkan pesan bahwa pembagian tidak bisa dilakukan, lalu proses dihentikan pada saat itu juga (dengan memanfaatkan keyword `return`). Jika nilai pembagi valid, maka proses pembagian diteruskan. diff --git a/content/A-go-command.md b/content/A-go-command.md index 907786226..0e63aea79 100644 --- a/content/A-go-command.md +++ b/content/A-go-command.md @@ -1,14 +1,14 @@ # A.6. Command -Pengembangan aplikasi Go tak jauh dari hal-hal yang berbau CLI atau *Command Line Interface*. Proses inisialisasi project, kompilasi, testing, eksekusi program, semuanya dilakukan lewat command line. +Pengembangan aplikasi Go pastinya tak akan jauh dari hal-hal yang berbau CLI atau *Command Line Interface*. Di Go, proses inisialisasi project, kompilasi, testing, eksekusi program, semuanya dilakukan lewat command line. Go menyediakan command `go`, dan pada chapter ini kita akan mempelajari beberapa di antaranya. -> Pada pembelajaran chapter ini, pembaca tidak harus praktek, cukup pelajari saja untuk tahu. Mengenai praktek sendiri akan dimulai pada chapter selanjutnya, yaitu [A.7. Program Pertama: Hello World](/A-hello-world.html). +> Pada pembelajaran chapter ini, pembaca tidak harus menghafal dan mempraktekan semuanya, cukup ikuti saja pembelajaran agar mulai familiar. Perihal prakteknya sendiri akan dimulai pada chapter selanjutnya, yaitu [A.7. Program Pertama: Hello World](/A-hello-world.html). ## A.6.1. Command `go mod init` -*Command* `go mod init` digunakan untuk inisialisasi project pada Go (menggunakan Go Modules). Untuk nama project bisa menggunakan apapun, tapi umumnya adalah disamakan dengan nama direktori. +*Command* `go mod init` digunakan untuk inisialisasi project pada Go yang menggunakan Go Modules. Untuk nama project bisa menggunakan apapun, tapi umumnya disamakan dengan nama direktori/folder. Nama project ini penting karena nantinya berpengaruh pada *import path sub packages* yang ada dalam project tersebut. @@ -20,7 +20,7 @@ go mod init ## A.6.2. Command `go run` -*Command* `go run` digunakan untuk eksekusi file program (file ber-ekstensi `.go`). Cara penggunaannya dengan menuliskan *command* tersebut diikuti argumen nama file. +*Command* `go run` digunakan untuk eksekusi file program, yaitu file yang ber-ekstensi `.go`. Cara penggunaannya dengan menuliskan *command* tersebut diikuti argumen nama file. Berikut adalah contoh penerapan `go run` untuk eksekusi file program `main.go` yang tersimpan di path `project-pertama` yang path tersebut sudah diinisialisasi menggunakan `go mod init`. @@ -31,7 +31,7 @@ go run main.go ![Eksekusi file program menggunakan `go run`](images/A_go_command_1_go_run.png) -*Command* `go run` hanya bisa digunakan pada file yang nama package-nya adalah `main`. Lebih jelasnya dibahas pada chapter selanjutnya ([A.7. Program Pertama: Hello World](/A-hello-world.html)). +*Command* `go run` hanya bisa digunakan pada file yang nama package-nya adalah `main`. Lebih jelasnya dibahas pada chapter selanjutnya, yaitu ([A.7. Program Pertama: Hello World](/A-hello-world.html)). Jika ada banyak file yang package-nya `main` dan file-file tersebut berada pada satu direktori level dengan file utama, maka eksekusinya adalah dengan menuliskan semua file sebagai argument *command* `go run`. Contohnya bisa dilihat pada kode berikut. @@ -39,11 +39,9 @@ Jika ada banyak file yang package-nya `main` dan file-file tersebut berada pada go run main.go library.go ``` -> Lebih jelasnya perihal argument dan flag akan dibahas pada chapter [A.48. Arguments & Flag](/A-command-line-args-flag.html)) - ## A.6.3. Command `go test` -Go menyediakan package `testing`, berguna untuk keperluan unit test. File yang akan di-test harus memiliki akhiran `_test.go`. +Go menyediakan package `testing`, berguna untuk keperluan pembuatan file test. Pada penerapannya, ada aturan yang wajib diikuti yaitu nama file test harus berakhiran `_test.go`. Berikut adalah contoh penggunaan *command* `go test` untuk testing file `main_test.go`. @@ -57,50 +55,53 @@ go test main_test.go *Command* ini digunakan untuk mengkompilasi file program. -Sebenarnya ketika eksekusi program menggunakan `go run`, terjadi proses kompilasi juga. File hasil kompilasi akan disimpan pada folder temporary untuk selanjutnya langsung dieksekusi. +Sebenarnya ketika eksekusi program menggunakan `go run` didalamnya terjadi proses kompilasi juga. File hasil kompilasi kemudian disimpan pada folder temporary untuk selanjutnya langsung dieksekusi. -Berbeda dengan `go build`, *command* ini menghasilkan file *executable* atau *binary* pada folder yang sedang aktif. Contohnya bisa dilihat pada kode berikut. +Berbeda dengan `go build`, *command* ini menghasilkan file *executable* atau *binary* pada folder yang sedang aktif. Contoh praktiknya bisa dilihat di bawah ini. ![Kompilasi file program menghasilkan file executable](images/A_go_command_4_go_build.png) -Pada contoh di atas, project `project-pertama` di-build, menghasilkan file baru pada folder yang sama, yaitu `project-pertama.exe`, yang kemudian dieksekusi. *Default*-nya nama project akan otomatis dijadikan nama *binary*. +Di contoh, project `project-pertama` di-build, hasilnya adalah file baru bernama `project-pertama.exe` berada di folder yang sama. File *executable* tersebut kemudian dieksekusi. -Untuk nama executable sendiri bisa diubah menggunakan flag `-o`. Contoh: +*Default* nama file binary atau executable adalah sesuai dengan nama project. Untuk mengubah nama file executable, gunakan flag `-o`. Contoh: ``` go build -o go build -o program.exe ``` -> Untuk sistem operasi non-windows, tidak perlu menambahkan akhiran `.exe` pada nama *binary* +> Khusus untuk sistem operasi non-windows, tidak perlu menambahkan akhiran `.exe` pada nama *binary* ## A.6.5. Command `go get` -*Command* `go get` digunakan untuk men-download package. Sebagai contoh saya ingin men-download package Kafka driver untuk Go pada project `project-pertama`. +*Command* `go get` digunakan untuk men-download package atau *dependency*. Sebagai contoh, penulis ingin men-download package Kafka driver untuk Go pada project `project-pertama`, maka command-nya kurang lebih seperti berikut: ```bash cd project-pertama go get github.com/segmentio/kafka-go -dir ``` ![Download package menggunakan `go get`](images/A_go_command_6_go_get.png) -Pada contoh di atas, `github.com/segmentio/kafka-go` adalah URL package kafka-go. Package yang sudah terunduh tersimpan dalam temporary folder yang ter-link dengan project folder di mana *command* `go get` dieksekusi, menjadikan project tersebut bisa meng-*import* package terunduh. +Pada contoh di atas, bisa dilihat bahwa URL `github.com/segmentio/kafka-go` merupakan URL package kafka-go. Package yang sudah terunduh tersimpan dalam temporary folder yang ter-link dengan project folder di mana *command* `go get` dieksekusi, menjadikan project tersebut bisa meng-*import* package yang telah di-download. -Untuk mengunduh dependensi versi terbaru, gunakan flag `-u` pada command `go get`, misalnya: +Untuk mengunduh package/dependency versi terbaru, gunakan flag `-u` pada command `go get`, contohnya: ``` go get -u github.com/segmentio/kafka-go ``` -Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-luar project maka akan diunduh ke pada GOPATH. +Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-luar path project maka dependency yang ter-unduh akan ter-link dengan GOPATH, bukan dengan project. + +## A.6.6. Command `go mod download` + +*Command* `go mod download` digunakan untuk men-download dependency. -## A.6.6. Command `go mod tidy` +## A.6.7. Command `go mod tidy` -*Command* `go mod tidy` digunakan untuk memvalidasi dependensi. Jika ada dependensi yang belum ter-download, maka akan otomatis di-download. +*Command* `go mod tidy` digunakan untuk memvalidasi dependency sekaligus men-download-nya jika memang belum ter-download. -## A.6.7. Command `go mod vendor` +## A.6.8. Command `go mod vendor` Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir serial chapter A, pada chapter [A.61. Go Vendoring](/A-go-vendoring.html). diff --git a/content/A-go-vendoring.md b/content/A-go-vendoring.md index 35cb011aa..c2deefdb0 100644 --- a/content/A-go-vendoring.md +++ b/content/A-go-vendoring.md @@ -1,14 +1,14 @@ # A.61. Go Vendoring -Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk menyimpan dependensi di lokal. +Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk menyimpan copy dependency di lokal dalam folder project. ## A.61.1. Penjelasan -Vendoring di Go merupakan kapabilitas untuk mengunduh semua dependency atau *3rd party*, untuk disimpan di lokal dalam folder project, dalam folder bernama `vendor`. +Vendoring di Go memberikan kita kapabilitas untuk mengunduh semua dependency atau *3rd party*, untuk disimpan di lokal dalam folder project, dalam subfolder bernama `vendor`. -Dengan adanya folder tersebut, maka Go tidak akan *lookup* 3rd party ke cache folder, melainkan langsung mempergunakan yang ada dalam folder `vendor`. Jadi tidak perlu download lagi dari internet. +Dengan adanya folder tersebut, maka Go tidak akan *lookup* 3rd party ke cache folder ataupun ke GOPATH, melainkan langsung mengambil dari yang ada dalam folder `vendor`. Jadi kalau dependency sudah ada di dalam `vendor`, maka kita tidak perlu download lagi dari internet menggunakan command `go mod download` ataupun `go mod tidy`. -Ok lanjut. +Ok lanjut ke praktek ya. ## A.61.2. Praktek Vendoring @@ -38,7 +38,7 @@ func main() { } ``` -Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adlah gubrak. +Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adalah gubrak. ![Vendoring](images/A_go_vendoring_1_vendor.png) @@ -46,14 +46,7 @@ Bisa dilihat, sekarang library gubrak *source code*-nya disimpan dalam folder `v ## A.61.3 Build dan Run Project yang Menerapkan Vendoring -Untuk membuat proses build lookup ke folder vendor, kita tidak perlu melakukan apa-apa, setidaknya jika versi Go yang diinstall adalah 1.14 ke atas. Maka command build maupun run masih sama. - -``` -go run main.go -go build -o executable -``` - -Untuk yg menggunakan versi Go di bawah 1.14, penulis sarankan untuk upgrade. Atau bisa gunakan flag `-mod=vendor` untuk memaksa Go lookup ke folder `vendor`. +Cara agar Go lookup ke folder `vendor` saat build adalah dengan menambahkan flag `-mod=vendor` sewaktu build atau run project. ``` go run -mod=vendor main.go @@ -62,11 +55,9 @@ go build -mod=vendor -o executable ## A.61.3. Manfaat Vendoring -Manfaat vendoring adalah pada sisi kompatibilitas dan kestabilan 3rd party. Jadi dengan vendor, misal 3rd party yang kita gunakan di itu ada update yg sifatnya tidak *backward compatible*, maka aplikasi kita tetap aman karena menggunakan yang ada dalam folder `vendor`. - -Jika tidak menggunakan vendoring, maka bisa saja saat `go mod tidy` sukses, namun sewaktu build error, karena ada fungsi yg tidak kompatibel lagi misalnya. +Manfaat vendoring adalah pada sisi kompatibilitas & kestabilan 3rd party, selain itu kita tidak perlu repot mendownload dependency karena semuanya sudah ada di lokal. -Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan saja. +Konsekuensi penerapan vendoring adalah size project menjadi cukup besar. Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan saja. --- diff --git a/content/A-golang-generics.md b/content/A-golang-generics.md index 8af491f11..e79828b1a 100644 --- a/content/A-golang-generics.md +++ b/content/A-golang-generics.md @@ -1,20 +1,20 @@ # A.65. Go Generics -Pada chapter ini kita akan belajar tentang Generics di Go. +Pada chapter ini kita akan belajar tentang penerapan Generics di Go. ## A.65.1. Konsep Generic Programming -Generic Programming adalah salah satu metode dalam penulisan kode program, di mana tipe data dalam kode didefinisikan menggunakan tipe data yang tipe pastinya adalah dituliskan belakangan saat kode tersebut di-call atau dieksekusi. Konsep ini sudah cukup umum terutama pada bahasa yang static type. +Generic Programming adalah salah satu metode dalam penulisan kode program, di mana tipe data dalam kode didefinisikan menggunakan suatu tipe yang tipe pastinya ditulis belakangan saat kode tersebut di-call atau dieksekusi. Konsep generic ini cukup umum diterapkan terutama pada bahasa pemrograman yang mengadopsi static typing. -Di Go, kita punya tipe `interface{}` yang biasa difungsikan sebagai tipe untuk menampung data yang tidak pasti tipe datanya. Generic dan `interface{}` berbeda. Tipe `interface{}` akan membungkus data aslinya atau *underlying value*-nya, dan untuk mengakses data tersebut, kita perlu menerapkan *type assertion*, contohnya `data.(int)`. +Di Go, kita punya tipe `any` atau `interface{}` yang biasa difungsikan sebagai penampung data yang tidak pasti tipe datanya. Generic berbeda dibanding `any`. Tipe `any` dalam prakteknya membungkus data asli atau *underlying value*-nya, dengan pengaksesan data asli tersebut dilakukan via metode *type assertion*, contohnya `data.(int)`. -Berbeda dibanding `interface{}`, pada penggunaan generic kita perlu mendefinisikan cakupan tipe data yang kompatibel untuk dipakai saat pemanggilan kode, atau bisa juga menggunakan keyword `comparable`, yang artinya tipe data adalah kompatibel dengan tipe apapun. +Berbeda dibanding `any`, pada Generic kita perlu mendefinisikan cakupan tipe data yang kompatibel untuk digunakan saat pemanggilan kode. -Ok, mari kita lanjut ke pembahasan yang lebih teknis agar tidak bingung. +Ok, mari kita lanjut ke praktek saja agar tidak makin bingung. ## A.65.2. Penerapan Generic pada Fungsi -Mari kita mulai pembelajaran dengan kode di bawah ini: +Mari kita mulai pembelajaran dengan kode sederhana berikut: ```go package main @@ -37,9 +37,11 @@ func main() { Pada kode di atas, didefinisikan sebuah fungsi `Sum()` yang tugasnya menghitung total atau *summary* dari data slice numerik yang disisipkan di parameter. Dalam `main()`, kita panggil fungsi tersebut untuk menghitung total dari sejumlah data dengan tipe `[]int`. Saya rasa sampai sini cukup jelas. -Fungsi `Sum()` memiliki satu limitasinya, yaitu hanya bisa digunakan pada data yang tipenya `[]int`, tidak bisa untuk tipe slice numerik lain. Bagaimana jika menggunakan tipe `interface{}`? apakah bisa? bisa saja sebenarnya, tapi pastinya lebih report karena sulit untuk menerapkan *type assertion* kalau tidak tau tipe pasti parameter `numbers` itu apa. Penggunaan `interface{}` perlu dibarengi dengan penerapan [reflection API](/A-reflect.html). +Fungsi `Sum()` memiliki satu limitasinya, yaitu hanya bisa digunakan pada data yang tipenya `[]int`, tidak bisa untuk tipe slice numerik lain. Bagaimana jika menggunakan tipe `interface{}`? apakah bisa? bisa saja sebenarnya, tapi pastinya lebih report karena sulit untuk menerapkan *type assertion* kalau tidak tau pasti cakupan tipe yang di-support oleh parameter `numbers` itu apa saja. -Di sini kita bisa terapkan Generic, kita akan modifikasi fungsi di atas agar bisa menampung tipe data slice numerik lainnya diluar `[]int`. +> Alternatifnya, penggunaan `interface{}` bisa dibarengi dengan penerapan [reflection API](/A-reflect.html). + +Nah, agar tidak repot, di sini kita akan terapkan Generic. Kode akan dimodifikasi atas agar bisa menampung tipe data slice numerik lainnya diluar tipe `[]int`. Ok, sekarang ubah kode fungsi `Sum` menjadi seperti di bawah ini: @@ -93,11 +95,13 @@ func main() { } ``` +Output program: + ![Golang generic](images/A_generics_1.png) ## A.65.3. Comparable Data Type pada Fungsi Generic -Selanjutnya kita modifikasi lagi fungsi `Sum` agar tipe kompatibel `V` di sini kompatibel dengan tipe numerik lainnya seperti `float64`. Caranya sangat mudah, cukup tambahkan tipe datanya pada statement `V int` dengan delimiter pipe (`|`). +Selanjutnya, modifikasi lagi fungsi `Sum` agar tipe kompatibel `V` di sini bisa kompatibel dengan tipe numerik lainnya seperti `float64`. Caranya sangat mudah, cukup tambahkan tipe data yang diinginkan untuk kompatibel pada statement `V int` menggunakan delimiter pipe (`|`). ```go func Sum[V int | float32 | float64](numbers []V) V { @@ -126,7 +130,7 @@ fmt.Println("total:", total3) ![Golang generic](images/A_generics_2.png) -Nice, hasilnya sesuai harapan. Sampai sini kita sudah paham bagaimana cara pendefinisian tipe kompatibel pada fungsi dan cara pemanfaatannya. +Jos gandos, hasilnya sesuai harapan. Sampai sini kita sudah paham bagaimana cara pendefinisian tipe kompatibel pada fungsi dan cara pemanfaatannya. ## A.65.4. Tipe Argumen Saat Pemanggilan Fungsi Generic @@ -146,15 +150,15 @@ Sum[float32]([]float32{2.5, 7.2}) Sum[float64]([]float64{1.23, 6.33, 12.6}) ``` -Di case ini (dan banyak case lainnya), tipe data kompatibel tidak perlu dituliskan secara eksplisit karena secara cerdas kompiler bisa mendeteksi tipe yang kompatibel berdasarkan tipe data parameter saat pemanggilan fungsi. +Di case ini (dan banyak case lainnya), tipe data yang sudah kompatibel tidak perlu dituliskan secara eksplisit karena kompiler secara cerdas bisa mendeteksi tipe yang kompatibel berdasarkan tipe data parameter saat pemanggilan fungsi. ## A.65.5. Keyword `comparable` -Sekarang kita akan belajar kegunaan satu keyword penting, yaitu `comparable`. Keyword tersebut merupakan tipe data yang kompatibel dengan semua tipe yang ada. +Sekarang kita akan belajar kegunaan satu keyword penting lainnya, yaitu `comparable`. Keyword ini merepresentasikan semua tipe data yang kompatibel. Pada kode di atas kita menggunakan `V int | float32 | float64` untuk mendefinisikan tipe yang kompatibel dengan tipe `int`, `float32`, dan `float64`. Jika ingin membuat tipe `V` kompatibel dengan banyak tipe lainnya, tambahkan saja tipe2 yang diinginkan. Atau, jika ingin kompatibel dengan **semua tipe data** maka gunakan `comparable`, penulisannya menjadi `V comparable`. -Ok, mari kita coba terapkan. O iya, sebelum mulai, agar pembaca makin paham perihal fungsi generic, kita siapkan 2 fungsi yang mirip berikut: +Ok, mari kita coba terapkan. Kita tidak akan menerapkan `comparable` pada contoh di atas karena fungsi `Sum()` kita desain untuk komputasi nilai numerik. Jika `comparable` diterapkan disitu jadinya kurang pas. Oleh karena itu kita siapkan 2 fungsi baru yang mirip berikut sebagai bahan praktek selanjutnya. ```go func SumNumbers1(m map[string]int64) int64 { @@ -183,16 +187,16 @@ func main() { } ``` -Dua fungsi di atas mirip, tapi memiliki beberapa perbedaan: +Dua fungsi di atas mirip, tapi memiliki beberapa perbedaan yaitu: -1. Penulisan `SumNumbers1` adalah non-generic, sedangkan `SumNumbers2` adalah generic. -2. Pada `SumNumbers1`, kita menggunakan kombinasi dua tipe data untuk membentuk `map`, yaitu `string` sebagai map key dan `int64` sebagai map value. -3. Pada `SumNumbers2`, kita breakdown pendefinisian tipe data map menjadi lebih mendetail: +1. Penulisan `SumNumbers1()` adalah non-generic, sedangkan `SumNumbers2()` adalah generic. +2. Pada `SumNumbers1()`, kita menggunakan kombinasi dua tipe data untuk membentuk `map`, yaitu `string` sebagai map key dan `int64` sebagai map value. +3. Pada `SumNumbers2()`, kita breakdown pendefinisian tipe data map menjadi lebih mendetail: - Tipe map key adalah `K` yang tipe datanya kompatibel dengan semua tipe data. - Tipe map value adalah `V` yang tipe datanya kompatibel dengan `int64` dan `float64`. - Yang sebelumnya `map[string]int64` kini menjadi `map[K]V`. -Karena `SumNumbers2` menggunakan generic, maka fungsi ini mendukung sangat banyak tipe data karena menggunakan kombinasi dari tipe `K` yang kompatibel dengan semua tipe; dan tipe `V` yang kompatibel dengan `int64` dan `float64`. +Karena `SumNumbers2()` menggunakan generic, maka fungsi ini mendukung sangat banyak tipe data karena menggunakan kombinasi dari tipe `K` yang kompatibel dengan semua tipe; dan tipe `V` yang kompatibel dengan `int64` dan `float64`. - `map[string]int64` - `map[interface{}]int64` @@ -206,7 +210,7 @@ Jalankan kode, lihat hasilnya. ## A.65.6. Generic *Type Constraint* -Selanjutnya buat fungsi `SumNumbers3`, isinya kurang lebih sama, hanya saja pada tipe data generic kita tidak menggunakan `V int64 | float64`, yang digunakan adalah `Number` yang merupakan tipe data baru (generic *type constraint*). +Selanjutnya buat fungsi `SumNumbers3()` yang isinya kurang adalah lebih sama. Kali ini kita tidak menggunakan `V int64 | float64`, melainkan menggunakan tipe `Number` yang merupakan tipe data baru yang akan kita buat juga (generic *type constraint*). ```go type Number interface { @@ -222,15 +226,15 @@ func SumNumbers3[K comparable, V Number](m map[K]V) V { } ``` -Cara pendefinisian generic *type constraint* adalah seperti pendefinisan tipe data kustom menggunakan keyword `type`, bedanya adalah di sini `interface{}` dipergunakan sebagai tipe, dan di dalamnya di-embed 2 tipe yang diinginkan untuk menjadi *comparable type*, yaitu `int64` dan `float64`. Dari sini, selanjutnya tipe `Number` bisa dimanfaatkan sebagai tipe data kompatibel dalam generic. +Cara pendefinisian generic *type constraint* adalah seperti pendefinisan tipe data kustom menggunakan keyword `type`, bedanya adalah di sini `interface{}` dipergunakan sebagai tipe, yang di dalamnya di-embed 2 tipe yang diinginkan untuk menjadi *comparable type*, yaitu `int64` dan `float64`. Hasilnya, tipe `Number` bisa dimanfaatkan dalam penerapan generic sebagai tipe data yang kompatibel. > Perlu diketahui, tipe yang didefinisikan menggunakan *type constraint* ini hanya bisa dimanfaatkan pada generic. Tipe jenis ini tidak bisa digunakan di luar scope kode generic. Sebagai contoh, coba deklarasikan `var s Number` dalam fungsi `main()`, hasilnya akan muncul syntax error. -Ok, sekarang mari ubah pemanggilan fungsi `SumNumbers2` pada main menjadi `SumNumbers3` dan lihat hasilnya, jalan. +Ok, sekarang ubah pemanggilan fungsi `SumNumbers2()` pada main menjadi `SumNumbers3()` lalu coba jalankan dan lihat hasilnya, pasti outputnya sama, menandakan bahwa kode program berjalan sesuai desain. ## A.65.7. Struct Generic -Generic juga bisa diterapkan pada pendefinisian struct, contohnya seperti berikut: +Generic juga bisa diterapkan pada struct, contohnya: ```go type UserModel[T int | float64] struct { @@ -259,7 +263,7 @@ func main() { } ``` -Pada penulisan struct, sisipkan notasi generic. Lalu pada deklarasi variabel object, tulis secara eksplisit tipe data untuk variabel kompatibel. +Cukup tuliskan notasi generic pada deklarasi struct. Kemudian siapkan variabel object, tulis secara eksplisit tipe data untuk variabel kompatibel. ![Golang generic](images/A_generics_4.png) @@ -267,9 +271,7 @@ Pada penulisan struct, sisipkan notasi generic. Lalu pada deklarasi variabel obj Sampai artikel ini ditulis, generic tidak bisa diterapkan pada method (meski bisa diterapkan pada fungsi) ---- - -Ok, sekian pembahasan mengenai generics. Jika ada update perihal generic API akan penulis update ke chapter ini juga. +> Penulis akan update konten chapter ini jika ada update pada spesifikasi generic API. --- diff --git a/content/A-gopath-dan-workspace.md b/content/A-gopath-dan-workspace.md index eee21cc12..c266f6bbe 100644 --- a/content/A-gopath-dan-workspace.md +++ b/content/A-gopath-dan-workspace.md @@ -1,10 +1,16 @@ -# A.4. GOPATH Dan Workspace +# A.4. GOPATH dan Workspace -> PERINGATAN! Setup Go project menggunakan GOPATH kurang dianjurkan untuk Go versi terbaru. Lebih baik gunakan [A.3. Setup Go Modules](/A-setup-go-project-dengan-go-modules.html). Tapi meski demikian, bukan berarti GOPATH tidak berguna sama sekali, jadi silakan ikuti panduan berikut jika mau. +Pada chapter ini kita akan belajar tentang apa itu GOPATH beserta cara setupnya. + +> ⚠️ INFORMASI ⚠️ +> +> Setup Go project menggunakan GOPATH kurang dianjurkan untuk Go versi terbaru. Lebih baik gunakan [A.3. Setup Go Modules](/A-setup-go-project-dengan-go-modules.html). +> +> Namun meski demikian, bukan berarti GOPATH tidak berguna sama sekali, jadi silakan ikuti panduan berikut jika diperlukan. ## A.4.1. Variabel `GOPATH` -**GOPATH** adalah variabel yang digunakan oleh Go sebagai rujukan lokasi di mana semua folder project disimpan, kecuali untuk yg diinisialisasi menggunakan Go Modules. GOPATH berisikan 3 buah sub-folder: `src`, `bin`, dan `pkg`. +**GOPATH** adalah variabel yang digunakan oleh Go sebagai rujukan lokasi di mana semua folder project disimpan (kecuali untuk yg diinisialisasi menggunakan Go Modules). GOPATH berisikan 3 buah sub-folder: `src`, `bin`, dan `pkg`. Project di Go bisa ditempatkan dalam `$GOPATH/src`. Sebagai contoh anda ingin membuat project dengan nama `belajar`, maka **harus** dibuatkan sebuah folder dengan nama `belajar`, ditempatkan dalam `src` (`$GOPATH/src/belajar`). diff --git a/content/A-goroutine.md b/content/A-goroutine.md index 653650da0..758b71cc0 100644 --- a/content/A-goroutine.md +++ b/content/A-goroutine.md @@ -1,8 +1,10 @@ # A.30. Goroutine -Goroutine mirip dengan thread, tapi sebenarnya bukan. Sebuah *native thread* bisa berisikan sangat banyak goroutine. Mungkin lebih pas kalau goroutine disebut sebagai **mini thread**. Goroutine sangat ringan, hanya dibutuhkan sekitar **2kB** memori saja untuk satu buah goroutine. Eksekusi goroutine bersifat *asynchronous*, menjadikannya tidak saling tunggu dengan goroutine lain. +Goroutine secara konsep mirip seperti *thread*, meskipun sebenarnya berbeda. Sebuah *native thread* bisa berisikan sangat banyak goroutine. Mungkin lebih pas kalau goroutine disebut sebagai **mini thread**. Goroutine sangat ringan, hanya dibutuhkan sekitar **2kB** memori saja untuk satu buah goroutine. Eksekusi goroutine bersifat *asynchronous*, menjadikannya tidak saling tunggu dengan goroutine lain. > Karena goroutine sangat ringan, maka eksekusi banyak goroutine bukan masalah. Akan tetapi jika jumlah goroutine sangat banyak sekali (contoh 1 juta goroutine dijalankan pada komputer dengan RAM terbatas), memang proses akan jauh lebih cepat selesai, tapi memory/RAM pasti bengkak. +> +> Selain itu, dalam pengaplikasiannya jangan hanya terpaku pada size goroutine yang kecil tersebut, tapi pertimbangkan juga kode/proses/logic yang dibuat di dalam goroutine itu sekompleks apa, karena hal tersebut sangat berpengaruh dengan konsumsi resource hardware. Goroutine merupakan salah satu bagian paling penting dalam *concurrent programming* di Go. Salah satu yang membuat goroutine sangat istimewa adalah eksekusi-nya dijalankan di multi core processor. Kita bisa tentukan berapa banyak core yang aktif, makin banyak akan makin cepat. @@ -12,7 +14,7 @@ Mulai chapter **A.29** ini hingga **A.34**, lalu dilanjut **A.56** dan **A.57**, ## A.30.1. Penerapan Goroutine -Untuk menerapkan goroutine, proses yang akan dieksekusi sebagai goroutine harus dibungkus ke dalam sebuah fungsi. Pada saat pemanggilan fungsi tersebut, ditambahkan keyword `go` di depannya, dengan itu goroutine baru akan dibuat dengan tugas adalah menjalankan proses yang ada dalam fungsi tersebut. +Untuk menerapkan goroutine, proses yang akan dieksekusi sebagai goroutine harus dibungkus ke dalam sebuah fungsi, ini hukumnya wajib. Kemudian nantinya saat pemanggilan fungsi, tambahkan keyword `go` di depannya, dengan ini maka goroutine baru dibuat dengan tugas adalah menjalankan proses yang ada dalam fungsi tersebut. Berikut merupakan contoh implementasi sederhana tentang goroutine. Program di bawah ini menampilkan 10 baris teks, 5 dieksekusi dengan cara biasa, dan 5 lainnya dieksekusi sebagai goroutine baru. @@ -45,23 +47,25 @@ Pembuatan goroutine baru ditandai dengan keyword `go`. Contohnya pada statement Fungsi `fmt.Scanln()` mengakibatkan proses jalannya aplikasi berhenti di baris itu (**blocking**) hingga user menekan tombol enter. Hal ini perlu dilakukan karena ada kemungkinan waktu selesainya eksekusi goroutine `print()` lebih lama dibanding waktu selesainya goroutine utama `main()`, mengingat bahwa keduanya sama-sama asnychronous. Jika itu terjadi, goroutine yang belum selesai secara paksa dihentikan prosesnya karena goroutine utama sudah selesai dijalankan. +Output program: + ![Implementasi goroutine](images/A_goroutine_1_goroutine.png) -Bisa dilihat di output, tulisan `"halo"` dan `"apa kabar"` bermunculan selang-seling. Ini disebabkan karena statement `print(5, "halo")` dijalankan sebagai goroutine baru, menjadikannya tidak saling tunggu dengan `print(5, "apa kabar")`. +Bisa dilihat di output, tulisan `"halo"` dan `"apa kabar"` bermunculan selang-seling. Ini disebabkan karena statement `print(5, "halo")` dijalankan sebagai goroutine, menjadikannya tidak saling tunggu dengan `print(5, "apa kabar")`. Pada gambar di atas, program dieksekusi 2 kali. Hasil eksekusi pertama berbeda dengan kedua, penyebabnya adalah karena kita menggunakan 2 prosesor. Goroutine mana yang dieksekusi terlebih dahulu tergantung kedua prosesor tersebut. ---- +## A.30.2. Penjelasan tambahan -Berikut adalah penjelasan tambahan tentang beberapa fungsi yang baru kita pelajari di atas. +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: -## A.30.1.1. Penggunaan Fungsi `runtime.GOMAXPROCS()` +#### ◉ Penggunaan Fungsi `runtime.GOMAXPROCS()` Fungsi ini digunakan untuk menentukan jumlah core atau processor yang digunakan dalam eksekusi program. Jumlah yang diinputkan secara otomatis akan disesuaikan dengan jumlah asli *logical processor* yang ada. Jika jumlahnya lebih, maka dianggap menggunakan sejumlah prosesor yang ada. -## A.30.1.2. Penggunaan Fungsi `fmt.Scanln()` +#### ◉ Penggunaan Fungsi `fmt.Scanln()` Fungsi ini akan meng-capture semua karakter sebelum user menekan tombol enter, lalu menyimpannya pada variabel. diff --git a/content/A-hash-sha1.md b/content/A-hash-sha1.md index c6128c378..213fdce2d 100644 --- a/content/A-hash-sha1.md +++ b/content/A-hash-sha1.md @@ -1,6 +1,6 @@ # A.47. Hash SHA1 -Hash adalah algoritma enkripsi untuk mengubah text menjadi deretan karakter acak. Jumlah karakter hasil hash selalu sama. Hash termasuk *one-way encryption*, membuat hasil dari hash tidak bisa dikembalikan ke text asli. +Hash adalah algoritma enkripsi satu arah untuk mengubah text menjadi deretan karakter acak. Jumlah karakter hasil hash selalu sama. Hash termasuk *one-way encryption*, hasil dari hash tidak bisa dikembalikan ke text asli. SHA1 atau **Secure Hash Algorithm 1** merupakan salah satu algoritma hashing yang sering digunakan untuk enkripsi data. Hasil dari sha1 adalah data dengan lebar **20 byte** atau **160 bit**, biasa ditampilkan dalam bentuk bilangan heksadesimal 40 digit. @@ -55,7 +55,6 @@ import "time" func doHashUsingSalt(text string) (string, string) { var salt = fmt.Sprintf("%d", time.Now().UnixNano()) var saltedText = fmt.Sprintf("text: '%s', salt: %s", text, salt) - fmt.Println(saltedText) var sha = sha1.New() sha.Write([]byte(saltedText)) var encrypted = sha.Sum(nil) @@ -89,7 +88,7 @@ func main() { } ``` -Hasil ekripsi fungsi `doHashUsingSalt` akan selalu beda, karena salt yang digunakan adalah waktu. +Hasil ekripsi fungsi `doHashUsingSalt()` akan selalu beda, karena salt yang digunakan adalah waktu. ![Hashing dengan salt](images/A_hash_2_hash_salt_sha1.png) diff --git a/content/A-hello-world.md b/content/A-hello-world.md index 7718ab007..df9f388b5 100644 --- a/content/A-hello-world.md +++ b/content/A-hello-world.md @@ -1,8 +1,8 @@ # A.7. Program Pertama: Hello World -Semua persiapan sudah selesai, saatnya masuk pada sesi programming. Program pertama yang akan kita buat adalah aplikasi kecil yang menampilkan text **Hello world**. +Semua persiapan sudah selesai, saatnya masuk pada sesi programming. Program pertama yang akan kita buat adalah cukup terkenal di kalangan programmer, yaitu program untuk memunculkan text **Hello world**. -Pada chapter ini akan dijelaskan secara komprehensif *step-by-step* mulai dari awal. Mulai dari pembuatan project, pembuatan file program, sesi penulisan kode (coding), hingga eksekusi program. +Proses pembelajaran di chapter ini akan disampaikan secara runtun dan komprehensif, *step-by-step* mulai dari awal. Mulai dari pembuatan project, pembuatan file program, sesi penulisan kode (coding), hingga eksekusi program. ## A.7.1. Inisialisasi Project @@ -24,7 +24,7 @@ Buka editor, di sini penulis menggunakan VSCode. Cari menu untuk menambahkan pro ## A.7.3. Menyiapkan File Program -File program di sini maksudnya adalah file yang isinya *source code* Go. File ini berekstensi `.go`. +File program di sini maksudnya adalah file yang isinya *source code* Go. Ciri khas file program adalah memiliki ekstensi `.go`. Di dalam project yang telah dibuat, siapkan sebuah file dengan nama bebas, yang jelas harus ber-ekstensi `.go`. Pada contoh ini saya menggunakan nama file `main.go`. @@ -34,9 +34,9 @@ Pembuatan file program bisa dilakukan lewat CLI atau browser, atau juga lewat ed ## A.7.4. Program Pertama: Hello Word -Setelah project folder dan file program sudah siap, saatnya untuk *programming*. +Setelah project folder dan file program sudah siap, saatnya untuk *coding*. -Di bawah ini merupakan contoh kode program sederhana untuk memunculkan text **Hello world** ke layar output command prompt. Silakan salin kode berikut ke file program yang telah dibuat. Sebisa mungkin jangan copy paste. Biasakan untuk menulis dari awal, agar cepat terbiasa dan familiar dengan Go. +Silakan salin kode berikut ke file program yang telah dibuat. Sebisa mungkin jangan copy paste. Biasakan untuk menulis dari awal, agar cepat terbiasa dan familiar dengan Go. ```go package main diff --git a/content/A-instalasi-editor.md b/content/A-instalasi-editor.md index c18c770d0..9e8e55f1b 100644 --- a/content/A-instalasi-editor.md +++ b/content/A-instalasi-editor.md @@ -1,11 +1,9 @@ # A.5. Instalasi Editor -Proses pembuatan aplikasi menggunakan Go akan lebih maksimal jika didukung oleh editor atau **IDE** yang pas. Ada cukup banyak pilihan bagus yang bisa dipertimbangkan, di antaranya: Brackets, JetBrains GoLand, Netbeans, Atom, Visual Studio Code, Sublime Text, dan lainnya. +Proses pembuatan aplikasi menggunakan Go akan lebih maksimal jika didukung oleh editor atau **IDE** yang pas. Ada cukup banyak pilihan bagus yang bisa dipertimbangkan, di antaranya: JetBrains GoLand, Visual Studio Code, Netbeans, Atom, Sublime Text, dan lainnya. Penulis sarankan untuk memilih editor yang paling nyaman digunakan, preferensi masing-masing pastinya berbeda. Penulis sendiri lebih sering menggunakan **Visual Studio Code**. Editor ini sangat ringan, mudah didapat, dan memiliki ekstensi yang bagus untuk bahasa Go. Jika pembaca ingin menggunakan editor yang sama, maka silakan melanjutkan panduan berikut. -Pada chapter ini akan dijelaskan bagaimana cara instalasi editor Visual Studio Code. - ## A.5.1. Instalasi Editor Visual Studio Code 1. Download Visual Studio Code di [https://code.visualstudio.com/Download](https://code.visualstudio.com/Download), pilih sesuai dengan sistem operasi yang digunakan. diff --git a/content/A-interface-kosong.md b/content/A-interface-kosong.md index cb8ab83e9..ca9e8edbb 100644 --- a/content/A-interface-kosong.md +++ b/content/A-interface-kosong.md @@ -1,10 +1,12 @@ -# A.28. Interface Kosong (Any) +# A.28. Any / interface{} / Interface Kosong -Interface kosong atau *empty interface* yang dinotasikan dengan `interface{}` atau `any`, merupakan tipe data yang sangat spesial. Variabel bertipe ini bisa menampung segala jenis data, bahkan array, pointer, apapun. Tipe data dengan konsep ini biasa disebut dengan **dynamic typing**. +Interface kosong atau *empty interface* yang dinotasikan dengan `interface{}` atau `any`, merupakan tipe data yang sangat spesial karena variabel bertipe ini bisa menampung segala jenis data, baik itu numerik, string, bahkan array, pointer, apapun. -## A.28.1. Penggunaan `interface{}` +> Dalam konsep pemrograman umum, konsep variabel yang bisa menampung banyak jenis tipe data disebut dengan **dynamic typing**. -`interface{}` merupakan tipe data, sehingga cara penggunaannya sama seperti pada tipe data lainnya, hanya saja nilai yang diisikan bisa apa saja. Contoh: +## A.28.1. Penggunaan `any` / `interface{}` + +`any` atau `interface{}` merupakan tipe data, sehingga cara penggunaannya sama seperti tipe data pada umumnya, perbedaannya pada variabel bertipe ini nilainya bisa diisi dengan apapun. Contoh: ```go package main @@ -61,9 +63,9 @@ data = map[string]any{ } ``` -## A.28.3. Casting Variabel Interface Kosong +## A.28.3. Casting Variabel Any / Interface Kosong -Variabel bertipe `interface{}` bisa ditampilkan ke layar sebagai `string` dengan memanfaatkan fungsi print, seperti `fmt.Println()`. Tapi perlu diketahui bahwa nilai yang dimunculkan tersebut bukanlah nilai asli, melainkan bentuk string dari nilai aslinya. +Variabel bertipe `interface{}` bisa ditampilkan ke layar sebagai `string` dengan memanfaatkan fungsi print, seperti `fmt.Println()`. Tapi perlu diketahui bahwa nilai yang dimunculkan tersebut bukanlah nilai asli, melainkan bentuk text dari nilai aslinya. Hal ini penting diketahui, karena untuk melakukan operasi yang membutuhkan nilai asli pada variabel yang bertipe `interface{}`, diperlukan casting ke tipe aslinya. Contoh seperti pada kode berikut. @@ -92,7 +94,7 @@ Pada contoh kedua, `secret` berisikan array string. Kita memerlukan string terse ![Casting pada variabel bertipe `interface{}`](images/A_interface_kosong_2_interface_casting.png) -Teknik casting pada interface disebut dengan **type assertions**. +Teknik casting pada `any` disebut dengan **type assertions**. ## A.28.4. Casting Variabel Interface Kosong Ke Objek Pointer @@ -115,7 +117,7 @@ Variabel `secret` dideklarasikan bertipe `interface{}` menampung referensi objek ## A.28.5. Kombinasi Slice, `map`, dan `interface{}` -Tipe `[]map[string]interface{}` adalah salah satu tipe yang paling sering digunakan (menurut saya), karena tipe data tersebut bisa menjadi alternatif tipe slice struct. +Tipe `[]map[string]interface{}` adalah salah satu tipe yang paling sering digunakan untuk menyimpan sekumpulan data berbasis *key-value*. Tipe tersebut merupakan alternatif dari slice struct. Pada contoh berikut, variabel `person` dideklarasikan berisi data slice `map` berisikan 2 item dengan key adalah `name` dan `age`. diff --git a/content/A-interface.md b/content/A-interface.md index c8bb9a55e..136d76ed0 100644 --- a/content/A-interface.md +++ b/content/A-interface.md @@ -1,12 +1,12 @@ # A.27. Interface -Interface adalah kumpulan definisi method yang tidak memiliki isi (hanya definisi saja), yang dibungkus dengan nama tertentu. +Interface adalah definisi suatu kumpulan method yang tidak memiliki isi, jadi hanya definisi header/schema-nya saja. Kumpulan method tersebut ditulis dalam satu block interface dengan nama tertentu. -Interface merupakan tipe data. Nilai objek bertipe interface zero value-nya adalah `nil`. Interface mulai bisa digunakan jika sudah ada isinya, yaitu objek konkret yang memiliki definisi method minimal sama dengan yang ada di interface-nya. +Interface merupakan tipe data. Objek bertipe interface memiliki zero value yaitu `nil`. Variabel bertipe interface digunakan untuk menampung nilai objek konkret yang memiliki definisi method minimal sama dengan yang ada di interface. ## A.27.1. Penerapan Interface -Yang pertama perlu dilakukan untuk menerapkan interface adalah menyiapkan interface beserta definisi method nya. Keyword `type` dan `interface` digunakan untuk pendefinisian interface. +Untuk menerapkan interface, pertama siapkan deklarasi tipe baru menggunakan keyword `type` dan tipe data `interface` lalu siapkan juga isinya (definisi method-nya). ```go package main @@ -20,11 +20,11 @@ type hitung interface { } ``` -Pada kode di atas, interface `hitung` memiliki 2 definisi method, `luas()` dan `keliling()`. Interface ini nantinya digunakan sebagai tipe data pada variabel, di mana variabel tersebut akan menampung objek bangun datar hasil dari struct yang akan kita buat. +Di atas, interface `hitung` dideklarasikan memiliki 2 buah method yaitu `luas()` dan `keliling()`. Interface ini nantinya digunakan sebagai tipe data pada variabel untuk menampung objek bangun datar hasil dari struct yang akan dibuat. -Dengan memanfaatkan interface `hitung`, perhitungan luas dan keliling bangun datar bisa dilakukan, tanpa perlu tahu jenis bangun datarnya sendiri itu apa. +Dengan adanya interface `hitung` ini, maka perhitungan luas dan keliling bangun datar bisa dilakukan tanpa perlu tahu jenis bangun datarnya sendiri itu apa. -Siapkan struct bangun datar `lingkaran`, struct ini memiliki method yang beberapa di antaranya terdefinisi di interface `hitung`. +Selanjutnya, siapkan struct bangun datar `lingkaran`, struct ini memiliki definisi method yang sebagian adalah ada di interface `hitung`. ```go type lingkaran struct { @@ -44,9 +44,9 @@ func (l lingkaran) keliling() float64 { } ``` -Struct `lingkaran` di atas memiliki tiga method, `jariJari()`, `luas()`, dan `keliling()`. +Struct `lingkaran` memiliki tiga buah method yaitu `jariJari()`, `luas()`, dan `keliling()`. -Selanjutnya, siapkan struct bangun datar `persegi`. +Berikutnya, siapkan struct bangun datar `persegi` berikut: ```go type persegi struct { @@ -62,9 +62,9 @@ func (p persegi) keliling() float64 { } ``` -Perbedaan struct `persegi` dengan `lingkaran` terletak pada method `jariJari()`. Struct `persegi` tidak memiliki method tersebut. Tetapi meski demikian, variabel objek hasil cetakan 2 struct ini akan tetap bisa ditampung oleh variabel cetakan interface `hitung`, karena dua method yang ter-definisi di interface tersebut juga ada pada struct `persegi` dan `lingkaran`, yaitu `luas()` dan `keliling()`. +Perbedaan struct `persegi` dengan `lingkaran` terletak pada method `jariJari()`. Struct `persegi` tidak memiliki method tersebut. Tetapi meski demikian, variabel objek hasil cetakan 2 struct ini akan tetap bisa ditampung oleh variabel cetakan interface `hitung`, karena dua method yang ter-definisi di interface tersebut juga ada pada struct `persegi` dan `lingkaran`, yaitu method `luas()` dan `keliling()`. -Buat implementasi perhitungan di `main`. +Sekarang buat implementasi perhitungan di fungsi `main()`. ```go func main() { @@ -89,7 +89,7 @@ Dari variabel tersebut, method `luas()` dan `keliling()` diakses. Secara otomati ![Pemanfaatan interface](images/A_interface_1_interface.png) -Method `jariJari()` pada struct `lingkaran` tidak akan bisa diakses karena tidak terdefinisi dalam interface `hitung`. Pengaksesannya dengan paksa akan menyebabkan error. +Method `jariJari()` pada struct `lingkaran` tidak akan bisa diakses karena tidak terdefinisi dalam interface `hitung`. Pengaksesannya secara paksa menyebabkan error. Untuk mengakses method yang tidak ter-definisi di interface, variabel-nya harus di-casting terlebih dahulu ke tipe asli variabel konkritnya (pada kasus ini tipenya `lingkaran`), setelahnya method akan bisa diakses. @@ -102,11 +102,13 @@ var bangunLingkaran lingkaran = bangunDatar.(lingkaran) bangunLingkaran.jariJari() ``` -Perlu diketahui juga, jika ada interface yang menampung objek konkrit di mana struct-nya tidak memiliki salah satu method yang terdefinisi di interface, error juga akan muncul. Intinya kembali ke aturan awal, variabel interface hanya bisa menampung objek yang minimal memiliki semua method yang terdefinisi di interface-nya. +> Metode casting pada tipe data interface biasa disebut dengan **type assertion** + +Perlu diketahui juga, jika ada interface yang menampung objek konkrit yang mana struct-nya tidak memiliki salah satu method yang terdefinisi di interface, maka error akan muncul. Intinya kembali ke aturan awal, variabel interface hanya bisa menampung objek yang minimal memiliki semua method yang terdefinisi di interface tersebut. ## A.27.2. Embedded Interface -Interface bisa di-embed ke interface lain, sama seperti struct. Cara penerapannya juga sama, cukup dengan menuliskan nama interface yang ingin di-embed ke dalam interface tujuan. +Interface bisa di-embed ke interface lain, sama seperti struct. Cara penerapannya juga sama, cukup dengan menuliskan nama interface yang ingin di-embed ke dalam body interface tujuan. Pada contoh berikut, disiapkan interface bernama `hitung2d` dan `hitung3d`. Kedua interface tersebut kemudian di-embed ke interface baru bernama `hitung`. @@ -131,7 +133,7 @@ type hitung interface { } ``` -Interface `hitung2d` berisikan method untuk kalkulasi luas dan keliling, sedang `hitung3d` berisikan method untuk mencari volume bidang. Kedua interface tersebut diturunkan di interface `hitung`, menjadikannya memiliki kemampuan untuk menghitung luas, keliling, dan volume. +Interface `hitung2d` berisikan method untuk kalkulasi luas dan keliling, sedang `hitung3d` berisikan method untuk mencari volume bidang. Kedua interface tersebut embed ke interface `hitung`, menjadikannya memiliki kemampuan untuk mengakses method `luas()`, `keliling()`, dan `volume()`. Next, siapkan struct baru bernama `kubus` yang memiliki method `luas()`, `keliling()`, dan `volume()`. @@ -155,7 +157,7 @@ func (k *kubus) keliling() float64 { Objek hasil cetakan struct `kubus` di atas, nantinya akan ditampung oleh objek cetakan interface `hitung` yang isinya merupakan gabungan interface `hitung2d` dan `hitung3d`. -Terakhir, buat implementasi-nya di main. +Terakhir, buat implementasi-nya di fungsi `main()`. ```go func main() { @@ -168,7 +170,7 @@ func main() { } ``` -Bisa dilihat di kode di atas, lewat interface `hitung`, method `luas`, `keliling`, dan `volume` bisa di akses. +Bisa dilihat di kode di atas, lewat interface `hitung`, method `luas()`, `keliling()`, dan `volume()` bisa di akses. Pada chapter [A.23. Pointer](/A-pointer.html) dijelaskan bahwa method pointer bisa diakses lewat variabel objek biasa dan variabel objek pointer. Variabel objek yang dicetak menggunakan struct yang memiliki method pointer, jika ditampung ke dalam variabel interface, harus diambil referensi-nya terlebih dahulu. Contohnya bisa dilihat pada kode di atas `var bangunRuang hitung = &kubus{4}`. diff --git a/content/A-json.md b/content/A-json.md index 72ce09b14..d20c97510 100644 --- a/content/A-json.md +++ b/content/A-json.md @@ -1,10 +1,10 @@ # A.53. JSON Data -**JSON** atau *Javascript Object Notation* adalah notasi standar yang umum digunakan untuk komunikasi data dalam web. JSON merupakan subset dari *javascript*. +**JSON** atau *Javascript Object Notation* adalah notasi standar penulisan data yang umum digunakan untuk komunikasi antar aplikasi/service. JSON sendiri sebenarnya merupakan subset dari *javascript*. Go menyediakan package `encoding/json` yang berisikan banyak fungsi untuk kebutuhan operasi json. -Pada chapter ini, kita akan belajar cara untuk konverstri string yang berbentuk json menjadi objek Go, dan sebaliknya. +Pada chapter ini, kita akan belajar cara untuk konverstri string yang ditulis dalam format json menjadi objek Go, dan sebaliknya. ## A.53.1. Decode JSON Ke Variabel Objek Struct @@ -24,9 +24,9 @@ type User struct { } ``` -Struct `User` ini nantinya digunakan untuk membuat variabel baru penampung hasil decode json string. Proses decode sendiri dilakukan lewat fungsi `json.Unmarshal()`, dengan json string tersebut dimasukan ke statement fungsi tersebut. +Struct `User` ini nantinya digunakan untuk membuat variabel baru penampung hasil decode json string. Proses decode sendiri dilakukan lewat fungsi `json.Unmarshal()`, dalam penggunaannya data json string dimasukan sebagai argument pemanggilan fungsi. -Silakan tulis kode berikut. +Contoh praktiknya bisa dilihat di bawah ini. ```go func main() { @@ -46,19 +46,19 @@ func main() { } ``` -Fungsi unmarshal hanya menerima data json dalam bentuk `[]byte`, maka dari itu data json string pada kode di atas di-casting terlebih dahulu ke tipe `[]byte` sebelum dipergunakan pada fungsi unmarshal. +Fungsi unmarshal hanya menerima data json dalam bentuk `[]byte`, maka dari itu data json string perlu di-casting terlebih dahulu ke tipe `[]byte`, sebelum akhirnya digunakan pada pemanggilan fungsi `json.Unmarshal()`. -Juga, perlu diperhatikan, argument ke-2 fungsi unmarshal harus diisi dengan **pointer** dari objek yang nantinya akan menampung hasilnya. +Perlu diperhatikan, argument ke-2 pemanggilan fungsi tersebut harus diisi dengan variabel **pointer** yang nantinya akan menampung hasil operasi decoding. ![Decode data json ke variabel objek](images/A_json_1_decode.png) -Jika kita perhatikan lagi, pada struct `User`, salah satu property-nya yaitu `FullName` memiliki **tag** `json:"Name"`. Tag tersebut digunakan untuk mapping informasi json ke property yang bersangkutan. +Property `FullName` milik struct `User` memiliki **tag** `json:"Name"`. Tag tersebut digunakan untuk mapping informasi field json ke property struct. -Data json yang akan diparsing memiliki 2 property yaitu `Name` dan `Age`. Kebetulan penulisan `Age` pada data json dan pada struktur struct adalah sama, berbeda dengan `Name` yang tidak ada pada struct. +Data json yang akan di-parsing memiliki 2 property yaitu `Name` dan `Age`. Di contoh, penulisan `Age` di data json dan pada struktur struct adalah sama, berbeda dengan `Name` yang ada di data json tapi tidak ada di struct. Dengan menambahkan tag json, maka property `FullName` struct akan secara cerdas menampung data json property `Name`. -> Pada kasus decoding data json string ke variabel objek struct, semua level akses property struct penampung harus publik. +> Pada operasi decoding data json string ke variabel objek struct, semua level akses property struct penampung harus publik. ## A.53.2. Decode JSON Ke `map[string]interface{}` & `interface{}` @@ -85,7 +85,7 @@ fmt.Println("age :", decodedData["Age"]) ## A.53.3. Decode Array JSON Ke Array Objek -Decode data dari array json ke slice/array objek masih sama, siapkan saja variabel penampung hasil decode dengan tipe slice struct. Contohnya bisa dilihat pada kode berikut. +Operasi decode data dari array json ke slice/array objek caranya juga sama. Langsung praktek saja agar lebih jelas. Siapkan sebuah variabel baru untuk menampung hasil decode dengan tipe slice struct, lalu gunakan pada fungsi `json.Unmarshal()`. ```go var jsonString = `[ @@ -107,11 +107,11 @@ fmt.Println("user 2:", data[1].FullName) ## A.53.4. Encode Objek Ke JSON String -Setelah sebelumnya dijelaskan beberapa cara decode data dari json string ke objek, sekarang kita akan belajar cara **encode** data objek ke bentuk json string. +Setelah sebelumnya dijelaskan beberapa cara decode data dari json string ke objek, sekarang kita akan belajar cara **encode** data objek di Go ke bentuk json string. -Fungsi `json.Marshal` digunakan untuk encoding data ke json string. Sumber data bisa berupa variabel objek cetakan struct, `map[string]interface{}`, atau slice. +Fungsi `json.Marshal()` digunakan untuk encoding data ke json string. Sumber data bisa berupa variabel objek cetakan struct, data bertipe `map[string]interface{}`, slice, atau lainnya. -Pada contoh berikut, data slice struct dikonversi ke dalam bentuk json string. Hasil konversi berupa `[]byte`, casting terlebih dahulu ke tipe `string` agar bisa ditampilkan bentuk json string-nya. +Pada contoh berikut, data slice struct dikonversi ke dalam bentuk json string. Hasil konversi adalah data bertipe `[]byte`, maka pastikan untuk meng-casting terlebih dahulu ke tipe `string` agar bisa ditampilkan bentuk json string-nya. ```go var object = []User{{"john wick", 27}, {"ethan hunt", 32}} @@ -125,7 +125,7 @@ var jsonString = string(jsonData) fmt.Println(jsonString) ``` -Output: +Output program: ![Encode data ke JSON](images/A_json_2_encode.png) diff --git a/content/A-komentar.md b/content/A-komentar.md index ba737d4ba..0790e783b 100644 --- a/content/A-komentar.md +++ b/content/A-komentar.md @@ -1,6 +1,6 @@ # A.8. Komentar -Komentar biasa dimanfaatkan untuk menyisipkan catatan pada kode program, menulis penjelasan/deskripsi mengenai suatu blok kode, atau bisa juga digunakan untuk me-*remark* kode (men-non-aktifkan kode yg tidak digunakan). Komentar akan diabaikan ketika kompilasi maupun eksekusi program. +Komentar biasa dimanfaatkan untuk untuk menyisipkan catatan pada kode program, atau untuk menulis penjelasan/deskripsi mengenai suatu blok kode, atau bisa juga digunakan untuk me-*remark* kode (men-non-aktifkan kode yg tidak digunakan). Komentar selalu diabaikan ketika kompilasi maupun eksekusi program. Ada 2 jenis komentar di Go, *inline* & *multiline*. Pada pembahasan ini akan dijelaskan tentang penerapan dan perbedaan kedua jenis komentar tersebut. @@ -22,7 +22,7 @@ func main() { } ``` -Mari kita praktekan kode di atas. Siapkan file program baru dalam project folder (bisa buat project baru atau gunakan project yang sudah ada). Kemudian isi dengan kode di atas, lalu jalankan. +Mari kita praktekan kode di atas. Siapkan file program baru dalam project folder (bisa buat project baru atau gunakan project yang sudah ada). Kemudian isi file dengan kode di atas, lalu jalankan. ![Contoh komentar inline](images/A_komentar_1_inline_comment.png) diff --git a/content/A-konstanta.md b/content/A-konstanta.md index 52c3a60e5..e3c130222 100644 --- a/content/A-konstanta.md +++ b/content/A-konstanta.md @@ -1,30 +1,30 @@ # A.11. Konstanta -Konstanta adalah jenis variabel yang nilainya tidak bisa diubah. Inisialisasi nilai hanya dilakukan sekali di awal, setelah itu variabel tidak bisa diubah nilainya. +Konstanta adalah jenis variabel yang nilainya tidak bisa diubah setelah dideklarasikan. Inisialisasi nilai konstanta hanya dilakukan sekali saja di awal, setelah itu variabel tidak bisa diubah nilainya. ## A.11.1. Penggunaan Konstanta -Data seperti **pi** (22/7), kecepatan cahaya (299.792.458 m/s), adalah contoh data yang tepat jika dideklarasikan sebagai konstanta daripada variabel, karena nilainya sudah pasti dan tidak berubah. +Data seperti **pi** (22/7), kecepatan cahaya (299.792.458 m/s), adalah contoh data yang tepat untuk dideklarasikan sebagai konstanta (daripada variabel), karena nilainya sudah pasti dan tidak akan berubah. -Cara penerapan konstanta sama seperti deklarasi variabel biasa, selebihnya tinggal ganti keyword `var` dengan `const`. +Cara penerapan konstanta sama seperti deklarasi variabel biasa, perbedaannya ada pada keyword yang digunakan, yaitu `const` (bukan `var`). ```go const firstName string = "john" fmt.Print("halo ", firstName, "!\n") ``` -Teknik type inference bisa diterapkan pada konstanta, caranya yaitu cukup dengan menghilangkan tipe data pada saat deklarasi. +Teknik type inference bisa diterapkan pada konstanta, caranya cukup dengan menghilangkan tipe data pada saat deklarasi. ```go const lastName = "wick" fmt.Print("nice to meet you ", lastName, "!\n") ``` -#### • Penggunaan Fungsi `fmt.Print()` +#### ◉ Penggunaan Fungsi `fmt.Print()` -Fungsi ini memiliki peran yang sama seperti fungsi `fmt.Println()`, pembedanya fungsi `fmt.Print()` tidak menghasilkan baris baru di akhir outputnya. +Fungsi ini memiliki peran yang sama seperti fungsi `fmt.Println()`, perbedaannya fungsi `fmt.Print()` tidak menghasilkan baris baru di akhir output-nya. -Perbedaan lainnya adalah, nilai pada parameter-parameter yang dimasukkan ke fungsi tersebut digabungkan tanpa pemisah. Tidak seperti pada fungsi `fmt.Println()` yang nilai paremeternya digabung menggunakan penghubung spasi. +Perbedaan lainnya: nilai argument parameter yang ditulis saat pemanggilan fungsi akan di-print tanpa pemisah. Tidak seperti pada fungsi `fmt.Println()` yang nilai argument paremeternya dipisah menggunakan karakter spasi. ```go fmt.Println("john wick") @@ -37,13 +37,11 @@ fmt.Print("john", " ", "wick\n") Kode di atas menunjukkan perbedaan antara `fmt.Println()` dan `fmt.Print()`. Output yang dihasilkan oleh 5 statement di atas adalah sama, meski cara yang digunakan berbeda. -Bila menggunakan `fmt.Println()` tidak perlu menambahkan spasi di tiap kata, karena fungsi tersebut akan secara otomatis menambahkannya di sela-sela nilai. Berbeda dengan `fmt.Print()`, perlu ditambahkan spasi, karena fungsi ini tidak menambahkan spasi di sela-sela nilai parameter yang digabungkan. +Bila menggunakan `fmt.Println()`, maka tidak perlu menambahkan spasi di tiap kata, karena fungsi tersebut akan secara otomatis menambahkannya di sela-sela text. Berbeda dengan `fmt.Print()` yang perlu ditambahkan spasi, karena fungsi ini tidak menambahkan spasi secara otomatis di sela-sela nilai text yang digabungkan. ## A.11.2. Deklarasi Multi Konstanta -Sama seperti variabel, konstanta juga dapat dideklarasikan secara bersamaan. - -Berikut adalah contoh deklarasi konstanta dengan tipe data dan nilai yang berbeda. +Sama seperti variabel, konstanta juga dapat dideklarasikan secara bersamaan. Berikut adalah contoh deklarasi konstanta dengan tipe data dan nilai yang berbeda. ```go const ( diff --git a/content/A-map.md b/content/A-map.md index 4991d3058..d8fa8aff8 100644 --- a/content/A-map.md +++ b/content/A-map.md @@ -1,12 +1,12 @@ # A.17. Map -**Map** adalah tipe data asosiatif yang ada di Go, berbentuk *key-value pair*. Untuk setiap data (atau value) yang disimpan, disiapkan juga key-nya. Key harus unik, karena digunakan sebagai penanda (atau identifier) untuk pengaksesan value yang bersangkutan. +**Map** adalah tipe data asosiatif yang ada di Go yang berbentuk *key-value pair*. Data/value yang disimpan di map selalu disertai dengan key. Key sendiri harus unik, karena digunakan sebagai penanda (atau identifier) untuk pengaksesan value yang disimpan di map. -Kalau dilihat, `map` mirip seperti slice, hanya saja indeks yang digunakan untuk pengaksesan bisa ditentukan sendiri tipe-nya (indeks tersebut adalah key). +Kalau dilihat, `map` mirip seperti slice, hanya saja identifier yang digunakan untuk pengaksesan bukanlah index numerik, melainkan bisa dalam tipe data apapun sesuai dengan yang diinginkan. ## A.17.1. Penggunaan Map -Cara menggunakan map cukup dengan menuliskan keyword `map` diikuti tipe data key dan value-nya. Agar lebih mudah dipahami, silakan perhatikan contoh di bawah ini. +Cara pengaplikasian map cukup mudah, dengan menuliskan keyword `map` diikuti tipe data key dan value-nya. Silakan perhatikan contoh di bawah ini agar lebih jelas. ```go var chicken map[string]int @@ -19,23 +19,25 @@ fmt.Println("januari", chicken["januari"]) // januari 50 fmt.Println("mei", chicken["mei"]) // mei 0 ``` -Variabel `chicken` dideklarasikan sebagai map, dengan tipe data key adalah `string` dan value-nya `int`. Dari kode tersebut bisa dilihat bagaimana cara penggunaan keyword `map`. +Variabel `chicken` dideklarasikan bertipe data map, dengan key ditentukan tipenya adalah `string` dan tipe value-nya `int`. Dari kode tersebut bisa dilihat bagaimana cara penerapan keyword `map` untuk pembuatan variabel. -Kode `map[string]int` maknanya adalah, tipe data `map` dengan key bertipe `string` dan value bertipe `int`. +Kode `map[string]int` merepresentasikan tipe data `map` dengan key bertipe `string` dan value bertipe `int`. -Default nilai variabel `map` adalah `nil`. Oleh karena itu perlu dilakukan inisialisasi nilai default di awal, caranya cukup dengan tambahkan kurung kurawal pada akhir tipe, contoh seperti pada kode di atas: `map[string]int{}`. +Zero value atau nilai default variabel `map` adalah `nil`. Dari sini maka penting untuk menginisialisasi nilai awal map agar tidak `nil`. Jika dibiarkan `nil`, ketika map digunakan untuk menampung data pasti memunculkan error. -Cara menge-set nilai pada sebuah map adalah dengan menuliskan variabel-nya, kemudian disisipkan `key` pada kurung siku variabel (mirip seperti cara pengaksesan elemen slice), lalu isi nilainya. Contohnya seperti `chicken["februari"] = 40`. Sedangkan cara pengambilan value adalah cukup dengan menyisipkan `key` pada kurung siku variabel. +Cara untuk inisialisasi map dengan menambahkan kurung kurawal buka tutup di akhir penulisan map, contoh: `map[string]int{}`. -Pengisian data pada map bersifat **overwrite**, ketika variabel sudah memiliki item dengan key yang sama, maka value lama akan ditimpa dengan value baru. +Cara menambahkan item pada map adalah dengan menuliskan variabel-nya, kemudian diikuti dengan `key` pada kurung siku variabel (mirip seperti cara pengaksesan elemen slice), lalu operator `=`, kemudian nilai/data yang ingin disimpan. Contohnya seperti `chicken["februari"] = 40`. Sedangkan cara mengakses item map dengan cukup dengan menuliskan nama variabel diikuti kurung siku dan `key`. + +Pengisian data pada map bersifat **overwrite**, artinya variabel sudah memiliki item dengan key yang sama, maka value item yang lama (dengan key sama) akan ditimpa dengan value baru. ![Pengaksesan data map](images/A_map_1_map_set_get.png) -Pada pengaksesan item menggunakan key yang belum tersimpan di map, akan dikembalikan nilai default tipe data value-nya. Contohnya seperti pada kode di atas, `chicken["mei"]` menghasilkan nilai 0 (nilai default tipe `int`), karena belum ada item yang tersimpan menggunakan key `"mei"`. +Pengaksesan item menggunakan key yang belum tersimpan di map, menghasilkan data berupa nilai default sesuai tipe data value. Contohnya kode `chicken["mei"]` menghasilkan nilai 0 (nilai default tipe `int`), hal ini karena variabel map `chicken` tidak memiliki item dengan key `"mei"`. ## A.17.2. Inisialisasi Nilai Map -Zero value dari map adalah `nil`, maka tiap variabel bertipe map harus di-inisialisasi secara explisit nilai awalnya (agar tidak `nil`). +Zero value dari map adalah `nil`. Disarankan untuk menginisialisasi secara explisit nilai awalnya agar tidak `nil`. ```go var data map[string]int @@ -47,7 +49,7 @@ data["one"] = 1 // tidak ada error ``` -Nilai variabel bertipe map bisa didefinisikan di awal, caranya dengan menambahkan kurung kurawal setelah tipe data, lalu menuliskan key dan value di dalamnya. Cara ini sekilas mirip dengan definisi nilai array/slice namun dalam bentuk key-value. +Nilai variabel bertipe map bisa didefinisikan di awal, caranya dengan menambahkan kurung kurawal setelah tipe data, kemudian menuliskan key dan value di dalam kurung kurawal tersebut. Cara ini sekilas mirip dengan definisi nilai array/slice namun dalam bentuk key-value. ```go // cara horizontal @@ -70,11 +72,11 @@ var chicken4 = make(map[string]int) var chicken5 = *new(map[string]int) ``` -Khusus inisialisasi data menggunakan keyword `new`, yang dihasilkan adalah data pointer. Untuk mengambil nilai aslinya bisa dengan menggunakan tanda asterisk (`*`). Topik pointer akan dibahas lebih detail ketika sudah masuk [A.23. Pointer](/A-pointer.html). +Khusus inisialisasi data menggunakan keyword `new`, yang dihasilkan adalah data pointer. Untuk mengambil nilai aslinya bisa dengan menggunakan tanda asterisk (`*`). Topik pointer nantinya dibahas lebih detail pada chapter [A.23. Pointer](/A-pointer.html). ## A.17.3. Iterasi Item Map Menggunakan `for` - `range` -Item variabel `map` bisa di iterasi menggunakan `for` - `range`. Cara penerapannya masih sama seperti pada slice, pembedanya data yang dikembalikan di tiap perulangan adalah key dan value, bukan indeks dan elemen. Contohnya bisa dilihat pada kode berikut. +Item variabel `map` bisa di iterasi menggunakan `for` - `range`. Cara penerapannya masih sama seperti pada slice, dengan perbedaan pada map data yang dikembalikan di tiap perulangan adalah key dan value (bukan indeks dan elemen). Contohnya bisa dilihat pada kode berikut. ```go var chicken = map[string]int{ @@ -93,7 +95,7 @@ for key, val := range chicken { ## A.17.4. Menghapus Item Map -Fungsi `delete()` digunakan untuk menghapus item dengan key tertentu pada variabel map. Cara penggunaannya, dengan memasukan objek map dan key item yang ingin dihapus sebagai parameter. +Fungsi `delete()` digunakan untuk menghapus item dengan key tertentu pada variabel map. Cara penggunaannya, dengan memasukan objek map dan key item yang ingin dihapus sebagai argument pemanggilan fungsi `delete()`. ```go var chicken = map[string]int{"januari": 50, "februari": 40} @@ -107,15 +109,15 @@ fmt.Println(len(chicken)) // 1 fmt.Println(chicken) ``` -Item yang memiliki key `"januari"` dalam variabel `chicken` akan dihapus. +Operasi di atas membuat item dengan key `"januari"` dalam variabel map `chicken` dihapus. ![Hapus item Map](images/A_map_3_map_delete_item.png) -Fungsi `len()` jika digunakan pada map akan mengembalikan jumlah item. +Penggunaan fungsi `len()` pada map mengembalikan informasi jumlah item. ## A.17.5. Deteksi Keberadaan Item Dengan Key Tertentu -Ada cara untuk mengetahui apakah dalam sebuah variabel map terdapat item dengan key tertentu atau tidak, yaitu dengan memanfaatkan 2 variabel sebagai penampung nilai kembalian pengaksesan item. Return value ke-2 ini adalah opsional, isinya nilai `bool` yang menunjukkan ada atau tidaknya item yang dicari. +Ada cara untuk mengetahui apakah dalam variabel map terdapat item dengan key tertentu atau tidak, yaitu dengan memanfaatkan 2 variabel sebagai penampung nilai kembalian pengaksesan item. Return value ke-2 sifatnya opsional, boleh ditulis boleh juga tidak. Isinya nilai `bool`, jika berisi `true` menandakan bahwa item yang dicari ada di map, jika `false` maka tidak ada. ```go var chicken = map[string]int{"januari": 50, "februari": 40} @@ -130,11 +132,9 @@ if isExist { ## A.17.6. Kombinasi Slice & Map -Slice dan `map` bisa dikombinasikan, dan sering digunakan pada banyak kasus, contohnya seperti data array yang berisikan informasi siswa, dan banyak lainnya. - -Cara menggunakannya cukup mudah, contohnya seperti `[]map[string]int`, artinya slice yang tipe tiap elemen-nya adalah `map[string]int`. +Slice dan `map` bisa dikombinasikan, dan pada praktiknya cukup sering digunakan, contohnya untuk keperluan penyimpanan data array yang berisikan informasi siswa, dan banyak lainnya. -Agar lebih jelas, silakan praktekan contoh berikut. +Cara penerapannya cukup mudah, contohnya `[]map[string]int`, tipe tersebut artinya adalah sebuah slice yang tipe setiap elemen-nya adalah `map[string]int`. Agar lebih jelas, silakan praktekan contoh berikut. ```go var chickens = []map[string]string{ @@ -148,9 +148,9 @@ for _, chicken := range chickens { } ``` -Variabel `chickens` di atas berisikan informasi bertipe `map[string]string`, yang kebetulan tiap elemen memiliki 2 key yang sama. +Variabel `chickens` di atas berisikan 3 buah item bertipe `map[string]string`. Ketiga item tersebut dideklarasikan memiliki 2 key yang sama, yaitu `name` dan `gender`. -Jika anda menggunakan versi go terbaru, cara deklarasi slice-map bisa dipersingkat, tipe tiap elemen tidak wajib untuk dituliskan. +Penulisan tipe data tiap item adalah opsional. Boleh ditulis atau tidak. Contoh alternatif penulisan: ```go var chickens = []map[string]string{ @@ -160,7 +160,7 @@ var chickens = []map[string]string{ } ``` -Dalam `[]map[string]string`, tiap elemen bisa saja memiliki key yang berbeda-beda, sebagai contoh seperti kode berikut. +Dalam `[]map[string]string`, tiap elemen bisa saja memiliki key yang berbeda-beda, contohnya seperti kode berikut. ```go var data = []map[string]string{ diff --git a/content/A-method.md b/content/A-method.md index 491676ade..ba0d3f47f 100644 --- a/content/A-method.md +++ b/content/A-method.md @@ -1,12 +1,14 @@ # A.25. Method -**Method** adalah fungsi yang menempel pada `type` (bisa `struct` atau tipe data lainnya). Method bisa diakses lewat variabel objek. +**Method** adalah fungsi yang menempel pada suatu tipe data, misalnya custom `struct`. Method bisa diakses lewat variabel objek yang dibuat dari tipe custom struct tersebut. -Keunggulan method dibanding fungsi biasa adalah memiliki akses ke property struct hingga level *private* (level akses nantinya akan dibahas lebih detail pada chapter selanjutnya). Dan juga, dengan menggunakan method sebuah proses bisa di-enkapsulasi dengan baik. +Keunggulan method dibanding fungsi biasa adalah method memiliki akses ke property struct hingga level akses *private*. Selain itu, dengan menggunakan method, suatu proses bisa di-enkapsulasi dengan baik. + +> Perihal topik level nantinya dibahas secara terpisah pada chapter berikutnya ## A.25.1. Penerapan Method -Cara menerapkan method sedikit berbeda dibanding penggunaan fungsi. Ketika deklarasi, ditentukan juga siapa pemilik method tersebut. Contohnya bisa dilihat pada kode berikut: +Cara penerapan method sedikit berbeda dibanding fungsi. Saat proses deklarasi, pada method perlu ditentukan juga siapa pemiliknya. Contohnya bisa dilihat pada kode berikut, dua method diciptakan sebagai property dari struct bernama `student`. ```go package main @@ -28,9 +30,9 @@ func (s student) getNameAt(i int) string { } ``` -Cara deklarasi method sama seperti fungsi, hanya saja perlu ditambahkan deklarasi variabel objek di sela-sela keyword `func` dan nama fungsi. Struct yang digunakan akan menjadi pemilik method. +Cara deklarasi method mirip seperti fungsi, tapi dalam penulisannya perlu ditambahkan deklarasi variabel objek di sela-sela keyword `func` dan nama fungsi. Pada contoh di atas struct `student` ditentukan sebagai pemilik method. -`func (s student) sayHello()` maksudnya adalah fungsi `sayHello` dideklarasikan sebagai method milik struct `student`. Pada contoh di atas struct `student` memiliki dua buah method, yaitu `sayHello()` dan `getNameAt()`. +`func (s student) sayHello()` maksudnya adalah fungsi `sayHello` dideklarasikan sebagai method milik struct `student`. Di contoh, struct `student` memiliki dua buah method yaitu `sayHello()` dan `getNameAt()`. Contoh pemanfaatan method bisa dilihat pada kode berikut. @@ -44,32 +46,34 @@ func main() { } ``` -Output: +Output program: ![Penggunaan method](images/A_method_1_method.png) -Cara mengakses method sama seperti pengaksesan properti berupa variabel. Tinggal panggil saja methodnya. +Cara mengakses method sama seperti pada pengaksesan property, yaitu dengan cukup panggil saja nama methodnya. ```go s1.sayHello() var name = s1.getNameAt(2) ``` -Method memiliki sifat yang sama persis dengan fungsi biasa. Seperti bisa berparameter, memiliki nilai balik, dan lainnya. Dari segi sintaks, pembedanya hanya ketika pengaksesan dan deklarasi. Bisa dilihat di kode berikut, sekilas perbandingan penulisan fungsi dan method. +Method memiliki sifat yang sama persis dengan fungsi biasa, yaitu bisa memiliki parameter, nilai balik, dan sifat-sifat lainnya. + +Dari segi sintaks, perbedaan yang paling terlihat hanya di bagian penulisan deklarasi dan cara pengaksesan. Silakan lihat kode berikut agar lebih jelas: ```go -func sayHello() { -func (s student) sayHello() { +func sayHello() { } +func (s student) sayHello() { } -func getNameAt(i int) string { -func (s student) getNameAt(i int) string { +func getNameAt(i int) string { } +func (s student) getNameAt(i int) string { } ``` ## A.25.2. Method Pointer -Method pointer adalah method yang variabel objek pemilik method tersebut berupa pointer. +Method pointer adalah method yang dimana variabel objek pemilik method tersebut adalah berbentuk pointer. -Kelebihan method jenis ini adalah, ketika kita melakukan manipulasi nilai pada property lain yang masih satu struct, nilai pada property tersebut akan di rubah pada reference nya. Lebih jelasnya perhatikan kode berikut. +Kelebihan method jenis ini adalah ketika kita melakukan manipulasi nilai pada property lain yang masih satu struct, nilai pada property tersebut bisa diubah di-level reference-nya. Lebih jelasnya perhatikan kode berikut. ```go package main @@ -106,13 +110,13 @@ func main() { } ``` -Output: +Output program: ![Penggunaan method pointer](images/A_method_2_method_pointer.png) -Setelah eksekusi statement `s1.changeName1("jason bourne")`, nilai `s1.name` tidak berubah. Sebenarnya nilainya berubah tapi hanya dalam method `changeName1()` saja, nilai pada reference di objek-nya tidak berubah. Karena itulah ketika objek di print value dari `s1.name` tidak berubah. +Setelah statement `s1.changeName1("jason bourne")` dieksekusi, nilai `s1.name` tidak berubah. Sebenarnya nilainya berubah tapi hanya dalam method `changeName1()` saja, nilai pada reference objek-nya tidak berubah. -Keistimewaan lain method pointer adalah, method itu sendiri bisa dipanggil dari objek pointer maupun objek biasa. +Keistimewaan lain method pointer adalah method itu sendiri bisa dipanggil dari objek pointer maupun objek biasa. ```go // pengaksesan method dari variabel objek biasa @@ -124,28 +128,31 @@ var s2 = &student{"ethan hunt", 22} s2.sayHello() ``` ---- +## A.25.3. Penjelasan tambahan -Berikut adalah penjelasan tambahan mengenai beberapa hal pada chapter ini. +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: -#### • Penggunaan Fungsi `strings.Split()` +#### ◉ Penggunaan Fungsi `strings.Split()` -Pada chapter ini ada fungsi baru yang kita gunakan: `strings.Split()`. Fungsi ini berguna untuk memisah string menggunakan pemisah yang ditentukan sendiri. Hasilnya adalah array berisikan kumpulan substring. +Pada chapter ini ada fungsi baru yang kita gunakan saat praktek, yaitu `strings.Split()`. Fungsi ini berguna untuk memisahkan string menggunakan pemisah yang kita tentukan sendiri. Hasilnya berupa array berisikan kumpulan substring. ```go strings.Split("ethan hunt", " ") // ["ethan", "hunt"] ``` -Pada contoh di atas, string `"ethan hunt"` dipisah menggunakan separator spasi `" "`. Maka hasilnya terbentuk array berisikan 2 data, `"ethan"` dan `"hunt"`. +Pada contoh di atas, string `"ethan hunt"` dipisah menggunakan separator spasi `" "`, hasilnya adalah array berisikan 2 elemen, `"ethan"` dan `"hunt"`. ## A.25.3. Apakah `fmt.Println()` & `strings.Split()` Juga Merupakan Method? -Setelah tahu apa itu method dan bagaimana penggunaannya, mungkin akan muncul di benak kita bahwa kode seperti `fmt.Println()`, `strings.Split()` dan lainnya-yang-berada-pada-package-lain adalah merupakan method. Tapi sayangnya **bukan**. `fmt` di situ bukanlah variabel objek, dan `Println()` bukan merupakan method-nya. +Setelah tahu apa itu method dan bagaimana penggunaannya, mungkin akan muncul di benak kita bahwa kode seperti `fmt.Println()`, `strings.Split()` dan lainnya-yang-berada-pada-package-lain adalah merupakan method. Jawabannya,**bukan!**. `fmt` di situ bukanlah variabel objek, dan `Println()` bukan merupakan method. + +`fmt` adalah nama **package** yang di-import (bisa dilihat pada kode `import "fmt"`). Sedangkan `Println()` adalah **nama fungsi**. Untuk mengakses fungsi yang berada pada package lain, harus dituliskan juga nama package-nya, contoh: -`fmt` adalah nama **package** yang di-import (bisa dilihat pada kode `import "fmt"`). Sedangkan `Println()` adalah **nama fungsi**. Untuk mengakses fungsi yang berada pada package lain, harus dituliskan nama package-nya. Hal ini berlaku juga di dalam package `main`. Jika ada fungsi dalam package main yang diakses dari package lain yang berbeda, maka penulisannya `main.NamaFungsi()`. +- Statement `fmt.Println()` berarti pengaksesan fungsi `Println()` yang berada di package `fmt` +- Statement `strings.Split()` berarti pengaksesan fungsi `Split()` yang berada di package `strings` -Lebih detailnya akan dibahas pada chapter selanjutnya. +Lebih detailnya dibahas pada chapter selanjutnya. --- diff --git a/content/A-pipeline-context-cancellation.md b/content/A-pipeline-context-cancellation.md index 442227033..e904ca11e 100644 --- a/content/A-pipeline-context-cancellation.md +++ b/content/A-pipeline-context-cancellation.md @@ -19,7 +19,7 @@ Jadi kurang lebih akan ada dua result: Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi *concurrent* tanpa *cancellation*. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, di sini kita tulis langsung agar bisa cepat dimulai bagian program konkuren. -#### • Import Packages dan Definisi Variabel +#### ◉ Import Packages dan Definisi Variabel ```go package main @@ -40,7 +40,7 @@ const contentLength = 5000 var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.61-pipeline-cancellation-context") ``` -#### • Definisi struct `FileInfo` +#### ◉ Definisi struct `FileInfo` ```go type FileInfo struct { @@ -51,7 +51,7 @@ type FileInfo struct { } ``` -#### • Fungsi `main()` +#### ◉ Fungsi `main()` ```go func main() { @@ -65,7 +65,7 @@ func main() { } ``` -#### • Fungsi `randomString()` +#### ◉ Fungsi `randomString()` ```go func randomString(length int) string { @@ -82,7 +82,7 @@ func randomString(length int) string { } ``` -#### • Fungsi `generateFiles()` +#### ◉ Fungsi `generateFiles()` ```go func generateFiles() { @@ -111,7 +111,7 @@ func generateFiles() { log.Printf("%d/%d of total files created", counterSuccess, counterTotal) } ``` -#### • Fungsi `generateFileIndexes()` +#### ◉ Fungsi `generateFileIndexes()` ```go func generateFileIndexes() <-chan FileInfo { @@ -131,7 +131,7 @@ func generateFileIndexes() <-chan FileInfo { } ``` -#### • Fungsi `createFiles()` +#### ◉ Fungsi `createFiles()` ```go func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo { @@ -179,7 +179,7 @@ Hasil eksekusi program: Ok, sekarang kita akan refactor kode tersebut, kita tambahkan mekanisme *cancellation* menggunakan `context.Context` API. Silakan duplikasi file program, lalu ikuti petunjuk berikut. -#### • Import package `context` +#### ◉ Import package `context` Tambahkan package `context` dalam block import packages. @@ -191,7 +191,7 @@ import ( ) ``` -#### • Tambahkan definisi konstanta timeout +#### ◉ Tambahkan definisi konstanta timeout Di sini saya tentukan timeout adalah 3 detik. Nantinya kita akan modifikasi angka timeout untuk keperluan testing. @@ -199,7 +199,7 @@ Di sini saya tentukan timeout adalah 3 detik. Nantinya kita akan modifikasi angk const timeoutDuration = 3 * time.Second ``` -#### • Penerapan context di fungsi `main()` +#### ◉ Penerapan context di fungsi `main()` Pada fungsi main, lakukan sedikit perubahan. Yang sebelumnya ada statement berikut: @@ -253,7 +253,7 @@ Jadi pada contoh yang kita tulis di atas, kurang lebih yang akan dilakukan adala * Fungsi `generateFilesWithContext()` dipanggil dengan disisipkan object context. * Callback `context.CancelFunc` dipanggil secara deferred. Ini merupakan idiomatic Go dalam penerapan context. Meskipun context sudah punya timeout atau deadline dan kita tidak perlu meng-*cancel* context secara manual, sangat dianjurkan untuk tetap memanggil callback `cancel()` tersebut secara deferred. -#### • Modifikasi fungsi `generateFiles()` +#### ◉ Modifikasi fungsi `generateFiles()` Isi dari fungsi `generateFiles()` kita ubah menjadi pemanggilan fungsi `generateFilesWithContext()` dengan parameter context kosong. @@ -319,7 +319,7 @@ Nah jadi lewat seleksi kondisi 2 case di atas, kita bisa dengan mudah mengidenti Selain beberapa hal yang sudah saya sampaikan, ada *minor changes* lainnya, yaitu pada pemanggilan fungsi `generateFileIndexes()` dan `createFiles()` ditambahkan argument context. -#### • Penambahan context pada fungsi `generateFiles()` +#### ◉ Penambahan context pada fungsi `generateFiles()` Kenapa ini perlu? karena **meski eksekusi fungsi `generateFilesWithContext()` otomatis di stop ketika cancelled, proses di dalamnya akan tetap berjalan jika tidak di-*handle* dengan baik *cancellation*-nya.** @@ -355,7 +355,7 @@ Dibanding sebelumnya, perbedaannya adalah ada *channel selection*. Jadi di bagia * Jika ada notif cancel paksa, maka case pertama akan terpenuhi, dan perulangan di-`break`. * Selebihnya, pengiriman jobs akan berlangsung seperti normalnya. -#### • Penambahan context pada fungsi `createFiles()` +#### ◉ Penambahan context pada fungsi `createFiles()` Hal yang sama (cancel di level sub prosees) juga perlu diterapkan pada `createFiles()`, karena jika tidak, maka proses pembuatan file akan tetap berjalan sesuai dengan jumlah jobs yang dikirim meskipun sudah di-cancel secara paksa. diff --git a/content/A-properti-public-dan-private.md b/content/A-properti-public-dan-private.md index 20c6a62f7..9382caad9 100644 --- a/content/A-properti-public-dan-private.md +++ b/content/A-properti-public-dan-private.md @@ -1,49 +1,51 @@ # A.26. Properti Public dan Private (Exported vs Unexported) -Chapter ini membahas mengenai *property modifier* public dan private dalam Go. Kapan sebuah struct, fungsi, atau method bisa diakses dari package lain dan kapan tidak. +Chapter ini membahas tentang *property modifier* public dan private yang ada di pemrograman Go. Peran dari *property modifier* adalah sebagai penentu kapan suatu struct, fungsi, atau method bisa diakses dari package lain dan kapan tidak. -Di Go sebenarnya tidak ada istilah *public modifier* dan *private modifier*. Yang ada adalah **exported** yang kalau di bahasa lain ekuivalen dengan *public modifier*, dan **unexported** untuk *private modifier*. +Di Go sebenarnya tidak ada istilah *public modifier* dan *private modifier*. Yang ada adalah **exported** (yang kalau di bahasa lain ekuivalen dengan *public modifier*), dan **unexported** untuk *private modifier*. ## A.26.1. Intro -Intro ini ditulis agar pembaca tau ekspektasi yang penulis kejar pada chapter ini. +Intro ini ditulis agar pembaca tau ekspektasi chapter ini sebenarnya apa. -Pembahasan kali ini memiliki beberapa perbedaan dibanding lainnya. Jika pembaca mengikuti secara berurutan, membaca penjelasan dan pembahasan yang sudah tertulis, maka **pasti akan mendapati 3 buah error**. Di tiap-tiap error, sebenarnya sudah terlampir: +Pembahasan kali ini memiliki beberapa perbedaan dibanding chapter lainnya. Jika pembaca mengikuti pembelajaran di chapter ini secara berurutan, dan benar-benar membaca penjelasan serta pembahasan yang sudah tertulis, maka nantinya **pasti menemui 3 buah error**. + +Di setiap error tersebut, sebenarnya sudah terlampir informasi berikut: 1. Screenshot error -2. Penjelasan penyebab error -3. Cara resolve atau solusi dari ketiga error tersebut. +2. Penjelasan penyebab terjadinya error +3. Cara resolve atau mengatasi error + +Penulis menerima cukup banyak email dari pembaca mengenai beberapa error di chapter ini. Kesimpulan penulis: -Kesimpulan dari email-email yang penulis dapati: **pembaca bingung karena mendapati error, dan tidak tau cara mengatasi error tersebut. Padahal sudah ada keterangan yang jelas bahwa error tersebut pasti muncul, dan juga sudah dijelaskan cara mengatasinya. Ini kemungkinan besar disebabkan karena pembaca hanya copy-paste source code, tanpa membaca penjelasan-penjelasan yang padahal sudah tertulis cukup mendetail**. +**Pembaca bingung karena mendapati error, dan tidak tau apa yang harus dilakukan. Padahal sudah ada keterangan yang cukup jelas bahwa error tersebut pasti muncul, dan sudah disediakan juga penjelasan beserta cara mengatasinya. Ini kemungkinan besar disebabkan karena pembaca hanya copy-paste source code dari chapter ini, tanpa benar-benar membaca penjelasan yang padahal sudah ditulis cukup detail**. -> Saya sangat anjurkan, untuk itu saya mohon **jangan hanya *copas* source code, usahakan dibaca! dipelajari! dan dipahami!** *No hard feeling* ya 👌😁 +> Saya sangat anjurkan untuk **tidak hanya *copas* source code, usahakan dibaca! dipelajari! dan dipahami!** *No hard feeling* ya 👌😁 ## A.26.2. Exported Package dan Unexported Package -Pengembangan aplikasi dalam *real development* pasti membutuhkan banyak sekali file program. Tidak mungkin dalam sebuah project semua file memiliki nama package `main`, biasanya akan dipisah sebagai package berbeda sesuai bagiannya. +Pengembangan aplikasi dalam *real development* pasti membutuhkan banyak sekali file program. Tidak mungkin dalam satu buah project semua source code di tulis di hanya 1 package `main` saja, umumnya akan dipisah ke beberapa package berbeda yang masing-masing punya tugas sendiri yang berbeda satu sama lain. Project folder selain berisikan file-file `.go` juga bisa berisikan sub-folder lainnya. Di Go, setiap folder atau sub-folder adalah satu package, file-file yang ada di dalam sebuah folder package-nya harus sama. Dan package pada file-file tersebut harus berbeda dengan package pada file-file lainnya yang berada pada folder berbeda. -> Jadi mudahnya, 1 folder adalah 1 package. - -Dalam sebuah package, biasanya kita menulis sangat banyak komponen, entah itu fungsi, struct, variabel, atau lainnya. Komponen tersebut bisa leluasa digunakan dalam package yang sama. Contoh sederhananya seperti program yang telah kita praktekan pada chapter sebelum-sebelumnya, dalam package `main` ada banyak yang di-*define*: fungsi, variabel, closure, struct, dan lainnya; ke semuanya bisa langsung dimanfaatkan. +> Sederhananya, 1 folder adalah 1 package. -Jika dalam satu program terdapat lebih dari 1 package, atau ada package lain selain `main`, maka komponen dalam package lain tersebut tidak bisa diakses secara bebas dari file yang package-nya `main`, karena tiap komponen memiliki hak akses. +Dalam sebuah package, biasanya kita menulis sangat banyak komponen, bisa berupa fungsi, struct, variabel, atau lainnya. Komponen-komponen tersebut bisa secara leluasa dipergunakan di kode yang masih berada di dalam package yang sama. Contohnya seperti program yang telah kita praktekan pada chapter sebelum-sebelumnya, dalam package `main` ada banyak yang di-*define*: fungsi, variabel, closure, struct, dan lainnya; semuanya bisa langsung dimanfaatkan. -Ada 2 jenis hak akses di Go: +Jika dalam satu program terdapat lebih dari 1 package, atau ada package lain selain `main`, maka komponen dalam package lain tersebut tidak bisa diakses secara bebas dari file yang package-nya `main`, perlu dilihat dulu level akses yang sudah ditentukan apa. - - Hak akses **Exported** atau **public**. Menandakan komponen tersebut diperbolehkan untuk diakses dari package lain yang berbeda - - Hak akses **Unexported** atau **private**. Berarti komponen hanya bisa diakses dalam package yang sama, bisa dalam satu file saja atau dalam beberapa file yang masih 1 folder. +Go mengenal 2 jenis level akses atau hak akses: -Penentuan hak akses yang tepat untuk tiap komponen sangatlah penting. + - Hak akses **Exported** atau **public**. Menandakan bahwa komponen boleh untuk diakses dari package lain + - Hak akses **Unexported** atau **private**. Berarti komponen hanya bisa diakses dari file yang package-nya sama, bisa dalam satu file yang sama atau di file berbeda yang masih 1 folder yang package-nya pastinya sama. -Di Go cara menentukan level akses atau modifier sangat mudah, penandanya adalah **character case** huruf pertama nama fungsi, struct, variabel, atau lainnya. Ketika namanya diawali dengan huruf kapital menandakan kalau *exported* (atau *public*). Dan sebaliknya, jika diawali huruf kecil, berarti *unexported* (atau private). +Cara menentukan level akses atau modifier di Go sangat mudah, yaitu dengan mengacu ke **character case** huruf pertama nama fungsi, struct, variabel, atau lainnya. Ketika namanya diawali dengan huruf kapital maka level aksesnya adalah *exported* (atau *public*). Dan sebaliknya, jika diawali huruf kecil, berarti *unexported* (atau private). ## A.26.3. Penggunaan Package, Import, Dan Hak Akses *Exported* dan *Unexported* Agar lebih mudah dipahami, maka langsung saja kita praktekan. -Pertama buat folder proyek baru bernama `belajar-golang-level-akses`, inisialisasi sebagai project dengan nama yang sama. Kemudian buat file baru bernama `main.go` di dalamnya, lalu set nama package file tersebut sebagai **main**. +Pertama buat folder proyek baru bernama `belajar-golang-level-akses`, gunakan nama folder tersebut sebagai nama project. Kemudian buat file baru bernama `main.go` di dalamnya, lalu tentukan nama package file tersebut sebagai **main**. Kemudian, buat sub-folder baru bernama `library` di dalam folder `belajar-golang-level-akses`. Di dalam folder `library`, buat file baru `library.go`, set nama package-nya **library**. @@ -65,12 +67,12 @@ func introduce(name string) { } ``` -File `library.go` yang telah dibuat ditentukan nama package-nya adalah `library` (sesuai dengan nama folder), berisi dua buah fungsi, `SayHello()` dan `introduce()`. +File `library.go` yang telah dibuat ditentukan nama package-nya adalah `library` (sesuai dengan nama folder), isinya dua buah fungsi `SayHello()` dan `introduce()`. - Fungsi `SayHello()`, level aksesnya adalah publik, ditandai dengan nama fungsi diawali huruf besar. - Fungsi `introduce()` dengan level akses private, ditandai oleh huruf kecil di awal nama fungsi. -Selanjutnya kita lakukan tes apakah memang fungsi yang ber-modifier private dalam package `library` tidak bisa diakses dari package lain. +Selanjutnya kita siapkan beberapa kode tambahan untuk keperluan testing apakah memang fungsi yang ber-modifier private dalam package `library` tidak bisa diakses dari package lain. Buka file `main.go`, lalu tulis kode berikut. @@ -87,26 +89,26 @@ func main() { Bisa dilihat bahwa package `library` yang telah dibuat tadi, di-import ke dalam package `main`. -Folder utama atau root folder dalam project yang sedang digarap adalah `belajar-golang-level-akses`, sehingga untuk import package lain yang merupakan subfolder, harus dituliskan lengkap path folder nya, seperti `belajar-golang-level-akses/library`. +Di awal telah ditentukan bahwa nama project (yang juga merupakan nama folder) adalah `belajar-golang-level-akses`, maka untuk import package lain yang merupakan subfolder, pada syntax import harus dituliskan lengkap, contoh: `belajar-golang-level-akses/library`. -Penanda root folder adalah tempat di mana file `go.mod` berada. +> Penanda root folder adalah tempat di mana file `go.mod` berada -Ok, kita lanjut. Perhatikan kode berikut. +Kembali ke pembahasan kode, silakan perhatikan kode berikut: ```go library.SayHello() library.introduce("ethan") ``` -Cara pemanggilan fungsi yang berada dalam package lain adalah dengan menuliskan nama package target diikut dengan nama fungsi menggunakan *dot notation* atau tanda titik, seperti `library.SayHello()` atau `library.introduce("ethan")` +Cara pemanggilan fungsi yang berada dalam package lain adalah dengan menuliskan nama package target diikut dengan nama fungsi menggunakan *dot notation* atau tanda titik, seperti `library.SayHello()` atau `library.introduce("ethan")`. OK, sekarang coba jalankan kode yang sudah disiapkan di atas, hasilnya error. ![Error saat menjalankan program](images/A_properti_public_private_2_error.png) -Error di atas disebabkan karena fungsi `introduce()` yang berada dalam package `library` memiliki level akses *unexported* (atau *private*), fungsi ini tidak bisa diakses dari package lain (pada kasus ini `main`). Agar bisa diakses, solusinya bisa dengan menjadikannya ke bentuk *exported* (atau *public*), atau diubah cara pemanggilannya. Di sini kita menggunakan cara ke-2. +Error di atas disebabkan oleh fungsi `introduce()` yang berada dalam package `library` memiliki level akses *unexported* (atau *private*), maka fungsi ini tidak bisa diakses dari package lain (pada kasus ini package `main`). Solusi agar bisa diakses adalah dengan mengubah level aksesnya ke *exported* (atau *public*), atau bisa dengan mengubah cara pemanggilannya. -Tambahkan parameter `name` pada fungsi `SayHello()`, lalu panggil fungsi `introduce()` dengan menyisipkan parameter `name` dari dalam fungsi `SayHello()`. +Ok, sekarang kita akan coba cara ke-2, yaitu mengubah cara pemanggilannya. Tambahkan parameter `name` pada fungsi `SayHello()`, lalu masih di dalam fungsi tersebut panggil fungsi `introduce()` dan gunakan parameter `name`-nya. ```go func SayHello(name string) { @@ -115,7 +117,7 @@ func SayHello(name string) { } ``` -Di `main`, cukup panggil fungsi `SayHello()` saja, sisipkan sebuah string sebagai parameter. +Di fungsi `main()`, cukup panggil fungsi `library.SayHello()` saja. Isi parameternya dengan nilai string apapun, misalnya `"ethan"`. ```go func main() { @@ -131,7 +133,7 @@ Coba jalankan lagi. Level akses *exported* (atau public) dan *unexported* (atau private) juga bisa diterapkan di fungsi, struct, method, maupun property variabel. Cara penggunaannya sama seperti pada pembahasan sebelumnya, yaitu dengan menentukan **character case** huruf pertama nama komponen, apakah huruf besar atau kecil. -Belajar tentang level akses di Go akan lebih cepat jika langsung praktek. Oleh karena itu langsung saja. Hapus isi file `library.go`, buat struct baru dengan nama `student` di dalamnya. +Ok, lanjut ke praktek berikutnya. Hapus isi file `library.go`, lalu buat struct baru dengan nama `student` di dalamnya. ```go package library @@ -161,26 +163,26 @@ Setelah itu jalankan program. ![Error saat menjalankan program](images/A_properti_public_private_3_error.png) -Error muncul lagi, kali ini penyebabnya adalah karena struct `student` masih di set sebagai *unexported*. Ganti ke bentuk *exported* dengan cara mengubah huruf awalnya menjadi huruf besar, kemudian jalankan ulang. +Error muncul lagi, kali ini penyebabnya adalah karena struct `student` level aksesnya adalah *unexported*. Ubah ke bentuk *exported* dengan cara mengubah huruf awalnya menjadi huruf besar, kemudian jalankan ulang. ```go -// pada library/library.go +// file library/library.go type Student struct { Name string grade int } -// pada main.go +// file main.go var s1 = library.Student{"ethan", 21} fmt.Println("name ", s1.Name) fmt.Println("grade", s1.grade) ``` -Output: +Output program: ![Error lain muncul saat menjalankan program](images/A_properti_public_private_4_error.png) -Error masih tetap muncul, tapi kali ini berbeda. Error yang baru ini disebabkan karena salah satu properti dari struct `Student` adalah *unexported*. Properti yg dimaksud adalah `grade`. Ubah ke bentuk *exported*, lalu jalankan lagi. +Error masih tetap muncul, tapi kali ini berbeda. Error yang baru ini disebabkan karena salah satu properti dari struct `Student` adalah *unexported*. Properti yg dimaksud adalah `grade`. Solusinya ubah ke bentuk *exported*, lalu jalankan ulang program. ```go // pada library/library.go @@ -201,11 +203,10 @@ Dari contoh program di atas, bisa disimpulkan bahwa untuk menggunakan `struct` y ## A.26.5. Import Dengan Prefix Tanda Titik -> PERINGATAN! Penggunaan tanda titik pada saat import package bisa menyebabkan kode menjadi ambigu, karena alasan tersebut teknik import ini kurang direkomendasikan. - Seperti yang kita tahu, untuk mengakses fungsi/struct/variabel yg berada di package lain, nama package nya perlu ditulis, contohnya seperti pada penggunaan `library.Student` dan `fmt.Println()`. -Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (`.`) setelah penulisan keyword `import`. Maksud dari se-level di sini adalah, semua properti di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seperti ketika mengakses sesuatu dari file yang sama. +Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (`.`) setelah penulisan keyword `import`. Maksud dari se-level di sini adalah, semua property di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seolah-olah property tersebut berada di file yang sama. Contoh: + ```go import ( . "belajar-golang-level-akses/library" @@ -221,9 +222,13 @@ func main() { Pada kode di atas package `library` di-import menggunakan tanda titik. Dengan itu, pemanggilan struct `Student` tidak perlu dengan menuliskan nama package nya. -## A.26.6. Pemanfaatan Alias Ketika Import Package +> PERINGATAN! +> +> Penggunaan tanda titik pada saat import package bisa menyebabkan kode menjadi ambigu, karena alasan tersebut teknik import ini kurang direkomendasikan. + +## A.26.6. Pemanfaatan Alias Saat Import Package -Fungsi yang berada di package lain bisa diakses dengan cara menuliskan nama-package diikuti nama fungsi-nya, contohnya seperti `fmt.Println()`. Package yang sudah di-import tersebut bisa diubah namanya dengan cara menggunakan alias pada saat import. Contohnya bisa dilihat pada kode berikut. +Fungsi yang berada di package lain bisa diakses dengan cara menuliskan nama-package diikuti nama fungsi-nya, contohnya seperti `fmt.Println()`. Package yang sudah di-import tersebut bisa diubah nama pemanggilannya dengan menerapkan teknik alias yang dituliskan saat import. Contohnya bisa dilihat pada kode berikut. ```go import ( @@ -237,15 +242,15 @@ func main() { Pada kode di-atas, package `fmt` di tentukan aliasnya adalah `f`, untuk mengakses `Println()` cukup dengan `f.Println()`. -## A.26.7. Mengakses Properti Dalam File Yang Package-nya Sama +## A.26.7. Mengakses Property Dalam File Yang Package-nya Sama -Jika properti yang ingin di akses masih dalam satu package tapi berbeda file, cara mengaksesnya bisa langsung dengan memanggil namanya. Hanya saja ketika eksekusi, file-file lain yang yang nama package-nya sama juga ikut dipanggil. +Jika property yang ingin di akses masih dalam satu package tapi file-nya berbeda, cara mengaksesnya bisa langsung dengan memanggil namanya seperti biasa. Hanya saja saat eksekusi, file-file lain yang yang nama package-nya sama tersebut harus ikut disertakan dalam command `go run`. -Langsung saja kita praktekan, buat file baru dalam `belajar-golang-level-akses` dengan nama `partial.go`. +Langsung saja kita praktekan, buat file baru dalam folder `belajar-golang-level-akses` dengan nama `partial.go`. ![File `partial.go` disiapkan setara dengan file `main.go`](images/A_properti_public_private_5_structure.png) -Tulis kode berikut pada file `partial.go`. File ini kita set package-nya `main` (sama dengan nama package file `main.go`). +Tulis kode berikut pada file `partial.go`. File tersebut kita tentukan nama package-nya adalah `main` (sama dengan nama package file `main.go`). ```go package main @@ -257,7 +262,7 @@ func sayHello(name string) { } ``` -Hapus semua isi file `main.go`, lalu silakan tulis kode berikut. +Hapus semua isi file `main.go`, ganti dengan kode berikut. ```go package main @@ -273,17 +278,19 @@ Sekarang terdapat 2 file berbeda (`main.go` dan `partial.go`) dengan package ada go run main.go partial.go ``` -Fungsi `sayHello` pada file `partial.go` bisa dikenali meski level aksesnya adalah *unexported*. Hal ini karena kedua file tersebut (`main.go` dan `partial.go`) memiliki package yang sama. +Fungsi `sayHello` pada file `partial.go` bisa dikenali meski level aksesnya adalah *unexported*. Hal ini karena kedua file tersebut (`main.go` dan `partial.go`) memiliki nama package yang sama. -> Cara lain untuk menjalankan program bisa dengan perintah `go run *.go`, dengan cara ini tidak perlu menuliskan nama file nya satu per satu. +> Alternatif yang lebih praktis untuk menjalankan program bisa dengan perintah `go run *.go`, dengan cara ini maka tidak perlu menuliskan nama file-nya satu per satu. ![Pemanggilan fungsi unexported dari dalam package yang sama](images/A_properti_public_private_6_multi_main.png) -## A.26.7.1. Fungsi `init()` +## A.26.8. Penjelasan Tambahan + +#### ◉ Fungsi `init()` -Selain fungsi `main()`, terdapat juga fungsi spesial, yaitu `init()`. Fungsi ini otomatis dipanggil pertama kali ketika aplikasi di-run. Jika fungsi ini berada dalam package main, maka dipanggil lebih dulu sebelum fungsi `main()` dieksekusi. +Selain fungsi `main()`, terdapat juga fungsi spesial yaitu `init()`. Fungsi ini otomatis dipanggil saat pertama kali program dijalankan. Jika fungsi ini ditulis di package-package lain yang di-import di `main`, maka semua fungsi `init()` tersebut dipanggil lebih dulu sebelum fungsi `main()`. -Langsung saja kita praktekkan. Buka file `library.go`, hapus isinya lalu isi dengan kode berikut. +Agar lebih jelas mari praktekan. Buka file `library.go`, hapus isinya lalu isi dengan kode berikut. ```go package library @@ -319,14 +326,14 @@ func main() { } ``` -Package `library` di-import, dan variabel `Student` dikonsumsi. Pada saat import package, fungsi `init()` yang berada di dalamnya langsung dieksekusi. +Package `library` di-import, dan variabel `Student` dikonsumsi pada fungsi `main()`. Sewaktu package di-import, fungsi `init()` yang berada di dalamnya langsung dieksekusi. -Property variabel objek `Student` akan diisi dan sebuah pesan ditampilkan ke console. - -Dalam sebuah package diperbolehkan ada banyak fungsi `init()` (urutan eksekusinya adalah sesuai file mana yg terlebih dahulu digunakan). Fungsi ini dipanggil sebelum fungsi `main()`, pada saat eksekusi program. +Di dalam fungsi `init()`, property variabel objek `Student` diisi dan sebuah pesan ditampilkan ke console. ![Fungsi `init()`](images/A_properti_public_private_7_init.png) +Di Go, setiap package masing-masing boleh memiliki fungsi `init()`. Fungsi tersebut hanya akan dieksekusi ketika package di-import dengan urutan eksekusinya adalah sesuai dengan package mana yg di-import terlebih dahulu. Dan kesemua fungsi `init()` dipanggil sebelum fungsi `main()`. + ---