Skip to content

Kadai3 2 kimuson13 - #50

Open
kimuson13 wants to merge 21 commits into
gopherdojo:masterfrom
kimuson13:kadai3-2-kimuson13
Open

Kadai3 2 kimuson13#50
kimuson13 wants to merge 21 commits into
gopherdojo:masterfrom
kimuson13:kadai3-2-kimuson13

Conversation

@kimuson13

Copy link
Copy Markdown

課題3-2 分割ダウンローダを作ろう

  • Rangeアクセスを用いる
  • いくつかのゴルーチンでダウンロードしてマージする
  • エラー処理を工夫する
  • キャンセルが発生した場合の実装を行う

pgetを参考にしました。ダウンロードのテストを書くのがうまくいかなかったです。それについて教えていただけると助かります。Resetという名前のコミット以降から見てください。

Comment thread kadai3-2/kimuson13/download/option.go Outdated
parallel := flg.Int("p", runtime.NumCPU(), "separate Content-Length with this argument")
timeout := flg.Int("t", 30, "timeout for this second")
filename := flg.String("f", "paralleldownload", "save the file as this name")
flg.Parse(args)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

失敗するケースを再現できなかったのですが、errorを返す関数であるのでエラー処理しておいた方が良さそうです。

Comment thread kadai3-2/kimuson13/download/download.go Outdated
}

acceptRange := res.Header.Get("Accept-Ranges")
fmt.Fprintf(os.Stdout, "got: Accept-Ranges: %s\n", acceptRange)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fprintfもerrorを返す巻数なので、エラー処理しておいた方が良いです

Comment thread kadai3-2/kimuson13/download/download.go Outdated
if err != nil {
return err
}
sub.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここでsub.Closeを呼ぶと上でエラーになったときにクローズされない可能性があります。そのため

		sub, err := os.Open(n)
		if err != nil {
			return err
		}
                 defer sub.Close()

このようにエラー処理の直後にdeferで閉じ処理をするまでが基本的な書き方です。

ただし今回のようにループでdeferを使うのは問題があるケースが多いです。最後にsubに対してループ回数分deferが実行されるので意図しない動作になります。なので、結論としてループの中の処理を関数に切り出してその中でdeferを使うのが適切かと思います。

Comment thread kadai3-2/kimuson13/download/download.go Outdated
if err != nil {
return err
}
defer file.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L210でも指摘しましたが、ループでdeferは避けましょう

Comment thread kadai3-2/kimuson13/download/download.go Outdated
if err != nil {
return err
}
defer fh.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラー周りで複数指摘を入れていますが、これらを見つけるためにgithub.com/kisielk/errcheckというツールを使っています。

[~/go/src/github.com/kimuson13/gopherdojo-studyroom/kadai3-2/kimuson13] % errcheck ./...
download/download.go:77:13:     fmt.Fprintf(os.Stdout, "Start HEAD request to check Content-Length\n")
download/download.go:91:13:     fmt.Fprintf(os.Stdout, "got: Accept-Ranges: %s\n", acceptRange)
download/download.go:100:13:    fmt.Fprintf(os.Stdout, "got: Content-Length: %v\n", contentLength)
download/download.go:127:19:    defer file.Close()
download/download.go:164:13:    fmt.Fprintf(os.Stdout, "start GET request: bytes=%d-%d\n", r.low, r.high)
download/download.go:170:22:    defer res.Body.Close()
download/download.go:180:20:    defer output.Close()
download/download.go:197:16:    defer fh.Close()
download/download.go:210:12:    sub.Close()
download/option.go:23:11:       flg.Parse(args)

deferでのエラーハンドリングは少し工夫が必要な上、おきてもログに出すぐらいしかできないケースが多いのですが、それらに埋もれて本当にハンドルしなきゃいけないものが漏れるよりかは指摘がゼロになるように直した方が早いかと思うのでそうすることをお勧めします。

以下のような形で無名関数を使えば良いので参考にしてください。
https://joniy-joniy.hatenablog.com/entry/2017/12/31/182004

Comment thread kadai3-2/kimuson13/download/download.go Outdated
//Preparate method define the variables to Donwload
func (d *Downloader) Preparate() error {
if d.parallel < 1 {
d.parallel = 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flagでデフォルト runtime.NumCPU()になっているのでそれに揃えるか、この関数自体をバリデーション用の関数として(名前もValidationとかに)、その上で1未満だとエラーを返すとかの方が良いのではと思います。

Comment thread kadai3-2/kimuson13/download/download.go Outdated
}

if d.timeout < 1 {
d.timeout = 5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここもflagでは30sなので、そちらに揃える方が自然です。
かつ数字を直接書くよりは定数にしてflagの方もそれを使うのが良いでしょう。

Comment thread kadai3-2/kimuson13/download/download.go Outdated
}

//Preparate method define the variables to Donwload
func (d *Downloader) Preparate() error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一応現状ソースのままならエラーが返ることは無いので、エラーを返す関数にする必要がないですね

Comment thread kadai3-2/kimuson13/main.go Outdated
if err := os.RemoveAll("tempdir"); err != nil {
fmt.Println("Error:", err)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempdirの削除はdownload.goに持たせるべきなので、そこにまとめましょう

if err != nil {
return err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downloadでtempdirの作成、MergeFileでtempdirの削除を行なっていますが、エラーになったときにtmpdirが消されないケースがあるので、ここで以下のように作成の関数と削除の関数を作り削除の関数をdeferを使って呼ぶと良いです

if err := createTmpDir(); err != nil {
    return err
}
defer deleteTmpDir()

@gosagawa

Copy link
Copy Markdown

Downloadのテストはファイルをダウンロードして、それがいろいろな条件のもと意図したファイルが落とせているかを検証すれば良さそうです。その際におそらく困るのがURLで、使っていい外部のURLがあったとしても毎回テストの度にアクセスが走ることやテストのURLにアクセスできる環境を担保しなければならないなどの問題があります。URLが用意できればいいということは、サーバがあればいいということなのでダミーサーバを作れればやりたいことができそうです。

ダウンロードできるまでは試しに書いてみました。(拡張子はgoに直してください、githubでそのまま送れなかったので)
download_test.txt
download/_testdata/foo.pngを作れば動くはずです。
ここからファイルが完全に一致すること、様々なオプションを変えてみて動くことやエラーが出ることを検証すればテストしては良さそうです。

同じようにやっているケースとして、私が参加したGopher道場三回でよく書けているケースを貼っておきます。参考にしてください!
gopherdojo/dojo3#50

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants