golang

Golang ทำ CRUD Service APIs (No Database) พร้อมเขียน Test ด้วย

ช่วงนี้ผู้เขียนกำลังเริ่มเรียนรู้เกี่ยวกับ Golang มาสักระยะ วันนี้ก็เลยอยากจะพามาลงบทเรียนการเริ่มพัฒนา Service API ด้วยภาษา Go โดยบทความนี้จะขอ Coding โดยใช้ Echo Framework สำเร็จเพื่อรวดเร็วในการพัฒนา Web Service API มาเริ่มกันเลย

เตรียมความพร้อมก่อน

  • ต้องการ Go runtime เวอร์ชั่น v1.13 หรือมากกว่า สำหรับ Echo v.4
  • IDE หรือ Editor (VScode,Sublime,Atom) แล้วแต่ความถนัด (Vscode ติดตั้ง Extensions Golang)
  • Postman IDE หรือ Terminal ตามความถนัด เช่นกัน

เริ่มโค๊ดกันเลย

  • initial go project go mod init poolsawat.com-echo-crud
  • download echo framework libs
[email protected] poolsawat.com-echo-crud % go get github.com/labstack/echo/v4
go: added github.com/labstack/echo/v4 v4.9.0
go: added github.com/labstack/gommon v0.3.1
go: added github.com/mattn/go-colorable v0.1.11
go: added github.com/mattn/go-isatty v0.0.14
go: added github.com/valyala/bytebufferpool v1.0.0
go: added github.com/valyala/fasttemplate v1.2.1
go: added golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
go: added golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
go: added golang.org/x/sys v0.0.0-20211103235746-7861aae1554b
go: added golang.org/x/text v0.3.7
  • สร้าง models/user.go
package models

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
  • สร้าง routes/user.go
package routes

import (
	"net/http"
	"strconv"

	"github.com/labstack/echo/v4"
	"poolsawat.com-echo-crud/models"
)

var (
	seq = 1
)

type RouteUserHandler struct {
	Users map[int]*models.User
}

//----------
// Handlers
//----------

func (h *RouteUserHandler) CreateUser(c echo.Context) error {
	u := &models.User{
		ID: seq,
	}
	if err := c.Bind(u); err != nil {
		return err
	}
	h.Users[u.ID] = u
	seq++
	return c.JSON(http.StatusCreated, u)
}

func (h *RouteUserHandler) GetUser(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id"))
	return c.JSON(http.StatusOK, h.Users[id])
}

func (h *RouteUserHandler) UpdateUser(c echo.Context) error {
	u := new(models.User)
	if err := c.Bind(u); err != nil {
		return err
	}
	id, _ := strconv.Atoi(c.Param("id"))
	h.Users[id].Name = u.Name
	return c.JSON(http.StatusOK, h.Users[id])
}

func (h *RouteUserHandler) DeleteUser(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id"))
	delete(h.Users, id)
	return c.NoContent(http.StatusNoContent)
}

func (h *RouteUserHandler) GetAllUsers(c echo.Context) error {
	return c.JSON(http.StatusOK, h.Users)
}
  • สร้าง server.go file กำหนดเป็น main package
package main

import (
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"poolsawat.com-echo-crud/models"
	"poolsawat.com-echo-crud/routes"
)

func main() {
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	users := map[int]*models.User{}

	route := &routes.RouteUserHandler{users}

	// Routes
	e.GET("/users", route.GetAllUsers)
	e.POST("/users", route.CreateUser)
	e.GET("/users/:id", route.GetUser)
	e.PUT("/users/:id", route.UpdateUser)
	e.DELETE("/users/:id", route.DeleteUser)

	// Start server
	e.Logger.Fatal(e.Start(":1323"))
}
  • ทดสอบ Service APIs ทั้งหมด
  • [POST] /users
curl -X POST \
  -H 'Content-Type: application/json' \
  -d '{"name":"Poolsawat.com 001"}' \
  localhost:1323/users
  • [GET] /users
