はじめに
エンジニア2年目のTKDSです!
今回は変数のシャドーイングについて調べました。
Goを用いて、シャドーイングに関する例を2つほど示します。
変数のシャドーイングとは?
シャドーイングは、内部スコープで宣言された変数が外部スコープの同名変数を内部スコープ内では上書きしてしまうことです。
サンプルコードを以下に示します。
Go
いずれの言語でも変数が再宣言されて上書きされていることがわかります。
今回は、Goについて扱っていきます。
シャドーイングが関わるケースの一例として、エラーハンドリングの例を示します。
エラーハンドリングの例
Goのエラーハンドリングを題材にシャドーイングについて、実際に試してみます。
Goではerrorのログ出力などをdeferを使って最後にまとめることができます。
- エラー時のログ出力を個別にするケース
https://go.dev/play/p/aVNJ81TxWY-
package main import ( "fmt" "log/slog" ) func main() { err := example() fmt.Println(err) } func example() error { cfg1, err := dummyFunc("") fmt.Println("first: ", cfg1) if err != nil { slog.Info("in block") return err } cfg2, err := dummyFunc("") fmt.Println("Second: ", cfg2) if err != nil { slog.Info("in block") return err } cfg3, err := dummyFunc("error") fmt.Println("third: ", cfg3) if err != nil { slog.Info("in block") return err } return nil } func dummyFunc(message string) (string, error) { if message == "" { return "no message", nil } return "dummy", fmt.Errorf(message) }
- エラー時のログ出力をまとめて行うケース
https://go.dev/play/p/T8guyspQDBU
package main import ( "fmt" "log/slog" ) func main() { err := example() fmt.Println(err) } func example() (err error) { defer func() { if err != nil { slog.Info("in block") } }() cfg1, err := dummyFunc("") fmt.Println("first: ", cfg1) if err != nil { return err } cfg2, err := dummyFunc("") fmt.Println("Second: ", cfg2) if err != nil { return err } cfg3, err := dummyFunc("error") fmt.Println("third: ", cfg3) if err != nil { return err } return nil } func dummyFunc(message string) (string, error) { if message == "" { return "no message", nil } return "dummy", fmt.Errorf(message) }
シャドーイングとエラーハンドリングの例
では、シャドーイングがどのように関わってくるのか、下記に示します。
例1) シャドーイングが起こっても大丈夫なケース
https://go.dev/play/p/u-X4E2BzV1-
package main import "fmt" func main() { err := example() fmt.Println(err) } func example() (err error) { defer func() { fmt.Printf("defer block:%s\n", err) }() fmt.Println(err) cfg, err := dummyFunc("block 1 error") if err != nil { return err } fmt.Println(cfg) return nil } func dummyFunc(message string) (string, error) { if message == "" { return "no message", nil } return "dummy", fmt.Errorf(message) }
cfg, err := dummyFunc("block 1 error")
でerrが上書きされています。
再度上書きされることはなくreturnされるため、想定通り、block 1 error
が出力されます。
例2 ) シャドーイングで挙動がおかしくなるケース
https://go.dev/play/p/-8aV2X6An3u
package main import "fmt" func main() { err := example() fmt.Println(err) } func example() (err error) { defer func() { fmt.Printf("defer block:%s\n", err) }() fmt.Println(err) cfg, err := dummyFunc("block 1 error") if err != nil { cfg, err := dummyFunc("シャドーイング") fmt.Println(cfg) if err != nil { return err } return nil } fmt.Println(cfg) return nil } func dummyFunc(message string) (string, error) { if message == "" { return "no message", nil } return "dummy", fmt.Errorf(message) }
cfg, err := dummyFunc("block 1 error")
とifブロック内でerrを上書きされています。
最外部で変数に代入されたエラーであるblock 1 error
が上書きされてしまいます。
問題点と対策
例2では、例1で返せていたエラーが返せなくなります。
具体的には、exampleの最外部で取得したエラーが返されなくなります。
cfg, err := dummyFunc("block 1 error")
で返ってきたエラーが、18行目からのif文の中でシャドーイングされたままリターンされています。
上書き対策としては名前付き変数をブロック内で重複しにくい名前にするなどが考えられます。
これにより、上書きリスクを減らせます。
乱用するとプログラムがわかりにくくなるリスクがありますが、errorなどは毎回別の名前を使うのは大変なので、うまく活用すればプログラムを簡潔に保てるテクニックです。
まとめ
今回は変数のシャドーイングについて調べました。
またGoのエラーハンドリング時に踏みそうな間違いのケースを示しました。
同名の変数をスコープ内で上書きするケースが多いのは、エラーハンドリングで定型文が多いGoで起きやすいミスかもしれないので気をつけたいと思いました。
ここまで読んでいただき、ありがとうございました!
年に1度の技術イベント「RAKUS Tech Conference」を開催します!!
今年もラクス開発本部主催の技術カンファレンス、「RAKUS Tech Conference 2024」を開催します!
「RAKUS Tech Conference」は、SaaS開発における取り組みや知見を紹介する、ラクス開発本部主催の技術カンファレンスです。 ラクス開発本部のミッションに込めた想いをエンジニア/デザイナーが生の声でお届けします。
皆さまのご参加、お待ちしております!
techcon.rakus.co.jp