生涯未熟

生涯未熟

プログラミングをちょこちょこと。

golangのパッケージマネージャdepのIssue覗いてみた

今まではglideを使ってたのですが、公式ご謹製ということでdepをこれから使っていこうかなと思いまして、一旦Issueを覗いてみました。

その中でも気になったIssueはこちら。

github.com

dep initして生成されたmanifest.jsonが空だよ」ってIssueなんですが、pksunkaraさんが「僕のリポジトリdep init試してみたけど空だったよ」とコメントを寄せていたのでやってみました。

そのリポジトリはこれ↓

github.com

で、試してみたんですが確かにmanifest.jsonが空・・・

何でだろうとちょっとdepの中身追っかけてみました。

まずはdep initを実行するとinit.gofunc Runが動きます。 func Runの中でmanifest.jsonに書き込む文字列を用意している部分が以下です。

for pr, v := range pd.ondisk {
    // That we have to chop off these path prefixes is a symptom of
    // a problem in gps itself
    pkgs := make([]string, 0, len(pd.dependencies[pr]))
    prslash := string(pr) + "/"
    for _, pkg := range pd.dependencies[pr] {
        if pkg == string(pr) {
            pkgs = append(pkgs, ".")
        } else {
            pkgs = append(pkgs, strings.TrimPrefix(pkg, prslash))
        }
    }

    l.P = append(l.P, gps.NewLockedProject(
        gps.ProjectIdentifier{ProjectRoot: pr}, v, pkgs),
    )
}

pd.ondisk$GOPATH/srcに存在しているパッケージの一覧なのですが、このpd.ondiskが空になっているがためにmanifest.jsonが空になっていました。

つまり、プログラム上で使っているパッケージが$GOPATH/srcに無いと、manifest.jsonは空になるということです。

ふぅむ・・・ここまで調べて「んじゃmanifest.jsonって一体何なんだろうか」って疑問が浮かびました。

ちょっとこの辺りはまた後日調べてみようと思います🙇

mattnさんのリファクタリングを読み解く

現在絶賛開発中のkirimoriですが、なんとGolang界隈で有名なmattnさんにリファクタリングをして頂くという、とても嬉しい事態がありました✨

kirimoriについてはこちら↓

syossan.hateblo.jp

リファクタリング前提でかなり雑に書いていたのですが、めちゃくちゃ良い感じにコードを直して頂けたので自分の勉強のために読み解いてみます👏

リファクタリング

kirimoriは以下の機能を有しています。

で、構成的には kirimori.go に全てのコマンドの処理をベタ書きにしてある感じになっております。

リファクタリング前のコード

リファクタリング

細かく読み解く前に、mattnさんによってリファクタリングされた完成形がこちらになります。

リファクタリング後のコード

かなり構成が変わってますね・・・如何にリファクタリング前のコードが酷かったのか窺い知れます🤔

では、読み解いていきましょう。

ポインタレシーバを使う

ASTのVisitor構造体に対してVisitメソッドを定義する際に以下のように値レシーバを用いていました。

func (v AddVundleVisitor) Visit(node ast.Node) (w ast.Visitor) {
    // 省略
}

ただし、値レシーバを用いた場合にはnilのポインタ変数でメソッドを呼び出すとpanicが発生しますが、ポインタレシーバの場合は発生しません。(構造体のフィールドをメソッド内で参照している場合はpanicが発生します)

なので、以下のようにポインタレシーバを使うようにリファクタリングされておりました。

func (v *AddVundleVisitor) Visit(node ast.Node) (w ast.Visitor) {
    // 省略
}

ここら辺の話は、この記事の説明が物凄く分かりやすかったです🙇

skatsuta.github.io

追記:mattnさんから訂正が!🙇

コマンド毎の処理をパッケージに分割する

リファクタリング前にはコマンド毎の処理は以下のように書いていました。

