データサイエンティストがPythonでシステム開発する前に読む記事

広告運用自動化関連のプロジェクトで、他部署のデータサイエンティストがアルゴリズムの開発担当として、私がシステム担当兼レビュワーとしてアサインされました。

ただまだ分析以上のプログラミング経験はあまり無いそうで、知らない単語が芋づる式に出てくる状態のようです。それぞれの考え方を教えようにも、このコロナ影響下ではホワイトボードも使えないし、zoomのミーティングの時間を取るのもお互い負担が大きかったので、まずQiitaで共有することにします。

同僚に間違ったことを教えていたら困るので、もし誤っている箇所やアドバイスがあればコメント頂けると嬉しいです。


# バッチプログラムの実行について

現在開発している広告運用自動化システムでは、[AWS Data Pipeline](https://aws.amazon.com/jp/datapipeline/)を使っておおよそ以下のような環境で動いています。

* 事前にソースコードをまとめてzipファイルにしてS3にアップロードしておく
  * この処理はリポジトリのmasterブランチにpushしたときにCI/CDツールが動いて自動で行っています
* Data Pipelineの定期実行機能により、[EC2](https://aws.amazon.com/jp/ec2/)インスタンスを立ち上げる
* Data Pipelineが「S3からプログラムや依存ライブラリをインストールして実行する」シェルスクリプトを実行する
* EC2インスタンスをシャットダウンする

もし今後Dockerを使ったシステムに移行した場合は「ソースコードをまとめてzipファイルにしてS3にアップロード」が「Dockerイメージをビルドして[Amazon ECR](https://aws.amazon.com/jp/ecr/)にプッシュする」に変わります。少し余談ですが、「アプリ開発者自身がDockerfileでプロダクトに必要な言語やミドルウェアを記述できるようになり、インフラ部門に依存せずに素早く設定を更新できる」というメリットもあります。

そもそも、AWSのサービスが多すぎて「このサービスとこのサービスって何が違うんだ？」などと困る場面もあるかもしれません。私も苦労しました。AWSのソリューションアーキテクトのテキストの関連項目に目を通しておくことといいかもしれません。

* [AWS認定資格試験テキスト　AWS認定 ソリューションアーキテクト-アソシエイト](https://www.sbcr.jp/product/4797397390/)

また、現状では各バッチ処理を指定時間にcron実行しているだけで、連鎖的に失敗して再実行にも手間がかかっています。それをバッチの依存関係を明示して解決するのがAirflowやDigDagなどのワークフローエンジンです。（※AWS Datapipelineもワークフローとして実行するためのサービスなのですが、GUIの管理が煩雑でうまく設定できていません）

* [2019年のワークフローエンジンまとめ](https://qiita.com/elyunim26/items/15db924e4c9833e5050a)

他の問題を優先して対処していたのですが、ずっと解決しないのも問題なのできちんと動こうと思っています。

# 開発用ツールについて

gitやpoetryなどについて質問された項目です。

## Pythonやライブラリのバージョンについて

開発では「プロジェクトAでは最新のPython3.8を使いたいが、プロジェクトBではライブラリが対応してないのでPython3.7を使いたい」というような場合があります。

私は `pyenv` でPython本体のバージョンを切り替え、ライブラリは `poetry` を使って管理しています。次の記事を参考にしてください。

* [pyenv+pipx+PoetryでPythonのきれいな開発環境を手に入れる](https://qiita.com/ninomiyt/items/a3f09a997763c5d817b1)
* [My Python Development Environment, 2020 Edition](https://jacobian.org/2019/nov/11/python-environment-2020/)

少し前まで `poetry` と同じ用途で [`Pipenv`](https://pipenv-ja.readthedocs.io/ja/translate-ja/)が流行っていたのですが、現在開発が滞っているようです。例えば「[If this project is dead, just tell us](https://news.ycombinator.com/item?id=21781421)」というISSUEが作られてしまっていました。（※ただし、先程確認したら[2020年4月1日に2年越しにリリース](https://github.com/pypa/pipenv/releases)されているようで、完全に開発が止まっているわけでは無さそうです）

poetryを利用すると `pyproject.toml` と `poetry.lock` というファイルが生成されます。 `pyproject.toml` が利用しているPythonライブラリのバージョンなどのプロジェクトの設定で、 `poetry.lock` が実際にインストールした依存ライブラリのバージョンを固定するためのものです。それには[以下のようなメリットがあります](https://python-poetry.org/docs/basic-usage/)。

> Committing this file to VC is important because it will cause anyone who sets up the project to use the exact same versions of the dependencies that you are using. Your CI server, production machines, other developers in your team, everything and everyone runs on the same dependencies, which mitigates the potential for bugs affecting only some parts of the deployments. 
>
> （Google翻訳）このファイルをVCにコミットすると、プロジェクトを設定したすべてのユーザーが、使用している依存関係とまったく同じバージョンを使用するようになるため、重要です。CIサーバー、プロダクションマシン、チーム内の他の開発者、すべておよび全員が同じ依存関係で実行されるため、デプロイメントの一部にのみ影響するバグの可能性が軽減されます。

Pythonに限らず、他のプログラミング言語でも同じようなツールが存在します。例えばRubyのrbenvやBundler、node.jsならnvmやnpm（他にもいろいろあるらしい）などを使います。他言語でもツールごとに微妙にカバーしている範囲は違いますが、お互い影響を与え合って似ている部分も多く、例えば `***.lock` が用意されていたりします。

## コードのバージョン管理(git)について

gitについては長くなってしまうので入門記事を読んでください。最初は難解だと思いますが、「ブランチ」の必要性を理解すると自分で調べられるようになると思います。

基本的に「別のブランチで開発（コミット）して、リリース用ブランチにマージする」というスタイルで開発します。これにより複数人が別の機能を追加したいとき、同時に別ブランチで開発できます。

* [GitとSubversionの構造的な違い｜GitのブランチはSubversionのブランチとは全く違うモノ](https://www.ricksoft.jp/blog/archives/9483/)

> SubversionとGitの大きな違いはブランチ管理あると私は考えています。最近のソースコードのバージョン管理では、機能開発やバグ修正の専用ブランチを作ってリリース用ブランチへマージする開発スタイルが主流になってきており、開発プロジェクトにおいてコードのブランチ管理は必要不可欠であると言えます。

上記にある通り、git以前からSubversionというツールもあったのですが、ブランチの使い勝手が非常に悪いせいでほぼ駆逐されてしまったそうです。余談ですが、[リーナス・トーバルズ（gitやLinuxカーネルの開発者の偉大なプログラマーだが、口が悪いことでも有名）が特にこの点でSubversionをこき下ろしている](https://cpplover.blogspot.com/2015/04/git10linus-torvals.html)というニュースもありました。

「Github上でリリース用ブランチにマージすることを依頼する」のがよく聞くプルリクエストです。また、ブランチの運用方法にはgitflowやgithubflowなどがあり、今やってる広告運用自動化のシステムではgitflowを採用しています。

* [Git利用時のフローはどれを使うか](https://qiita.com/tkhm/items/cc7855d32d640687b43c)

## sshについて

ssh接続して作業することもあると思います。その際に、秘密鍵のほうを絶対に他人に漏らさないように気をつけてください。

* [SSHの公開鍵認証における良くある誤解の話](https://qiita.com/angel_p_57/items/2e3f3f8661de32a0d432)

> 公開鍵認証において、サーバに登録するのはユーザの公開鍵です。そして、認証時には秘密鍵そのものではなく、生成した署名データ、それも流用困難なものをサーバに提示します。そのため第三者はもちろんサーバであっても秘密鍵を入手したり、署名を悪用することは困難です。


# 実装について

Pythonのコードを実装する際に気をつける点をまとめておきます。

## ドキュメンテーションや型ヒントについて

他の人が読みやすい状態にしておく。ひとまずこれやっといてください。

* README.mdをきちんと書く
* Docstrings（関数にあるコメント）をきちんと書く

Docstringsも何種類かスタイルがあり、numpyスタイルやGoogleスタイルなどがあります。自分たちはGoogleスタイルを採用しています。

* [GoogleスタイルのPython Docstringの入門](https://qiita.com/11ohina017/items/118b3b42b612e527dc1d)

私はVSCodeのプラグインの[Python Docstring Generator](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring)で補完するように設定しています。他のIDEやエディタでも同様のツールがあると思います。

型ヒントも**基本的には**ドキュメンテーションの役割だけを果たしています。そのためコードの実行時に間違った型を入れてもエラーが出ません。

```python
def greeting(name: str) -> str:
    return f"Hello, {name}"

print(greeting(1))
# => "Hello, 1"
```

「基本的」というのは、 [`mypy`](https://github.com/python/mypy) という静的解析ツールで型情報が間違ってないか自動でテストを行うことができることと、一部のライブラリ（後述する`pydantic`など）では実行時に型の情報を使う場合もあるからです。

型ヒントを使う際は、 `List[str]` だとか `Dict[str, str]` などの記述方法を知っておく必要があります。余裕があるときに次の記事を読んでおくと良いです。

* [Pythonではじまる、型のある世界](https://qiita.com/icoxfog417/items/c17eb042f4735b7924a3)
* [typing --- 型ヒントのサポート](https://docs.python.org/ja/3/library/typing.html)


## ロギングについて

私のコードの中に `logging` を使ったコードがあり、Pythonの公式ドキュメントの[Logging HOWTO](https://docs.python.org/ja/3/howto/logging.html)も圧があるので困ってしまったと思います。

簡単に説明すると、 `print` 関数に比べてログを残す際に便利な以下のような機能があります。

* 出力先（ログファイル、標準出力）を設定できる
* ログレベル（DEBUG, INFO, ERRORなど）を切り分けられる。また「開発環境ではDEBUGを表示するが、本番ではINFO以上にする」などの切り捨て設定もできます。

より具体的には以下の記事を参考にすると良いと思います。

* [Pythonでprintを卒業してログ出力をいい感じにする](https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19)
* [ログ設計指針](https://qiita.com/nanasess/items/350e59b29cceb2f122b3) ※JavaやPHPが対象で、少しログレベルの内容が違うので注意

ただ、私も開発時は厳密にやっているわけではなく、実装時のデバッグは `print` を使って必要なら後で `logging` を使った形に置き換えるように実装しています。

## アプリケーションの設定について

アプリケーションの設定（どのS3パスを使うとか、不動産マーケットを実行するかとか）は以下のような方法で行います。

* 環境変数にセットする
* コマンドライン引数で渡す
* 設定ファイル(jsonやyamlなど)を用意する


### 環境変数を使う

アプリケーションの設定は、基本的に環境変数にセットします。[Twelve-Factor App](https://12factor.net/ja/)という有名なアプリケーション開発の指針にこの項目があります。

> アプリケーションの 設定 は、デプロイ（ステージング、本番、開発環境など）の間で異なり得る唯一のものである。設定には以下のものが含まれる。
>
> * データベース、Memcached、他のバックエンドサービスなどのリソースへのハンドル
> * Amazon S3やTwitterなどの外部サービスの認証情報
> * デプロイされたホストの正規化されたホスト名など、デプロイごとの値

こうすることで、いろいろとメリットがあります。実感しやすいのだと以下のようなものです。

* 開発環境と本番環境でデータソース（S3、RDB、BigQuery...）を切り替えたい場合にも、コードを変更せずに実現できる
* 秘匿情報がコード内に現れないので安全で、OSSとしても公開できる

Pythonのコードでは普通、環境変数は [`os.environ`](https://docs.python.org/ja/3/library/os.html#os.environ) を使って以下のようなコードを書くことになります。

```python
import os
SOME_VALUE = os.environ['SOME_VALUE']

# 数値のリストがほしいときなど、少し型変換が必要だったり煩雑なんですよね…
SOME_INT = int(os.environ["SOME_INT"])
```

ただ、私は最近 `pydantic` というライブラリを使っています。それを使うと以下のようなコードになります。

* [Pythonで環境変数を読み込むときはpydanticを使うと便利](https://qiita.com/ninomiyt/items/ee676d7f9b780b1d44e8)

```python
from pydantic import BaseSettings

class Settings(BaseSettings):
    some_value: str
    some_int: int # 自動でintに変換してくれる

settings = Settings()
```

開発時に `.env` というファイルを利用することがあり、自分の使っているMacの環境変数をセットせずに、一時的にファイルから読み込むことができます。ちなみにpydanticも.envの利用もサポートしているので便利です。

ここで実際に私が実装したコードを見て、「pydanticで環境変数を読み込んでいるのに、osモジュールがimportされているのはなぜだろう」と疑問に思ったと思います。それは消し忘れなので後で隠滅しておきます。

また、今のシステムで動いているコードでも、環境変数として切り出すべき設定項目がコード内に含まれてしまっているものも多いです。他のコードを参考にする場合は気をつけてください。


### コマンドライン引数を使う

環境変数との使い分けは明確な基準を持っていたわけではありませんが、[コマンドライン引数と環境変数の使い分け基準](http://anton0825.hatenablog.com/entry/2017/10/20/000000)という記事を見つけました。今回実装している広告運用費のアラート機能では、「開発時に賃貸マーケットごとだけ実行して確認する」みたいな用途も多そうだったのでコマンドライン引数で指定するようにしています。

Pythonの標準にも [`argparse`](https://docs.python.org/ja/3/howto/argparse.html) というライブラリがありますが、多くのPythonプログラマーは [`click`](https://click.palletsprojects.com/en/7.x/)を使っています。

* [Python: コマンドラインパーサの Click が便利すぎた](https://blog.amedama.jp/entry/2015/10/14/232045)

少し余談ですが、awesome-pythonというリポジトリにPythonの便利ライブラリのうち、 `click` のような評判の良いものの情報がまとまっています。他のプログラミング言語に挑戦する場合もawesome-xxxで調べると大抵同じような情報が見つかるでしょう。

* [vinta/awesome-python](https://github.com/vinta/awesome-python)




## テストコードについて

今回は単純な通知処理なのでテストコードはサボってしまいましたが、各モジュールごとにユニットテストを行います。標準ライブラリと `unittest` とサードパーティ製の `pytest` がありますが、私は使い勝手の面で `pytest` をおすすめします。

* [pytest ヘビー🐍ユーザーへの第一歩](https://www.m3tech.blog/entry/pytest-summary)

あと、[tox](https://tox.readthedocs.io/en/latest/)というツールも見つかると思いますが、「複数のPythonバージョンでpytestやunittestを実行するためのツール」であり、特定のシステム上で動かす場合はPythonバージョンは固定されているはずなので今は考えなくていいです。ライブラリを自作するときに導入を考えればいいです。

境界値分析などの知識が多少必要となります。コロナ騒ぎが明けたら、QAの人にこの本を借りると良いと思います。

* [知識ゼロから学ぶソフトウェアテスト【改訂版】](https://www.shoeisha.co.jp/book/detail/9784798130606)


## その他、プログラミング全般で気をつけること

コードレビューで教えます。書籍であれば、このあたりがおすすめです。

* [Effective Python ――Pythonプログラムを改良する59項目](https://www.oreilly.co.jp/books/9784873117560/)
* [リーダブルコード ――より良いコードを書くためのシンプルで実践的なテクニック](https://www.oreilly.co.jp/books/9784873115658/)

また、こちらの本もピッタリかもしれません。今回WEBサーバーの話は全く書いていませんが、この本にはそこで詰まるだろうことも書かれています。

* [自走プログラマー ～Pythonの先輩が教えるプロジェクト開発のベストプラクティス120](https://gihyo.jp/book/2020/978-4-297-11197-7)

# 最後に

強くなれ💪