21. Fungsi dan error
Kamu sudah memanggil fungsi sejak Bab 07: fmt.Println,
strings.ToUpper, strconv.Atoi,
rand.Intn. Sekarang kamu menulis fungsimu sendiri. Fungsi
memberi nama pada sepotong pekerjaan yang bisa dipakai ulang, yaitu
langkah bernama dari Bab 05 yang diubah menjadi kode.
Bab ini juga menepati janji dari Bagian 3. Bab ini menjelaskan nilai
kedua dari strconv.Atoi dan ReadString, yang
selama ini kamu buang dengan _.
Mendefinisikan dan memanggil
Definisi paling sederhana:
func greet() {
fmt.Println("Hello!")
}
func main() {
greet() // Hello!
greet() // Hello!
}Baca seperti ini: definisikan fungsi greet yang
tidak menerima apa-apa dan menjalankan isinya saat dipanggil.
func memulai definisi, () menampung apa yang
diterimanya, dan isinya berada di antara { dan
}. Panggil fungsi dengan menulis namanya dan
().
Sekarang kamu bisa melihat apa sebenarnya kerangka dari Bab 07.
func main() { ... } adalah definisi fungsi biasa, dan
main adalah fungsi yang dipanggil Go untukmu saat program
mulai. Fungsi lainnya hanya berjalan kalau ada yang memanggilnya.
Mulai sekarang, blok kode yang mendefinisikan fungsi juga menampilkan
main, tetapi tetap tidak menampilkan baris
package dan import.
Fungsi ditulis di luar main, di tingkat
paling atas file, dan urutannya tidak penting: main bisa
memanggil fungsi yang didefinisikan lebih bawah. Fungsi di dalam
main tidak bisa dikompilasi:
func main() {
func greet() {
fmt.Println("Hello!")
}
greet()
}syntax error: unexpected greet, expected (
syntax error: non-declaration statement outside function body
Baris kedua hanya akibat dari baris pertama. Pindahkan
greet keluar dari main, dan keduanya
hilang.
Parameter
Fungsi menerima input lewat parameter di dalam tanda kurung. Setiap parameter punya nama dan tipe:
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
greet("Keiko") // Hello, Keiko!
greet("Arif") // Hello, Arif!
}name hidup selama satu panggilan, dan pemanggil
memberikan nilainya, yang disebut argumen. Kalau
parameternya lebih dari satu, pisahkan dengan koma:
func add(a int, b int) {
fmt.Printf("%d + %d = %d\n", a, b, a+b)
}
func main() {
add(3, 4) // 3 + 4 = 7
}Kalau parameter yang berdampingan punya tipe yang sama, Go mengizinkan kamu menulis tipenya sekali saja. Kamu akan melihat ini di kode orang lain, dan artinya sama:
func add(a, b int) {Tipe diperiksa sebelum program berjalan. Memberikan tipe yang salah berhenti dengan error seperti di Bab 11:
func add(a int, b int) {
fmt.Printf("%d + %d = %d\n", a, b, a+b)
}
func main() {
add(3, "four")
}cannot use "four" (untyped string constant) as int value in argument to add
Setiap argumen wajib ada
Go tidak punya parameter opsional, dan parameter tidak punya nilai bawaan. Panggilan harus memberikan persis argumen yang diminta fungsi. Terlalu sedikit:
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
greet()
}not enough arguments in call to greet
have ()
want (string)
Terlalu banyak juga berhenti dengan cara yang sama. Pesannya berbunyi
too many arguments, dengan baris have dan
want yang sama untuk dibandingkan:
too many arguments in call to greet
have (string, string)
want (string)
Kalau kamu ingin nilai cadangan, tulis sendiri. Parameter adalah variabel di dalam fungsi, jadi pemeriksaan dari Bab 17 bisa dipakai padanya. Fungsi kedua dengan nama yang jelas lalu bisa memberikan nilainya:
func greet(name string) {
if name == "" {
name = "World"
}
fmt.Printf("Hello, %s!\n", name)
}
func greetEveryone() {
greet("")
}
func main() {
greet("Keiko") // Hello, Keiko!
greetEveryone() // Hello, World!
}return: mengembalikan
nilai
Untuk menghasilkan nilai alih-alih mencetak, pakai
return, dan tulis tipe nilainya setelah tanda kurung:
func square(n int) int {
return n * n
}
func main() {
result := square(5)
fmt.Println(result) // 25
fmt.Println(square(7)) // 49
fmt.Println(square(3) + 1) // 10
}return langsung mengakhiri fungsi dan menyerahkan
nilainya kepada pemanggil. Fungsi yang menjanjikan nilai harus mencapai
return di setiap jalur. Kalau tidak:
func isPositive(n int) bool {
if n > 0 {
return true
}
}
func main() {
fmt.Println(isPositive(5))
}missing return
Go melaporkannya di } penutup, karena di situlah fungsi
bisa berakhir tanpa nilai.
Fungsi yang tidak punya tipe setelah tanda kurungnya tidak mengembalikan apa-apa, dan Go tidak mengizinkan kamu memakai hasilnya sebagai nilai:
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
result := greet("Keiko")
fmt.Println(result)
}greet("Keiko") (no value) used as value
Buka exercises/21/01-square.go. Tambahkan fungsi
cube yang mengembalikan n * n * n, lalu cetak
cube(3).
Beberapa nilai kembalian
Fungsi Go bisa mengembalikan beberapa nilai sekaligus. Tulis tipenya
di dalam tanda kurung, dan pisahkan nilainya dengan koma di
return:
func divide(a int, b int) (int, int) {
return a / b, a % b
}
func main() {
quotient, remainder := divide(17, 5)
fmt.Println(quotient, remainder) // 3 2
}Kamu sudah menjadi pihak penerima sejak Bab 14, ketika
strconv.Atoi memberimu dua nilai. Ini penugasan dua
variabel yang sama dengan yang kamu temui di Bab 11. Kamu harus menerima
semuanya, karena satu variabel tidak bisa menampung dua nilai:
func divide(a int, b int) (int, int) {
return a / b, a % b
}
func main() {
result := divide(17, 5)
fmt.Println(result)
}assignment mismatch: 1 variable but divide returns 2 values
Kalau kamu hanya butuh sebagian, tulis _ di tempat yang
lain. Hanya itulah yang selama ini dilakukan _ di
text, _ :=:
func divide(a int, b int) (int, int) {
return a / b, a % b
}
func main() {
_, remainder := divide(20, 6)
fmt.Println(remainder) // 2
}Ini adalah exercises/21/02-divmod.go.
return awal
untuk pemeriksaan penjaga
Karena return langsung mengakhiri fungsi, ini berguna
untuk menangani kasus mudah lebih dulu:
func absolute(n int) int {
if n >= 0 {
return n
}
return -n
}
func main() {
fmt.Println(absolute(5)) // 5
fmt.Println(absolute(-7)) // 7
}else tidak diperlukan. Kalau cabang pertama cocok,
fungsi selesai di situ, dan baris setelah if tidak pernah
berjalan. Ini disebut penjaga (guard), atau
return awal.
Cakupan: tempat variabel hidup
Variabel yang dibuat di dalam fungsi hanya hidup selama satu panggilan. Begitu fungsi selesai, variabel itu hilang:
func counterStep() int {
count := 0
count = count + 1
return count
}
func main() {
fmt.Println(counterStep()) // 1
fmt.Println(counterStep()) // 1
fmt.Println(counterStep()) // 1
}Setiap panggilan mendapat count yang baru. Parameter
juga begitu: mengubahnya di dalam fungsi tidak mengubah variabel milik
pemanggil. Fungsi lain juga tidak bisa melihatnya:
func setUp() {
count := 0
count = count + 1
}
func main() {
setUp()
fmt.Println(count)
}undefined: count
Untuk menyimpan nilai antarpanggilan, deklarasikan variabelnya di luar semua fungsi. Semua fungsi dalam file itu lalu bisa memakainya:
var steps = 0
func step() int {
steps = steps + 1
return steps
}
func main() {
fmt.Println(step()) // 1
fmt.Println(step()) // 2
fmt.Println(step()) // 3
}Bab 11 mengatakan bahwa := hanya bisa dipakai di dalam
fungsi, dan di sinilah hal itu berpengaruh. Di luar fungsi, kamu harus
menulis var.
exercises/21/04-outside-function.go sengaja memuat
kesalahan ini, dan menjalankannya berhenti dengan:
# command-line-arguments
exercises\21\04-outside-function.go:11:1: syntax error: non-declaration statement outside function body
Pakai variabel di luar fungsi kalau memang perlu. Kebanyakan data
lebih baik diberikan lewat parameter dan dikembalikan dengan
return, karena dari panggilannya kamu bisa melihat apa yang
dibaca dan diubah fungsi. Bab 26 menunjukkan cara yang lebih rapi untuk
menyatukan data yang berkaitan. Contoh-contoh ini ada di
exercises/21/03-counter-scope.go.
Error: nilai kedua
strconv.Atoi mengembalikan dua nilai: angkanya, dan
laporan apakah konversinya berhasil. Laporan itu bertipe
error. Isinya salah satu dari dua hal:
nil, kata Go yang berarti "tidak ada
apa-apa", kalau semuanya baik, atau keterangan tentang masalahnya.
Simpan di variabel, biasanya bernama err, dan periksa tepat
di baris berikutnya:
number, err := strconv.Atoi(text)
if err != nil {
fmt.Println("That is not a whole number.")
return
}Baca err != nil sebagai "ada error". Ini adalah
exercises/21/05-check-atoi.go, di dalam
main:
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter a whole number: ")
text, _ := reader.ReadString('\n')
number, err := strconv.Atoi(strings.TrimSpace(text))
if err != nil {
fmt.Println("That is not a whole number.")
fmt.Println("The error says:", err)
return
}
fmt.Println("You typed the number", number)return di dalam main mengakhiri program.
Tiga kali jalan, dengan mengetik 0, lalu abc,
lalu hanya Enter:
Enter a whole number: 0
You typed the number 0
Enter a whole number: abc
That is not a whole number.
The error says: strconv.Atoi: parsing "abc": invalid syntax
Enter a whole number:
That is not a whole number.
The error says: strconv.Atoi: parsing "": invalid syntax
Ini menyelesaikan masalah dari Bab 14 dan 17: 0
sungguhan dan teks yang salah tidak lagi terlihat sama, karena kamu
bertanya kepada err, bukan kepada angkanya. Kalau
err bukan nil, angkanya bukan jawaban, jadi
jangan dipakai.
strconv.ParseFloat bekerja dengan cara yang sama:
x, err := strconv.ParseFloat("abc", 64)
fmt.Println(x) // 0
fmt.Println(err) // strconv.ParseFloat: parsing "abc": invalid syntaxerr juga menangkap pengecualian yang disebut Bab 15,
yaitu bilangan bulat yang terlalu besar untuk int:
n, err := strconv.Atoi("99999999999999999999")
fmt.Println(n) // 9223372036854775807
fmt.Println(err) // strconv.Atoi: parsing "99999999999999999999": value out of rangeGo tidak membiarkan kamu melupakan err secara tidak
sengaja. Variabel yang tidak pernah dipakai membuat program gagal
dikompilasi, seperti yang ditunjukkan Bab 10, jadi kamu harus memeriksa
err atau membuangnya dengan sengaja memakai
_:
number, err := strconv.Atoi("42")
fmt.Println(number)err declared and not used
Mengembalikan error buatanmu sendiri
Fungsi buatanmu bisa melaporkan masalah dengan cara yang sama.
Tambahkan error sebagai tipe kembalian terakhir. Saat
gagal, kembalikan error yang dibuat dengan errors.New, dari
package errors. Saat berhasil, kembalikan
nil:
func safeDivide(a int, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}Kalau nilai harus ada di depan, 0 mengisi tempatnya saat
gagal. Untuk memasukkan nilai ke dalam pesan, pakai
fmt.Errorf, yang menerima verb yang sama dengan
fmt.Sprintf. Fungsi yang hanya perlu melaporkan masalah
cukup mengembalikan error:
func checkLevel(level int) error {
if level < 1 || level > 99 {
return fmt.Errorf("level %d is not from 1 to 99", level)
}
return nil
}Pemanggil langsung memeriksanya, sama seperti pada
strconv.Atoi.
exercises/21/06-your-own-errors.go memanggil keduanya, dan
mencetak:
Result: 3
Error: cannot divide by zero
<nil>
level 150 is not from 1 to 99
Dua baris terakhir menunjukkan dua hal yang bisa ditampung
error. nil tercetak sebagai
<nil>.
Buka exercises/21/06-your-own-errors.go. Tambahkan
fungsi checkAge yang mengembalikan error kalau argumennya
di bawah 0, dan nil kalau tidak. Panggil dari
main dengan kedua jenis nilai.
Mengetahui bahwa input sudah habis
Bab 20 memperingatkan bahwa perulangan validasi mencetak pesannya selamanya kalau input habis. Ini terjadi kalau input berasal dari file, bukan dari keyboard. Di keyboard, pada Windows, menekan Ctrl + Z lalu Enter mengakhiri input dengan cara yang sama.
Nilai kedua dari ReadString melaporkan hal ini. Nilainya
adalah error yang bukan nil kalau tidak ada
lagi yang bisa dibaca:
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
fmt.Println("No more input.")
break
}Pemeriksaannya punya dua bagian karena baris terakhir sebuah file
mungkin tidak punya akhiran baris. ReadString lalu
memberimu teks itu dan error sekaligus, dan teksnya masih
bagus. Hanya error tanpa teks yang berarti input sudah habis.
Ini adalah exercises/21/07-read-until-end.go, di dalam
main:
reader := bufio.NewReader(os.Stdin)
total := 0
for {
fmt.Print("Number: ")
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
fmt.Println("No more input.")
break
}
number, err := strconv.Atoi(line)
if err != nil {
fmt.Println("Skipped:", line)
continue
}
total += number
}
fmt.Println("Total:", total)Contoh sesi, dengan input yang habis setelah 7:
Number: 5
Number: abc
Skipped: abc
Number: 7
Number: No more input.
Total: 12
Perhatikan number, err := strconv.Atoi(line).
err sudah ada di putaran ini, tetapi number
baru, jadi := boleh dipakai, dan err dipakai
ulang. Aturannya sama dengan di Bab 15.
Sekarang perulangan validasi bisa diselesaikan. Bab 20 mengatakan
bahwa pemeriksaan rentangnya tidak bisa menerima 0 sebagai
jawaban yang baik. Dengan err, bisa, dan perulangan
berhenti kalau input habis. Ini adalah
exercises/21/09-validate-volume.go, di dalam
main:
reader := bufio.NewReader(os.Stdin)
var volume int
for {
fmt.Print("Volume (0 to 10): ")
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
fmt.Println("No more input.")
return
}
volume, err = strconv.Atoi(line)
if err == nil && volume >= 0 && volume <= 10 {
break
}
fmt.Println("That is not a volume from 0 to 10.")
}
fmt.Println("Volume set to", volume)Contoh sesi:
Volume (0 to 10): abc
That is not a volume from 0 to 10.
Volume (0 to 10): 150
That is not a volume from 0 to 10.
Volume (0 to 10): 0
Volume set to 0
Memasukkan baris pembacaan ke dalam fungsi
Bab 15 mengulang baris pembacaan untuk setiap nilai, dan menjanjikan
cara yang lebih baik. Inilah caranya, di
exercises/21/08-read-functions.go:
// Chapter 21 example: the input-reading lines, in functions of their own.
// Run with: go run exercises/21/08-read-functions.go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
var reader = bufio.NewReader(os.Stdin)
// readLine prints the prompt and gives back one trimmed line. The error is
// not nil when there is no more input.
func readLine(prompt string) (string, error) {
fmt.Print(prompt)
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
return "", err
}
return line, nil
}
// readInt asks until it gets a whole number. The error is not nil when
// there is no more input.
func readInt(prompt string) (int, error) {
for {
line, err := readLine(prompt)
if err != nil {
return 0, err
}
number, err := strconv.Atoi(line)
if err == nil {
return number, nil
}
fmt.Println("That is not a whole number.")
}
}
func main() {
a, err := readInt("First number: ")
if err != nil {
fmt.Println("No more input.")
return
}
b, err := readInt("Second number: ")
if err != nil {
fmt.Println("No more input.")
return
}
fmt.Printf("Sum: %d\n", a+b)
}Beberapa hal dari bab ini bertemu di sini:
readerpindah ke luarmain, memakaivar, karenareadLinejuga membutuhkannya. Sebuah program tetap hanya perlu membuat satu.readLinemengerjakan prompt, pembacaan, pemangkasan, dan pemeriksaan. Fungsi ini mengembalikan baris dan error.readIntmemanggilreadLine, dan bertanya lagi sampai mendapat bilangan bulat. Kalauerrbukannil, error itu diteruskan kepada pemanggilnya.mainsekarang hanya menyebutkan apa yang diinginkannya, dan memeriksa apakah input sudah habis.
Contoh sesi, dan satu kali jalan tanpa input sama sekali:
First number: 3
Second number: abc
That is not a whole number.
Second number: 4
Sum: 7
First number: No more input.
Mini-proyek Bagian 1 menunjukkan fungsi yang bukan bagian dari Go:
guess := readGuess()Sekarang kamu bisa menulisnya. Isinya satu baris, karena
return bisa meneruskan semua nilai dari panggilan lain:
func readGuess() (int, error) {
return readInt("Guess: ")
}Karena pembacaan bisa gagal, panggilan yang sebenarnya menerima dua nilai, dan memeriksa errornya:
guess, err := readGuess()
if err != nil {
fmt.Println("No more input. The number was", target)
return
}exercises/21/10-read-guess.go adalah seluruh Game Tebak
Angka yang ditulis dengan cara ini. Berbeda dari versi di Bagian 4,
abc tidak lagi dihitung sebagai tebakan.
Buka exercises/21/08-read-functions.go. Tambahkan fungsi
readFloat yang bekerja seperti readInt tetapi
memakai strconv.ParseFloat, dan pakai untuk menanyakan
harga.
PR
Starter PR ada di exercises/21/homework/. Setiap starter
bisa langsung dijalankan dan mencetak satu baris pengganti. Tulis
fungsimu di luar main.
Soal 1 — Sapa
Buka exercises/21/homework/01-greet.go. Tulis fungsi
greet yang menerima name dan mencetak
Hello, <name>!. Panggil dengan tiga nama yang
berbeda.
Soal 2 — isEven
Buka exercises/21/homework/02-is-even.go. Tulis
isEven, yang menerima int dan
mengembalikan (bukan mencetak) true atau
false untuk menyatakan apakah angka itu genap. Panggil pada
beberapa nilai di dalam fmt.Println supaya nilai bool-nya
terlihat.
Soal 3 — Clamp
Buka exercises/21/homework/03-clamp.go. Tulis
clamp(x int, lo int, hi int) int, yang mengembalikan
lo kalau x di bawah lo,
hi kalau di atas hi, dan x kalau
tidak keduanya. Uji dengan tiga panggilan yang mencakup setiap
kasus.
Tantangan — Persen
Buka exercises/21/homework/04-percent.go. Tulis
parsePercent(text string) (int, error). Fungsi ini mengubah
text menjadi int dan mengembalikan angka itu dengan error
nil. Kalau teksnya bukan angka, fungsi ini mengembalikan
error dari strconv.Atoi. Kalau angkanya di bawah
0 atau di atas 100, fungsi ini mengembalikan
error buatanmu sendiri dari errors.New. Tulis fungsi kedua,
report, yang memanggilnya, langsung memeriksa error-nya,
dan mencetak hasilnya. Panggil report dengan
"85", "abc", dan "150".
Stuck atau sudah selesai? Buka halaman solusi PR.