func makeApp() *cli.App {
    app := cli.NewApp()

    app.Name = "kirimori"
    app.Usage = "Add Vim Plugin Tool"
    app.Version = "1.0"

        app.Commands = []cli.Command{
        {
            Name:    "init",
            Aliases: []string{"i"},
            Usage:   "create setting file",
            Action: func(c *cli.Context) error {
                            // 40行ほどの処理
            },
        },
        {
            Name:    "add",
            Aliases: []string{"a"},
            Usage:   "add plugin",
            Action: func(c *cli.Context) error {
                            // 80行ほどの処理
            },
        },
        {
            Name:    "remove",
            Aliases: []string{"r"},
            Usage:   "remove plugin",
            Action: func(c *cli.Context) error {
                            // 70行ほどの処理
            },
        },
        {
            Name:    "list",
            Aliases: []string{"l"},
            Usage:   "list plugin",
            Action: func(c *cli.Context) error {
                            // 40行ほどの処理
            },
        },
    }

    return app
}

この処理を書きながら「さすがに可読性が悪いよなぁ・・・」と思ってましたが、この一連のコマンド処理をpackageに分割されてました。

func makeApp() *cli.App {
    app := cli.NewApp()

    app.Name = "kirimori"
    app.Usage = "Add Vim Plugin Tool"
    app.Version = "1.0"

    app.Commands = []cli.Command{
        {
            Name:    "init",
            Aliases: []string{"i"},
            Usage:   "create setting file",
            Action:  cmdInit,
        },
        {
            Name:    "add",
            Aliases: []string{"a"},
            Usage:   "add plugin",
            Action:  cmdAdd,
        },
        {
            Name:    "remove",
            Aliases: []string{"r"},
            Usage:   "remove plugin",
            Action:  cmdRemove,
        },
        {
            Name:    "list",
            Aliases: []string{"l"},
            Usage:   "list plugin",
            Action:  cmdList,
        },
    }

    return app
}

こっちの方が断然見やすいですね!

printlnからfmt.Printlnへ修正

これ全く知らなかったんですが、ビルトイン関数のprintやprintlnは書き込み先が標準エラー出力でfmtパッケージのPrintやPrintlnは標準出力なのですね・・・😫

参考記事🙇

qiita.com

path/filepathの使用

設定ファイルのパスをこんな風に処理してました。

setting_file_path string = home_path + "/.kirimori.toml"

こうするよりも path/filepath のJoinを使った方がスマートでした😥

settingFilePath string = filepath.Join(homePath, ".kirimori.toml")

Windows対応

完全にWindowsをガン無視で作っていましたが、リファクタリングWindows対応まで付けて頂きました・・・!😂

runtimeパッケージを使って処理を分ければ良いという知見を学びました!

f runtime.GOOS == "windows" {
    vimrcName = "_vimrc"
} else {
    vimrcName = ".vimrc"
}

golintを使う

これまたlint系も後で良いやーって感じでやってたので、golintでの指摘点を大量に修正して頂いてました😫

また、twitterのフォロワーさんにgometalinterというgoでのlintの集合体を扱うツールを教えてもらいました!

便利なので今後使っていきたいと思います!✨

github.com

プラグインマネージャー毎にパッケージに分割する

プラグインマネージャー毎の処理を scanAddLineForVundle みたいに For[プラグイン名] って感じでプラグイン毎に名称を分けてたんですが、こちらもパッケージにそれぞれ分けて頂けました👏

これでまたkirimori.go本体がスッキリ!

引数をos.Fileからio.Readerに修正

NewScannerに引き渡すために各関数の引数をos.Fileにしていたのですが、io.Readerに修正されておりました。

これはテストの際にわざわざファイルを用意してOpenして、ってやるよりかio.Readerを生成して喰わせる方が楽だからなのかな?と思っております。(この辺自信が無いのでどなたかツッコミがあればツッコミ入れて下さい🙇)

変数名を短く

全体的に変数名を短く修正されており、以下の記事にあるようにGolangの文化なのかなと。

blog.sigbus.info

癖で長く変数名書いてるので、短くても伝わるような設計心掛けたいです✨

