Shall we Go ตอนที่ 4 – โครงสร้าง file และ directory ของ Go project

ตอนนี้มาปูพื้นฐานความเข้าใจโครงสร้าง file และ directory ของ Go project กัน

โจทย์ตัวอย่างเราจะมี Main Program 1 ตัว และมี Library อีก 1 ตัว

สามารถ download source code ได้ที่:
https://github.com/shall-we-go/stringutil
https://github.com/shall-we-go/reverse-hello-world

โครงสร้าง file และ directory

$GOPATH/
    src/
        github.com/
            shall-we-go/
                stringutil/
                    .git/
                    reverse.go
                    reverse_test.go
                reverse-hello-world/
                    .git/
                    main.go

ข้อสังเกตุ

  1. โครงสร้าง และ directory จะสัมพันธ์กับ URL ของ Git repo จากตัวอย่างเราจะมี repo อยู่ที่ https://github.com/shall-we-go/stringutil และ https://github.com/shall-we-go/reverse-hello-world
  2. ไฟล์ Test จะต้องตั้งชื่อเป็น *_test.go โดย prefix ไม่ได้มีความเกี่ยวข้องอะไรกับชื่อไฟล์ ของ code ที่จะ test แต่การตั้งชื่อให้สอดคล้องกัน เช่น reverse.go กับ reverse_test.go จะทำให้ project ดูเป็นระเบียบเรียบร้อย แยกแยะได้ง่ายว่าไฟล์ไหน test ไฟล์ code ไหน
  3. ไฟล์ Test จะอยู่ package เดียวกับ ไฟล์ code โดยจริงๆสามารถอยู่คนละ package กันก็ได้ แต่ถ้าอยู่ package เดียวกันจะสะดวกกว่าเพราะไม่ต้อง import package ที่จะถูก test เข้ามา และสามารถเรียกใช้ function นั้นตรงๆได้เลยโดยไม่ต้องระบุ package นอกจากนี้เวลาสั่ง Go test tool เช่น $ go test github.com/shall-we-go/stringutil ก็ลดความสับสนว่าเรากำลังจะ run test ของ package ไหน

ลองทดสอบโดยใช้คำสั่งนี้ $ go run github.com/shall-we-go/reverse-hello-world ถ้าไม่มีอะไรผิดพลาดจะได้

Hello World

ทีนี้มาทำความเข้าใจ code กันบ้าง
Library
reverse.go

1
2
3
4
5
6
7
8
9
10
11
12
package stringutil
 
// Package stringutil contains utility functions for working with strings.
 
// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}

ข้อสังเกตุ

  1. ชื่อ package จะต้องตรงกับชื่อ directory ที่เก็บไฟล์
  2. สำหรับ Library นั้น ห้ามมีชื่อ package “main” ในไฟล​์ใดๆก็ตาม ไม่อย่างนั้นจะฟ้อง error เวลา import จาก package อื่น ลองแก้ code และทดสอบดู
  3. ชื่อไฟล์ ไม่เกี่ยวข้องใดๆกับ package หรือ function จะตั้งชื่อไฟล์เป็นอะไรก็ได้

Program
main.go

1
2
3
4
5
6
7
8
9
10
package main
import (
	"fmt"
 
	"github.com/shall-we-go/stringutil"
)
 
func main() {
	fmt.Println(stringutil.Reverse("dlroW olleH"))
}

ข้อสังเกตุ

  1. ชื่อ package คือ main และไม่จำเป็นต้องตรงกับชื่อ directory ที่เก็บไฟล์ แต่ในการใช้คำสั่ง $ go run จะมองหา package “main” เท่านั้น แล้วถ้าไม่ใช่ main package ล่ะ? ลองแก้ code และทดสอบดู
  2. ชื่อ package คือ main และไม่จำเป็นต้องตรงกับชื่อ directory ที่เก็บไฟล์ แต่ในการใช้คำสั่ง $ go run จะมองหา package main เท่านั้น แล้วถ้าไม่ใช่ main package ล่ะ? ลองแก้ code และทดสอบดู
  3. มีการ import library “github.com/shall-we-go/stringutil” ซึ่งเป็น path ตั้งแต่ $GOPATH/src/
  4. ในการใช้คำสั่ง $ go run github.com/shall-we-go/reverse-hello-world เป็นการเรียก main() ในไฟล์ใดๆใน package ที่ระบุ แล้วถ้ามี main() มากกว่า 1 ไฟล์ล่ะ? ลองแก้ code และทดสอบดู

แล้วถ้า Library นี้ไม่ได้เป็น Local library ล่ะ? ให้ลองทดสอบดังนี้

  1. ลบ directory stringutil
  2. ใช้คำสั่ง $ go run github.com/shall-we-go/reverse-hello-world จะเจอ error ประมาณนี้
    main.go:6:2: cannot find package "github.com/shall-we-go/stringutil" in any of:
    	/usr/local/Cellar/go/1.11/libexec/src/github.com/shall-we-go/stringutil (from $GOROOT)
    	/d/go/src/github.com/shall-we-go/stringutil (from $GOPATH)
  3. ใช้คำสั่ง $ go get github.com/shall-we-go/stringutil หรือ $ go get -v ./... ที่ directory $GOPATH/src/ เพื่อ download library จาก GitHub (มีอีกวิธีคือการใช้ go mod ซึ่งเนื้อหาจะอยู่ในตอนต่อๆไป)
  4. ลองทดสอบโปรแกรมอีกครั้งก็จะใช้งานได้ปกติละ

ติดตามต่อตอนหน้า มาดูเรื่อง Testing กัน

Leave a Reply

Your email address will not be published. Required fields are marked *