Golang 101: ทำความรู้จักภาษาโกฉบับโปรแกรมเมอร์
Tags: go, tutorial, programming
Published: 2021-01-01 07:28:27

# Golang 101: พื้นฐานโกฉบับโปรแกรมเมอร์

### ภาษาโกคือ?

ภาษา Go (หรือเรียกว่า Golang) ถูกสร้างโดย Google เป้าหมายคือภาษาสำหรับเขียนโปรแกรมแนว System Programming การเขียนโปรแกรมในฝั่ง Backend เช่นการสร้าง API Server หรือ Network Application

จุดเด่นของภาษาโกคือ

#### Compiler Language
เป็นภาษาแบบ Compiler คือมีการแปล Source Code ทั้งหมดให้กลายเป็น Executable File (ภาษาเครื่อง) ซึ่งคอมพิวเตอร์สามารถนำไปรันได้ทันที ไม่ต้องแปลคำสั่งใหม่ทุกรอบแบบภาษาแนว Script เช่น Node.js หรือ PHP ทำให้ทำงานได้เร็วส์!

#### Static Type
โกเป็นภาษาแบบ Static-Type คือตัวแปรต้องกำหนดชนิดตั้งแต่แรก ทำให้คอมไพเลอร์ช่วยเราเช็กข้อผิดพลาดได้ตั้งแต่ตอนเขียนโปรแกรมเลย ถ้าใครเคยเขียนโปรแกรมขนาดใหญ่ด้วยภาษาแนว Dynamic-Type เช่น Python, JavaScript, PHP น่าจะรู้ความปวดหัวและบั๊กของการใช้ตัวแปรแบบไม่มีการกำหนดชนิด (ภายหลังมีการสร้าง TypeScript มาใช้แทน JavaScript, ส่วน PHP ตั้งแต่เวอร์ชัน 7 ก็ถูกเพิ่ม Type ให้ตัวแปรแล้ว ... เหลือแค่ Python นี่แหละ)

#### ลักษณะของภาษา
- ใช้เวลาคอมไพล์น้อย ได้งานเร็ว เขียนโค้ดแล้วไม่ต้องมานั่งรอนานๆ
- ออกแบบมาสำหรับเขียนโปรแกรมแบบ Parallel โดยเฉพาะ
- Syntax ของภาษาถูกออกแบบให้ใช้งานง่ายมากๆ โดยใช้คอนเซ็ปตามฉบับของภาษา C (แต่ไม่ต้องใส่ `;`) ไม่ค่อยมีคำสั่งพิเศษๆ ทำให้กรณีทำงานเป็นทีม แต่ละคนจะเขียนโค้ดออกมาคล้ายๆ กัน

#### Garbage Collector 
ภาษาโกมีการใช้ Garbage Collector สำหรับเคลียร์ตัวแปรหรืออ็อบเจคที่ไม่ถูกใช้งานแล้วออกจากหน่วยความจำให้โดยโปรแกรมเมอร์ไม่ต้องจัดการเอง