まとめ

というわけで、初心者Go使いには物凄く学びが多いPRでした!

わざわざ時間を割いてリファクタリングしていただいたmattnさんに多謝🙇

golangでScanが上手く動かなかったので原因調べてみた

どうも、今日も元気にGolang生活しております。

今回はbufioパッケージにあるScanの話をば。

問題の発生

とあるライブラリを使ってコードを書いていた時に、Scanを実行しても動かないという現象に遭遇しました。

コードは以下のような感じ

で、 hoge.txt の中身が出力されるのが予想される挙動だったんですが、出力されず・・・

どうしてだろうと、libraryFuncの中を読んでいくとこちらでもfを受け取ってScanを走らせていたことが分かり、「もしやScanって2回動かすとダメなのか?」と以下のようなコードを書いて調査してみました。

一旦、Scanを2回実行してみる形でコードを組んでみました。

で、これを実行してみてもやはり動かない。

原因は?

色々と調べてみてもあんまり理解を得なくて、結局Twitterで凄いエンジニアの方たちに調べてもらった結果、「ファイルのオフセットが回りきってる」という結論に。

というわけで、これまたコード書いて調べてみました。

実行してみると、「0 10」という結果が。

10行のファイルだったので、2回目実行前には既にファイルオフセットが最大の10になっていることが分かりますね。

対処法

対処としては、再度Scanを実行する前にファイルをオープンし直せば大丈夫でしょう。(何か他に良い方法を知っている方がおられましたらご教示下さい・・・)

[追記] もしくは2回目のScan実行前に f.Seek(0, 0) でファイルオフセットを0に戻してもいいかもしれません。

コードとしてはこんな感じ。

これで想定した挙動になりました!

思わぬところでハマりましたが、原因調べてハラオチできたので満足です。

教えてくださった方ありがとうございました!

webpackerを試してみた

Rails5.1からwebpackが標準装備されるということで、試しにRails5.0.1 + webpackerでやってみました。

インストール

Rails5.0.1でプロジェクトを作成し、Gemfileに gem 'webpacker', github:'rails/webpacker' としてmasterから取ってくる。(rails webpacker:install:react を使いたかったため)

bundle install なりでインスコ出来たら bundle exec rails webpacker:install で必要ファイルを生成する。

生成できたらbundle exec rails webpacker:install:reactとすることでReactのファイルが生成されたりします。

bundle exec rails webpacker:install:reactを実行した際に、Yarn executable was not detected in the system.と怒られる場合があります。

僕の場合、yarnのバージョンが古くてyarnpkgのエイリアスが入っていなかったので怒られていました。

webpacker/yarn.tt at master · rails/webpacker · GitHub

↑を見るとyarnpkgとして実行しているのが分かると思います。

なので、yarnのバージョンを上げてから実行しましょう。

これでサンプルのReactファイルが生成され、yarnも実行されるはずです。

また、webpackerのREADME.mdを雑に日本語にしましたので、気になる方は読んでみてください。

github.com

yarnpkgに関してのこのツイートが無ければ永久にエラーで止まったままでした・・・感謝🙏

kirimoriというコマンドラインツールを作ってみた

久々にGolangコマンドラインツール作りました。

その名もkirimori(切り盛り)。

github.com

何が出来るの?

Vimプラグインを追加・削除・一覧表示が出来ます。

例えば、

$ kirimori add Shougo/neocomplete

と実行することで、指定した .vimrc にVundle/NeoBundle/dein.vimのいずれか指定した形式で行を追加します。

Vundleの場合は以下の行が追記されます。

Bundle 'Shougo/neocomplete'

で、後は BundleInstall を実行するだけ。

要らなくなったら、

$ kirimori remove Shougo/neocomplete

で、行を削除。

一覧が見たい場合は、

$ kirimori list
Shougo/neocomplete
Shougo/unite.vim
Shougo/neosnippet.vim

という形で表示されます。

かなり大雑把にコード書いたので、PR・ISSUEなど大募集しております。