Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
使い捨てコードの書き方	
  

    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しておくこと	
  
•  どうせパースだけなので、実際の入力データ
   から数行ひっぱってくれば十分	
  
•  網羅性を追求しない	
  
•  品質を追求しない	
  
•  実際に動かしてこけたら、その都度こけた入
   力をテストに追加	
  
使い捨て python コードの書き方
エキ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!
	
  
おしまい	
  

More Related Content

使い捨て python コードの書き方

  • 1. 使い捨てコードの書き方   2012/08/25   @shiumachi  
  • 2. お前誰よ?   •  Sho  Shimauchi    (  @shiumachi  )   •  Cloudera  の問い合わせ担当   •  技術質問から人生相談まで幅広く対応  
  • 3. サポートに必要なもの   •  Linux  コマンド   –  sed,  awk,  grep,  find,  sort  …   –  これで対応できるなら問題なし   •  シェルスクリプト   –  for  i  in  `ls`  と  for  i  in  `seq  N`  があればなんとかなる   •  正規表現   –  最強   –  最強と使いやすいかどうかは別問題   •  軽量言語   –  上記3つで簡単に対応できない場合はこれを使う   –  特に集計処理とか入ると正規表現ブン回すより簡単だし 再利用しやすい  
  • 4. ユースケース   •  ほとんどがパース・集計処理   –  ログ内の特定の文字をカウントする   –  シーケンスIDをピックアップし、ギャップを検出、カ ウントする   –  特定のIDを時系列で追跡し、クラスタ内での移動 経路を追う   •  その一回だけでしか使わないことが大半なの で、使い捨てのコードを書くことが多い  
  • 5. ゴール   とにかく楽に使い捨てたい   できれば再利用したい  
  • 6. 使い捨て方(1)   適当なディレクトリに適当に書く   •  論外   •  まず再利用不可能   –  場所がわからん   –  何に使ったかわからん  
  • 7. 使い捨て方(2)   git  で綺麗に管理する   •  理想的だが案外面倒   •  どのコードも目的が違うので、ドキュメントの 整理・構造化も必要   •  同一名で投げ込むことができず、名前空間の 管理も必要  
  • 8. 使い捨て方(3)  gist  に突っ込んでおく   •  多くの人が選んでいるであろう方法   •  保存・管理の手間のコストパフォーマンスを考 えるとこれがベスト  
  • 9. テスト   使い捨てコードだったらテストなんていらなく ね?     違います。楽するためにテスト必要  
  • 10. xUnit/TDD  の(個人的な)弊害   •  xUnit   –  クラス必須みたいに見えること   –  使い捨てコードでクラス必須って考えるだけでだるく なる(個人的に)   •  TDD   –  入門書を読むと 100%  原則を守らないといけないよう に感じる  
  • 11. nose   •  色々便利な特徴はあるが、使い捨てコードに おける大きな特徴は2つ   •  テストクラス作成が不要   –  test_*.py  としておいて、def  test_*:  というメソッド を書いておけば nosetests  で自動実行できる   •  nose.tools     –  eq_(a,  b)  だけでほぼ全てまかなえる  
  • 12. テスト=ドキュメント   •  頭の中で設計したら、ドキュメントじゃなくテス トとしてdumpしておくこと   •  どうせパースだけなので、実際の入力データ から数行ひっぱってくれば十分   •  網羅性を追求しない   •  品質を追求しない   •  実際に動かしてこけたら、その都度こけた入 力をテストに追加  
  • 14. エキPy  11  章より   テストのメリットは4つ   1.  ソフトウェアのリグレッションの防止 2.  コード、の品質の向上 3.  最適で低レベルなドキュメントの提供 4.  よりすばやく、信頼性の高いコードの生産
  • 15. エキPy  11  章より   テストのメリットは4つ   1.  ソフトウェアのリグレッションの防止 2.  コード、の品質の向上 3.  最適で低レベルなドキュメントの提供 4.  よりすばやく、信頼性の高いコードの生産   使い捨てコードにおいては3,4  だけで十分
  • 16. テストもgistにアップロードする   •  gistは一つのコンテンツに複数ファイルアップ ロード可能   •  テストも一緒に突っ込んでおく   –  テストを読めば何をするコードか大体わかる   –  別の環境(Mac  -­‐>  Linux  など)でも動くかどうかテス ト可能  
  • 18. 実際の入力データ   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  
  • 19. やりたいこと   •  ログに出力されているログレベル(INFO,   WARN,  ERROR,  …)  を集計したい   •  「それ awk  でできんじゃね?」とは言ってはい けない  
  • 20. テストコード   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))! 入力データをベタ貼り  
  • 21. サンプルコード   def parse(line):!     arr = line.strip().split() !     log_level = arr[2]!     return log_level!  
  • 22. テストは通るが実際には動かない   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  
  • 23. 失敗した行をそのままテストに追加   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))  
  • 24. 失敗した行をそのままテストに追加   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))  
  • 25. コードもIndexErrorに対応   def parse(line):!     arr = line.strip().split()!     try: !         log_level = arr[2]!     except:!         log_level = '_NULL'!     return log_level!