使い捨て python コードの書き方
DESCRIPTION
pyfes LT 2012.08 でしゃべったときのスライドですTRANSCRIPT
使い捨てコードの書き方
2012/08/25 @shiumachi
お前誰よ?
• Sho Shimauchi ( @shiumachi ) • Cloudera の問い合わせ担当 • 技術質問から人生相談まで幅広く対応
サポートに必要なもの • Linux コマンド – sed, awk, grep, find, sort … – これで対応できるなら問題なし
• シェルスクリプト – for i in `ls` と for i in `seq N` があればなんとかなる
• 正規表現 – 最強 – 最強と使いやすいかどうかは別問題
• 軽量言語 – 上記3つで簡単に対応できない場合はこれを使う – 特に集計処理とか入ると正規表現ブン回すより簡単だし
再利用しやすい
ユースケース
• ほとんどがパース・集計処理 – ログ内の特定の文字をカウントする – シーケンスIDをピックアップし、ギャップを検出、カ
ウントする – 特定のIDを時系列で追跡し、クラスタ内での移動
経路を追う
• その一回だけでしか使わないことが大半なので、使い捨てのコードを書くことが多い
ゴール
とにかく楽に使い捨てたい できれば再利用したい
使い捨て方(1) 適当なディレクトリに適当に書く
• 論外 • まず再利用不可能 – 場所がわからん – 何に使ったかわからん
使い捨て方(2) git で綺麗に管理する
• 理想的だが案外面倒 • どのコードも目的が違うので、ドキュメントの
整理・構造化も必要 • 同一名で投げ込むことができず、名前空間の
管理も必要
使い捨て方(3) gist に突っ込んでおく
• 多くの人が選んでいるであろう方法 • 保存・管理の手間のコストパフォーマンスを考
えるとこれがベスト
テスト
使い捨てコードだったらテストなんていらなくね?
違います。楽するためにテスト必要
xUnit/TDD の(個人的な)弊害 • xUnit – クラス必須みたいに見えること – 使い捨てコードでクラス必須って考えるだけでだるく
なる(個人的に) • TDD – 入門書を読むと 100% 原則を守らないといけないよう
に感じる
nose
• 色々便利な特徴はあるが、使い捨てコードにおける大きな特徴は2つ
• テストクラス作成が不要 – test_*.py としておいて、def test_*: というメソッド
を書いておけば nosetests で自動実行できる
• nose.tools – eq_(a, b) だけでほぼ全てまかなえる
テスト=ドキュメント
• 頭の中で設計したら、ドキュメントじゃなくテストとしてdumpしておくこと
• どうせパースだけなので、実際の入力データから数行ひっぱってくれば十分
• 網羅性を追求しない • 品質を追求しない • 実際に動かしてこけたら、その都度こけた入
力をテストに追加
エキPy 11 章より テストのメリットは4つ
1. ソフトウェアのリグレッションの防止
2. コード、の品質の向上
3. 最適で低レベルなドキュメントの提供
4. よりすばやく、信頼性の高いコードの生産
エキPy 11 章より テストのメリットは4つ
1. ソフトウェアのリグレッションの防止
2. コード、の品質の向上
3. 最適で低レベルなドキュメントの提供
4. よりすばやく、信頼性の高いコードの生産
使い捨てコードにおいては3,4 だけで十分
テストもgistにアップロードする
• gistは一つのコンテンツに複数ファイルアップロード可能
• テストも一緒に突っ込んでおく – テストを読めば何をするコードか大体わかる – 別の環境(Mac -‐> Linux など)でも動くかどうかテス
ト可能
サンプルコード
• hYps://gist.github.com/3460244
実際の入力データ hadoop のネームノード(マスタ)のログ 2012-‐06-‐04 13:30:59,197 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: STARTUP_MSG: /************************************************************ STARTUP_MSG: Starcng NameNode STARTUP_MSG: host = sho-‐mba.local/192.168.100.130 STARTUP_MSG: args = [] STARTUP_MSG: version = 0.20.2-‐cdh3u4 STARTUP_MSG: build = git://ubuntu-‐slave01/var/lib/jenkins/workspace/CDH3u4-‐Full-‐RC/build/cdh3/hadoop20/0.20.2-‐cdh3u4/source -‐r 214dd731e3bdb687cb55988d3f47dd9e248c5690; compiled by 'jenkins' on Mon May 7 13:01:39 PDT 2012 ************************************************************/ 2012-‐06-‐04 13:30:59,680 INFO org.apache.hadoop.metrics.jvm.JvmMetrics: Inicalizing JVM Metrics with processName=NameNode, sessionId=null 2012-‐06-‐04 13:30:59,683 INFO org.apache.hadoop.hdfs.server.namenode.metrics.NameNodeMetrics: Inicalizing NameNodeMeterics using context object:org.apache.hadoop.metrics.spi.NullContext 2012-‐06-‐04 13:30:59,788 INFO org.apache.hadoop.hdfs.ucl.GSet: VM type = 64-‐bit 2012-‐06-‐04 13:30:59,788 INFO org.apache.hadoop.hdfs.ucl.GSet: 2% max memory = 19.9175 MB 2012-‐06-‐04 13:30:59,788 INFO org.apache.hadoop.hdfs.ucl.GSet: capacity = 2^21 = 2097152 entries 2012-‐06-‐04 13:30:59,789 INFO org.apache.hadoop.hdfs.ucl.GSet: recommended=2097152, actual=2097152 2012-‐06-‐04 13:30:59,940 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: fsOwner=sho (auth:SIMPLE) 2012-‐06-‐04 13:30:59,940 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: supergroup=supergroup 2012-‐06-‐04 13:30:59,940 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: isPermissionEnabled=true 2012-‐06-‐04 13:30:59,953 INFO org.apache.hadoop.hdfs.server.namenode.FSNamesystem: dfs.block.invalidate.limit=1000
やりたいこと
• ログに出力されているログレベル(INFO, WARN, ERROR, …) を集計したい
• 「それ awk でできんじゃね?」とは言ってはいけない
テストコード def test_parse():! input = "2012-06-04 13:31:07,065 INFO org.apache.hadoop.net.NetworkTopology: Adding a new node: /default-rack/127.0.0.1:50010"! input2 = "2012-06-04 13:31:05,466 WARN org.apache.hadoop.util.PluginDispatcher: Unable to load dfs.namenode.plugins plugins"!
expected = 'INFO'! expected2 = 'WARN’! eq_(expected, parse(input))! eq_(expected2, parse(input2))!
入力データをベタ貼り
サンプルコード
def parse(line):! arr = line.strip().split() ! log_level = arr[2]! return log_level!
テストは通るが実際には動かない 2012-06-04 13:30:59,197 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: STARTUP_MSG: "/************************************************************"Traceback (most recent call last):" File "nn_parse.py", line 23, in <module>" main(sys.stdin)" File "nn_parse.py", line 11, in main" log_level = parse(line)" File "nn_parse.py", line 5, in parse" log_level = arr[2]"IndexError: list index out of range
失敗した行をそのままテストに追加 def test_parse():! input = "2012-06-04 13:31:07,065 INFO org.apache.hadoop.net.NetworkTopology: Adding a new node: /default-rack/127.0.0.1:50010"! input2 = "2012-06-04 13:31:05,466 WARN org.apache.hadoop.util.PluginDispatcher: Unable to load dfs.namenode.plugins plugins"!
input3 = "/************************************************************"! expected = 'INFO'! expected2 = 'WARN'! expected3 = '_NULL'! eq_(expected, parse(input))! eq_(expected2, parse(input2))! eq_(expected3, parse(input3))
失敗した行をそのままテストに追加 def test_parse():! input = "2012-06-04 13:31:07,065 INFO org.apache.hadoop.net.NetworkTopology: Adding a new node: /default-rack/127.0.0.1:50010"! input2 = "2012-06-04 13:31:05,466 WARN org.apache.hadoop.util.PluginDispatcher: Unable to load dfs.namenode.plugins plugins"!
input3 = "/************************************************************"! expected = 'INFO'! expected2 = 'WARN'! expected3 = '_NULL'! eq_(expected, parse(input))! eq_(expected2, parse(input2))! eq_(expected3, parse(input3))
コードもIndexErrorに対応
def parse(line):! arr = line.strip().split()! try: ! log_level = arr[2]! except:! log_level = '_NULL'! return log_level!
おしまい