PyCon Kyishu 2022 - 静的コード解析から見出す一人前Pythonistaへの道

7.2K Views

January 22, 22

スライド概要

これからPythonを始めようと思っている方は「Pythonってどういう書き方をすれば良いんだろう」と考えるでしょう。 プログラムを実行しつつ全てのバグを見つけようとしている方は「コロンが抜けてるのくらい自動で検出してくれないかなあ」と考えるでしょう。 とりあえず動いているからヨシなコードを書いている方は「importの順番やコードの整形を自動でやってくれないかなあ」と考えるでしょう。

問題ありません。Pythonはその悩みを全て解決してくれます。

コーディング規約pep8に準拠したコーディングルールチェックライブラリがあります。 構文エラーを検出してくれるライブラリがあります。 importの順番も良い感じにソートを提案してくれるライブラリがあります。 そして、自動で全てを反映して自動整形してくれます。

profile-image

新卒で地元製本工場に入社。 システム営業としてBtoC事業の開発やカスタマーに従事し 時には自ら体を張りマスコットキャラクターとして活動することで、業界に今まで無かった風を吹かせ話題を作ることに成功したが、 運営が進むにつれ保守開発がメインとなってきてしまっていたことから、新しい技術を習得するために転職を決意。 2016年6月から地元SIerで働き始め、プロに囲まれた切磋琢磨の環境で自分が所詮井の中の蛙であった事を悟り、修行に入る。 学生時代から「多機能君」と呼ばれていた幅広さを、広く浅くな器用貧乏で終わらせず業務レベルに持っていき名を残すために奮闘している。

関連スライド

各ページのテキスト
1.

静的コード解析から見出す一人前 Pythonistaへの道 (株)日本システム技研 Japan System Laboratory.inc Yuki Takino 1

2.

お前、誰よ? 滝野 優紀 - Yuki Takino 長野県を拠点とする日本システム技研所属 普段の業務ではPython/Djangoをよく使う 2歳と5歳の娘を持つ2児のパパエンジニア 現在よく乾く洗濯物の干し方を修行中 Django本を共著で1年かけて書いたのに、諸般の事情により99%お 蔵入りになったという悲しみを背負った男 moonwalkerpoday 他 ・PyCon JP 2018を始めとした登壇 moon̲in̲nagano ・Django Congress JP 2021といったイベントのスタッフ業 ・小規模イベントでの突LT jsl-takino など細かーく活動中 2

3.

前提 • このトークは初学者向けです。 • 静的解析の第一歩としてまず読みやすいコードにしましょう ね、というところに主軸をおくのでフォーマッターの話が多く なります。 • どうやったらコードのバグをなくせるか、というお話ではあり ません。 3

4.

このトークで何を話したいか • 静的解析ことはじめ。 • なぜ静的解析するのか、どうやってするのか、いつす るのか。 • どんなことを機械に任せられるのか。 4

5.

何を持って帰ってほしいか • 静的解析のあらまし。 • 見やすいコードを意識する必要性。 • 抱えてる問題に対する解決案。 • 静的解析への深い興味。 5

6.

このトークで話さないこと • 静的解析最強ベストプラクティス • 仮想環境の作成からライブラリインストールまでの手順 • 各ライブラリのオプションや設定ファイルの書き方など、掘り下げた内容 • CI(継続的インテグレーション)を使う話 • 型解析に関するなどのディープな話(Pyright, mypy, Pylance等) • 静的セキュリティチェックに関するディープな話(Bandit, semgrep等) 6

7.

ちなみにPythonこれから始めようと思ってる人、 または始めたての人はどれくらいいますか?✋ チョットデキル(ちょっとしかできないとは言ってない)勢以外 7

8.

っていう質問は清水川さんがされたので割愛😇 8

9.

目次 1.まず静的解析って? 1. じゃあ動的テストって? 2. 静的解析と動的テストの違い 2.Pythonの静的解析ライブラリ一例 1. Linter ̶ コードチェッカー 2. Formatter ̶ コード自動整形 3. Static Typing ̶ 型チェック 4. SAST ̶ 静的セキュリティチェック 3.git hookでさらに便利に 4.総括 5.尺余り 9

10.

コロナの第6波が来ていて色々大変ですが、 2年ぶりの開催、スタッフの皆様本当にお疲れ様です。 10

11.

まず静的解析って?🤔 11

12.

静的解析ざっくり解説 コードを実行せずに品質、スタイル、セキュリティの検証を行うこと。 コードレビューなども含まれる。 (静的テストとも言われるが、静的解析の方が好まれるらしい) 12

13.

