開発を効率的に進めるためのツール設定

Atsushi Odagiri氏:この時間はビギナーセッションで、内容はPythonでの開発を効率的に進めるためのツール設定です。

まず「ツール設定」という趣旨の説明と、あとは今日説明するツールがflake8、black、mypy、pytest、toxなどがあります。そしてそれぞれを使うためにエディタ設定をするという流れになっております。

先に自己紹介させていただきますと、私、Atsushi Odagiriという名前を縮めて、aodagという名前でネット上では活動しております。

今、株式会社オープンコレクターというところで仕事をしております。よく「どんなサービスを作っていらっしゃるんですか?」とキラキラした目で聞かれますけど、受託の会社ですので、寂しい思いをするのであまりそういう質問をしないでください。

あとpylonsproject.jpという、Webアプリケーションフレームワークを作っているプロジェクトの日本ドメインを保持してメンテをしておりますが、たいがい毎年3,000円払うだけみたいな活動をしております。

今日のお題は、開発を効率的に進めるためのツールを知りましょうというものです。「効率的に進める」というのは何なのかというと、つまらないミスをしないようにしましょうということです。効率的と聞いて、「よし、俺の能力がすげえパワーアップするぜ!」みたいな、そんな話はまったくありません。皆さんがやっているコーディングがだいたい60パーセントの効率だとしたら、ツールを使って80パーセントに上げましょうという渋い話です。

「つまらないミスをしないようにがんばる」と言っても、「ミスするな!」と言われて、「いや、それはミスはしたくないよ」とがんばるんですが、やっぱりがんばるだけではミスはいつか出てくる。でも機械であれば何度そういうチェックをしても疲れないので、ツールを使うことでつまらないミスを減らして、手戻りを少なく効率的に開発を進めましょうということです。

そのための準備ですが、みなさんは実際今から「ちゃんと手を動かしてやるぜ!」という人ですか?

(会場挙手)

いますね。がんばってください。いつでもvenvで作業しましょう。たぶん作ってあると思うので飛ばします。

静的チェックツール「flake8」

ではまずflake8から説明します。flake8というのは静的チェックツールです。「静的」というのはどういうことかというと、コードを実行せずにチェックしてくれるツールです。

ユニットテストとかは実際に動かすことでエラーを発見するツールですが、静的チェックツールというのは、コードの状況を見て「これバグっぽくない?」というところをチェックしてくれるツールです。

「ユニットテストを使えばいいんじゃないか」という話もありますが、なぜ静的チェックツールを使うかというと、ユニットテストで実行した場合のエラーというのはもうバグなんです。その前に静的チェックによってバグにつながるコードをすぐに判別することができます。

あと、レビュアーに「あれ、この変数使ってなくね?」とか「このimportって意味あるの?」とか細かいチェックを受けて直すより、機械に言われて直したほうが気分はいいです。あと、レビュアーももっと深いレビューをしたいのに、そういう細かいチェックからしないといけないのは気分があまりよろしくないです。お互いの精神衛生のために静的チェックツールを使って、「なんかやべえな」というコードは事前にチェックしておきましょう。

flake8のインストール方法は簡単で、「pip install flake8」です。

この場でみなさんが一気にインストールをするとネットワークがどうなるかわからないです。もし失敗して、隣の人がやっていたら、「お願いします」と言って見せてもらって、「ああ、なるほど」と思ってください。

flake8には.flake8とかsetup.cfg、tox.iniといった設定ファイルがたくさんあります。でもflake8のほかにもmypyとかblackとかいろんなツールがあるのに、それらの独自なファイルを全部使っていると、トップディレクトリにファイルが散らばって、どこに書いてあるかわからなくなります。たいていのツールはsetup.cfgかtox.iniの2つに設定を書けるようになっているので、そちらに書くのがいいでしょう。

どんな設定ファイルになるかというと、flake8というセクションがあって、たいていの場合その中に書くのはmax-line-lengthを伸ばすという設定と、あとはignoreしたい、「ここはチェックしなくていいよ」というものを入れるのがよくある設定です。

flake8の実行例

ではflake8の実行例です。さあみなさんこれを見てみましょう。

イラッとしますよね? 「さすがにこんなバカなコードを書く人なんていないだろ」と思うかもしれません。例えばaddが上にも下にもあります。「さあ、このfの中でaddはどっちが呼ばれるの?」となるでしょう。

これは短いので、みなさんパッと見て「こんなんアホやん」みたいな感じになるんですけど、1,000行、2,000行の中でこういう状況になると意外と気がつかないものです。例えばaddなんていう汎用的な名前はけっこう何箇所も書いてしまうものなんです。こういうのを探し当てることができます。

ちなみに、addが2つあるというので、「じゃあ下のaddのほうが正しいじゃん?」と思うかもしれませんけど、flake8にチェックさせると、「F811 redefinition of~」 のところで、「addが再定義されているよ」と誰が見てもわかるようになっていますね。

