Download - Pyconjp2016 pyftplib
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/80dc9419a97046e5ee6b148312859ef2c2ed1df3
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/command.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を使うと楽
まとめ
• コードを書き始める前に• 作りたいものを決めて、要件、仕様、設計
• コードを書く• 外枠(パッケージ)
• 仮組み
• モジュール化
• テストコード
• 使いやすさ向上させる
自分にあった開発手順を持ち、
スムーズに品質良くソフトウェアを作れるようになれるといいですね。