静的解析 実行タイミングは色々ある git commitする時 任意にコマンド叩いた時 プラグインでリアルタイムに 例 •コーディング規約(PEP 8)に則ったコードスタイルチェック •コードスタイルの自動整形 •構文チェック •セキュリティチェック 13

14.

動的テストざっくり解説 実際にコードを実行して行う検証 テスト準備フェーズ 例 •○○をした場合△△となるといったテストケースの作成 •テストコードの実装 14

15.

動的テスト テスト実行フェーズ 例 •テストケースに沿って動作確認 •テストコードの実行 15

16.

動的テスト テスト完了フェーズ 例 •テストケースごとに正常/異常の記録 •テストコードがコケたところの修正 16

17.

静的解析と動的テストの違い 静的解析 動的テスト タイミング 開発中 開発後 ※ コスト 低 高 検査内容 できること/わかること ※ ・コードの品質 ・パフォーマンス ・構文の正常性 ・コードの自動整形 ・実際の挙動 ・パフォーマンス異常の検知 ・バグの発見 ・typoなどの指摘 単体テストなどは事前にテストコードを書くこともある(テスト駆動開発) 17

18.

水分補給スライド(1/3) ちなみに遠征組じゃない人は どれくらいいますか?✋ 18

19.

Pythonの静的解析ライブラリ一例 19

20.

プラグイン含めると星の数ほどある 😂 20

21.

Linter コードチェッカー 21

22.

flake8 「Python コードチェッカー」で調べると だいたい最初に行き着く。 ■使用をおすすめする時 👀 ・Pythonのコーディング規約に則ってるか調べたい時 ・勝手に直されるのは怖いから調べるだけにしたい時 22

23.

flake8 pycodestyle, pyflakes, mccabeのラッパー •pycodestyleはコードがコーディング規約(PEP 8)に則って書かれて いるかのチェック •pyflakesは構文エラーのチェック •mccabeは循環的複雑度のチェック(デフォルトOFF) •flake8はプラグインも多い(後述のisortやmypyもある) 23

24.

flake8 PEP 8に準拠してないコードは flake8̲test.py import os def output(word): print(word) output('hello') $ flake8 flake8_test.py 24

25.

flake8 怒られる flake8̲test.py import os def output(word): print(word) output('hello') $ flake8 flake8_test.py flake8_test.py:1:1: F401 'os' imported but unused flake8_test.py:7:1: E305 expected 2 blank lines after … 25

26.

flake8 直す flake8̲test.py def output(word): print(word) output('hello') $ flake8 flake8_test.py OK 26

27.

Formatter コード自動整形 27

28.

Formatter 1.black 2.isort 3.autopep8 4.YAPF 28

29.

black IPythonのメジャーアップデートで、デフォルトの再 フォーマットに採用されたりと台頭してきてる。 ■使用をおすすめする時 👀 ・コードスタイルで議論したくない時 ・スタイルガイドをガチガチに組むのが嫌な時 29

30.

black カスタマイズ性が低いフォーマッター •カスタマイズ性が低いということは、プロジェクトを跨いでもコード スタイルが同じになるということなので学習コストが減る •コーディングスタイルは、厳格なPEP 8 •まだベータ版 •フォーマットはほとんどblackに委譲する意思が必要 •問: なぜこうなる? 答: blackがそうしたから 30

31.

black black̲test.py ※公式documentから引用 縦並びなど許さない # in: $ black black_test.py a = [ 1, 2, 3, 4 ] # out: a = [1, 2, 3, 4] 31

32.

black black̲test.py ※公式documentから引用 # in: でもお尻カンマあるなら許しちゃう a = [ 1, 2, 3, 4, ] $ black black_test.py # out: a = [ 1, 2, 3, 4, ] 32

33.

black 長い場合はインデントされた行に再配置 $ black black_test.py black̲test.py ※公式documentから引用 # in: ImportantClass.important_method(exc, limit, lookup_lines, capture_locals, extra_argument) # out: ImportantClass.important_method( exc, limit, lookup_lines, capture_locals, extra_argument ) 33

34.

black それでも長かったら更に分解 $ black black_test.py black̲test.py ※公式documentから引用 # in: def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False): """Applies `variables` to the `template` and writes to `file`.""" with open(file, 'w') as f: ... # out: def very_important_function( template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False, ): """Applies `variables` to the `template` and writes to `file`.""" with open(file, "w") as f: ... 34

35.

black以外の話いる? と言われるとトークここで終わるのでご勘弁🙏 35

36.

Formatter 1.black 2.isort 3.autopep8 4.YAPF 36

37.