curl localhost:1323/users
  • [GET] /users/1
curl localhost:1323/users/1
  • [PUT] /users/1
curl -X PUT \
  -H 'Content-Type: application/json' \
  -d '{"name":"Poolsawat.com 001 Updated"}' \
  localhost:1323/users/1
  • [DELETE] /users/1
curl -X DELETE localhost:1323/users/1
  • สุดท้ายอย่าลืมเขียน Test routes/user_test.go
package routes_test

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"poolsawat.com-echo-crud/routes"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"poolsawat.com-echo-crud/models"
)

func TestUsers(t *testing.T) {

	var (
		mockDB = map[int]*models.User{
			1: &models.User{1, "Poolsawat.com"},
		}
		extectedOne = "{\"id\":1,\"name\":\"Poolsawat.com\"}"
		extectedAll = "{\"1\":{\"id\":1,\"name\":\"Poolsawat.com\"}}"
	)

	t.Run(`should created user success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(extectedOne))
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.CreateUser(c)) {
			assert.Equal(t, http.StatusCreated, rec.Code)
			assert.Equal(t, extectedOne, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should get all users success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodGet, "/users", nil)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.GetAllUsers(c)) {
			assert.Equal(t, http.StatusOK, rec.Code)
			assert.Equal(t, extectedAll, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should get user by id success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodGet, "/users", nil)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)
		c.SetPath("/users/:id")
		c.SetParamNames("id")
		c.SetParamValues("1")

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.GetUser(c)) {
			assert.Equal(t, http.StatusOK, rec.Code)
			assert.Equal(t, extectedOne, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should update user success`, func(t *testing.T) {
		// Given
		expected := "{\"id\":1,\"name\":\"Poolsawat.com update\"}"

		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodPut, "/users", strings.NewReader(expected))
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)
		c.SetPath("/users/:id")
		c.SetParamNames("id")
		c.SetParamValues("1")

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.UpdateUser(c)) {
			assert.Equal(t, http.StatusOK, rec.Code)
			assert.Equal(t, expected, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should delete user by id success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodDelete, "/users", nil)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)
		c.SetPath("/users/:id")
		c.SetParamNames("id")
		c.SetParamValues("1")

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.DeleteUser(c)) {
			assert.Equal(t, http.StatusNoContent, rec.Code)
			assert.Equal(t, "", strings.TrimSpace(rec.Body.String()))
		}
	})

}
  • รัน test ด้วย go test ./routes/user.go ./routes/user_test.go -v

ขอบคุณที่ติดตามครับ

Golang Program Flow Control EP5

Golang ก็เหมือนภาษาอื่น ๆ คือจะมี IF, ELSE, FOR LOOPS, SWITH CASE อื่น ๆ

IF, ELSE IF, ELSE คำสั่งหากินของทุก ๆ ภาษา เกือบจะถูกเรียกว่า AI

อธิบายรวดเดียวกันไปเลย ด้วยโค๊ดข้างล่างนี้

// if condition_that_evaluates_to_boolean{
//      perform action1
// }else if condition_that_evaluates_to_boolean{
//      perform action2
// }else{
//      perform action3
// }

price, inStock := 100, true

if price >= 80 { // parenthesis are no required to enclose the testing condition
    fmt.Println("Too Expensive")
}

if price <= 100 && inStock == true { //the same with: if price <= 100 && inStock { }
    fmt.Println("Buy it!")
}

// In Go there is not such a thing like the Truthiness of a variable.
// Error:
// if price {
//  fmt.Println("We have price!")
// }

// only one if branch will be executed
if price < 100 {
    fmt.Println("It's cheap!")
} else if price == 100 {
    fmt.Println("On the edge")
} else { //executed only once if all the if branches are false (it's optional)
    fmt.Println("It's Expensive!")
}

Simple IF

อธิบายรวดเดียวกันไปเลย ด้วยโค๊ดข้างล่างนี้ ง่ายดี

