Pythonでpyftpdlibを使って FTPサーバーを作る際に 使ったテクニックの紹介 Shinya Okano PyConJP 2016
お前、誰よ • 岡野 真也 • tokibito • Pythonを使って10年ぐらい仕事してます • https://twitter.com/tokibito • 所属: 株式会社ビープラウド
伝えたいこと ソフトウェアを開発する際、 闇雲にコードを書いて作っていくのではなく、 手順(工程)を意識すること
アウトライン 1. 題材としたFTPサーバーアプリケーションの紹介 2. ツール(アプリケーション)開発の流れ 3. 要件、仕様、設計を書き起こしてみよう 4. どこから書き始めるか 5. 先に外枠だけ作ってしまう(パッケージング、コマンド化) 6. 実装の仮組み 7. モジュールを分ける 8. テストコードを書く 9. 自動テストを設定する 10. 設定ファイルやログ出力の整備 11. Dockerで動かせるようにしてみる
1.題材としたFTPサーバーアプリケーション の紹介 • FTPについては詳しくは説明しません • コンピュータ間でファイルを転送するのに使うプロトコルです • RFCいくつもあるので調べれば出てきます • クライアントだとFFFTPなどが有名 • サーバー実装はいろいろあります • 今回のFTPサーバー • 1ユーザーのみ • なるべく設定項目を減らす • 既存のソフトウェアの設定が面倒なので、目的を果たす単機能なもの • マスカレードアドレス、パッシブモードとポートは設定できること • soloftpdという名前でPyPIに登録済み • https://github.com/tokibito/soloftpd
2.ツール(アプリケーション)開発の流れ 1. 何をしたいのか、まとめる(企画、要件定義) 2. 要件を満たすシステムの入力や出力の詳細、データの流れやシ ステムの処理をまとめる(仕様策定) 3. 要件、仕様をどうやって実現するのか考えてまとめる(設計) 4. 設計に対応する実装をする、システムを構築する(実装) 5. 実装、構築して出来上がったものが、要件や仕様を満たしている か確認する(試験) 6. 完成 小さいツールなら、ある程度簡略化、省略するといいです。 参考: http://tokibito.hatenablog.com/entry/2016/08/01/000820
3.要件、仕様、設計を書き起こしてみよう • 要件、仕様、設計の一例 • https://github.com/tokibito/soloftpd/blob/master/README.rst • どんな機能が必要なのか、何を作るのか書き出してみよう • どのように使うのか、書き出してみよう • コマンド名やコマンドラインオプション • 設定ファイル • 入力ファイル、出力ファイルの仕様など • ちょっとしたツールなら、簡易的な記述でも十分 • コードをすぐに書けそうな状態=設計ができてる とても大切な工程です。 これができないまま進めると、完成は遠いです 何を作りたいのか、できるだけハッキリさせておきましょう。
補足. pyftpdlib • FTPサーバーを実装するためのPythonモジュール • 認証のカスタマイズ • ストレージのカスタマイズ • FTPプロトコルの拡張
4.どこから書き始めるか • 設計はできました。 • では作りましょう。 • →どこから始める? • パッケージ設定、ライセンス、README • あんまり何も考えなくても定型的な部分 • 後回しにすると面倒な部分 • 最初のコミット • https://github.com/tokibito/soloftpd/commit/80dc9419a97046e5ee 6b148312859ef2c2ed1df3
5.先に外枠を作ってしまう • まずは外枠のパッケージの部分を用意する • setup.pyを書く • わからなければ、他のサードパーティモジュールなどの真似をすればいい • https://packaging.python.org/distributing/ • コマンドツールであれば、console_scriptsを設定するのが便利 • 誰かに見られて困るものでなければ、PyPIに登録して使えるようにし とくと楽 • 自分専用のツールだったとしても登録しとけば、便利です
補足. ライセンス • OSSを利用するのであれば、大雑把にでもライセンス体系は把握し ておいたほうが良いでしょう • 自分のツールをどのライセンスで公開するのか、調べるのもいい勉 強になります。 • PythonのライセンスはPSF License(Python Software Foundation License)です。 • BSDスタイル、GPL互換 • https://docs.python.org/3/license.html
6.実装の仮組み • 動かしながら作れるほうが、イメージは湧きやすい • とりあえず入出力できるようにしてみる • 細かい部分を後回しにして、処理の流れを先に実装する • 関数やメソッドの呼び出しで、仮の値を返す実装 • 設定によって分岐する部分を固定値にする • 仮実装の部分はTODOコメントを入れておく • 後でgrepなどで見つけやすいように • FTPサーバーの例. • 設定ファイルの読み込みなどは後で実装 • 認証ハンドラを固定のユーザーで実装 • FTPサーバーを起動して、クライアントから接続できるように
7.モジュールを分ける • 仮組みができたら、細かい部分の実装をする • 機能に名前をつけられるなら、モジュール、クラスに分けておく • 機能単位で実装し、組み込んでいく • FTPサーバーの例. • 設定ファイルを読み込むクラスを実装 • 実装したクラスを組み込み • 認証クラスを実装 • 実装したクラスを組み込み
補足. FTPサーバーのクラス関係
補足. 関数 or クラス • 関数とクラスのどちらを使うか • Pythonの関数とクラスの違い • 関数 = 処理 • クラス = 状態と処理 • 大雑把な経験則: • 状態(入力)によって処理の分岐が発生するなら、クラスにしたほうがよい • クラス同士を入れ子にする場合は、役割を文章で説明できる単位にする • 処理の共通化 • 仕様、設計の上で共通化していないものは、冗長でも分けておく(コピペ上等、設計直 せ) • クラスの継承は、安易に使わない • is-a関係とhas-a関係
8.テストコードを書く • フレームワークは標準のunittestをまずは使う • テストコードの書き方 • 関数、クラス、モジュール単位でテストコードを書く • 結合テストは、設計変更に弱いのでなるべく単体から。 • 機能実装時に一緒にやれればなお良い • テストコードが書きづらい? • 関数、クラス、モジュール設計の問題 • ほとんどの場合がこれ • 処理をテストするために準備しないといけない状態が多すぎる • 処理が大きすぎ • 結合度が高すぎ • 対象がテストしづらい場合 • 非同期処理はテストしづらい。気合でやるしかない。つらい。
補足. テストフレームワーク、テストランナー • テストフレームワーク • TestCase、TestSuiteやアサーション関数、テストフィクスチャなど、テストコード を書くための機能、枠組みを提供 • doctest、unittest、pytest、noseなどが提供 • テストランナー • テストコードを実行し、結果を表示する機能 • テストコードを自動的に見つける機能を持つものもある • doctest、unittest、pytest、noseなどが提供 • フレームワークとランナーは、別のライブラリを使える • unittestフレームワークを使ってテストコードを書く • pytestでテストコードを実行する
補足. テストの実行 • setup.pyのコマンドを使う方法 • python setup.py test • py.testを使う場合 • py.test tests
9.自動テストを設定する • 復数のPythonバージョン、実装でテストを実行したい • toxを使おう • https://tox.readthedocs.io/en/latest/ • https://github.com/tokibito/soloftpd/blob/master/tox.ini • 継続的インテグレーション(Continuous Integration, CI)をしたい • TravisCIやCircleCIを使おう • Python復数バージョンでのマトリクスであればTravisCIのほうが簡単かも • https://travis-ci.org/ • https://github.com/tokibito/soloftpd/blob/master/.travis.yml
10.設定ファイルやログ出力の整備 • 設定ファイルのフォーマット • Pythonの標準モジュールだとJSONやiniファイルが使いやすい • その他だとXML、YAMLなど。 • 設定ファイルを読み込む処理をどこに持たせるか • 設定情報を扱うクラスに実装しておくとか • 設定情報は辞書で扱うこともできるが、属性になっているほうがコードは見 やすい • コード中の記号を減らせる • 設定項目が少ないならクラス化しないのもアリ • ログ出力は、loggingモジュールを使う • 設定ファイルから設定できるようにしておけば、作り込む必要はさほどない
補足. loggingモジュールでログを出力する • モジュールのglobalにlogging.getLoggerでオブジェクトを用意してお いて、infoやerrorなどのメソッドを呼ぶ • https://github.com/tokibito/soloftpd/blob/master/soloftpd/comman d.py#L8
11.Dockerで動かせるようにしてみる • Dockerコンテナで動かせると便利なところ • OSのバージョン依存から脱却 • インストール手順の省略 • PyPIに登録しているならば、requirements.txtだけ作ればすぐに登録 できるイメージを使える • https://hub.docker.com/_/python/ • official repository • python:3.5-onbuild • 例: • https://github.com/tokibito/docker-soloftpd • dockerコマンドのオプション指定が面倒な場合は、docker-compose を使うと楽
まとめ • コードを書き始める前に • 作りたいものを決めて、要件、仕様、設計 • コードを書く • 外枠(パッケージ) • 仮組み • モジュール化 • テストコード • 使いやすさ向上させる 自分にあった開発手順を持ち、 スムーズに品質良くソフトウェアを作れるようになれるといいですね。

Pyconjp2016 pyftplib