isort ライブラリに富むPythonなだけに、なにも考えず importしまくった悲しいコードを整理してくれる君 ■使用をおすすめする時 👀 ・importの並び順をコーディング規約に則りたい時 ・手動でimport整理するのめんどくさい時 37

38.

isort PEP 8に準拠したimportの並び替え •PEP 8準拠の順番でimportを並び替えてくれる •flake8-isortというflake8のプラグインも公開されている 38

39.

isort flake8-isortで激怒されるこんなコードも isort̲test.py ※ 公式Githubから引用 from my_lib import Object import os from my_lib import Object3 from my_lib import Object2 import sys 39

40.

isort PEP 8準拠で並び替えてくれる $ isort isort_test.py isort̲test.py ※ 公式Githubから引用 import os import sys from my_lib import Object, Object2, Object3 40

41.

Formatter 1.black 2.isort 3.autopep8 4.YAPF 41

42.

autopep8 「Python 自動整形」で調べると 以前はだいたい最初に行き着いた。 ■使用をおすすめする時 👀 ・Pythonのフォーマッターをとりあえず味わいたい時 ・日本人の方が作ったライブラリを使いたい時 42

43.

autopep8 PEP 8に準拠してコードを自動整形 •歴史がある •PEP 8準拠でコードを自動整形してくれる 43

44.

autopep8 flake8で激怒されるこんなコードも autopep8̲test.py ※公式Githubから引用 def example1(): some_tuple=( 1,2, 3,'a' return some_tuple $ flake8 autopep8_test.py autopep8_test.py:2:15: E225 autopep8_test.py:2:17: E201 autopep8_test.py:2:21: E231 autopep8_test.py:2:26: E231 autopep8_test.py:2:31: E202 autopep8_test.py:2:33: E703 autopep8_test.py:3:22: W292 ); missing whitespace around operator whitespace after '(' missing whitespace after ',' missing whitespace after ',' whitespace before ')' statement ends with a semicolon no newline at end of file 44

45.

autopep8 PEP 8準拠で修正してくれる $ autopep8 autopep8_test.py --in-place --aggressive —aggressive ※ ̶aggressiveが2つあるのは修正レベルをあげるため。typoではない autopep8̲test.py def example1(): some_tuple = (1, 2, 3, 'a') return some_tuple 45

46.

Formatter 1.black 2.isort 3.autopep8 4.YAPF 46

47.

YAPF black / YAPF戦争に関してはノーコメントで。 ■使用をおすすめする時 👀 ・フォーマットルールを色々カスタマイズしたい時 47

48.

YAPF カスタマイズ性が高いフォーマッター •安心のGoogle製 •公式ではまだアルファ版とされている •PEP 8に準拠してスルーされる部分も再フォーマットしてくれる •スタイルガイドに従ってなくてもYAPFに任せれば大丈夫という状況 になることを目標としている •(読み方はヤプフ?ヤップエフ?etc?) 48

49.
[beta]
YAPF

yapf̲test.py ※公式Githubから引用

x = {

'a':37,'b':42,

'c':927}

色々と見づらいこんなコードも

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo (
object ):
def f
(self
):
return
37*-+2
def g(self, x,y=42):
return y
def f (
a ) :
return
37+-+a[42-x : y**3]

49

50.
[beta]
x = {'a': 37, 'b': 42, 'c': 927}

YAPF

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')

再フォーマットしてくれる
$ yapf yapf_test.py --in-place

class foo(object):
def f(self):
return 37 * -+2
def g(self, x, y=42):
return y

def f(a):
return 37 + -+a[42 - x:y**3]
50

51.

水分補給スライド(2/3) 手洗いうがいはしっかりとしましょう。 この時期は乾燥がきつい。 51

52.

Static Typing 型チェック 52

53.

型についてざっくり解説🤔 1/2 静的型付け 動的型付け 言語 C, Java, TypeScript… Python, Ruby… 特徴 プログラム実行前に型を決める プログラム実行前に型を決めない 例 「関数aの返り値は整数である」 関数aが整数を返した場合、「関数a と決めた場合、関数aで文字列が返 の返り値は整数である」と判断する ることはない 53

54.

型についてざっくり解説🤔 2/2 Python 3.5から導入されたType Hintsを使って、 動的型付け言語であるPythonに型を定義することができるようになった。 型定義なし 型定義あり def add(a, b): return a + b def add(a: int, b: int) -> int: return a + b 変数はint型?float型? 変数はint型で、それを加算したint型 それともlist型??🤔 が返ってくるとひと目で分かる!😁 54

55.