package main
 
import (
    "fmt"
    "strconv"
)
 
func main() {
 
    // converting string to int:
    i, err := strconv.Atoi("45")
 
    // error handling
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(i)
 
    }
 
    // simple (short) statement ->  the same effect as the above code
    // i and err are variables scoped to the if statement only
    if i, err := strconv.Atoi("34"); err == nil {
        fmt.Println("No error. i is ", i)
    } else {
        fmt.Println(err)
    }
}

For Loops การวนทำ ตามจำนวนข้อมูล

อธิบายรวดเดียวกันไปเลย ด้วยโค๊ดข้างล่างนี้ ง่ายดี


package main
 
import "fmt"
 
func main() {
 
    // printing numbers from 0 to 9
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
 
    // has the same effect as a while loop in other languages
    // there is no while loop in Go
    j := 10
    for j >= 0 {
        fmt.Println(j)
        j--
    }
 
    // handling of multiple variables in a for loop
    for i, j := 0, 100; i < 10; i, j = i+1, j+1 {
        fmt.Printf("i = %v, j = %v\n", i, j)
    }
 
    // infinite loop
    // sum := 0
    // for {
    //  sum++
    // }
    // fmt.Println(sum) //this line is never reached
}

หากต้องการที่จะ break (หยุดการทำงาน) หรือ continue (ต่อเนื่อง จะข้ามรอบการทำงานนั้น) จะทำอย่างไร

 //** CONTINUE STATEMENT **//
 
// It works just the same as in C,  Java or Python.
// The continue statement rejects all the remaining statements in the current iteration of the loop
// and moves the control back to the top of the loop.


// printing even numbers less than or equal to 10
for i := 1; i <= 10; i++ {
    if i%2 != 0 {
        continue    // skipping the remaining code in this iteration
    }
    fmt.Println(i)
}


// **BREAK STATEMENT **//

// It is used to terminate the innermost for or switch statement.
// It works just the same as in C,  Java or Python.

// finding 10 numbers divisible by 13 
count := 0 
for i := 0; true; i++ {
    if i%13 == 0 {
        fmt.Printf("%d is divisible by 13\n", i)
        count++
    }

    if count == 10 { //if 10 numbers were found, break!
        break //it breaks the current loop (inner loop if there are more loops)
    }
}

// the break statement is not terminating the program entirely;
fmt.Println("Just a message after the for loop")

จะลืมได้ไง Switch Statement

package main
 
import "fmt"
 
func main() {
 
    language := "golang"
 
    switch language {
    case "Python": //values must be comparable (compare string to string)
        fmt.Println("You are learning Python! You don't use { } but indentation !! ")
        // an implicit break is added here
    case "Go", "golang": //compare language with "Go" OR "golang"
        fmt.Println("Good, Go for Go!. You are using {}!")
    default:
        // the default clause the equivalent of the else clause of an if statement
        // and gets executed if no testing condition is true.
        fmt.Println("Any other programming language is a good start!")
    }
 
    n := 5
    // comparing the result of an expression which is bool to another bool value
    switch true {
    case n%2 == 0:
        fmt.Println("Even!")
    case n%2 != 0:
        fmt.Println("Odd!")
    default:
        fmt.Println("Never here!")
    }
 
    //** Switch simple statement **//
 
    // Syntax: statement (n:=10), semicolon and a switch condition
    //(true in this case, we are comparing boolean expressions that return true)
    // we can remove the word "true" because it's the default
    switch n := 10; true {
    case n > 0:
        fmt.Println("Positive")
    case n < 0:
        fmt.Println("Negative")
    default:
        fmt.Println("Zero")
    }
}

Golang Data Types และ Operators EP4