次にもう1個下に「F841 local variable ‘c’ is〜」と書いてあるんですけど、「c作ってるけど入れてない、使ってない」というものです。これはかなりバグにつながるやつですよね。たぶん上のやつをどうにか修正しようとしたのに、マージミスでreturn a + b が残っちゃって、結局きれいにしたcを使ってないみたいなケアレスミスです。

これぐらいだったらすぐ見つかるんですけど、複雑なコードになると見つけるのは大変です。flake8は機械ですので、こういうのをちゃんと的確に見つけてくれます。ということで、それをちゃんとflake8を実行してこういうエラーがチェックできるかどうかをやってみましょう。

このエラーを修正するときれいなコードになります。

ここでまたflake8を実行すると、警告が出ない。ということがみなさんできたと思います。flake8はこういう感じで、ちょっとしたミスをチェックしてくれるツールです。

PEP8準拠のコードフォーマッター「black」

次にblackにいきます。blackというのはPEP8準拠のコードフォーマッターです。さて、ビギナーセッションとはいえ、PEP8がわからない人はいますか?

(会場挙手)

少しいますね。PEPはPython Enhancement Proposalと言って、Pythonのいろんな決めごとを作っているものですが、その8番目がPythonコードのフォーマッティングについての説明です。それに準拠してコードをきれいに整形してくれるツールがコードフォーマッターです。

blackは一番新しいコードフォーマッターです。昔からautopep8とかyapfとかいろいろあるんですけど、なぜblackが最近流行りになってきているのかというと、blackは設定がほとんどできないんです。

あとは、autopep8かyapfというのは、PEP8に従っているコードだったらそれはそのままにしてくれるんです。だから、「自分的にはここの引数は全部縦に並べておきたい」とやっておいたときに、autopep8とかは縦に並べたままにしてくれるのですが、blackは行の長さに引っかからないかぎり問答無用で全部横に並べます。

「俺がきれいに並べたコードが全部blackによって破壊される!」みたいなことを言ってくる人がけっこういるんですが、もうそういうので悩むのはやめましょう。blackがそう言うんだからもう全部blackでやる。blackは何回やっても同じ結果になります。「だって、事前に整形するんだったら、またその事前の整形の流派とかが出てくるわけじゃん?」という感じですね。

あと、ほとんど設定できないということで、例えば「ここの縦に並べるのだけはちょっとこののプロジェクトでは変えよう」というのが発生するのを防げます。プロジェクトごとにちょろちょろ変えたような微妙に違うautopep8の設定があったりするわけですけど、そういうのはもうやめて、「もうblackで全部いきましょう」ということです。

ただし、blackはPython3.6以上でしか使えないので、3.5の人は早く3.6に上げましょう。

なぜフォーマッターを使うのかという話です。「PEP8で決まっているんだからフォーマットしようよ」という話はもちろんあるのですが、なぜフォーマットをするのかというと、まず圧倒的に読みやすいからです。あと、フォーマットされたコードの中で変なことをしていると、だいたいすぐわかります。

「ifダダダダダダダ:」みたいな超読みにくいコード、「これは絶対何かがおかしい」と比較的すぐわかるのがフォーマッティングのいいところです。フォーマッティングをすると、何か変なことをしていたら変に見えるんです。

ではこのblackをどうやって使うかというと、「pip install black」とすると、blackがインストールされて使えるようになります。

一応blackは設定ファイルpyproject.tomlがございますが、私としてはデフォルトでいっていただきたいです。ただ行の長さをちょっと変えたいという人はいるかもしれないです。120とか200とか、「もう2,000でもいいよ」とか。

blackはデフォルトが88という超中途半端な長さになっているので、そこが気に食わない人は設定してもいいかもしれないと思います。ただ、プロジェクトごとにいろいろ変わると鬱陶しいので、「もうプロジェクト全体でデフォルトでいこう」ということなら、それはそれでいいと思います。

blackには実際にフォーマットしてしまう以外の使い方があります。まず使ってほしいのが、blackでコードをフォーマットをしたら、この--checkオプションを使ってblackにフォーマット済みになっているかどうか判定できるというものです。例えばこれをCIとかでチェックして、「ちゃんとフォーマットしていないとマージされない」みたいなガチ仕様でいくのもよいでしょう。

あと、--diffを使うとどう変換されるのかが出てきます。CI上で引っかかってどうやって直すのかがすぐわからないとき、--diffも一緒に出してあげることですぐに「あっ、ここ直さなきゃ」とわかるようにできます。なので、blackを使うときは、自分だけフォーマットが引けることに加え、--checkとか--diffとかを使ってみんなで守っていきましょうという、こういうコマンドラインのオプションも使っていただきたいと思います。