mypy Type Hintsを実装してもPython単体では結局エラーを吐いてく れないので意味ないじゃん、と思ってた時期もありました。 ■使用をおすすめする時 👀 ・コードにType Hintsを書いた時 ・コードの可読性を上げたい時 55

56.

mypy Type Hintsと型の整合性チェック •コードに記述されたType Hintsを、実際の型と比べて整合性が取れて いるかのチェック •flake8-mypyというflake8のプラグインも公開されている •まだベータ版 56

57.

mypy 引数の型も返す型も違うコードは mypy̲test.py def return_value(word: str) -> list[bool, str]: return True, word print(return_value(123)) $ mypy mypy_test.py 57

58.
[beta]
mypy
怒られる
mypy̲test.py

def return_value(word: str) -> list[bool, str]:
return True, word

print(return_value(123))
$ mypy mypy_test.py
mypy_test.py:1: error: "list" expects 1 type argument, but 2 given
mypy_test.py:2: error: Incompatible return value type (got "Tuple[bool, str]", expected "List[Any]")
mypy_test.py:5: error: Argument 1 to "return_value" has incompatible type "int"; expected "str"
Found 3 errors in 1 file (checked 1 source file)

58

59.

mypy 直す mypy̲test.py def return_value(word: str) -> tuple[bool, str]: return True, word print(return_value(‘hello’)) $ mypy mypy_test.py Success: no issues found in 1 source file 59

60.

mypy 余談 Python3.9からlistとdictを組み込み型で定義できるようになってる < 3.9 import typing def return_value(word: str) -> typing.Tuple[bool, str]: return True, word print(return_value(‘hello’)) >= 3.9 def return_value(word: str) -> tuple[bool, str]: return True, word print(return_value(‘hello’)) 60

61.

SAST Static Application Security Testing 静的セキュリティチェッカー 61

62.

Bandit 今まで書いていたコードが、実はセキュリティ的にまずいコー ドだった!ということはよくある話かもしれない。 ■使用をおすすめする時 👀 ・コードのセキュリティを高めたい時 62

63.

Bandit Pythonのコードによくあるセキュリティ上の問題を発見する •csv, htmlなど複数のフォーマットでレポートを作成できる •標準出力からの受け渡しが可能 と思ったら今はバグって動かなかった。 63

64.

Bandit SQLに変数を受け入れるコードは bandit̲test.py ※公式Githubのbandit/examples/から引用 from django.db.models.expressions import RawSQL from django.contrib.auth.models import User User.objects.annotate(val=RawSQL('secure', [])) User.objects.annotate(val=RawSQL('%secure' % 'nos', [])) $ bandit bandit_test.py 64

65.

Bandit 怒られる $ bandit bandit_test.py ・・・ Test results: >> Issue: [B611:django_rawsql_used] Use of RawSQL potential SQL attack vector. Severity: Medium Confidence: Medium Location: bandit_test.py:5:26 More Info: https://bandit.readthedocs.io/en/latest/plugins/b611_django_rawsql_used.html 4 User.objects.annotate(val=RawSQL('secure', [])) 5 User.objects.annotate(val=RawSQL('%secure' % 'nos', [])) -------------------------------------------------・・・ Files skipped (0): 65

66.

Bandit 直す $ bandit bandit_test.py ・・・ Run metrics: Total issues (by severity): Undefined: 0.0 Low: 0.0 Medium: 0.0 High: 0.0 Total issues (by confidence): Undefined: 0.0 Low: 0.0 Medium: 0.0 High: 0.0 Files skipped (0): 66

67.

Bandit レポートも作成できる(html例) $ bandit bandit_test.py --format html --output test.html 67

68.

水分補給スライド(3/3) 寒いとガンガン湿度下がるからつらい。 68

69.

git hookでさらに便利に 69

70.

pre-commit gitでバージョン管理をしている場合はぜひ入れたい。 処理に時間がかかってもイライラしないこと。 ■使用をおすすめする時 👀 ・gitを使っている時 ・自動で静的解析したいとき ・コミットされるコードに統一感をもたせたい時 70

71.

pre-commit コミット実行前に任意のスクリプトを実行してくれる •手動で静的解析を実行する必要がないため、さらにコストが下がる •Linterをコミット直前に実行することで、スタイルガイドに違反する コードのコミットを防止 •Formatterをコミット直前に実行することで、最適なコードに自動整形 •SASTをコミット直前に実行することで、セキュリティ的に問題のある コードのコミットを防止 •サポートしているhookはたくさんある https://pre-commit.com/hooks.html 71

72.