Data Types ก็คล้าย ๆ เหมือนภาษาอื่น ๆ

  • NUMERIC TYPES ภายใต้ type นี้จะย่อยอีกเยอะมาก อธิบายตามโค๊ดข้างล่าง
    • uint เก็บค่าตั้งแต่จำนวนที่เป็นบวก รวมถึง 0
    • int เก็บค่า เป็นลบ 0 และบวก
    • float เก็บค่าที่รูปแบบทศนิยม
    • byte นามแผงของ uint8
    • rune นามแผงของ int32
// uint8      the set of all unsigned  8-bit integers (0 to 255)
// uint16      the set of all unsigned 16-bit integers (0 to 65535)
// uint32      the set of all unsigned 32-bit integers (0 to 4294967295)
// uint64      the set of all unsigned 64-bit integers (0 to 18446744073709551615)

// int8        the set of all signed  8-bit integers (-128 to 127)
// int16       the set of all signed 16-bit integers (-32768 to 32767)
// int32       the set of all signed 32-bit integers (-2147483648 to 2147483647)
// int64       the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

// uint     either 32 or 64 bits
// int      same size as uint

// float32     the set of all IEEE-754 32-bit floating-point numbers
// float64     the set of all IEEE-754 64-bit floating-point numbers
// complex64   the set of all complex numbers with float32 real and imaginary parts
// complex128  the set of all complex numbers with float64 real and imaginary parts

// byte        alias for uint8
// rune        alias for int32

ตัวอย่างโค๊ด Numeric Types รูปแบบต่าง ๆ

//int type
var i1 int8 = -128     //min value
fmt.Printf("%T\n", i1) // => int8

var i2 uint16 = 65535  //max value
fmt.Printf("%T\n", i2) // => int16

var i3 int64 = -324_567_345  // underscores are used to write large numbers for a better readability
fmt.Printf("%T\n", i3)       // => int64
fmt.Printf("i3 is %d\n", i3) // => i3 is -324567345 (underscores are ignored)

//float64 type
var f1, f2, f3 float64 = 1.1, -.2, 5. // trailing and leading zeros can be ignored
fmt.Printf("%T %T %T\n", f1, f2, f3)

//rune type
var r rune = 'f'
fmt.Printf("%T\n", r) // => int32 (rune is an alias to int32)
fmt.Printf("%x\n", r) // => 66,  the hexadecimal ascii code for 'f'
fmt.Printf("%c\n", r) // => f
  • bool type เป็นได้แค่ true, false
  • string ข้อความ ต่างๆ หลาย ๆ ตัวอักษรรวมเป็น string
//bool type
var b bool = true
fmt.Printf("%T\n", b) // => bool

//string type
var s string = "Hello Go!"
fmt.Printf("%T\n", s) // => string

Array vs Slice Types

  • Array เป็น Data Type ที่กำหนดขนาดชัดเจน กำหนด size
  • Slice เป็น Data Type ที่ไม่กำหนดขนาด
//array type
var numbers = [4]int{4, 5, -9, 100}
fmt.Printf("%T\n", numbers) // =>  [4]int

//slice type
var cities = []string{"London", "Bucharest", "Tokyo", "New York"}
fmt.Printf("%T\n", cities) // => []string

Map การเก็บค่าข้อมูลโดยการกำหนด Key: Value

  • Key ของ Map จะ unique ไม่มีทางซ้ำกันได้
  • Value สามารถกำหนดได้ โดยทุก elements จะต้องเป็น Data Type แบบเดียวกัน
//map type
balances := map[string]float64{
    "USD": 233.11,
    "EUR": 555.11,
}
fmt.Printf("%T\n", balances) // => map[string]float64

Struct การกำหนดรูปแบบที่คล้ายการทำ Model, JPA Class ในภาษา Java

  • สามารถกำหนดตั้งชื่อได้เอง กำหนด properties ได้ไม่จำกัด แต่ละ property จะกำหนดด้วย Data Types ที่แตกต่างกันได้
//struct type
type Person struct {
    name string
    age  int
}
var you Person
fmt.Printf("%T\n", you) // => main.Person