ではblackの実行例です。これは正しいコードですし、たぶんPEP8には違反していないはずです。なのでautopep8とかyapfとかを使うと、これはこのまま残るはずです。

でも、blackはそんなことはお許しにならないので、ちゃんと横に並べます。これはblack --diffを使いました。black --diffを使うと、「このコードはこうやって整形するよ」というdiffが出てきます。--diffオプションをつけなければ実際この内容に整形してくれます。

なので、みなさんも試す場合にはこういう無茶なフォーマッティングを書いてblackをかけることで、超きれいになるというのを確認していただきたいなと思います。

type hintをチェックするための「mypy」

次にmypyです。flake8はおそらくもう導入済みの方はけっこういるかと思います。flake8を個人的に使っている人?

(会場挙手)

プロジェクト全体でも使っている人?

(会場挙手)

CI上でチェックまでやっている人?

(会場挙手)

けっこう少なくなりましたですね。mypyを使っている人?

(会場挙手)

おっ。プロジェクトでmypyを使っている人?

(会場挙手)

ignore_missing_importsなしでmypyを使っている人? 

(挙手なし)

さすがにいないね。mypyは、最近話題になってきているtype hintというのをチェックするためのツールです。分類上はflake8と同じ静的チェックツールなので、mypyを使って型チェックをすることができますが、その型チェックで実行時に影響を与えることはないです。

「なんでtype hintを入れるのか?」と。「だってPythonはスクリプトで簡単に書けるのに、そこにわざわざ型を書くとか超面倒じゃん」みたいな話になると思うのですが、「でもどっちにしろお前たちはドキュメントやdocstringのところに『str』とか型を書いているじゃないか」と。書いてない人? 

(挙手なし)

うん、大丈夫だね。ドキュメントやコメントで書くより、ソースコードの中に実際に型として書いてあったほうがより厳密になります。ただし書いていないからといってそのコードが動かないわけではないです。基本的には「このコードはこういう型が来るものとしている」という意思表明として書きます。

あと、ちゃんとtype hintを入れると、補完するエディタがきれいに補完をしてくれます。ただしtype hintが間違っている場合はほかも間違ってしまうので、ちゃんと正しいtype hintが書いてあるかをmypyでチェックすることによって、こういったエディタの補完も間違いがないように出てきます。

「mypyをちょっと入れたいな」という感じでobjectと書いている人がたくさんいるのですが、そこはAnyです。objectは「object」という型になってしまいます。そこには追加のプロパティを代入することもできないし、object以外のメソッドを呼ぶこともできないので、objectとAnyを間違いないようにしましょう。

mypyのインストールは、これまた簡単に「pip install mypy」で入ります。

そして、mypyの設定ファイルにはmypy.iniとかあるんですけど、やっぱりsetup.cfgに全部まとめておくほうがおすすめです。増えますからね。

さっきignore_missing_importsを使っているかと聞いたのですが、これは今のところほとんど必須のオプションになります。なぜかというと、使っているライブラリも全部 mypyチェックにいってしまうので、使おうとしているライブラリがmypy、type hintに対応しておかないと、そこでもうエラーになってしまいます。一応「なきゃないでしょうがない」です。過渡期で、おそらくこの過渡期が終わることはないでしょう。おそらくずっとこのオプションは必要になるに違いありません。そういう設定をするために「ignore_missing_imports = 1」 と、setup.cfgのmypyセクションに書いておきましょう。

ではmypyを使うと何がわかるのか? 先ほども出てきましたね。addをするときにNoneだったらちゃんと0に変えて足し算するというものです。でもちょっと間違えていて、 return a + bというのも先ほどのままですが、何が違うかというと、aはOptional[int]というタイピングをしてあります。bはintというタイピングをしてあります。

これでチェックをすると、fからaddを呼ぶときにOptionalのほうはNoneを渡すことができます。bはintと書いてあるので、ここのintはNoneを渡せないのでもう0と書いてあります。

なので、呼び出し方は大丈夫です。ただし、がんばってcに0を入れているのに、そのあとa + bと書いてあって、「なんかおかしいよね?」というのがありました。

これをmypyでチェックすると、「Unsupported operand types for + 〜」と書いてあるのですが、これはa + bのところで、Optional[int]+ intはできないとmypyが警告しています。

なぜかというと、Optional[int]にはNoneが来るかもしれないので、これではNone+ 1というふうにはできないと言っているのです。実はその上の「c = ~」というところで、ちゃんと0になるように作ってあるのに、これを使うのを忘れるという、flake8のときと同じミスを犯しています。mypyを使ってもだいたい同じようなことはチェックができます。

こういう警告をされていますので、先ほどのコードを写していた人は、ここのOptional[int]というところと、bにも「int」というのをつけてあげてからmypyを実行して、ちゃんとチェックできるかやってみていただければと思います。

先ほどのやつは、c +bに変えればいいですね。こう直してあげるとmypyは警告してこなくなります。