Slide 1

Slide 1 text

Pythonけものみち 〜Pythonで3年⽣かせるコードを⽬指して〜 (というオレオレFWの⾔い訳) @notch_man8600

Slide 2

Slide 2 text

2 ⾃⼰紹介 クラウドソーシングサービスなどを 開発しています notch_man twitter: @notch_man8600 ● 認定スクラムマスター(CSM) ● ラボのシステム開発の全責任を負う(⾟い) ● 学類パンフに載ったけど留年したよ(笑) ● 現場で都合良く使われています [概要] ● 2020年3⽉ ⾹川⾼専卒業 ● 2021年4⽉ 筑波⼤編⼊ ● enPiT2021(受講)&2022〜(メンター) [略歴]

Slide 3

Slide 3 text

3 おしながき 1. 最高のプログラミング言語、Pythonについて 2. サービスを実装する上での前提 3. サービス開発におけるPythonの辛いところ 4. できるだけ持続性のあるPythonコードを書くために (というオレオレFWの提案)

Slide 4

Slide 4 text

4 Pythonのいいところ ● あらゆるラピッドな物がゆるふわに作ることができる ● 大体のことが適当に実装出来て適当に動いて良い気分になれる ● 筑波大学の標準学習言語になるくらいの人気(?) ● これさえあればCSが学べる(けもの道では?)

Slide 5

Slide 5 text

5 最⾼の⾔語、Pythonの熱い点 ● ゆるふわな言語仕様 ● すぐに実行できる手軽さ ● 豊富で便利な数多くのライブラリ ● 機械学習に強い(謎指標) ● 超人気言語(GitHub曰く) ● 使ってる人が多いので情報が豊富

Slide 6

Slide 6 text

強すぎる武器は時に毒となる

Slide 7

Slide 7 text

7 ヤバい物体1 request = { "data": 1, => int "user_id": "example-user-id", => str "password": "password", "method": "add", } router = { "/add": lambda a, b: a + b, "/sub": lambda a, b: a - b, } if "method" in request: print(router[request["method"]](request["data"], 2)) try: print(request["1"]) # もしパスワードカラムみたいなものがあったら削除しよう request.pop("password") except Exception as e: pass print(request["user_id"])

Slide 8

Slide 8 text

8 可愛らしいdictの例 ● key-valueの型が揺れる ○ 静的解析で追跡しにくい ○ 自由に値を書き換える ● 気軽に抽象クラスでエラーを掴んでしまう ○ エラータイプが分からん ○ passの恐ろしさ ● 無い物が生まれたり、あるものが無くなったり ● ありとあらゆることが⾃由にできる

Slide 9

Slide 9 text

9 サービス開発の⼿段として抑えて欲しい点 ● とりあえず、3年は生かしてくれ ○ 3年は大幅には壊れない技術選定 ○ メンテナンスし続けられる技術選定 ○ ブロークンに変わる可能性の低い技術選定 ● コードの中と外に頼れるものであるか? ● コードの中に頼れるか ○ コメントアウト、型宣言・型ヒント、 docstring ● コードの外に頼れるか ○ テスト、静的解析、フォーマッタ...

Slide 10

Slide 10 text

10 ⻑⽣きさせるPythonコードを書くためのポイント ● 静的解析が効きやすいコードを書く ● ライブラリに依存しない設計にする ● 長く使うためのライブラリ選定 ● 比較的長生きするであろうソフトウェア設計 ● テストを整備する ● そんなものができるフレームワークはPythonで...

Slide 11

Slide 11 text

そこでオレオレかな?

Slide 12

Slide 12 text

12 Ore1_python ● https://github.com/notchman8600/dora-person ● 銅鑼を叩く人を投票するサイト ● Python3.11+FastAPIで書かれています(?)

Slide 13

Slide 13 text

13 Ore1_python ● 目指したもの ○ 適当にグワッとやってグワッと動く ○ DBはpython-mysql-connectorを使いたい ○ 環境変数の出し入れでフィーチャートグルが使える簡便さ ○ サバイブ性(主観) ● いい加減な保守でも死ににくいコード ○ 突然Slackが飛んで来たときに勘と雑なメトリクスで原因特定しやすい ○ 3年後に見ても思い出しやすい(IDEが頑張ってくれるやつ) ● 最悪、Router周りは気合いで書き直せるレベルのもの ○ データは長生きである

Slide 14

Slide 14 text

14 ここで鎮静剤投与 ● オレオレFWは家でやれ ○ 現場でやると同僚に殴られます ○ そういうときは🍺で黙らせる...(嘘です) ● ただ、こういう変な趣味が役立つことがあります ● 世の中の70%くらいの課題は市販のFWで何とかなります ○ 残りの30%がどうもならんのよ ● 綺麗事が通じない案件ではオレオレが思わぬ活躍を(後任は死ぬ)

Slide 15

Slide 15 text