Pointer Type เรื่องที่เข้าใจยาก ในบรรดาทุก Data Types

  • pointer คือตัวชี้ตำแหน่ง ถ้าใน GO ก็จะเป็นตำแหน่งการเก็บข้อมูลโดยจะไม่สามารถซ้ำๆ กับ address อื่น ได้
  • จะใช้สัญลักษณ์ * เพื่อบอกให้รู้ว่ากำลังกำหนด Type แบบ Pointer
  • โดยจะใช้ควบคู่กับ address ที่จะเป็นการบอกให้รู้ถึงที่อยู่ของ pointer นั้น ๆ จะใช้สัญลักษณ์ &
//pointer type
var x int = 2
ptr := &x                                                 // pointer to int
fmt.Printf("ptr is of type %T with value %v\n", ptr, ptr) // => ptr is of type *int with value 0xc000016168

Function Type

  • GO จะถือว่า function เป็น type รูปแบบนึงคล้าย javascript
//function type
fmt.Printf("%T\n", f) // => func()

func f() {
}

Operators อธิบายสั้น ๆ ด้วยโค๊ดข้างล่าง

a, b := 10, 5.5

//** ARITHMETIC OPERATORS **//
//  +       sum
// -        difference
// *        product
// /        quotient
// %        remainder
// there is no power operator in Go. Use math.Pow(a, b) for raising to a power.

fmt.Println(a + 5)   // => 15
fmt.Println(3.1 - b) // => -2.4
fmt.Println(a * a)   // => 100
fmt.Println(a / a)   // => 1
fmt.Println(11 / 5)  // => 2

// Go is a Strong Typed Language
// fmt.Println(a * b)       // =>  invalid operation: a * b (mismatched types int and float64)
fmt.Println(a * int(b))     // => 50
fmt.Println(float64(a) * b) // => 55

// IncDec Statements
// The "++" and "--" statements increment or decrement their operands by the untyped constant 1.
x := 10
x++ // x is 11. Same as: x += 1
x-- // x is 10. Same as: x -= 1

//** ASSIGNMENT OPERATORS **//
//  =   (simple assignment)
// +=   (increment assignment)
// -=   (decrement assignment)
// *=   (multiplication assignment)
// /=   (division assignment)
// %=   (modulus assignment)

a = 10
a += 2 // => a is 12
a -= 3 // => a is 9
a *= 2 // => a is 18
a /= 3 // => a is 6
a %= 5 // => a is 1

//** COMPARISON OPERATORS **//
//  ==      equal values
// !=       not equal
// >        left operand is greater than right operand
// <        left operand is less than right operand
// >=       left operand is greater than or equal to right operand
// <=       left operand is less than or equal to right operand

fmt.Println(5 == 6)   // => false
fmt.Println(5 != 6)   // => true
fmt.Println(10 > 10)  // => false
fmt.Println(10 >= 10) // => true
fmt.Println(5 < 5)    // => false
fmt.Println(5 <= 5)   // => true

//** LOGICAL OPERATORS **//
// &&       logical and
// ||       logical or
// !        logical negation

fmt.Println(0 < 2 && 4 > 1) // => true
fmt.Println(1 > 5 || 4 > 5) // => false
fmt.Println(!(1 > 2))       // => true

Converting Types

  • การแปลง Type ของ Data นึง ไปเป็นอีก Type Data นึง เช่น int -> string, string -> int
var x = 3   //int type
var y = 3.2 //float type

// x = x * y //compile error ->  mismatched types

x = x * int(y) // converting float64 to int
fmt.Println(x) // => 9

y = float64(x) * y //converting int to float64
fmt.Println(y)     // => 28.8

x = int(float64(x) * y)
fmt.Println(x) // => 259

//In Go types with different names are different types.
var a int = 5   // same size as int64 or int32 (platform specific)
var b int64 = 2 // int and int64 are not the same type

// a = b // error: cannot use b (type int64) as type int in assignment
a = int(b) // converting int64 to int (explicit conversion required)

// preventing unused variable error
_ = a

