Update users channels and messages via API - #102
Conversation
|
そう言えば messages に関しては並列化の余地がありますが
ので保留にしました。 |
koron
left a comment
There was a problem hiding this comment.
DownloadEntitiesToFile は Go 的な書き方からはほど遠い。APIRequest にどのような値を設定すべきかそれを呼ぶ人が意識しなければいけない(さらにドキュメントもない)のは論外といってよい。この設計は書く手間を減らして読む手間を犠牲にしている感がある。
Go的に考えるなら以下のような構成になる。
- まず conversations.history を正しく1ページ読める関数(メソッド)を作る
- 次にそれを1つのファイルに保存する仕組みを作る
- それを必要な回数イテレーション(=ページネーション)する仕組みを作る
- 必要なら複数ファイルに渡ったデータを1つのファイルにまとめる仕組みを作る
このとき既存のgeneratorの入力フォーマットも再考してよい
conversations.list や users.list は多少書く手間がかかっても別の入り口になってるべき。
DownloadConversationHistory, DownloadConversationList, DownloadUsersList みたいに。
それぞれ最小限の引数を受け取るのが好ましい。
いっぺんに3つ作るのではなく1つを正しく作る。それを3回繰り返す。そのうえでまとめられるところはまとめる、というのが正着ではないでしょうか。
以上、かなり辛目なレビューでした。
| "path/filepath" | ||
| ) | ||
|
|
||
| type APIRequest struct { |
There was a problem hiding this comment.
公開型はかならずコメントを付けましょう。
golint 入れておいて make lint で警告がでるんで
それが増えないようにしてください。
| } | ||
|
|
||
| func reverse(slice []interface{}) { | ||
| for i := 0; i < len(slice)/2; i++ { |
There was a problem hiding this comment.
毎回 len()/2 はあんま良くないですね。
こんな感じでどうでしょか?
| for i := 0; i < len(slice)/2; i++ { | |
| for i, j := 0, len(slice)-1; i < j; i++, j-- { |
| } | ||
| } | ||
|
|
||
| func DownloadEntitiesToFile(request APIRequest, destJSONFilePath string) error { |
| if !ok { | ||
| return "" | ||
| } | ||
| metadata := metadataValue.(map[string]interface{}) |
There was a problem hiding this comment.
キャスト(type assertion)に失敗したケースについて考慮が抜けてます。
他にも何か所かあるみたいですが指摘は省略しますので留意してください。
| metadata := metadataValue.(map[string]interface{}) | |
| metadata, ok := metadataValue.(map[string]interface{}) | |
| if !ok { | |
| return "" | |
| } |
| for true { | ||
| var json map[string]interface{} | ||
|
|
||
| fmt.Printf("GET %v page %d of %s ... ", request.ExtraParams, pageNum, request.APIMethod) |
There was a problem hiding this comment.
ログは log パッケージ使ったほうが良いです。
| fmt.Printf("GET %v page %d of %s ... ", request.ExtraParams, pageNum, request.APIMethod) | |
| log.Printf("GET %v page %d of %s ... ", request.ExtraParams, pageNum, request.APIMethod) |
There was a problem hiding this comment.
これ、今のところ場所によってまちまちなので統一したいですね > log or fmt
場所によっては fmt.Fprintln() で os.Stderr に出してるとこもあります
There was a problem hiding this comment.
そうなんですよね。ほんとは脳死で log.Printf 使っておいてほしいんです。
そうすれば後から logging ライブラリを別のやつに差し替えるとかも簡単なんで。
| list := json[request.JSONKey].([]interface{}) | ||
| fmt.Printf("fetched %s count: %d\n", request.JSONKey, len(list)) | ||
|
|
||
| results = append(results, list...) |
There was a problem hiding this comment.
patination が必要だと考えてるものを、メモリ上で全部合成するのはあんま嬉しくなさげ。
以下みたいに都度ファイルに出力してしまったほうが良いのでは?
もしくはドキュメントコメントにあんま大きなものは取らないで、みたいに書くべきか?
[
{"entity": "123456"},
{"entity": "123456"},
{"entity": "123456"},
{"entity": "123456"},
{"entity": "123456"},
{"entity": "123456"},
]reverse との兼ね合いはあるが…そもそもJSONに書き出す段階で reverse する=順序を気にする必要ってなんなんだろう?
There was a problem hiding this comment.
どうせ読み込む時は全部メモリに読みますが…しかも全期間分のものを同時に。ダウンロード時にだけそこを気にする必要ありますかね?
There was a problem hiding this comment.
いまここから手を付けておけばその方向性が示せますよね。
generatorも今の全部メモリに乗せる方式は破綻が見えてるので、直さなきゃいけないですから。
downloaderは SQLiteとかのローカルのDBに逐次乗せる。
generatorはそれを切り出して1ページずつHTML生成
みたいなのが目指すべきゴールかなと。
There was a problem hiding this comment.
generator 側はともかく、こちらは1チャンネル/1日分です。1チャンネルの1日分のログのサイズはたかが知れていると思っているんですが(実際に 400 件以上の発言があった場合でも JSON で 300KB に満たない、メモリに展開しても 1MB も取ることはなさそう)、そこを今頑張る必要あるでしょうか?
この部分は過去の全ログを処理する generator と違って、常に1日分なので今後ログが蓄積していっても処理する量が増えて行ったりはしません。
メモリも基本的に潤沢な今の時代にそこを気にするのは早すぎる最適化を感じてしまいます。
| return nil, fmt.Errorf("Error response: %v", json) | ||
| } | ||
|
|
||
| list := json[request.JSONKey].([]interface{}) |
There was a problem hiding this comment.
キーがみつからないケース。型アサーションに失敗したケースの考慮もれ。
| type APIRequest struct { | ||
| SlackToken string | ||
| APIMethod string | ||
| ExtraParams map[string]string | ||
| JSONKey string | ||
| Reverse bool | ||
| } |
There was a problem hiding this comment.
名前と設計があってない。
APIRequest だと将来的にすべてのAPIを叩けるようになりそうで、そのパラメータをすべて内包しそう。
slackclient.Do(APIRequest) みたいな使い方が想定される。
また DownloadEntitiesToFile() (Pagination) 専用だとしても必須パラメータとオプショナルがまざってて、使う法としては困惑する。
仮に自分がやるならこうなる
type DownloadEntitiesOptions struct {
ExtraParams map[string]string
Reverse bool
}
func DownloadEntitiesToFile(token, method, itemKey, dstName string, opts DownloadEntitiesOptions) error {もしくは token は必須だとしてメソッド生やす形かな。
type SlackClient struct {
token string
c *http.Client
}
type DownloadEntitiesOptions struct {
ExtraParams map[string]string
Reverse bool
}
func (sc *StackClient) DownloadEntitiesToFile(method, itemKey, dstName string, opts DownloadEntitiesOptions) error {There was a problem hiding this comment.
いい名前が思い付かなくて諦めたやつです…。
愚直に引数を10個でも20個でも並べた方がいい感じなんですね。構造体使うのやめてみます。
There was a problem hiding this comment.
必須パラメータはなるべく引数に倒したほうが使うときにわかりやすいです。
で省略しても問題ないオプショナルパラメーターは構造体に突っ込むと。
もしくは全部の引数を構造体に入れるなら、全フィールドに
「これは必須」
「こっちはオプショナルでこういう時に使ってね」
みたいなドキュメント書くと良いです。
| return nextCursorValue.(string) | ||
| } | ||
|
|
||
| func httpGetJSON(rawurl string, params map[string]string, dst interface{}) error { |
There was a problem hiding this comment.
APIの汎用性を考えると params は map[string][]string のが良いかもしれない。
同じ名前の query string ってなんだよって思うけれど url.Values はそれを考慮した設計になってるし、
まれにそういうAPIもある。
当然 values.Set ではなくて values.Add で地道に構築する形になる。
There was a problem hiding this comment.
そのケースは使わないだろうと判断して省略しました。
| "limit": "200", | ||
| }, | ||
| JSONKey: "messages", | ||
| Reverse: true, |
There was a problem hiding this comment.
この順序を保証しなきゃいけないのは、実は generator の処理の仕方に問題があるからで、それをココで吸収するのは好ましくない。
There was a problem hiding this comment.
本 PR とは独立した問題ですね。Issue 立てておいてもらえると…。 🙏
| var nextCursor string | ||
|
|
||
| pageNum := 1 | ||
| for true { |
|
個人的には |
thinca
left a comment
There was a problem hiding this comment.
レビューありがとうございます。勉強になります。
まず conversations.history を正しく1ページ読める関数(メソッド)を作る
次にそれを1つのファイルに保存する仕組みを作る
これをやってしまうと1日1ファイルで保存すると言う目的が達成できなくなってします。
全体としてどう直すのがいいのか、指摘からだとちょっとわからず…。
とりあえず細かい指摘の部分を直してみます。
| list := json[request.JSONKey].([]interface{}) | ||
| fmt.Printf("fetched %s count: %d\n", request.JSONKey, len(list)) | ||
|
|
||
| results = append(results, list...) |
There was a problem hiding this comment.
どうせ読み込む時は全部メモリに読みますが…しかも全期間分のものを同時に。ダウンロード時にだけそこを気にする必要ありますかね?
| return nextCursorValue.(string) | ||
| } | ||
|
|
||
| func httpGetJSON(rawurl string, params map[string]string, dst interface{}) error { |
There was a problem hiding this comment.
そのケースは使わないだろうと判断して省略しました。
| "limit": "200", | ||
| }, | ||
| JSONKey: "messages", | ||
| Reverse: true, |
There was a problem hiding this comment.
本 PR とは独立した問題ですね。Issue 立てておいてもらえると…。 🙏
| type APIRequest struct { | ||
| SlackToken string | ||
| APIMethod string | ||
| ExtraParams map[string]string | ||
| JSONKey string | ||
| Reverse bool | ||
| } |
There was a problem hiding this comment.
いい名前が思い付かなくて諦めたやつです…。
愚直に引数を10個でも20個でも並べた方がいい感じなんですね。構造体使うのやめてみます。
a1f3b12 to
84780c6
Compare
84780c6 to
ea295a7
Compare
|
#116 を見ましたが、理解しづらく、今の私ではレベルが足りずに足を引っ張りそうなのでこの件からは手を引きます。お手数をおかけしますが後は任せました。 |
users.jsonを更新するupdate-user-listサブコマンドchannels.jsonを更新するupdate-channel-listサブコマンドfetch-messagesサブコマンドを追加しました。
正直なところ設計とか命名とか全体的にイケてない感じがするので、アドバイスよろしくお願いします!