-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Add] TODOリクエスト機能の追加 #3
base: master
Are you sure you want to change the base?
Conversation
cogs/todo.py
Outdated
"to": to_user.id, | ||
"content": todo | ||
} | ||
self.bot.data["request"][hashed[:10]] = data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sha256 の前10文字だけ取ってくるって被らないのかな?
あと hashed[:10]
を下でも使っているので、request_id 的な変数を定義して代入すると分かりやすいと思う
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あと
hashed[:10]
を下でも使っているので、request_id 的な変数を定義して代入すると分かりやすいと思う
分かりやすいだけでなくで、修正漏れも防げる。いわゆるDRY原則というやつ。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
てか、ただ一意な ID 振りたいだけなら、UUID とか使えばいい気がする。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このIDを入力してリクエストの承認・拒否を行うので、長すぎると手入力がとても大変になるんですよね...
先程shunさんから「36進数に圧縮すればいい」という意見を頂いたので、そうするように修正コミットを出しました!
624a42cこちらです!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このIDを入力してリクエストの承認・拒否を行うので、長すぎると手入力がとても大変になるんですよね...
大体ダブルクリックして ctrl+C だろうけどね。
てか、いつの間にかメッセージ ID をリクエストの ID にするようになってる。かしこ。
shunさんから「36進数に圧縮すればいい」という意見を頂いた
[0-9a-z] で 36 ってわけか。かしこすぎる。
cogs/todo.py
Outdated
とコマンドを実行してください。""" | ||
try: | ||
await member.send(dm_msg) | ||
except: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
全ての Exception を握りつぶすんじゃなく、起こりうる Exception のみキャッチすると良いと思う。
cogs/todo.py
Outdated
|
||
del self.bot.data["request"][req_id] | ||
self.bot.save_data() | ||
return await ctx.send(f"> リクエストを拒否しました。") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return await ctx.send(f"> リクエストを拒否しました。") | |
return await ctx.send("> リクエストを拒否しました。") |
無意味な f-strings を発見!
|
たくさんの指摘ありがとうございます...! |
プルリクコメントに仕様書いてるけど、プルリクコメントだと流れちゃうよ。 |
READMEはこのPRが無事Mergeされたあとにすぐ書こうと計画してます。 |
第2引数が空でも動くように見える。空の TODO を送りつけられる仕様はどうなんだろう。 |
確かにこの設計はまずいですね...修正します、ありがとうございます! |
cogs/todo.py
Outdated
dm_msg = f""" | ||
>>> {ctx.author.mention}さんからTODOリクエストが届きました。 | ||
|
||
内容: | ||
・{todo} | ||
|
||
リクエストID: `{req_id}` | ||
|
||
このリクエストを承認する場合は`todo!request_approve {req_id}` | ||
拒否する場合は`todo!request_deny {req_id}` | ||
とコマンドを実行してください。""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python の 複数行文字列リテラルってインデント考慮してくれないんだ。
めちゃキモいな。
Python 界隈的には普通なのかもしれないけど、個人的には無理矢理にでも避けたいなこれ。
https://stackoverflow.com/a/33924077/12964914
というわけでちょっと探したけど、↑これぐらいしかワークアラウンド見つからんかった。
俺だったらこのワークアラウンド採用するかな。
まああくまで個人的価値観なので、採用するかは任せた。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これに関してはtextwrap.dedentという関数を使うことでインデントを治すのが一般的なようです。
あまり気にしていませんでしたが、暇があれば修正します、指摘ありがとうございます!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
textwrap.dedentという関数を使うことでインデントを治すのが一般的
そうなんだ。なるへそ。じゃあそれで
cogs/todo.py
Outdated
|
||
|
||
class TODOCog(commands.Cog): | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
def make_request(self, ctx, to_user, todo): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to_user
、「to」な「user」って意味なんだろうけど、「ユーザーに向けて」みたいに読めちゃうので混乱しちゃうな。
俺だったら user_send_to
とかかな。まあ俺の案もそんなにイケてる感じしなくて微妙なラインなので、別に修正しなくてもまあいいかって感じではある。
cogs/todo.py
Outdated
|
||
|
||
class TODOCog(commands.Cog): | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
def make_request(self, ctx, to_user, todo): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
見てる感じ内部用メソッドっぽいので、メソッド名は _
つけてあげるのが丁寧かな。
cogs/todo.py
Outdated
data = { | ||
"author": ctx.author.id, | ||
"to": to_user.id, | ||
"content": todo | ||
} | ||
self.bot.data["request"][hashed[:10]] = data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dict 使ってるのは JSON との相互変換しやすいからなんだろうけど、dict でこういうデータ扱うと色々メンテ大変だよ。
e.g.
- クラスと違って、データの仕様(どんなプロパティがあるか)がどこかにまとまってるわけではない。
- スペルミスしてもエラーが出てくれない。
クラスで作ったオブジェクトでも JSON と相互変換はできる。
ちょっと手間かかるので、あくまでリファクタリング課題って感じだけど、ちゃんとクラス定義してみるといいと思う。(ちょっと重いタスクかもしれないので、今はやらずに課題としてどこかに残しておく程度でも可。)
cogs/todo.py
Outdated
self.bot.save_data() | ||
return hashed[:10] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
メソッド名が make_request
だけど、内容は
- リクエストのデータを作成
- 作成したデータを bot オブジェクト内に格納
- 作成したデータを外部ファイルに保存(
bot.save_data
の呼び出し) - 作成したリクエストの ID を返す
の4つだね。
問題点:
- 一つのメソッドが複数のことをやっている。単一責任原則に反している。
- 一度に複数のことを実現しようとすると、人は混乱するし、バグを生みがち。
- その他詳しくは「単一責任原則」でググれ。
- 名前が実体を表していない(内容 1 は合っているが、内容 2 〜 4 は名前に表れていない)。
- 「メソッド名を見ただけで何をしているかが分かる」という状況が理想。そういう状況だと、脳に負担をかけずに読むことができる。今の状況だと、
make_request
を使っている部分を読む時に、「ええと、make_request
は本当はリクエストの保存をしているから…」という感じで、make_request
の中身まで頭に叩き込まないと読めないようになってしまっている。こういう状況では全体を俯瞰してチェックしたりするのが困難になる。
- 「メソッド名を見ただけで何をしているかが分かる」という状況が理想。そういう状況だと、脳に負担をかけずに読むことができる。今の状況だと、
というわけで、内容に沿ってちゃんと分割して、それぞれに内容に即したメソッド名をつけることをオススメする。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここらへんはすぐにはできそうではないので、今後の課題とさせていただきたいと思います、ご指摘ありがとうございます!
cogs/todo.py
Outdated
|
||
|
||
class TODOCog(commands.Cog): | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
def make_request(self, ctx, to_user, todo): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
オブジェクト指向のコツの一つは、データごとにロジックを整理すること。
例えば今回の場合、リクエストに関するロジックを Request
というクラスやモジュールにまとめるのが自然。
Cog はあくまで Discord とのやり取りを表現するクラス。
こういう IO 関係のロジックと、IO と関係ないプレーンなロジックを分離できるようになると、コードの見通しはすごいよくなる。
「Discord とのやりとりでエラーが起きてるようなら、Cog をチェックすればいい。」
「リクエストの保存の仕方でミスってるようなら、Request
をチェックすればいい。」
という感じ。
あと、IO を切り離すとテストコードでテストすることも可能になったりする。
ちなみに、save_data
も一つの IO といえる。この辺のロジックも分離できると更にスッキリするしテストしやすくなる。
俺だったら RequestRepository
みたいなクラス作るかな。「リポジトリパターン」っていうデザインパターンがあるのでそれを使う。詳しくはググれ。
まあ、このへんは設計レベルのリファクタリングであって、重いタスクかつ発展課題なので、「やらなきゃマージできない〜〜〜」とか思わなくていいよ。「ほーん、そうなんや。まあとりあえずマージしちゃお。」でいい。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
分離ですか...ちょっとすぐにはできそうもないので、今は今後の課題、という形で処理させていただこうと思います。
教えていただきありがとうございます!
cogs/todo.py
Outdated
await msg.add_reaction("👍") | ||
await msg.add_reaction("👎") | ||
|
||
def check(reac, user): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reac
みたいに無理やり略すのは個人的には奨励しない。
多分一般的にもあんまやらないんじゃないかなー、まあでも Python 界隈はこういう略し方好みそうな気もするし、ちょっと自信ないや。
一番の理由として、脳内で読み辛いんだよね。
「リアクション ドット メッセージ イコール〜」って読みたい。
「リアク ドット メッセージ イコール〜」だと頭に入らない。
まあ個人的意見なので採用するかは自由。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discord.pyのリファレンスのコード例の中でそんな略し方をしていた気がしたのでそうしていたんですが、今見たら普通にreaction
で書いてありました...
ありがとうございます!
cogs/todo.py
Outdated
@commands.command() | ||
async def request(self, ctx, member: discord.Member, *, todo): | ||
text = f""" | ||
>>> `{str(member)}`さんにtodoリクエストを送ります。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なんで全体的に引用メッセージ(>>>
がついてるやつ)なの?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
返信遅くなって申し訳ないです
そうしないと一行ずつ>
をつけなくてはいけないので
現在のデザインとして、BOTのメッセージには全て引用符をつけるような感じになってます
でもやはりembedのほうが見やすいと思うので、今後デザインは一新しようと思ってます
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あ、今思いついたけど、いちいち引用にするのってどこかで忘れそうなので、
async def send_bot_message(ctx, message):
await ctx.send(f">>> `{message}`)
みたいな util 関数作ってもいいかもねぇ。
embed に切り替えるのも一発でできるようになるかも。
ただ、なまじ共通化しすぎても柔軟性に欠けるから危険なんだよね。例外が色々出てきたりするとまた困ったりする。例外がどれだけ出てきそうかの将来予測次第かな。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
思ったよりクリティカルな問題もなかったし、不格好なところもなかった。つまんねーの。
なのでとりあえず Approve で、全部ただのコメントにしておく。好きにマージして。
良い設計(クラス分けとか)に関する感覚みたいなのがまだ無さそうなので、そのへん勉強すると捗ると思う。
cogs/todo.py
Outdated
@@ -47,13 +60,14 @@ async def on_message(self, message): | |||
msg = await message.channel.send(msg) | |||
await msg.add_reaction("👍") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
突然失礼します
レビュー見ました。
UTF-8のアイコンを使用するのは良いのですが、個人的に👍👍🏻など亜種も多数存在するため、定数を切ると良いと思いました
ICON_GOOD = "👍"
的な・・・?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かに定数を使ったほうがいいですね、ご指摘ありがとうございます!
反映させていただきます!
c2b8478 「変数にまとめた」ってコミットメッセージだけど、変数と定数は違うぞ(細かい定義は言語によるところあるけど) |
あっ...素でミスりました() |
c2b8478
to
4b300f0
Compare
皆さんのご指摘のおかげでかなりいいコードにすることができたと思います。 |
cogs/todo.py
Outdated
import numpy | ||
import textwrap | ||
|
||
REACTIONS = ["👍", "👎"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REACTIONS[0]
が up で REACTIONS[1]
が down なのは非自明。どっちがどっちかすぐ間違えちゃいそう。
REACTION_AGREE
と REACTION_DISAGREE
だとか、
REACTION_YES
と REACTION_NO
みたいな感じで、
役割を明らかにした名前にするのが良い。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あ、リアクションの集合を表す REACTIONS
も必要みたいね。
そしたら
REACTIONS = [REACTION_YES, REACTION_NO]
みたいにするのがいい。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あーなるほど!
そうさせていただきます!
cogs/todo.py
Outdated
await msg.add_reaction(REACTIONS[0]) | ||
await msg.add_reaction(REACTIONS[1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REACTIONS
の中身全部 for 文とかで add するようにした方が間違い無さそう。
(例えばリアクションの種類を増やしたくなった時とかに、こういう箇所での追加漏れを防ぎやすい。)
cogs/todo.py
Outdated
@@ -45,22 +61,23 @@ async def on_message(self, message): | |||
msg += f"{todo}\n" | |||
msg += "\nTODOに追加しますか?" | |||
msg = await message.channel.send(msg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message(受信したメッセージのオブジェクト)
msg(確認のために送信するメッセージのテキスト)
再代入して
msg(確認のために送信したメッセージのオブジェクト)
ってなんか凄く追いづらい気がする
変数名を
confirm_message_text
confirm_message
とかにすると分かりやすい
cogs/todo.py
Outdated
return await ctx.send(f">>> {ctx.author.mention}のTODOリスト\n\nNone") | ||
|
||
for i, todo in enumerate(l): | ||
for i, todo in enumerate(todo_list): | ||
todos += f"{i}: {todo}" + "\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todos
の追加の仕方がスマートじゃない気がする
enumerate を使ってるのは良い
空文字用意 → 必要なものと改行を結合 → 最後の改行を消す
って処理するくらいなら、リスト内包表記使って join するのが良いと思う
cogs/todo.py
Outdated
|
||
reaction, _ = await self.bot.wait_for("reaction_add", check=check) | ||
if str(reaction.emoji) == REACTIONS[0]: | ||
if not self.bot.data["todo"].get(str(message.author.id)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not self.bot.data["todo"].get(str(message.author.id)): | |
if str(message.author.id) not in self.bot.data["todo"]: |
get を使ってると、ぱっと見た時何書いてるかわからなかった
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
has_key の代用で key in dict が出来るので、そちらのほうが良いと思われる
deleted_todo = self.bot.data["todo"][str(ctx.author.id)][num] | ||
del self.bot.data["todo"][str(ctx.author.id)][num] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleted_todo = self.bot.data["todo"][str(ctx.author.id)][num] | |
del self.bot.data["todo"][str(ctx.author.id)][num] | |
deleted_todo = todo_list[num] | |
del todo_list[num] |
上で todo_list
定義してるんだし、そのまま使えば良さそう
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo_list
内のデータを削除すればself.bot.data
内のデータも一緒に削除できるんですか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あ、検証してみたところ消されました
Pythonの参照渡しというのはdict.getでも働くんですね...
ありがとうございます!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python は全部「値渡し」だよ。「Python の引数は参照渡し」ってデマ書いてるサイト、めちゃくちゃ多いけど。
cf.
https://qiita.com/ur_kinsk/items/949dabe975bdc1affb82
https://qiita.com/raccy/items/d4c5e7995a8fc90109ee
このへん一度ちゃんと理解したら、検証しなくても色々一発で予想できるようになると思う。
cogs/todo.py
Outdated
@request.command(aliases=["a"]) | ||
async def approve(self, ctx, req_id: str): | ||
req_todo = self.bot.data["request"].get(req_id) | ||
if not req_id: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not req_id: | |
if not req_todo: |
ミス?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deny の方と統一されてないので、何が正しいかわからなくなった
dm_msg = f"""\ | ||
>>> {ctx.author.mention}さんからTODOリクエストが届きました。 | ||
|
||
内容: | ||
・{todo} | ||
|
||
リクエストID: `{req_id}` | ||
|
||
このリクエストを承認する場合は`todo!request approve {req_id}` | ||
拒否する場合は`todo!request deny {req_id}` | ||
とコマンドを実行してください。 | ||
""" | ||
try: | ||
await member.send(textwrap.dedent(dm_msg)) | ||
except discord.Forbidden: | ||
await ctx.send(f"{member.mention}\n" + textwrap.dedent(dm_msg)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここIDだけではなく(TODOを追加するところと同じように)リアクションでできたら良さそうかな〜と思いました
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そこどうしようか迷ってたんですが、やっぱリアクション確認入れたほうがいいですね
ありがとうございます!
少し設計を変更させていただきました。
|
色々間違えたのでやり直しPRです
request
request_approve
request_deny
※リクエスト承認, 拒否はリクエストをしたユーザーには通知されない, 今後通知されるよう改善する可能性あり