//** CONVERTING NUMBERS TO STRINGS AND STRINGS TO NUMBERS **//

s := string(99)            // int to rune (Unicode code point)
fmt.Println(s)             // => 99, the ascii code for symbol c
fmt.Println(string(34234)) // => 34234 is the unicode code point for 薺

// we cannot convert a float to a string similar to an int to a string
// s1 := string(65.1) // error

// converting float to string
var myStr = fmt.Sprintf("%f", 5.12)
fmt.Println(myStr) // => 5.120000

// converting int to string
var myStr1 = fmt.Sprintf("%d", 34234)
fmt.Println(myStr1) // => 34234

// converting string to float
var result, err = strconv.ParseFloat("3.142", 64)
if err == nil {
    fmt.Printf("Type: %T, Value: %v\n", result, result) // => Type: float64, Value: 3.142
} else {
    fmt.Println("Cannot convert to float64!")
}

// Atoi(string to int) and Itoa(int to string).
i, err := strconv.Atoi("-50")
s = strconv.Itoa(20)
fmt.Printf("i Type is %T, i value is %v\n", i, i) // => i Type is int, i value is -50
fmt.Printf("s Type is %T, s value is %q\n", s, s) // => s Type is string, s value is "20"

Defined Types กำหนดชื่อ Type ใหม่

  • กำหนดชื่อของ Type ที่สื่อความหมายเฉพาะ
package main
 
import "fmt"
 
type age int        //new type, int is the underlying type
type oldAge age     //new type, int (not age) is the underlying type
type veryOldAge age //new type, int (not age) is the underlying type
 
func main() {
 
    // new type speed (underlying type uint)
    type speed uint
 
    // s1, s2 of type speed
    var s1 speed = 10
    var s2 speed = 20
 
    // performing operations with the new types
    fmt.Println(s2 - s1) // -> 10
 
    // uint and speed are different types (they have different names)
    var x uint
 
    // x = s1  //error different types
 
    // correct
    x = uint(s1)
    _ = x
 
    // correct
    var s3 speed = speed(x)
    _ = s3
}

Golang ทำไม package fmt ถึงสำคัญสำหรับผู้เริ่มต้น EP3

package fmt คืออะไร

ใช้ I/O ที่จัดรูปแบบด้วยฟังก์ชันที่คล้ายคลึงกับ printf และ scanf ของภาษา C รูปแบบการกร มาจาก C แต่ง่ายกว่า

fmt.Println

  • การ Print แบบขึ้นบรรทัดใหม่ ด้วย fmt.Println(“message”) //=> message
  • การ Print หลาย args แบบขึ้นบรรทัดใหม่ fmt.Println(“arg0 := “,10,”, arg1 := “,99) // => arg0 := 10, arg1 := 99
 fmt.Println("Hello Go World!") // => Hello Go World!

fmt.Printf

  • การ Print ที่จะสามารถกำหนด format ของ message ได้โดย format ที่กำหนดมีอยู่ด้วยกันหลายรูปแบบ เช่น
    • %d -> decimal
    • %f -> float
    • %s -> string
    • %q -> double-quoted string
    • %v -> value (any)
    • %#v -> a Go-syntax representation of the value
    • %T -> value Type
    • %t -> bool (true or false)
    • %p -> pointer (address in base 16, with leading 0x)
    • %c -> char (rune) represented by the corresponding Unicode code poi
 fmt.Printf()

ตัวอย่างการใช้งาน fmt.Printf

a, b, c := 10, 15.5, "Gophers"
grades := []int{10, 20, 30}
 
fmt.Printf("a is %d, b is %f, c is %s \n", a, b, c)    // => a is 10, b is 15.500000, c is Gophers
fmt.Printf("%q\n", c)                      // => "Gophers"
fmt.Printf("%v\n", grades)                 // => [10 20 30]
fmt.Printf("%#v\n", grades)                // => b is of type float64 and grades is of type []int
fmt.Printf("b is of type %T and grades is of type %T\n", b, grades) 
    // => b is of type float64 and grades is of type []int