pre-commit 設定は簡単 1. リポジトリ直下にconfigファイルを作成する $ touch .pre-commit-config.yaml 2. 設定を記述する(flake8の設定は省略) repos: - repo: github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 3. git hooksにpre-commitを作成 $ pre-commit install 72

73.

pre-commit あとはgit addしてコミットすればflake8を実行してくれる $ git commit -m "pre-commit flake8" ・・・ [INFO] Initializing environment for https://github.com/pycqa/flake8. [INFO] Installing environment for https://github.com/pycqa/flake8. [INFO] Once installed this environment will be reused. [INFO] This may take a few minutes... flake8...................................................................Faile d - hook id: flake8 - exit code: 1 flake8_test.py:1:1: F401 'os' imported but unused flake8_test.py:2:1: E302 expected 2 blank lines, found 0 flake8_test.py:5:1: E305 expected 2 blank lines after class or function definition, found 1 73

74.

平和な世界が訪れる 74

75.

pre-commit 複数の静的解析も組み合わせられる repos: - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v1.6.0 hooks: - id: autopep8 - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort - repo: https://github.com/pycqa/flake8 rev: 4.0.1 hooks: - id: flake8 75

76.

pre-commit 複数の静的解析が行われる $ git commit -m "pre-commit multiple” ・・・ autopep8.................................................................Failed - hook id: autopep8 - files were modified by this hook isort....................................................................Failed - hook id: isort - files were modified by this hook Fixing /Users/takino/PycharmProjects/workshop/git-test/isort_test.py flake8...................................................................Failed - hook id: flake8 - exit code: 1 flake8_test.py:1:1: F401 'os' imported but unused isort_test.py:1:1: F401 'os' imported but unused isort_test.py:2:1: F401 'sys' imported but unused isort_test.py:4:1: F401 'my_lib.Object' imported but unused isort_test.py:4:1: F401 'my_lib.Object2' imported but unused isort_test.py:4:1: F401 'my_lib.Object3' imported but unused 76

77.

pre-commit isortやblackのように競合するものもあるので注意 repos: - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort - repo: https://github.com/psf/black rev: 21.12b0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.931 hooks: - id: mypy 77

78.

pre-commit 競合してエラーが発生 $ git commit -m "pre-commit fix conflict” ・・・ isort....................................................................Failed - hook id: isort - files were modified by this hook Fixing /Users/takino/PycharmProjects/workshop/git-test/isort_test.py black....................................................................Failed - hook id: black - files were modified by this hook reformatted isort_test.py All done! ✨ 🍰 ✨ 1 file reformatted. mypy.....................................................................Passed 78

79.

pre-commit 競合するものはconfigの調整が必要 repos: - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black rev: 21.12b0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.931 hooks: - id: mypy 79

80.

pre-commit 無事git hookが実行された $ git commit -m "pre-commit fix conflict” ・・・ isort....................................................................Failed - hook id: isort - files were modified by this hook Fixing /Users/takino/PycharmProjects/workshop/git-test/isort_test.py black....................................................................Passed mypy.....................................................................Passed 80

81.

総括 •静的解析はあくまで手段であって目的ではない •自動整形されたコードを見ることでも結構気づきはある •静的解析ライブラリを何でもかんでもいれると競合してしまうことが あるのでご利用は計画的に •ライブラリに関しては一端しか触れられていないので、詳しくは君の 目で確かめてくれ 81

82.

尺余り 82

83.

ちょっと会社紹介させて…🙏 (旅費出してもらってる手前…) 83

84.

(株) 日本システム技研 Japan System Laboratory.inc • Python/Djangoを始めとしたwebアプリケーションの受託開発及び、フロン トエンド案件やIoT案件、その他プロダクト開発も手広くやってます。 • 無料コミュニティスペース「GEEKLAB.NAGANO」の運営 長野に来る時はぜひ寄ってください!!(ここから電車8時間 or 車15時間) 関東圏のお客様を中心にリモートワークでお仕事をやらせて頂いてます! 84

85.

関東圏のお客様を中心にリモートワークでお仕事をやらせて頂いてます! 実は… 85

86.

JSL勢九州初上陸!!🎉 86

87.

そして私は人生初飛行機!!🎉(ガチで) 87

88.

何かお仕事のご依頼や、 持ち案件に関するご相談、 その他お問い合わせがあれ ばお気軽にご連絡くださ い!! 88

89.

会社紹介終わり mission complete 89

90.

宣伝 同僚が共著者として携わったPython本が、 1月19日から物理本で販売中!!! < 書籍案内ページQR > こっちは無事出版 されてよかった… 90

91.

ご清聴ありがとうございました。 91