15 Ore1_python ● 作った物 ○ ルーター ○ Req/ResのDTO ○ Controller ○ Store ● 作らなかった物 ○ FastAPIで何とかなるところ ○ ルーティング周り ○ ロギング ■ newrelicに投げました ● 正直、FastAPIでええやん? ○ starletteとpydanticが死んだらどうするんや...? ○ 脱出を見込める何かにしておきたい ○ オレオレで全部作る必要は無い(重要)

Slide 16

Slide 16 text

16 ルーター ● エンドポイントを定義 ○ ルーティング周りはFastAPI ● 値を受け付けてラップして結果を返す責務 ● 認証・認可の検証 ● リクエストのバリデーション(pydantic+dto) ○ 最低限のvalidationはpydantic ○ 値の整合性やキャストなど細かいところは自前で実装 ● 後はControllerに渡す(DTOしか知らない世界) ● FastAPIの知識はここで終わり(重要)

Slide 17

Slide 17 text

17 可能性を収束させる ● 無限の可能性のある冒険をするな! ● APIのレスポンスとかは生辞書がそのまま帰ってくる ○ ex.) res.body.json() => {“message”:”hello world”} ● 我々はAPI仕様書やDDL(DBスキーマ)を見なければならない ○ それはつらくない? ● こういう自由な物体(あるいは何が入ってるか分からないもの)は ロジック側で適切なモデルに詰めて可能性を収束させる ○ 余計なnullチェックを回避 ○ 綺麗な値が入ることを(ある程度)保証できる ● この動画を視てください ○ https://www.youtube.com/watch?v=jjHwxWg2sWQ

Slide 18

Slide 18 text

@self.dora_router.get("/reset") async def reset_vote_count(request: Request): try: token = request.cookies.get("access_token") if token_str is None: raise HTTPException(status_code=400, detail="Bad Request") # 中略 payload = decode(token, SECRET_KEY, algorithms=[ALGORITHM]) github_id: str = payload.get("github_name") if github_id != "notchman8600": raise HTTPException(status_code=403, detail="君にその権限はない") except ExpiredSignatureError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Access token is expired", headers={"WWW-Authenticate": "Bearer"}, ) except PyJWTError: raise HTTPException(status_code=401, detail="Unauthorized") err = self.dora_controller.reset_count() if err is not None: raise HTTPException(status_code=500, detail="Internal Server Error") return RedirectResponse(url="/vote", status_code=303)

Slide 19

Slide 19 text

19 コントローラー ● リクエストオブジェクトを受け取りレスポンスを作る起点 ○ 個人的にはActionの方が好きですが、検索キーワード的に MVCとか意識して... ● Actionオブジェクトは単一の値 or DTOを許容する ○ ユーザーの送信データは基本的に危なっかしいので valid -> DTO ○ まあ、そうならないケースもあるしその辺りを統一する必要はあるのか? ■ よくを言えば全てDTOで包んだ方が良いのが単に面倒 ■ 逃げ道のアピールですよ(言い訳) ● APIコールはControllerから直接(1カ所しか無いし5行だからね) ● レスポンスはdtoで包んで可能性を収束させる ○ ロジックはinvalidなデータをviewへ流すな!

Slide 20

Slide 20 text

20 ストア ● アクセスカウンタと立候補者データを保存 ● redisとmysqlのインスタンスを注入 ○ python-mysql-connector ○ redis ● 責務が少ないしインフラレベルでストア層を分離する意味は分からなかった ので単一クラスで実装 ● この辺りを分けるかはどうかは理性が問われる

Slide 21

Slide 21 text

21 その他 ● エラー周りは全て例外を上げて終了 ● newrelicでログは全て監視 ○ まあ、ラピッドで立ち上げたし細かいロギングは苦情が出たら ...😇 ● 例外は大体ハンドラで掴まえてるのであそこにロガーを挿せばおk ○ 実はこの設計はヤバいのでController層である程度理性ある対応をした方が良い ○ 救えないエラーだけ例外を投げてハンドラ側のログ処理に委ねる設計が良い ● まあ、この辺りは時間がないので割愛(言い訳) ● テストはローカルのシェルスクリプトでE2E ○ 成功体験だけできればいいのでは?

Slide 22

Slide 22 text

22 まとめ ● ライブラリの知識は可能な限り少ない範囲に閉じ込める ● 無限の可能性のある物体は少ない範囲に閉じ込める ● 静的解析で追いやすい、あるいはエディタでジャンプしやすいコード ● ちょっとヤバいに気合いで対処しやすい(主観)

Slide 23

Slide 23 text

23 さいごに ● このコードはフィクションです ● 私の家庭菜園で飼い慣らされたコードの紹介です ● 実際の現場では使われておりません ● プロダクションでPythonはやめとけ警察です ● プロダクションでPythonを使うならコードの中ではなく、コードの外に頼った 作りにしましょう