fmt.Printf("The address of a: %p\n", &a)    // => The address of a: 0xc000016128
fmt.Printf("%c and %c\n", 100, 51011)       // =>  d and 읃  (runes for code points 101 and 51011)

แต่ถ้าอยากที่จะขึ้นบรรทัดใหม่ด้วยก็ให้ เพิ่ม \n ใน fmt.Printf(“%q\n”, c) ก็จะได้การขึ้นบรรทัดใหม่ด้วย

fmt.Sprintf

ใช้งานเหมือน Printf แต่จะ return เป็น string

a, b, c := 10, 15.5, "Gophers"
// fmt.Sprintf() returns a string. Uses the same verbs as fmt.Printf()
s := fmt.Sprintf("a is %d, b is %f, c is %s \n", a, b, c)
fmt.Println(s) // => a is 10, b is 15.500000, c is Gophers

Golang เรียนนรู้ Syntax ต่าง ๆ ของ GO EP2

Variables and Declarations

ตัวแปร และการประกาศค่า หลัก ๆ
  • ที่เห็นได้ชัดเจน จะมี 2 รูปแบบ คือกำหนด type ชัดเจน เช่น var s1 string และให้แปลง type ให้ตามข้อมูลที่ assign เริ่มต้น เช่น var s1 = “poolsawat.com” // จะได้ type string
  • การกำหนดค่าหลาย ๆ ค่า ก็สามารถ assings แบบนี้ var i, j int = 1, 2 ได้เช่นกัน
  • การประกาศค่าแบบสั้น ด้วย s1 := “poolsawat.com”
  • อื่น ๆ ตามโค๊ดตัวอย่างข้างล่าง
/////////////////////////////////
// Variables and Declarations
// Go Playground: https://play.golang.org/p/PKdAxUp8mNT
/////////////////////////////////
 
package main
 
import "fmt"
 
func main() {
 
    //** DECLARING VARIABLES **///
 
    // Syntax: var var_name type
    var s1 string
    s1 = "Learning Go!"
    fmt.Println(s1) // printing string s1
 
    //** TYPE INFERENCE **//
 
    // Go deduces automatically the type of the variable by looking at the initial value (bool, int, string etc)
 
    var k int = 6 // not necessary to say the type (int). It is inferred from the literal on the right side of =
    var i = 5     // type int
    var j = 5.6   // type float64
 
    // printing i, j and k
    fmt.Println("i:", i, "j:", j, "k:", k)
 
    // ii == jj  // -> error: cannot assign float to int (Go is a strong typed language)
 
    // declaring and initializing a new variable of type string (type inference)
    var s2 = "Go!"
    _ = s2 //in Go each variable must be used or there is a compile-time error
    // _ is the Blank Identifier and mutes the error of unused variables
    // _ can be only on the left hand side of the = operator
 
    // multiple assignments
    var ii, jj int
    ii, jj = 5, 8 // -> tuple assignment. It allows several variables to be assigned at once
 
    // swapping two variables using multiple assignments
    ii, jj = jj, ii
 
    fmt.Println(ii, jj)
 
    //** Short Declaration (works only in Block Scope!) **//
 
    // := (colon equals syntax) used only when declaring a new variable (or at least a new variable)
    // := tells go we are going to create a new variable and go figures out what type it will be
    s3 := "Learning golang!"
    _ = s3
 
    // can't use short declaration at Package Scope (outside main() or other function)
    // all statements at package scope must start with a Go keyword (package, var, import, func etc)
 
    // multiple short declaration
    car, cost := "Audi", 50000
    fmt.Println(car, cost)
 
    // redeclaration with short declaration syntax
    // at least one variable must be NEW on the left side of :=
    var deleted = false
    deleted, file := true, "a.txt"
    _, _ = deleted, file
 
    // expressions in short declarations are allowed
    sum := 5 + 2.5
    fmt.Println(sum)
 
    // multiple declaration is good for readability
    var (
        age       float64
        firstName string
        gender    bool
    )
    _, _, _ = age, firstName, gender
 
    // a concise way to declare multiple variables that have the same type
    var a, b int
    _, _ = a, b
 
}

