6
1

More than 3 years have passed since last update.

Golangでgotoキャンペーン開催

Last updated at Posted at 2020-07-18

GoTo文とは

プログラミング界のヴォルデモート卿。
記述してはいけないあの構文。
かつて、エドガー・ダイクストラは言った。Goto文は危険である。

Edgar Dijkstra : Go To Statement Considered Harmful

50年前以上のことであるが、プログラマなら誰もが聞いたことがあり、
物心ついたときから、Goto文を避けてきたとのではないかと思います。

ですが、比較的新しい(?) Go言語にもGoto文は存在し、標準ライブラリにも使われてますので、
Gotoキャンペーンにかこつけて、様々な利用例を探っていきたいと思います。

「構造化プログラミングの観点からgoto文を使うのは望ましくない」ものの「単にgoto文を使わなければ見通しが良くなる」という考えは“Go To Statement Considered Harmful”[4]でも否定されており、goto文の有無のみに固執するのは不毛である。構造化プログラミングの本質の一つは、状態遷移の適切な表現方法とタイミングを見極めることである

参考:Goto文 Wikipedia

Go言語のGoto文の仕様

参考:Goto_statements

gotoは同じ関数内の対応するラベルステートメントに制御を転送します。
func main(){
    log.Println(1)
    goto L
    log.Println(2)
L:
    log.Println(3)
}
--- 結果 ---
2
3
転送されない場合も、Label構文以降記述は実行されます。
func main(){
    if false {
      goto Label  
    }
Label:
    log.Println(1)
}
--- 結果 ---
1
gotoの時点でスコープに入っていなかった変数を宣言することはできません。
func main(){
    goto Label
    var str string
Label:
    log.Println(3)
}
--- 結果 ---
goto Label jumps over declaration of str at ./Main.go:XX:X
スコープが異なる場所にジャンプすることはできません。
func main(){
    goto Label
    if false {
    Label:
        log.Println(1)     
    }
}
--- 結果 ---
goto Label jumps into block starting at ./Main.go:X:XX

利用例を探す

本家、golangのGithubから利用例を探して行こうと思います。

エラーハンドリングに使う

Exeptionのthrow/catchがないGo言語で似たような物を実現する為の書き方ですね。
goにはdeferを使う書き方がありますが、こちらも見通しがよく良い書き方のようにみえます。

    case *Pointer, *Signature, *Slice, *Map, *Chan:
        if !x.isNil() {
            goto Error
        }
        // keep nil untyped - see comment for interfaces, above
        target = Typ[UntypedNil]
    default:
        goto Error
    }

    x.typ = target
    check.updateExprType(x.expr, target, true) // UntypedNils are final
    return

Error:
    check.errorf(x.pos(), "cannot convert %s to %s", x, target)
    x.mode = invalid
}

参考:https://github.com/golang/gofrontend/blob/master/libgo/go/go/types/expr.go

ネストしたLoop,条件分岐からの脱出(処理のスキップ)

上記のErrorとほぼ同様の使い方ですが、
ループと複雑な条件分岐から、処理へ転送例です。
breakを書かず、複数Loopを抜け、処理をスキップできます。

for ; nSrc < len(src); nSrc += size {
    c0 := src[nSrc]
    if c0 >= utf8.RuneSelf {
        r, size = '\ufffd', 1
        goto write
    }

...

write:
    if nDst+utf8.RuneLen(r) > len(dst) {
        return nDst, nSrc, transform.ErrShortDst
    }
    nDst += utf8.EncodeRune(dst[nDst:], r)
}
return nDst, nSrc, err

参考:https://github.com/golang/text/blob/master/encoding/japanese/iso2022jp.go

構文解析の条件分岐に利用する。

htmlタグ解析の条件分岐に使っているようです。
goto文は実行速度は早いのでしょうか?
形がきっちり定まっている物の解析には良いのかもしれません。
処理自体スッキリしてみえますね。

func (z *Tokenizer) readScript() {
    defer func() {
        z.data.end = z.raw.end
    }()
    var c byte

scriptData:
    c = z.readByte()
    if z.err != nil {
        return
    }
    if c == '<' {
        goto scriptDataLessThanSign
    }
    goto scriptData

scriptDataLessThanSign:
    c = z.readByte()
    if z.err != nil {
        return
    }
    switch c {
    case '/':
        goto scriptDataEndTagOpen
    case '!':
        goto scriptDataEscapeStart
    }
    z.raw.end--
    goto scriptData
....

参考:https://github.com/golang/net/blob/master/html/token.go

gotoを使ったループ

for内で、CheckAndLoopに戻りcontinueする条件に戻っています。
条件を外れた場合、再度Skipラベル以降の処理が実行される。

forの条件判定や、for ~ CheckAndLoopまでの処理されるパターン限定し、効率よく反復する目的なのでしょうか。
普段、チーム開発を行っている私はこういった物を書く機会は少ないでしょう。
パフォーマンスが著しく悪く最適化するような際は小さいpackageに切り出してこういった処理を書く機会があるかもしれません。

    for len(b.jobs) > 0 {
                ....
                ....
    CheckAndLoop:
        if !b.shouldVisit(pc, pos) {
            continue
        }
    Skip:

        inst := re.prog.Inst[pc]

        switch inst.Op {
        default:
            panic("bad inst")
        case syntax.InstFail:
            panic("unexpected InstFail")
        case syntax.InstAlt:
            if arg {
                arg = false
                pc = inst.Arg
                goto CheckAndLoop
            } else {
                b.push(re, pc, pos, true)
                pc = inst.Out
                goto CheckAndLoop
            }

参考:https://github.com/golang/gofrontend/blob/master/libgo/go/regexp/backtrack.go

最後に

GoのGoto文はC言語のものと違い、自由奔放な処理転送を行えるものとは少し違うということがわかりました。
用途によってはソースの見通しもよく綺麗にかけるので、よく理解し利用すれば他言語と比べてモヤッとしていた
部分を解消できるのではないでしょうか。

ただ、使われて来なかった背景もあり、馴染みのない構文の為、利用者の技術力によってしまうところもあり、
特にチームでの開発では、一箇所に書いたこの構文をどんな人に、どんな風に利用されるかわかりません。
 
レビューで頑張って取り切るか、小さなPackageでの利用に留め、
基本的には使わないにこしたことはないと思います。

ということで、私のGotoキャンペーンは中止です。。

その他、利用例や、こう使うといいよ!等ありましたら、教えていただけたら幸いです。
皆様のGotoキャンペーンをお待ちしております。

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1