當你需要學習新事物時你的方法是什麼?我有一個非常具體的,我在學習 Golang 時再次測試了它!
有太多的內容要講,但我在這裡的目的是列出我發現有用並且我專門花時間正確學習的內容。
在過去的兩周里,我一直在使用 Golang 學習和建立小型應用程式。目前,透過許多直播已經完成了近 50 小時的程式碼,能夠學到一些我之前在語言方面遇到的一些小問題的東西真是太棒了。
在這兩週的旅程中,我精心設計了:
一個小而非常簡單的外殼
Redis 基本實現
HTTP 1.1 協定實現
DNS伺服器的實現
以及針對一家非常酷的公司的工作測試(將在本文末尾提供)。
而這一切都是因為我的老闆再次要求我學習一項新技術來處理一些ScyllaDB PoC 和演示......我對這個決定不太滿意,但好吧,這是我的工作。
去年,我一直在學習 Rust,它對我來說可能仍然太複雜,但我學到了一些非常酷的概念,這些概念讓我轉向 Go 變得非常有魅力!
在這篇文章中,我將為您提供一些技巧和建議,以加快您的學習流程。
我是一名PHP 開發人員,我習慣了有史以來最好的CLI(是的,它是Artisan),但是在我作為開發人員的旅程中,我經歷過一些很棒的專案,其中許多是… :
貨物(生鏽)
國家公共管理 (JS)
作曲家 (PHP)
等等...
當我進入 Go 環境時,它開始成為一個真正的問題。至少對我來說,Golang 在工具和文件方面的開發者體驗還可以好很多。考慮到這一點,我決定從一開始就學習 3 個你必須學習的命令。
請記住:這只是一個帶有我自己解釋的演練。如果您需要詳細訊息,請打開文件:)
另外:go docs 很糟糕,請有人在那裡放一支語法螢光筆
根據您是否想要模組化應用程式或擁有一個有組織的環境,這首先將是最有用的命令。
go mod
指令管理專案中的所有依賴項,並負責autoremoving
不再使用的任何內容。
首先,在新的空資料夾中,讓我們使用go mod init
在專案中初始化一個新模組:
mkdir go-fodase
cd go-fodase
# go mod init <module-name>
go mod init github.com/danielhe4rt/go-fodase
這將在專案根目錄中建立一個名為go.mod
的新文件,它基本上是目前的內容:
模組名稱
你的 Go 版本
這是該文件,如果您想自己檢查一下:
# folder: ~/go-fodase
cat go.mod
# module github.com/danielhe4rt/gofodase
#
# go 1.23.2
之後,我真正喜歡的下一個東西是go mod tidy
,它基本上加入了所有缺少的依賴項並刪除了未使用的依賴項。
go mod tidy
第二個只是讓您記住它的存在並且非常有用!也許你的環境每次都會執行它,你會習慣看到導入消失:p
這可能是您將使用的最常用命令,因為您必須執行專案,但它的工作原理如下:
您應該指向包含main()
函數的檔案。
該檔案不必位於資料夾的根目錄中。
關於此命令要記住的最重要的一點是,當您執行go run
命令時,它將在當前目錄中查找go.mod
文件,並將其用作映射整個專案(導入、包等)的基礎。以下是一些範例:
# go run <your-main-file>
# Environment 1
# .
# ├── go.mod
# └── main.go
go run app.go
# Environment 2
# .
# ├── go.mod
# └── src
# └── main.go
go run src/app.go
這是我們的app.go
內容:
package main
import(
"fmt"
)
func main () {
fmt.Println("whats up! don't forget to like this article <3")
}
現在您已經了解了執行專案的基礎知識!從字面上看,你好世界!
我對 Go 的問題一直是它的編寫方式,但經過幾個小時的編碼後,我意識到它比我想像的要簡單。正如您可能已經猜到的,我擁有深厚的 PHP 背景和一些 Rust 經驗。
當我在 2023 年開始學習 Rust 時,幸運的是,我非常喜歡的一個人Nuno Maduro (Laravel)做了一個名為“PHP for Rust Developers”的演講,它給了我一些關於語法的基本介紹,並給了我一些知識。
反正當時對我有用,為什麼不做一些比較呢?
在 OOP 中,我們有類,這是將程式碼抽象化成小塊的一種非常酷的方式,並且您有「類似的」東西。 Golang 可以被視為一場奧德賽,因為將環境變成你想要的任何東西都可以是史詩般的開發。
請記住, Golang是一種“高級語言”,它提供“系統級”語法,使您可以輕鬆地使用“低級”實現。
在Go語法下,你可以
[結構] 透過在結構體前面加上type
前綴,加上「類別/結構體」名稱,然後加上struct
後綴來定義結構體。
[封裝] 透過以大寫或小寫名稱開頭來定義與類別/結構相關的元素的公開。
* [Visibility: "public"]: Set the item name to uppercase.
* [Visibility: "private/protected"]: Set the item name in lower case.
您可以將其用於: Structs
、 Struct Fields
、 Struct Methods
。仔細看看:
// -----------------------
// file: src/users/user.go
package users
type User struct {
name string // starts with uppercase: public
Age uint8 // starts with uppercase: public
}
// Starts with lowercase: private
func (u *User) getName() string {
return u.name
}
// Starts with uppercase: public
func (u *User) Greetings() string {
cheering := "Don't forget to follow me back!"
// Can consume same struct functions.
return fmt.Sprintf("Hey, %v (%v)! %v !", u.getName(), u.Age, cheering)
}
// -----------------
// file: src/main.go
package main
import "github.com/danielhe4rt/go-fodase/src/users"
func main() {
// ❌ You CAN'T start 'name'. Use a Setter Function for it.
// user := users.User{
// name: "danielhe4rt", // ❌
// Age: 25, // ✅
// }
// ✅ Now you're only initializing what you need.
user := users.User{Age: 25}
// Methods Calls
user.SetName("danielhe4rt") // ✅
user.getName() // ❌
user.Greetings() // ✅
currentName := user.name() // ❌
currentAge := user.Age // ✅
}
在 Rust 中,您有一種更明確的方法(更像 oop 的語言),其中:
[Struct] 使用前綴struct
定義一個結構,加入您的「類別/結構」名稱即可。
[封裝] 如果你想讓某些東西對其他「板條箱」公開,你應該在你想要公開的程式碼部分加上pub
前綴。
// file: src/users/mod.rs
// Make the struct `User` public
pub struct User {
// Private field: accessible only within the `users` module
name: String,
// Public field: accessible from other modules
pub age: u8,
}
impl User {
// Public method to create a new User with a name and age
pub fn new(age: u8) -> Self {
User {
name: String::new(), // Initialize with an empty name
age,
}
}
// Public setter method to set the private `name` field
pub fn set_name(&mut self, name: String) {
self.name = name;
}
// Private method to get the name
fn get_name(&self) -> &str {
&self.name
}
// Public method that uses both public and private fields/methods
pub fn greetings(&self) -> String {
let cheering = "Don't forget to follow me back!";
format!(
"Hey, {} ({} years old)! {}",
self.get_name(),
self.age,
cheering
)
}
}
// file: src/main.rs
mod users;
use crate::users::User;
fn main() {
// ❌ You CAN'T directly set the private `name` field
// let user = User {
// name: String::from("danielhe4rt"), // ❌
// age: 25, // ✅
// };
// ✅ Initialize the User using the constructor
let mut user = User::new(25);
// ✅ Use the setter method to set the private `name`
user.set_name(String::from("danielhe4rt"));
let greeting = user.greetings(); // ✅ Call the public `greetings` method
let current_age = user.age; // ✅ Access the public `age` field directly
let current_name = user.name; // ❌ You CAN'T access the private `name` field directly
let current_name = user.get_name(); // ❌ You CAN'T call the private `get_name` method directly
}
我想讓 PHP、Java 等內容變得明確,但如果你停下來想想,寫的程式碼更少,但它也會影響可讀性。
說實話,我是那種會嘗試將 LARAVEL 放入 Go 環境中的人,我不知道,但這已經在<a href="">Goravel</a>中完成了。不管怎樣,我真的很喜歡使用「介面/契約驅動開發」的想法,這是我第一次在一種語言中陷入困境。
在 Go 中,介面並不是在結構/類別中「實現」的,對於像我這樣的 OOP 人員來說,讓這樣的設計決策融入我的頭腦真是太瘋狂了。看看預期會發生什麼:
interface OAuthContract {
public function redirect(string $code);
public function authenticate(string $token);
}
class GithubOAuthProvider implements OAuthContract{
public function redirect(string $code) {}
public function authenticate(string $token) {}
}
class SpotifyAuthProvider implements OAuthContract{
public function redirect(string $code) {}
public function authenticate(string $token) {}
}
function authenticate(string $routeProvider): OAuthContract {
return match($provider) {
'github' => new GithubOAuthProvider(),
'spotify' => new SpotifyAuthProvider(),
default => throw OAuthProviderException::notFound(),
};
}
authenticate('github');
現在,說到去:你在結構內部沒有「介面」的明確實現,那就是,嗯......奇怪嗎?相反,您只需實作介面所需的方法,這些方法將在編譯時為您進行檢查。公平地說,這是一種編譯語言,它永遠不會成為問題,但我正在談論我對開發人員體驗的看法!
import (
"errors"
"fmt"
)
type OAuthContract interface {
Redirect(code string) error
Authenticate(token string) error
}
// GithubOAuthProvider implements the OAuthContract interface for GitHub.
type GithubOAuthProvider struct{}
// Redirect handles the redirection logic for GitHub OAuth.
func (g *GithubOAuthProvider) Redirect(code string) error {
// Implement GitHub-specific redirection logic here.
return nil
}
// Authenticate handles the authentication logic for GitHub OAuth.
func (g *GithubOAuthProvider) Authenticate(token string) error {
// Implement GitHub-specific authentication logic here.
return nil
}
// SpotifyOAuthProvider implements the OAuthContract interface for Spotify.
type SpotifyOAuthProvider struct{}
// Redirect handles the redirection logic for Spotify OAuth.
func (s *SpotifyOAuthProvider) Redirect(code string) error {
// Implement Spotify-specific redirection logic here.
fmt.Printf("Redirecting to Spotify with code: %s\n", code)
return nil
}
// Authenticate handles the authentication logic for Spotify OAuth.
func (s *SpotifyOAuthProvider) Authenticate(token string) error {
// Implement Spotify-specific authentication logic here.
fmt.Printf("Authenticating Spotify token: %s\n", token)
return nil
}
// AuthenticateFactory is a factory function that returns an instance of OAuthContract
func AuthenticateFactory(provider string) (OAuthContract, error) {
switch provider {
case "github":
return &GithubOAuthProvider{}, nil
case "spotify":
return &SpotifyOAuthProvider{}, nil
default:
return nil, &OAuthProviderError{Provider: provider}
}
}
無論如何,在用該語言編碼一段時間後,您就會習慣它。現在,我們來談談基礎環境為您提供的無需下載任何內容的功能!
現在我談論的是 Go 透過標準庫為您提供的一切,無需下載第三方套件。以下是一些按時間順序排列的時間表:
第一天:什麼?為什麼不像 JS/Java 那樣型別包含所有方法? (而且我討厭他們兩個)
第一周:等等,也許這很好(在了解了原始類型的包之後)
第二週:什麼?為什麼其他語言預設沒有這麼好的函式庫?
我不是開玩笑,每天我探索 go 時,我都會在標準庫下發現一些很酷的庫。那麼,讓我們開始討論原始類型。
與 PHP 一樣,與許多其他語言(Rust、Java、JS 等)不同,Golang 需要「輔助」函數來執行大多數相關的類型操作。我們可以將它們視為「貧血」類型,因為它們沒有附加的「實用性」。
// PHP Example (with a REALLY UGLY API)
// String manipulation using built-in PHP functions
$str = "Hello, World!";
// Check if the string contains a substring
$contains = strpos($str, "World") !== false;
echo "Contains 'World': " . ($contains ? "true" : "false") . "\n"; // Output: true
// Convert the string to uppercase
$upper = strtoupper($str);
echo "Uppercase: " . $upper . "\n"; // Output: HELLO, WORLD!
// Split the string by comma
$split = explode(",", $str);
echo "Split by comma: ";
print_r($split); // Output: Array ( [0] => Hello [1] => World! )
因此,如果您正在使用“String”類型,則可以使用其他套件(例如strconv
或strings
來操作它!但這裡有一條黃金法則,永遠不要忘記要查看哪個套件:如果您的類型是string
,請尋找具有複數名稱的相同套件!
簡而言之,這將為您提供與[]Type
和Type
相關的函數:
字串類型-> import ("strings")
用於以下操作: Contains() 、 Upper() 、 Split() ...
位元組類型-> import ("bytes")
用於Include() 、 Compare() 、 Split() ... 等操作
等等!
看一下程式碼,大家可以自己驗證一下:
package main
import (
"bytes"
"fmt"
"strings"
)
func main() {
// String manipulation using the "strings" package
str := "Hello, World!"
// Check if the string contains a substring
contains := strings.Contains(str, "World")
fmt.Printf("Contains 'World': %v\n", contains) // Output: true
// Convert the string to uppercase
upper := strings.ToUpper(str)
fmt.Printf("Uppercase: %s\n", upper) // Output: HWith this intELLO, WORLD!
// Split the string by comma
split := strings.Split(str, ",")
fmt.Printf("Split by comma: %v\n", split) // Output: [Hello World!]
}
這應該很簡單,但我為此掙扎了一段時間,直到我進入我的腦海。也許使用 Laravel 及其輔助函數太多年讓我忘記了在沒有框架的情況下編碼是多麼困難:D
當我探索工具和專案時,我對許多專案都有了很好的介紹,我想列出每個專案以及我使用過的程式庫:
* **packages:**
* **fmt:** I/O library (Scan/Read and Write stuff on your screen)
* **os:** functions and helpers that talks directly with your Operational System.
* **strconv**: cast specific data-types to string or cast string to any defined type.
* **packages:**
* **net:** integration layer with network I/O protocols such as HTTP, TCP, UDP and Unix Domain Sockets
* [previous packages...]
* **packages:**
* **flag:** Captures your CLI arguments into variables
* **log:** Adds Log's channels to your application
* **crypto/rand:** Secure Cryptographic Random Generator
* **math/rand:** Math Numbers Random Generator
* **time:** Duration/Time Lib
這是所有套件實現的可捲動視圖,以便您可以查看它們。這裡有很多很酷的標準包可以引用。
注意:這是很多程式碼! :p
不要忘記評論您最喜歡的功能(除了 goroutine 和通道):p
package main
import (
crypto "crypto/rand"
"encoding/hex"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"strconv"
"time"
)
// =========================
// Example 1: fmt Package
// =========================
// fmtExample demonstrates basic input and output operations using the fmt package.
func fmtExample() {
fmt.Println("=== fmt Package Example ===")
var name string
var age int
// Prompt the user for their name
fmt.Print("Enter your name: ")
_, err := fmt.Scanln(&name)
if err != nil {
fmt.Println("Error reading name:", err)
return
}
// Prompt the user for their age
fmt.Print("Enter your age: ")
_, err = fmt.Scanln(&age)
if err != nil {
fmt.Println("Error reading age:", err)
return
}
// Display the collected information
fmt.Printf("Hello, %s! You are %d years old.\n\n", name, age)besides
}
// =========================
// Example 2: os Package
// =========================
// osExample showcases how to interact with the operating system, such as reading environment variables and exiting the program.
func osExample() {
fmt.Println("=== os Package Example ===")
// Retrieve the HOME environment variable
home := os.Getenv("HOME")
if home == "" {
fmt.Println("HOME environment variable not set.")
// Exit the program with a non-zero status code to indicate an error
// Uncomment the next line to enable exiting
// os.Exit(1)
} else {
fmt.Printf("Your home directory is: %s\n", home)
}
// Demonstrate exiting the program successfully
// Uncomment the next lines to enable exiting
/*
fmt.Println("Exiting program with status code 0.")
os.Exit(0)
*/
fmt.Println()
}
// =========================
// Example 3: strconv Package
// =========================
// strconvExample demonstrates converting between strings and other data types.
func strconvExample() {
fmt.Println("=== strconv Package Example ===")
// Convert string to integer
numStr := "456"
num, err := strconv.Atoi(numStr)
if err != nil {
fmt.Println("Error converting string to int:", err)
return
}
fmt.Printf("Converted string '%s' to integer: %d\n", numStr, num)
// Convert integer back to string
newNumStr := strconv.Itoa(num)
fmt.Printf("Converted integer %d back to string: '%s'\n\n", num, newNumStr)
}
// =========================
// Example 4: net Package
// =========================
// netExample demonstrates making a simple HTTP GET request using the net package.
func netExample() {
fmt.Println("=== net Package Example ===")
// Make an HTTP GET request to a public API
resp, err := http.Get("https://api.github.com")
if err != nil {
let greeting = user.greetings();
fmt.Println("Error making HTTP GET request:", err)
return
}
defer resp.Body.Close()
// Display the HTTP status code
fmt.Printf("Response Status: %s\n\n", resp.Status)
}
// =========================
// Example 5: flag Package
// =========================
// flagExample demonstrates how to capture command-line arguments using the flag package.
func flagExample() {
fmt.Println("=== flag Package Example ===")
// Define command-line flags
name := flag.String("name", "World", "a name to say hello to")
age := flag.Int("age", 0, "your age")
// Parse the flags
flag.Parse()
// Use the flag values
fmt.Printf("Hello, %s!\n", *name)
if *age > 0 {
fmt.Printf("You are %d years old.\n\n", *age)
} else {
fmt.Println()
}
}
// =========================
// Example 6: log Package
// =========================
// logExample demonstrates logging messages to a file using the log package.
func logExample() {
fmt.Println("=== log Package Example ===")
// Open or create a log file
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Failed to open log file: %s", err)
}
defer file.Close()
// Set the log output to the file
log.SetOutput(file)
// Log messages
log.Println("Application started")
log.Printf("Logging an event at %s", "some point")
log.Println("Application finished")
fmt.Println("Logs have been written to app.log\n")
}
// =========================
// Example 7: crypto/rand Package
// =========================
// cryptoRandExample demonstrates generating a secure random string using the crypto/rand package.
func cryptoRandExample() {
fmt.Println("=== crypto/rand Package Example ===")
// Generate a secure random string of 16 bytes (32 hex characters)
randomStr, err := generateSecureRandomString(16)
if err != nil {
fmt.Println("Error generating secure random string:", err)
return
}
fmt.Println("Secure Random String:", randomStr, "\n")
}
// generateSecureRandomString generates a hexadecimal string of the specified byte length using crypto/rand.
func generateSecureRandomString(length int) (string, error) {
bytes := make([]byte, length)
_, err := crypto.Read(bytes)
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// =========================
// Example 8: math/rand Package
// =========================
// mathRandExample demonstrates generating pseudo-random numbers and shuffling a slice using the math/rand package.
func mathRandExample() {
fmt.Println("=== math/rand Package Example ===")
// Seed the random number generator to ensure different outputs each run
rand.Seed(time.Now().UnixNano())
// Generate a random integer between 0 and 99
randomInt := rand.Intn(100)
fmt.Printf("Random integer (0-99): %d\n", randomInt)
// Generate a random float between 0.0 and 1.0
randomFloat := rand.Float64()
fmt.Printf("Random float (0.0-1.0): %f\n", randomFloat)
// Shuffle a slice of integers
nums := []int{1, 2, 3, 4, 5}
rand.Shuffle(len(nums), func(i, j int) {
nums[i], nums[j] = nums[j], nums[i]
})
fmt.Printf("Shuffled slice: %v\n\n", nums)
}
// =========================
// Example 9: time Package
// =========================
// timeExample demonstrates working with current time, formatting, parsing, sleeping, and measuring durations using the time package.
func timeExample() {
fmt.Println("=== time Package Example ===")
// Current time
now := time.Now()
fmt.Println("Current time:", now)
// Formatting time
formatted := now.Format("2006-01-02 15:04:05")
fmt.Println("Formatted time:", formatted)
// Parsing time from a string
parsed, err := time.Parse("2006-01-02", "2024-10-25")
if err != nil {
fmt.Println("Error parsing time:", err)
return
}
fmt.Println("Parsed time:", parsed)
// Sleep for 2 seconds
fmt.Println("Sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
fmt.Println("Awake!")
// Measuring duration
start := time.Now()
time.Sleep(1 * time.Second)
elapsed := time.Since(start)
fmt.Printf("Elapsed time: %s\n\n", elapsed)
}
// =========================
// Main Function
// =========================
func main() {
// Execute each example function sequentially
fmtExample()
osExample()
strconvExample()
netExample()
flagExample()
logExample()
cryptoRandExample()
mathRandExample()
timeExample()
fmt.Println("All examples executed.")
}
說真的,這太棒了!那麼,現在讓我們繼續進行測試。
在我使用 Go 的第二個專案中,我看到了在建立請求和回應物件時學習測試的機會。在 PHP 環境中,您可能正在使用第三方函式庫,例如PHPUnit
或Pest
。正確的?在 Go 環境中,這很簡單!您需要做的就是:
在套件內建立一個檔案:在person.go
中,您將編寫要測試的函數;
為您的套件建立一個測試檔案:** 建立一個名為person_test.go
的檔案並開始編寫您自己的測試!
假設我們的套件資料夾中有requests.go
和requests_test.go
,其中requests.go
是:
package request
import (
"bytes"
"fmt")
type VerbType string
const (
VerbGet VerbType = "GET"
VerbPost VerbType = "POST"
)
type Request struct {
Verb VerbType
Version string
Path string
Headers map[string]string
Params map[string]string
Body string
}
func NewRequest(payload []byte) Request {
payloadSlices := bytes.Split(payload, []byte("\r\n"))
verb, path, version := extractRequestLine(payloadSlices[0])
headers := extractHeaders(payloadSlices[1 : len(payloadSlices)-2])
body := payloadSlices[len(payloadSlices)-1]
req := Request{
Verb: VerbType(verb),
Version: version,
Path: path,
Headers: headers,
Params: map[string]string{},
Body: string(body),
}
return req
}
func extractHeaders(rawHeaders [][]byte) map[string]string {
headers := make(map[string]string)
for _, headerBytes := range rawHeaders {
fmt.Printf("%v\n", string(headerBytes))
data := bytes.SplitN(headerBytes, []byte(": "), 2)
key, value := string(data[0]), string(data[1]) // Accept (?)
headers[key] = value
}
return headers
}
func extractRequestLine(requestLine []byte) (string, string, string) {
splitRequestLine := bytes.Split(requestLine, []byte(" "))
verb := string(splitRequestLine[0])
path := string(splitRequestLine[1])
version := string(splitRequestLine[2])
return verb, path, version
}
如果(t *Testing.T).Errorf()
未在測試函數中呼叫,則 Go 中的測試被視為PASSED (green)
。它也遵循前面介紹的相同封裝概念:
以大寫字母開頭的測試函數由測試執行器辨識
以小寫字母開頭的測試函數將被忽略(通常是輔助函數)
package request
import (
"reflect"
"testing"
)
func TestNewRequest(t *testing.T) {
// Prepare
payload := []byte("GET /index.html HTTP/1.1\r\nHost: localhost:4221\r\nUser-Agent: curl/7.64.1\r\nAccept: */*\r\n\r\n")
// Act
got := NewRequest(payload);
// Assert
want := Request{
Verb: VerbGet,
Version: "HTTP/1.1",
Path: "/index.html",
Headers: nil,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("FAILED! NewRequest() = %v, want %v", got, want)
}
}
您可以做自己的輔助函數來測試。只要確保在這些測試中不要侵入模組域即可!
我從第一天起就開始使用Goland ,所以大多數事情對我來說都更容易。因此,每次開始新測試時,我都會得到這個帶有預設並行執行(goroutines)的 Unity 測試結構的樣板。
package request
import (
"reflect"
"testing"
)
func TestNewRequest(t *testing.T) {
type args struct {
payload []byte
}
tests := []struct {
name string
args args
want Request
}{
{
name: "Base Request Argless",
args: args{
payload: []byte("GET /index.html HTTP/1.1\r\nHost: localhost:4221\r\nUser-Agent: curl/7.64.1\r\nAccept: */*\r\n\r\n"),
},
want: Request{
Verb: VerbGet,
Version: "HTTP/1.1",
Path: "/index.html",
Headers: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewRequest(tt.args.payload); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewRequest() = %v, want %v", got, tt.want)
}
})
}
}
好的,現在我們知道用 Go編寫測試是多麼容易,但是執行它們呢?簡單的任務!您需要做的就是導航到套件資料夾並執行:
# ~/go_fodase
cd requests
ls
# requests.go request_test.go
go test # Run all tests inside the package in all files with suffix "_go"
# ...
# PASS
# ok github.com/danielhe4rt/go_fodase/requests/request 0.001s
go test -run=TestNewRequest # Filter an specific test
# PASS
# ok github.com/danielhe4rt/go_fodase/requests/request 0.001s
請寫下一些對你的東西的測試。如果你解耦所需的東西,這並不難:p
在我過去幾年的開發過程中,我一直試圖以適合我的需求的方式模組化我的所有專案,而不是陷入「乾淨的架構」或「領域驅動設計」的東西。然而,在我第一次嘗試拆分包時,我收到了「循環導入」錯誤,並心想:我多久沒見過類似的東西了?
在我使用 PHP 的 2 年中,我遇到了同樣的import hell
問題,你不能在特定的流程中匯入同一個檔案兩次。那是在我遇到PSR-4(自動加載)之前(它永遠改變了我的 PHP 時代!!),而現在,幾年前,我在 Go 中遇到了這個問題。
讓我們考慮一個循環導入的場景:
// ------------------
// A.php
require_once 'B.php';
class A {
public function __construct() {
$this->b = new B();
}
public function sayHello() {
echo "Hello from A\n";
}
}
// ------------------
// B.php
require_once 'A.php';
class B {
public function __construct() {
$this->a = new A();
}
public function sayHello() {
echo "Hello from B\n";
}
}
// ------------------
// index.php
require_once 'A.php';
$a = new A();
$a->sayHello();
// Fatal error: Uncaught Error: Maximum function nesting level of '256' reached...
當您嘗試在 Go 中編譯標記Cyclic Imports
內容時,您將收到以下錯誤:
import cycle not allowed
package path/to/packageA
imports path/to/packageB
imports path/to/packageA
此時此刻,您必須開始分解您的依賴項/套件以避免它。
TLDR :不要在將被多次載入的地方匯入相同的套件。
我沒有查,但這是我第一次在程式語言中看到保留字defer
。而且由於它不是“通用保留字”的一部分,我整整一周都忽略了它!
然後,我的一位同事Dusan在看到我在 Go 語言上掙扎了幾個小時後,給我上了一堂 Go 語言的記憶體管理課。 (是的,這是一個大喊大叫:p)
問題是:每當您開啟某個物件的緩衝區/連線時,您都應該關閉它!我記得2014年我使用MapleStory伺服器(Java)時,最常見的問題是記憶體洩漏,只是因為開發人員沒有關閉與資料庫的連線。
這可以忘記!但通過程式碼審查是不行的,哈哈
這是 Java 中的一個範例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.SQLException;
public class UnclosedConnectionExample {
public static void main(String[] args) {
// Database credentials
String url = "jdbc:mysql://localhost:3306/mydatabase";
String user = "username";
String password = "password";
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
while {
try {
// Establishing the connection
conn = DriverManager.getConnection(url, user, password);
System.out.println("Database connected successfully.");
// Creating a statement
stmt = conn.createStatement();
// Executing a query
String sql = "SELECT id, name, email FROM users";
rs = stmt.executeQuery(sql);
// Processing the result set
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email);
}
// Intentionally not closing the resources
// rs.close() // <-------------------
// This leads to resource leaks
} catch (SQLException e) {
// rs.close() // <-------------------
e.printStackTrace();
}
}
}
}
在編寫 Golang 程式碼時,他們提供了這個defer
屬性,以便您記住在開啟內容後立即將其關閉。
Defer 代表“延遲”,這是在程式碼的特定部分執行結束後“清理”資源的一種方法。
import(
"github.com/gocql/gocql"
)
func main() {
cluster := gocql.NewCluster("localhost:9042")
session, err := cluster.CreateSession()
if err != nil {
log.Fatal("Connection Refused!")
}
defer session.Close() // After the function ends, the method will be called
// Migrate stuff
fmt.Println("Migrating Tables...")
database.Migrate(session)
// ... rest of your code
// After the last line of code, the function `session.Close()` will run without you trigger it.
}
您也可以在一個函數內有許多defer
,並且DEFER ORDER很重要!如果您先延遲database2 ,然後推遲database1 ,則兩個進程都將以相同的順序進行清理。
import(
"github.com/gocql/gocql"
)
func main() {
cluster := gocql.NewCluster("localhost:9042")
loggingSession, _ := cluster.CreateSession()
appSession, _ := cluster.CreateSession()
defer appSession.Close()
defer loggingSession.Close()
// ... rest of your code
// Defer Order
// ...
// appSession.close() 1st
// loggingSession.close() 2nd
}
這是一個非常簡單的方法不要搞砸防止你的專案出現記憶體洩漏。請記住在您傳輸任何內容時使用它。
錯誤處理首先類似於:檢查您正在使用的函數是否返回error
類型並每次操每次都驗證它!這是我正在談論的一個例子:
cluster := gocql.NewCluster("localhost:9042")
appSession, err := cluster.CreateSession()
// NIL STUFF
if err != nil {
// do something
log.Fatal("Well, something went wrong.")
}
defer appSession.close()
// Keep your flow.
// ...
老實說,我討厭這種語法。然而,它是語言的一部分,並且是您在編碼期間會遇到的東西。
有錯誤的函數可以回傳error
或(T, error)
,幸運的是 Go 不會讓你忘記這一點。
func SumNumbers(a, b int) (int, error) {
result := a + b
if result < 0 {
return nil, fmt.Errorf("The result is negative.")
}
return result, nil
}
// Using it
* always return when possible
* spam the type error
result, err := SumNumbers(-11, 5);
if err != nil {
log.Fatal(err) // Ends the app or handle in the way you want.
}
用err != nil
發送垃圾郵件你的程式碼,你會沒事的,我保證! :D
除了花在理解環境上的所有壓力和時間之外,與我的Twitch 觀眾一起學習新語言是一個很酷的挑戰。他們中的許多人長期以來一直要求我檢查一下,我們就在這裡。
所有這些點都反映了我個人對該語言的發展經驗,目的是分享我在這兩週的學習過程中所經歷的事情。
最近,我的隊友要求我完成 ScyllaDB 挑戰,它教會了我很多關於並行性、池和速率限制的知識。這是許多公司為了讓自己的產品表現更好而面臨的挑戰!
挑戰的目標是建立一個小型 Go 命令列應用程式,將一些隨機資料插入 ScyllaDB,同時限制請求數量。
您可以在此處找到儲存庫挑戰: github.com/basementdevs/throttle-requests-scylla-test 。我們還招人呢!您可以在我們的職業部分找到空缺職位!
感謝您的閱讀!我希望這篇文章能為學習 Golang 提供有價值的見解。請隨意分享您的想法或經驗。
原文出處:https://dev.to/danielhe4rt/this-is-all-what-ive-learned-about-go-in-two-weeks-1bf2