Types and Zero Values

ชนิดของตัวแปร และค่า 0 (ศูนย์)

  • ตัวอย่าง type ของ GO เช่น string, int, float และอื่น ๆ เพิ่มเติม
  • หากไม่ initial value ให้ GO จะ set default value ให้ int // initialized with 0, float //initialized with 0.0,bool //initialized with false และ string //initialized with empty string
  • การตรวจสอบ type ของตัวแปร สามารถทำได้หลายวิธี 1 ในวิธีที่ง่าย คือ %T ตัวอย่าง fmt.Printf(“The type of name is: %T\n”, name)
/////////////////////////////////
// Types and Zero Values
// Go Playground: https://play.golang.org/p/zItROROXi64
/////////////////////////////////
 
package main
 
import "fmt"
 
func main() {
 
    // you must provide a type for each variable you declare or Go should be able to infer it
    var a int = 10
    var b = 15.5      // type inference (deduction)
    c := "Gopher"     // short declaration, type inference
    _, _, _ = a, b, c // Blank Identifier (_) to get rid of unused variable error
 
    // Go is a Statically and Strong Typed Programming Language
    // a = 3.14 -> error. A variable cannot change it's type
    // a = b    -> error. It's not allowed to assign a type to another type
 
    //** ZERO VALUES **//
 
    // An uninitialized variable or empty variable  will get the so called ZERO VALUE
    // The zero-value mechanism of Go ensures that a variable always holds a well defined value of its type
    var value int                         // initialized with 0
    var price float64                     // initialized with 0.0
    var name string                       // initialized with empty string -> ""
    var done bool                         // initialized with false
    fmt.Println(value, price, name, done) // -> 0 0.0 ""  false
}

Comments and Naming Convention

การ comment code และการตั้งชื่อตัวแปร และค่าอื่น ๆ
  • comment บรรทัดเดียว จะใช้ // my code un use
  • comments หลาย ๆ บรรทัด /* my code un use multi lines */
  • การตั้งชื่อไม่ควรยาวเกินความจำเป็น ต้องสื่อกับหน้าที่ของชนิดนั้น ๆ และต้องง่านต่อการดูในภายหลัง
  • ไม่ควรตั้งชื่อที่ใช้ _ (underscore) ในการตั้งชื่อ
/////////////////////////////////////////
// Comments and Naming Conventions in Go
// Go Playground: https://play.golang.org/p/pprI80SPMkS
/////////////////////////////////////////
 
package main
 
//** COMMENTS **//
 
// this is a single line comment
 
/*
 This is a block comment.
 a := 10
 fmt.Println(a)
*/
 
var name = "John Wick" // inline comment
 
//** NAMING CONVENTIONS IN GO **//
 
// Naming Conventions are important for code readability and maintainability.
 
// use short, concise names especially in shorter scopes
// common names for common types:
var s string   //string
var i int      //index
var num int    //number
var msg string //message
var v string   //value
var err error  //error value
var done bool  //bool, has been done?
 
// use mixedCase a.k.a camelCase instead of snake_case (variables and  functions)
var maxValue = 100  // recommended (camelCase)
var max_value = 100 // not recommended (snake_case)
 
// recommended
func writeToFile() {
}
 
// not recommended
func write_to_file() {
}
 
// write acronyms in all caps
var writeToDB = true // recommended
var writeToDb = true // not recommended
 
func main() {
 
    // use fewer letters, don’t be too verbose especially in smaller scopes
    var packetsReceived int // NOT OK, too verbose
    var n int               // OK
    _, _ = packetsReceived, n
 
    // an uppercase first letter has special significance to go (it will be exported in other packages)
}