بررسی سه مسئله رایج برنامه نویسی در زبان Go | راهنمای پیشرفته
هر زبان برنامهنویسی خصوصیات منحصر به فردی دارد. ما در این مقاله به بررسی سه مسئله رایج برنامه نویسی در زبان Go میپردازیم. پاسخ این مسائل در زبان جاوا ممکن است کاملاً متفاوت باشد. میتوان گفت که پاسخ این مسائل در زبان جاوا سرراستتر است، اما برای حل آنها در Go به کمی کار بیشتری نیاز داریم.
Golang روشهای منحصر به فردی برای حل این مسائل دارد. شاید راهحلهایی که در این مقاله ارائه میشوند، در ابتدای امر به نظر شما چندان سرراست نباشند، اما با کمی تأمل و اندیشه با آنها بیشتر آشنا میشوید. همچنین باید اشاره کنیم که حتماً محتمل است که روشهای بهتری برای حل این مسائل در Go وجود داشته باشد.
اینک نوبت آن رسیده است که مسائل مورد اشاره را بررسی و حل کنیم.
مسئله رایج اول در زبان برنامه نویسی Go
باید یک مجموعه از آیتمها را نگهداری کنیم. اما میدانیم که Go ساختمان دادهای به نام مجموعه (Set) ندارد. یک راهحل محتمل این است که به جای Set از Map استفاده کنیم. مجموعه کلیدهای یک Map در Go شامل مجموعه یکتایی از آیتمها است.
به این منظور میتوانید به صورت زیر عمل کنید:
1package main
2import "fmt"
3type Set struct {
4 m map[string]bool
5}
6func NewSet() Set {
7 m := make(map[string]bool)
8 return Set{m: m}
9}
10func (s *Set) Contains(val string) bool {
11 _, ok := s.m[val]
12 return ok
13}
14func (s *Set) Add(val string) {
15 s.m[val] = true
16}
17func (s *Set) Remove(val string) {
18 delete(s.m, val)
19}
20func main() {
21 s := NewSet()
22 s.Add("foo")
23 fmt.Printf("s has foo: %t. s has bar: %t\n", s.Contains("foo"), s.Contains("bar"))
24
25 s.Remove("foo")
26
27 fmt.Printf("s has foo: %t. s has bar: %t\n", s.Contains("foo"), s.Contains("bar"))
28
29}
مزیت استفاده از Map به عنوان یک ساختمان داده بنیادی برای مجموعه این است که همچنان از جستجوی فوق سریع کلیدهای نگاشت و بهینهسازی بنیادی هش کردن بهرهمند هستیم و در نهایت به نوشتن کد کمتری نیاز داریم.
مسئله رایج دوم در زبان برنامه نویسی Go
فرض کنید میخواهیم دو مقدار را با هم مقایسه کنیم، اما میدانیم که استفاده از عملگر == همیشه ممکن نیست. برای یافتن راهحل این مسئله باید با طرز کار == و جاهایی که میتوان از آن استفاده کرد یا نکرد آشنا شویم. ابتدا یک struct شامل map یا sclice تعریف میکنیم.
1type ABC struct {
2 a int
3 b string
4 c []int
5}
6Error:
7invalid operation: a == b (struct containing []int cannot be compared)
در ادامه Struct را با اشارهگرها تعریف میکنیم. میدانیم که اشارهگرها را میتوان با هم مقایسه کرد، اما همیشه نتیجه مطلوب به دست نمیدهد.
1a, b := 1, 1
2fmt.Println(&a == &b) // False
استفاده از reflect.DeepEqual
اکنون کد ما تا حدود زیادی مطابق انتظار کار میکند:
1//ABC - A simple type
2type ABC struct {
3 a int
4 b string
5 c []int
6}
7var a = ABC{a: 1, b: "10", c: []int{1, 2}}
8var b = ABC{a: 1, b: "10", c: []int{1, 2}}
9reflect.DeepEqual(a, b)
10Example #2
11a, b := 1, 1
12fmt.Println(&a == &b) // False
13fmt.Println(reflect.DeepEqual(&a, &b)) // True
این کد نتایج بهتری ارائه میکند، اما اگر از اعداد اعشاری یا مقادیر زمانی در struct استفاده کرده باشید، باید آنها را نادیده بگیرید. در این موارد باید یک متد equals سفارشی بنویسید.
1//ABC - A simple type
2type ABC struct {
3 a int
4 b string
5 t time.Time // Ignore time while comparing to structs
6}
7var a = ABC{a: 1, b: "10", t: time.Now()}
8var b = ABC{a: 1, b: "10", t: time.Now()}
9fmt.Println(a == b, equals(a, b))
10func equals(val1, val2 ABC) bool {
11 return val1.a == val2.a && val1.b == val2.b
12}
شما نیاز به نوشتن تابعهای سفارشی equals ندارید، مگر این که چاره دیگری نباشد، اما شاید بپرسید آیا استفاده از reflect.DeepEqual ترجیحی بر عملگر == دارد؟ پاسخ این است که اساساً reflect.DeepEqual زمانی مقدار True میدهد که عملگر == این مقدار را بدهد. بنابراین به صورت پیشفرض میتوان از reflect.DeepEqual استفاده کرد، مگر این که مشکلات عملکردی داشته باشید:
1func BenchmarkOperator(t *testing.B) {
2 for i := 0; i < t.N; i++ {
3 if a == b {
4 }
5 }
6}
7func BenchmarkReflectDeep(t *testing.B) {
8 for i := 0; i < t.N; i++ {
9 if reflect.DeepEqual(a, b) {
10 }
11 }
12}
13BenchmarkOperator-8 44614131 24.8 ns/op 0 B/op 0 allocs/op
14BenchmarkReflectDeep-8 823174 1558 ns/op 96 B/op 2 allocs/op
چنان که میبینید عملگر == بسیار سریعتر از reflect.DeepEqual است.
مسئله رایج سوم در زبان برنامه نویسی Go
فرض کنید لازم است از یک struct به عنوان کلید یک Map استفاده کنید، اما این struct دارای Slice، اشارهگر یا فیلدهای است که باید نادیده بگیرید. یک راهحل این مسئله استفاده از ارزیابی کلید Map است. اما این ارزیابی در Go با استفاده از عملگر == و نه reflect.DeepEqual اجرا میشود.
یک روش برای حل این مشکل، استفاده از یک منطق سفارشی برای ایجاد کلید است.
1//Obvious solution that will not work
2type A struct {
3 i *int
4}
5i, j := 1, 1
6a, b := A{i: &i}, A{i: &j}
7m := map[A]bool{}
8m[a] = true
9_, ok := m[b]
10fmt.Println(ok) // False key b doesn't exist in map m
11//Custom keys- solution
12func customKey(a A) int {
13 return *a.i
14}
15i, j := 1, 1
16a, b := A{i: &i}, A{i: &j}
17 m := map[int]bool{}
18 m[customKey(a)] = true
19 _, ok := m[customKey(b)]
20 fmt.Println(ok)// This will return true
مسئله نهایی
مسئله چهارمی که در این مقاله بررسی میکنیم، روش مقایسه دو Map در Go است:
1key, val := "key", "val"
2key1, val1 := "key", "val"
3abc := map[*string]string{&key: val}
4abc2 := map[*string]string{&key1: val1}
5def := map[string]*string{key: &val}
6def2 := map[string]*string{key1: &val1}
7fmt.Println(reflect.DeepEqual(abc, abc2)) //false
8fmt.Println(reflect.DeepEqual(def, def2)) //true
نخستین نکتهای که باید توجه داشته باشیم این است که امکان مقایسه دو Map با استفاده از عملگر == وجود ندارد. همچنین شما نمیتوانید دو Map را با استفاده از reflect.DeepEqual مقایسه کنید. بر اساس قواعد reflect.DeepEqual، به ترتیب کلیدهای Map با عملگر = مقایسه میشوند و برای مقایسه مقادیر نیز از reflect.DeepEqual استفاده میشود.
به این ترتیب به پایان این مقاله میرسیم. امیدواریم این مطلب با موضوع بررسی سه مسئله رایج برنامهنویسی در زبان Go مورد توجه شما قرار گرفته باشد.