สามารถดาวน์โหลดคอมไพเลอร์ภาษาโกได้ที่ [https://golang.org](https://golang.org/dl/)

## Hello Go!

มาเริ่มเขียนโกกัน สร้างไฟล์ `main.go` ขึ้นมาก่อน แล้วเขียนโค้ดนี้ลงไป

```go
package main

import "fmt"

func main() {
    fmt.Println("Hello Go ~")
}
```

หลังจากนั้นสามารถสั่งรันโปรแกรมได้ด้วยคำสั่ง

```
go run main.go
```

ซึ่งเราจะได้ผลลัพธ์เป็นคำว่า **Hello Go ~** ออกมา

หากใครเคยเขียนภาษาตระกูล C มาก่อนน่าจะคุ้นๆ กับโค้ดชุดนี้ ภาษาโกจะเริ่มต้นทำงานที่ฟังก์ชันชื่อ `main()` 

การ print ค่าออกมาใช้คำสั่ง `Println()` จากแพกเกจชื่อ `fmt` ย่อมาจาก formatted I/O

> คำสั่งสำหรับการคอมไพล์ภาษาโกยังมีอีกมาก แต่เดี๋ยวเราจะพูดถึงเรื่องนี้กันต่อในบทต่อไป ในบทนี้ขอแสดงให้ดูภาพรวมของภาษาโกก่อนนะ

## Variable ตัวแปร

ภาษาโกเป็น Static-Type ที่ต้องประกาศตัวแปรและกำหนดชนิดอย่างชัดเจนก่อนใช้เสมอ

โดยจะใช้คีย์เวิร์ด `var` ตามด้วยชื่อตัวแปร และ Type

```go
var <varname> <vartype>
```

เช่น

```go
var num int
num = 7

var message string
message = "Expecto Patronum!"
```

ตำแหน่งของชนิดตัวแปรจะสลับกับภาษาตระกูล C ที่ปกติจะเอา Type ขึ้นก่อน เนื่องจากโกอยากให้ลักษณะการอ่านเป็นแบบมนุษย์ เช่น `var num int` ก็จะอ่านได้ว่า *สร้างตัวแปร num เป็นชนิด int*

### Basic Type

ชนิดตัวแปรในโกมีเหมือนๆ กับภาษาอื่นๆ แต่โกจะเน้นการตั้งชื่อชนิดตัวแปรโดยระบุชัดๆ ไปเลยว่าเรากำลังใช้ตัวแปรขนาดกี่ bit อยู่ (ไม่มีการแยก `int`/`long`, `float`/`double`, `char`/`string`)

```go
bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

float32 float64

complex64 complex128
```

ที่สำคัญมากๆ คือสำหรับโก ชนิดตัวแปร primitive (ชนิดตัวแปรพื้นฐาน) นั้น ไม่สามารถเป็น `nil` (หรือ `null` ในภาษาอื่น) ดังนั้นตั้งแต่สร้างตัวแปรขึ้นมา ถ้าเราไม่ได้กำหนดค่าให้มัน มันจะมีการกำหนด default value ให้เองเลย

Type | Zero Value
---|---
int, uint, float | `0`
complex | `(0+0i)`
string | `""` (empty string)
bool | `false`
function | `nil`

```go
var x int = 0
var y int
//x = 0, y = 0 too
```

### Type Inference

เป็นรูปแบบย่อสำหรับการสร้างตัวแปร โดยให้คอมไพล์เลยกำหนดไทป์ให้เราแทนจาก value ที่กำหนดไว้

```go
<variable> := <value>
```
ข้อสังเกตของการประกาศตัวแปรแบบนี้คือไม่ต้องใส่คำว่า `var` แล้วนะ แค่เปลี่ยน `=` เป็น `:=` ก็พอ
```go
num := 7
platform := 9.75
message := "Expecto Patronum!"

//มีค่าเท่ากับ

var num int = 7
var platform float64 = 9.75
var message string = "Expecto Patronum!"
```

สำหรับการประกาศตัวแปรแบบ int และ float นั้นจะถูกกำหนดเป็น `int` และ `float64` ทันที ถ้าอยากได้ตัวแปรขนาดอื่นจะใช้การสร้างตัวแปรแบบนี้ไม่ได้ ต้องกำหนดเอง

## Output

คำสั่ง I/O ขอโกเรียกว่าลอกมาจาก C เลย

```go
fmt.Print("Hello Go ~")
fmt.Println("Hello Go ~") //print with new line

house := "Ravenclaw"
point := 10
fmt.Printf("%v points to %v", point, house)
//output: 10 points to Ravenclaw
```

สำหรับ format การแสดงผลแบบมาตราฐานใช้ `%v` สำหรับ value ทั่วไป 
แต่ก็สามารถใช้ฟอร์แมทพวก `%d`, `%f` เพื่อจัดรูปแบบการแสดงผลตัวเลขที่ละเอียดขึ้นได้เช่นกัน

ส่วนฟอร์แมทอื่นๆ สามารถดูได้ที่ [https://golang.org/pkg/fmt/](https://golang.org/pkg/fmt/)

### String Interpolation

หากต้องการผสมค่าจากตัวแปรเข้ากันเป็น string สำหรับภาษาอื่นๆ ในยุคนี้ส่วนใหญ่จะใช้ `$` ในการบอกว่าตำแหน่งนั้นเป็นตัวแปร

```kt
var year = 2020
var month = 12
var date = 31
var s = "$year / $month / $date"
```

แต่สำหรับโกไม่มีอะไรแบบนั้น แต่จะใช้ฟังก์ชัน `sprintf` จากภาษา C (อีกแล้ว) ซึ่งทำงานคล้ายๆ กับ `printf` แต่รีเทิร์นค่ากลับมาเป็น string แทนการปริ๊นออกมา

```go
year := 2020
month := 12
date := 31
s := fmt.Sprintf("%v / %v / %v", year, month, date)
```


## Flow Control

คำสั่งควบคุมในภาษาโกนั้นเรียบง่ายมาก คือเหมือนภาษา C แต่ไม่จำเป็นต้องเขียน `()` ครอบตัว condition เอาไว้ (อารมณ์คล้ายๆ python และ swift)

แต่ block code จะต้องใส่ `{}` ด้วยนะ ละไม่ได้

### if-else
```go
if x > 10 {
    // x more than 10
} else {
    // else...
}
```

### loop

ลูปในโกมีข้อแตกต่างจากภาษา C คือมีแต่คำสั่ง `for` ให้ใช้สำหรับลูปทุกรูปแบบ

```go
// รูปแบบ for ธรรมดา
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// รูปแบบ while ก็ยังใช้คีย์เวิร์ด for
i := 0
for i < 10 {
    fmt.Println(i)
    i++
}

// หรือไม่กำหนดอะไรเลยก็ยังได้ มีค่าเท่ากับ while true
for {
    fmt.Println("Aways~")
}
```

### switch

ส่วน switch นั้นไม่ต้องเติม `break` ที่ท้ายบล็อกแล้วนะ 
```go
switch year {
  case 1997: fmt.Println("Philosopher's Stone")
  case 1998: fmt.Println("Chamber of Secrets")
  case 1999: fmt.Println("Prisoner of Azkaban")
  case 2000: fmt.Println("Goblet of Fire")
  case 2003: fmt.Println("Order of the Phoenix")
  case 2005: fmt.Println("Half-Blood Prince")
  case 2007: fmt.Println("Deathly Hallows")
  default: fmt.Println("Not Exist!")
}
```


## Array และ Slice

สำหรับตัวแปรแบบ vector ในภาษาโกนั้นมีให้ใช้ 2 แบบคือ
- array: fixed length ขนาดตายตัว ยืดหดไม่ได้
- slice: dynamic length ขนาดเปลี่ยนแปลงได้

array จะใช้สัญลักษณ์ `[n]`  เพื่อระบุว่าต้องการกี่ช่อง (กำหนดแล้วเปลี่ยนไม่ได้)
```go
var names [3]string
names[0] = "Harry"
names[1] = "Ron"
names[2] = "Hermione"

//หรือถ้าอยากกำหนดค่าไปพร้อมๆ กันเลย

names := [3]string{"Harry", "Ron", "Hermione"}

len(names) // 3: ใช้หาขนาดของ array
```

ส่วน slice นั้นวิธีประกาศเหมือน array ทุกอย่างแต่ไม่ต้องกำหนดขนาดลงไป

ตอนแรกที่สร้าง slice จะมีขนาดเป็น `0` ส่วนถ้าต้องการเพิ่มข้อมูลเข้าไปจะใช้คำสั่ง `append` (ซึ่งเป็นคำสั่งแบบ immutable นะ ถ้าต้องการให้ slice ตัวเดิมเปลี่ยนค่าไปด้วย ต้องเอาตัวแปรไปรับอีกที)
```go
var names []string
names = append(names, "Harry")
names = append(names, "Ron")
names = append(names, "Hermione")

//หรือสำหรับสายย่อ

names := []string{"Harry", "Ron", "Hermione"}
```

การวนลูปทั้ง array และ slice สามารถใช้ `for` แบบธรรมดา หรือใช้คู่กับคำสั่ง `range` เพื่อวนแบบ **foreach** ก็ได้
```go
names := []string{"Harry", "Ron", "Hermione"}

for index := 0, index < len(names); index++ {
    fmt.Println(index, names[index])
}

for index, name := range names {
    fmt.Println(index, name)
}
```


## Function

ฟังก์ชันของโกเหมือนภาษาตระกูลมีไทป์ทั่วไป โดยเราต้องกำหนดไทป์ของ parameter

```go
func main() {
    sayHi("Draco", "Malfoy")
}

func sayHi(firstName string, lastName string) {
    fmt.Printf("I'm %s, %s %s.", lastName, firstName, lastName)
}
```

รวมถึงถ้าฟังก์ชันนั้นมีการรีเทิร์นค่ากลับได้ ก็ต้องกำหนดไทป์ที่ด้านหลังของฟังก์ชันเช่นกัน (ถ้าไม่กำหนดจะถือว่าเป็น void คือฟังก์ชันที่ไม่มีการรีเทิร์นค่า)

```go
func add(x int, y int) int {
    return x + y
}
```

แต่จุดเด่นอีกอย่างของฟังก์ชันในโกคือเราสามารถรีเทิร์นค่ากลับได้หลายค่าในรูปแบบของ tuple ด้วย

```go
func f() (int, string, bool) {
    return 123, "val", true
}

func main() {
    number, word, boolean := f()
}
```

และเนื่องจากโกนั้นไม่มีแนวคิดเรื่อง try-catch (สร้าง Exception เพื่อ throw ไม่ได้) ดงนั้นฟังก์ชันไหนที่มีโอกาสเกิด Error ได้ จะนิยมตอบ Error กลับมาด้วยและเป็นเหมือนธรรมเนียมว่าฝั่ง caller จะต้องมีการเช็ก Error ด้วยทุกครั้ง

```go
package main

import (
    "errors"
    "fmt"
)

func divide(x float64, y float64) (float64, error) {
    if y == 0. {
        return 0., errors.New("division by zero")
    }
    return x / y, nil
}

func main() {
    r, err := divide(10, 5)

    if err != nil {
        // TODO: handle error
        panic(err)
    }

    fmt.Println(r)
}
```

หากเจอ error แล้วอยากให้โปรแกรมหยุดทำงาน สามารใช้คำสั่ง `panic()` หรือ `os.Exit(1)` ก็ได้


## Package

ในการเขียนโปรแกรมจริงๆ เราจำเป็นต้องจัดโค้ดให้เป็นส่วนๆ เพื่อความง่ายต่อการค้นหาและตรวจสอบ ยิ่งถ้าทำงานกันเป็นทีมแล้วไม่มีข้อตกลงอะไรกันเลย โค้ดเละแน่นอน

Package เป็นหนึ่งในฟีเจอร์ที่ช่วยให้เราจัดโค้ดให้เป็นสัดเป็นส่วนได้ โดยเราสามารถแยกไฟล์ของเราออกเป็นส่วนๆ แล้วจะใช้งานส่วนไหนก็ทำการ `import` เข้ามาใช้งานได้

สมมุติว่าโปรเจคของเราหน้าตาเป็นแบบนี้

```
src
└── myProject
    ├── main.go
    └── calcucator
        └── main.go
```
ในโปรเจคนี้เรามี 2 ไฟล์ คือ `main.go` และ `calcucator/main.go`

```go
// calcucator/main.go

func add(x int, y int) int {
    return x + y
}

func Calculate(x int, y int) int {
    return add(x, y)
}
```

ทีนี้ถ้าเราอยากเรียกใช้ฟังก์ชันที่อยู่ใน calculate เราก็ต้องโหลดมันเข้ามาซะก่อน

```go
// main.go

import "myProject/calculator"

func main() {
    x := calculator.Calculate(1, 2)
    
    // คำถาม
    // จะเกิดอะไรขึ้นถ้าเราเรียกใช้คำสั่ง
    y := calculator.add(1, 2)
}

```

ถ้าใครลองโค้ดตัวนี้แล้วจะพบว่าเราสามารถเรียกใช้คำสั่ง `calculator.Calculate()` ได้โดยไม่มีปัญหาอะไร แต่พอเป็น `calculator.add()` แล้วพังเลย!!

มันต่างกันตรงไหน?

สำหรับคนที่สังเกตเห็นว่าการตั้งชื่อฟังก์ชันมันแปลกๆ ใช่แล้ว! สิ่งที่ต่างกันคือชื่อฟังก์ชันนั่นเอง เพราะว่าโกมีการกำหนดไว้ว่า

**Capitalize หรือฟังก์ชันที่ตั้งชื่อขึ้นด้วยตัวอักษาตัวใหญ่ จะถือว่าเป็น public ให้ไฟล์โกอื่นๆ เรียกไปใช้งานได้ แต่ถ้าตั้งชื่อขึ้นด้วยอักษรตัวเล็กจะถือว่าเป็น private ใช้ได้แค่ในไฟล์นั้นเท่านั้น**

เรื่องนี้จะต่างจากภาษาอื่นที่นิยมใช้ keyword เช่น `public`, `private` มาระบุการเข้าถึงฟังก์ชันมากๆ ใช้แรกๆ อาจจะไม่ชินโดยเฉพาะคนที่มาจากภาษาแนว OOP ที่ชอบกำหนดว่าชื่อคลาสเท่านั้นที่จเป็น Capitalize ได้


## Struct

สตรัคเป็นการสร้างชนิดข้อมูลเก็บกลุ่มของตัวแปรหลายๆ ชนิดเข้าไว้ด้วยกัน สำหรับภาษาปกติ ถ้าเราต้องการ define กลุ่มของข้อมูลขึ้นมา เรามักจะใช้ class ในการสร้าง **แต่เนื่องจากภาษา Go ไม่มี class**เราจึงต้องมาใช้วิธีแบบ old school นั่นคือ struct แทน

> ภาษา Go มองว่าการเขียนโปรแกรมแบบ OOP โดยเฉพาะในส่วนของ Inheritance (การสืบทอด) นั้นเป็นสิ่งที่โปรแกรมเมอร์มักใช้งานผิด หรือทำให้เกิดการใช้งานผิดได้ง่ายมากเพราะ requirement มาเปลี่ยนหลังจากเริ่มเขียนโค้ดไปแล้ว 
> 
> โกต้องการให้เราเขียนโค้ดแบบเรียบง่าย ตรงไปตรงมากที่สุด มันเลยตัดคุณสมบัติข้อนี้ออกไป

รูปแบบการสร้าง struct จะใช้คีย์เวิร์ด `type` และ `struct` แบบนี้

```go
type student struct {
    name string
    year int
    house string
}
```
หลังจากสร้าง struct ขึ้นมาแล้ว เราก็เอาไปสร้างข้อมูลตามที่กำหนดไว้ได้

```go
hermione := student{
    name: "Hermione Granger", 
    year: 5, 
    house: "Gryffindor",
}

luna := student{
    name: "Luna Lovegood", 
    year: 4, 
    house: "Ravenclaw",
}
```
และเราสามารถใช้ `.` เพื่อเข้าถึง property ของ struct ได้

```go
package main

import "fmt"

type student struct {
    name string
    year int
    house string
}

func main() {
    harry := student{
        name: "Harry Potter", 
        year: 2, 
        house: "Gryffindor",
    }

    fmt.Printf("%v must not go back to Hogwarts", harry.name)
}
```
และเนื่องจากตามคอนเซ็ปของ struct แล้วมันไม่ใช่ class มันเลยมีได้แค่ properties แต่ไม่มี method 

แต่ยังก็ยังดีที่เราสามารถสร้างฟังก์ชันที่ผูกกับ struct ได้ แบบนี้

```go
type student struct {
    name string
    year int
    house string
}

func (s student) spell() {
    if s.name == "Harry Potter" {
        fmt.Println("Expelliarmus!")
    } else {
        fmt.Println("Stupefy!")
    }
}

func main() {
    harry := student{
        name: "Harry Potter", 
        year: 2, 
        house: "Gryffindor",
    }

    harry.spell()
}
```
จากตัวอย่าง เราสร้างฟังก์ชัน `spell` แต่มีการกำหนดว่าฟังก์ชันนี้จะใช้ได้กับ struct `student` เท่านั้น (มีการกำหนดชื่อตัวแปร `s` แทน student เอาไว้ใช้ในส่วนของฟังก์ชันด้วย)

> สำหรับเรื่องของ struct เดี๋ยวจะมีเขียนแยกเป็นอีกบทหนึ่งเลย เพราะมีรายละเอียดค่อนข้างเยอะ

---

ก็จบไปแล้วก็พื้นฐานและภาพรวมของภาษา Go น่าจะเห็นภาพกันแล้ว

คิดว่าสำหรับโปรแกรมเมอร์ที่เคยเขียนภาษาอื่นมาแล้ว เรื่อง syntax ของโกนั้นไม่น่าจะเป็นปัญหา เพราะตัวภาษาก็ออกแบบตามภาษา C อยู่แล้ว **แต่ก็ยังมีบางอย่างที่เป็นซิกเนเจอร์ของภาษาโกซึ่งเรายังไม่ได้พูดถึงในบทนี้อยู่นะ เช่น goroutine และ channel**

สรุปแล้วก็คือ โกคล้ายๆ ภาษา C ยุคใหม่ที่เขียนง่ายขึ้นหน่อย คอมไพล์เร็วขึ้นเยอะ แต่ด้วยหลักการทำให้ภาษา simple ทุกอย่าง ในให้คนที่เคยเขียนภาษายุคโมเดิร์นอาจจะขัดใจเล็กๆ น้อยๆ ไม่ว่าจะเป็นเรื่องที่โกเป็นภาษาแนว imperative จ๋ามากๆ (แนวภาษา C ไม่ค่อยมีฟีเจอร์จากโลก OOP และ Functional เลย) แถมจำกัดวิธีการเขียนค่อยข้างเยอะ ตัวแปรถ้าประกาศมาแล้วไม่ได้ใช้งาน ก็จะคอมไพล์ไม่ผ่านอีก

ในบทต่อไปเราจะเริ่มลงลึกในรายละเอียดของภาษากัน...



> ชอบภาษานี้มั้ย?
>
> [ความเห็นส่วนตัว] ขอตอบว่าถ้าในแง่การทำ system programming ให้มีประสิทธิภาพคือน่าสนใจมากๆ แต่ด้าน syntax ภาษานี้ไม่ผ่าน (เทียบกับทั้งภาษา high-level เช่น JavaScript หรือภาษา low-level กว่าเช่น Rust)