hkoba blog

プログラマーです。プログラミング言語ミーハーです。ツッコミ歓迎です。よろしくどうぞ(能代口調)

perldebugger (perl -d) を楽に使うコツ的な話

この記事は Perl Advent Calendar 2018 の 12/4 の記事です

Perl Debugger (perl -d) を効率的に使うコツ的な話を書きます。 perl の標準デバッガなんて役に立たないと思っている人のための記事です。

TL;DR step/next は最低限に。break したい個所が予め決まっているなら、デバッグ対象コードにダミー関数の呼び出しを加える手も有る。

Perl Debugger とは

Perl Debuggerperl に標準で添付されたデバッガで、perl 起動時に -d オプションを渡すことで利用できます。使い方は一般的なデバッガとよく似ていて、

  • l 関数名 で関数のソースを表示
  • b 行番号b 関数名 で breakpoint を設定
  • c で実行を継続, s で関数の中までステップ実行, n で次の行まで実行

といった感じです。Term::ReadLine::Gnu などをインストールすると、lb で関数名のタブ補完も効くようになります。 (参考)

h で出るヘルプ画面には沢山コマンドが並びますが、 使うコマンドはそう多くはありません。よく使うコマンドに印をつけたので 参考にどうぞ。

f:id:hkoba501:20181203212405p:plain
perl debugger の help 画面(hkoba のよく使うものに青印をつけてあります)

動的にロードされるコードに breakpoint を仕込むには

そんな Perl Debugger ですが、いざバグ取りに使おうとした時に 肝心のデバッグしたい関数に breakpoint を置けなくて困ることがあります。 この問題は、Perl がその関数が定義されているモジュールをまだ読み込んでいない時に起こります。

(一応、 b load FILE コマンドを使えば、 require でファイルがロードされる所に breakpoint を仕込むことが出来る、らしいのですが、 FILE のパスの扱いで私は失敗してばかり…なので使わなくなってしまいました)

特に Plack や Mojolicious のようなフレームワークを用いた開発で、動的に読み込まれる .psgi ファイルやモジュールの中のコードをデバッガで調べたい場合に、 step/next を打ち続けて絶望する初心者も散見されます。

正攻法で行く場合

このように動的にロードされるコードをデバッグする場合、正攻法では動的ロードを行う側のコードの挙動を理解して、 step/next を打つ回数を減らすことが大事です。

手順としては、

  • そのフレームワークがモジュールの読み込みを行う関数を探し、
  • そのローダー関数に最初の breakpoint を置いて実行、
  • ローダー関数に達したら、おもむろに r で return して自分のコードを実際に読み込ませる
  • その後、自分のコードに breakpoint を置く

という流れになるでしょう。以下の例は、Mojolicious から呼ばれる startup をデバッグする流れです。

逆転の発想:breakpointを置くためだけのダミー関数を使う

ただ、上記のやり方は個々のフレームワークの内部実装に対するある程度の理解が必要になるため、 準備に時間がかかりますし、正直面倒です。

それよりも、もし breakpoint を仕込みたい個所が予め決まっていて、 かつそのコードを書き換えることが可能なケースであれば、 breakpoint を置くためだけの(中身のない)ダミー関数を使う方法が有効です。

すなわち、

  • ダミー関数の名前を決める(例えば main::breakpoint)
  • break したい個所に、そのダミー関数への呼び出しを書き入れる(ただし注意有り)
 # This method will run once at server start
 sub startup {
   my $self = shift;
+
+  main::breakpoint();

   # Load configuration from hash returned by "my_app.conf"
   my $config = $self->plugin('Config');
  • デバッガを起動
  • x sub main::breakpoint {} などしてダミー関数を定義
    • (予めプログラム内で定義しておいても良い)
  • b main::breakpoint でダミー関数に breakpoint を設定
  • c でダミー関数まで飛ぶ
  • r で該当コードに移る

この方法なら実質 c 一発で該当のダミー関数まで到達出来るため、途中のフレームワークの実装に 関する知識は実質的に不要になります。

注意点:ダミー関数を呼んだままでは本番でコケる

ただし、このままでは(ダミー関数は本番には存在しないので)、 本番で動かないコードになってしまいます。ですので、このダミー関数の存在の有無を perl$pkg->can($methodName) で確認してから呼ぶようにすると良いでしょう。

  if (my $sub = main->can('breakpoint')) {
      $sub->();
  }

手順としては、こんな感じです>

なお、自作のコードの場合は、ダミー関数の定義を正式なコードに予め含めておくほうが楽です。 その場合は main::breakpoint ではなく、もっと別の名前空間に入れる方が衝突のリスクが少なくて安全です。breakpoint だけを入れたモジュール XXX::Breakpoint みたいなものを用意しておく手も有るでしょう。

終わりに

Perl Debugger で今どきのコードをデバッグする時に困りがちな『 breakpoint を置くのが大変』問題を、breakpoint 用のダミー関数で軽減する方法について解説しました。

明日は @ytnobody さんです。

(最後しか聞けなかったけど)吉祥寺pm16感想とりいそぎ

昨夜は 吉祥寺pm#16に参加してきました。

と言っても、私は仕事のトラブルで大遅刻!

勉強会って遅刻すると、すっごく行きづらいと言うか、気持ちが落ちませんか? 私は落ちます。でも懇親会の参加者が減ると主催者さんを困らせちゃうし…と思い、会場へ。

そんな落ち気味な気持ちで会場に着き、部屋に入ると、なぜかプロジェクターでゲーム…???

自作ゲームでプレゼンらしく、こりゃ凄いと思いました。

その後は休憩から LT タイム

どの LT も良かったのですが、個人的に一番刺さったのはこの LT、 スタートアップの一人技術者の立場からの採用の話でした。 曰く 『面接で高印象だった人に、お試しで働いてもらう』 作戦です。

  • 転職か副業を希望している人で、面接で高印象だった人に、
  • 業務委託として、お試しで働いてもらう。
  • リモートワーク+週一のミーティング

speakerdeck.com

確かに、マッチするか否かは一緒に仕事をしてみないと分からない部分が大きいので、これは合理的だと思いました。

実際にどんな仕事をリモートワークで依頼しているのかという質問には、(Webの)フロントエンドとスマホのアプリを例に挙げておられました。確かにそれなら仕事として切り出して発注しやすそうだなと。

なお、週一のミーティングは(相手の都合に合わせるので)どうしても土日どちらかに入れることになるそう。 経営・管理側でないと出来ない技だなあと思いつつ、体調管理についてうかがうと、職住近接で8時間?睡眠を死守しているとのことでした。

たまたまその後の懇親会でもお隣の席だったので、幾つもアドバイスを頂けました。

私も(私がお客さんに提供している)技術スタックのコア部分が、自分にしか扱えない、完全なトラック係数1、SPOF になっています。ビジネスの継続性のために、この技術の継承者をどう育てるかという点が、ずっと懸案でした。

それに対して頂いたアドバイスは、ずばり、それって弟子取りですよねと。そして>

相手をよく知らないまま採用して、後から相手の良い所を探しながら教育するのは、大変だから…と。

そうではなく、先に相手の中の「尊敬できる所」「愛せる所」「見どころ」を見出しておく。見いだせた人だけを、 採用する。つまり、採用する側の、「愛する覚悟」が定まった時だけ、採用するように…しないと辛いよねと。

…これは、とても響く話でした…

上赤さん、有難うございました。

吉祥寺pm15 で Runnable Module パターン(仮)の話をしてきました

kichijojipm.connpass.com

私のスライドはこちらです。 (以前書いたこの日記を、整理し直したものです)

hkoba.github.io

かんそう

毎度ながら、プロマネ・チーム作りの話から、PC Engine の開発の話とか、パンチカードの頃から戦ってこられた方が今はRiotjs って話まで…話の幅がすげーって思いました(こなみ

コードを2つに分けて書く - 一つは自分のため、もう一つは顧客のために

はじめに

この記事は、身近なプログラマー仲間に向けて書かれたまとめです。 私的な、身内向けの解説文ではあるのですが、 仕事でプログラムを組む人全般に通じるように書く、つもりです。

主張

私は基本的に Perl プログラマーです。ですが仕事では、 Perl で 1ファイルで全部書けるような小さな案件でも、

  1. Perl で書かれたモジュールと、
  2. (上記を呼ぶ) Zsh で書かれたスクリプト

のように、2つのプログラムに分けて書いたほうが良いと考えています。 この記事ではその理由を解説します。(この考えは PerlZsh の組み合わせ以外にも、応用できます)

どんなコード量・規模を想定した話か分からないと納得できない人もいると思うので、 参考として、この考えに基づいて書いた昔のコードを載せておきます。 (コードの解説はありません。要望があれば書きます) github.com

仕事のコードを2つの層に分けるメリット

1. 適材適所

一つの理由は、言語毎の得意領域の違いです。 例えばファイル名操作やプロセスの単純な起動とデータのやり取りは、少なくとも私にとっては、 Perl よりも Zsh の方が短時間で書け、 かつ動作検証もコマンド行で出来て楽である、といった風に。

ですが、理由はそれだけではありません。

2. 汎用を目指すコードと、汚れ役になるコードをファイル単位で隔離できる

もっと大きな理由は、ファイルを分けることにより、プログラマーとしての自分を納得させるためのコーディングと、 顧客第一でオーバーエンジニアリングを避けるコーディング、 2つのコーディング方針を使い分けることが出来るからです。

言うまでもないことですが、仕事のコードは顧客・案件に特化した定数 (ファイル名・ディレクトリ名・アカウント名・サーバー名…)を扱う必要が有ります。 汎用的なプログラムを書きたいなら、こうした定数を何らかの形で プログラムの外部に追い出す必要が有ります。

早くからプログラムをファイルとして2つに分けておくことにより、

  1. 『汎用性があるよう、キレイに書きたいコードを入れるファイル』
  2. 『定数をハードコードして良い、 汚れ役 ファイル』(雑事吸収層 と呼んでいます)
    • 顧客の今回の案件に特化した、具体性を殺しすぎないコードを書くファイル
    • (この部分は最初はファイルにせず zsh の shell 変数を使った repl 操作で済ませ、徐々に shell関数へ、そしてスクリプトファイルへとまとめる…でも有りです)

という風に、2つのプログラムで別々のゴール・価値基準を追求できるようになる、という考えです。 一つのプログラムで満たそうとすると無理が出るので、別々のプログラムに分ける、ということです。

脱線しますと、私がこの方針に至った背景には、よく知られたフレーズの一つ↓の影響が大きいように思います。2つの直交した問題領域を扱うなら、間にマッピングをかまさないと、片方に漏れ歪みが出るのは自然な現象ですよね。

『計算機科学のあらゆる問題は別のレベルのインダイレクション(間接参照)で解決できる』

この記事によれば誰が言い出したのかは諸説あるぽいですね>

blog.unfindable.net

設定ファイルは無限に拡張され、半端なプログラミング言語に成り果てる。なら最初から…

ここで、定数のハードコードを避けたいだけなら、 yaml や ini, json などの設定ファイルの読み込み機能を提供するだけで十分ではないか? プログラムをもう一つ用意するほどではないのでは?と疑問に思う人も多いかもしれません(要出典)。 これに対する私の意見は、(例え設定ファイル読み込み機能を提供する場合でも) ユーザーに渡す汚れ役プログラムを別途作成したほうが、費用対効果が良い、というものです。

その理由は、

  • 本来、設定ファイルは定数を書くためのもの。ロジック・ダイナミックな値を書くために設計されたものではない。
  • 顧客の事情にもロジックはあるし、ダイナミックな値を使って記述したいことは沢山有る
    • 共通の値(変数にしたい)、条件式と分岐、繰り返し、ワイルドカード・パターン…
  • 故に、設定ファイルで無理にロジックを表現しようとすると、あちこちに Ad-Hoc な eval を行う拡張ルールを定める必要が出る。

ここでもう一つの名フレーズが思い出されます。

「十分に複雑なCまたはFortranの プログラムは全て、後付けで、正式な仕様がなく、バグがてんこもりの、 遅い、CommonLispの半分の実装を含んでいる」

(訳はこちらの脚注より)

Greenspun's tenth rule - Wikipedia

http://practical-scheme.net/wiliki/wiliki.cgi/Lisp:GreenspunsTenthRule

あくまで私の解釈ですが、このフレーズが言う後付の出来損ない common lisp というのは、 設定ファイル機能の成れの果てを指しているのではないか、と思うのです。

いずれそうなる定めなら、最初から(チューリング完全な)プログラミング言語を持ち出したほうが良いのでは、と。

最後に

ここまで読んで下さりありがとうございました。

文章を書くのって、本当に時間がかかりますね…

メモ:Zsh で TSV を読みたいとき、どうするか

(いつも通り、ツッコミ歓迎です)

問題

例えば TSV (タブ区切りテキスト)形式の正誤表があり、そこから SQLite のデータベースファイルへ更新をかけたい… そんな時、皆さんだったらどうしますか? なお、レコード数は1,000行程度とします。

私はこういうケースだと Zsh を使って雑に UPDATE 文の列を作ることが多いです。 ただ、雑に書きすぎると入力の形式によっては上手く行かないので、その辺りをまとめてみました。

TL;DR IFS で分けるな、一行丸々 IFS= read -r line で読み込んでから col=( "${(@ps:\t:)line}" ) で配列に分けよう。

なお、SQLite は文字列をシングルクォートで囲って表現しますが、その中にシングルクォートが出現する時は '' のように二重にする必要があります。 今回は、この処理に以下の関数を使います。この SQL文字列をクォートする仕組みの解説は以前の日記を参照して下さい

function zqsql {
  local val=$1
  print -nr "${${(qq)val}//'\''/''}"
}

部分的にしか動かない方法: IFS を使う

まず、read と IFS の組み合わせを使う方法を書いてみました。 入力ファイル input1.tsv はこんな内容です。

id   old new
1111    ああああ    かかかか
2222    いいいい    きききき
3333    うううう    くくくく

実行

% i=0; while IFS=$'\t' read -r id old new; do
  ((i++)) || continue
  print "update t set name = $(zqsql $new) where id = $(zqsql $id);"
done < input1.tsv

update t set name = 'かかかか' where id = '1111';
update t set name = 'きききき' where id = '2222';
update t set name = 'くくくく' where id = '3333';

残念ながら、このやり方だと次の input2.tsv のように、入力に空文字列が入るケースで困ります。

id   flag1   old flag2   new
1111    foo ああああ    bar かかかか
2222    1   いいいい        きききき
3333        うううう    2   くくくく

実行の様子です。 1111 は正常ですが、 2222, 3333 の update 文は期待と違います。

% i=0; while IFS=$'\t' read -r id _ old _ new; do
  ((i++)) || continue
  print "update t set name = $(zqsql $new) where id = $(zqsql $id);"
done < input2.tsv

update t set name = 'かかかか' where id = '1111';
update t set name = '' where id = '2222';
update t set name = '' where id = '3333';

これは $IFS に(タブなどの)空白文字を設定した場合、複数の空白文字の連なりを一つのフィールド区切りとして認識しているから、でしょう。

# IFS にタブを設定した場合
% IFS=$'\t' read -A foo <<<$'foo\t\t\tbar'
# 配列の中は2要素
% print $#foo
2
% print -l "$foo[@]"
foo
bar
%

# IFS にカンマを設定した場合
% IFS=, read -A foo <<<"foo,,,bar"
# 配列の中は 4 要素
% print $#foo
4
% print -l "$foo[@]"
foo


bar
%

解決策:IFS では分割せずに、読み込み後に分割する

変数展開フラグの s:string: を使いました。 以下 zsh 公式マニュアルの引用です。

s:STRING:
     Force field splitting at the separator STRING.  Note that a STRING
     of two or more characters means that all of them must match in
     sequence; this differs from the treatment of two or more characters
     in the IFS parameter.  See also the = flag and the SH_WORD_SPLIT
     option.  An empty string may also be given in which case every
     character will be a separate element.

     For historical reasons, the usual behaviour that empty array
     elements are retained inside double quotes is disabled for arrays
     generated by splitting; hence the following:

          line="one::three"
          print -l "${(s.:.)line}"

     produces two lines of output for one and three and elides the empty
     field.  To override this behaviour, supply the '(@)' flag as well,
     i.e.  "${(@s.:.)line}".

今回の例に適用すると、こんな感じになります。

% i=0; while IFS= read -r line; do
  ((i++)) || continue

  # 変数 $line の中身を s(split) フラグで分割
  # タブをエスケープシーケンスで \t と書きたいので p (print) フラグを立てる
  # 空文字列を残したいので全体を "" で囲みつつ @ フラグも立てる
  col=("${(@ps:\t:)line}")

  print "update t set name = $(zqsql $col[5]) where id = $(zqsql $col[1]);"
done < input2.tsv

update t set name = 'かかかか' where id = '1111';
update t set name = 'きききき' where id = '2222';
update t set name = 'くくくく' where id = '3333';

謹賀新年/2018目標

あけましておめでとうございます

昨年度ご縁の有った方々、仲良くして下さった皆様、ありがとうございました。

今年も一年、よろしくお願いします。

2017の良かったこと

  • やっと引っ越し先が決まったこと
  • (多少は) fsharp, docker, golang, nodejs, jupyter notebook をかじれたこと…本当に多少だけど…

2017の反省点

  • 若手プログラマーに魅力的な場を提供できず、他社に引き抜かれてしまったこと
  • オレオレ言語の試作が停滞したこと

2018の目標

  • この本の内容の実践を試みる
    マネージャーは「人」を管理しないで下さい。

    マネージャーは「人」を管理しないで下さい。

  • 仕事の手離れ改善する
    • 自分の担当業務を Job Description へと書き起こし、業務内容の言語化(明文化)とモジュール化を進める。
    • タスク毎に、手順書、チェックリスト、(背景の)解説書を分けて書く。
  • OSS作り, Life work に充てる時間を少しでも増やす
    • Tcl/Tk 方面の成果もアピールしていきたい。特に sshcomm と tkhtml3
    • YATT::Lite 、取っつきやすくする(でも誰に?)
    • オレオレ言語の意味論練りのため、試作の数を打つこと
  • 仕事が忙しくなっても生活が荒れないような対策を取る

私信:つい同期と自分を比較してしまう、新人さんへ

先日お話したこと、まとめておきますね。 (以下は医学的な裏付けのある話ではなく、私の個人的な体験に基づくアドバイスです)

考えることも体力を使う、と認識する

まず『(全力で)思考する・考える』こと自体が『脳を疲れさせる』という点に気づくことが大事です。脳に疲れが溜まると、筋肉と同じで、平常時のようには動かなくなっていきます。 (ゲームの HP や MP みたいに考えても良いかもしれませんが、どちらかと言えば、 疲労ゲージ ・ダメージゲージが溜まっていく風に、私は感じます)

これはつまり、『同期と自分とを比較する』ことに脳を使うのも、 『プログラミングの演習問題を一心不乱にこなす』ことに脳を使うのも、 脳を一定量、疲れさせる行為であるという点は、同じだ、ということです。 限られた資源である思考体力を、どちらに割り当てるか、という問題と考えて良いでしょう。

もっと言うと、(私の個人的な経験ですが)、 同じことをグルグルと結論も無しに考え続けた時、脳は物凄く消耗する、 という問題も有ります。無理矢理でも思考に割り込んで、結論を出して思考ループを停止させること、 そして残った体力で現実を変える行動を始めること、その切り替えこそが大事です。

もちろん人間なので、自分より進みの速い同期とつい比較してしまうことは、 ゼロには出来ないかもしれません。でも、そればかり見ていては 肝心の演習に使う脳の体力まで消耗してしまい、本末転倒なのです。

ですから、 如何にして『思考を切り替える』か が、今のあなたの人生のテーマ、 課題、天の?試練なんだ、と考えては如何でしょうか?

思考を切り替える技を身につける

私のお勧めは、頭の中身をメモに書き出すこと、です。 頭の中だけで考えると歯止めがかからないので、代わりに、 実際に紙やコンピューター上に考えをメモや日記として書き出す習慣を 付けましょう、ということです。

書き出すことにはいくつかメリットが有ります。

  • 思考が生む疲れを知覚できるようになる(沢山書くと体が疲れるので、気づきやすくなる)
  • 書く速度に合わせて思考がスローダウンし、その分、脳の疲れが抑えられる (ループ時は消耗を抑えることが大事)

更に、後でメモを読み返すことで得られる効果もあります。

  • 繰り返しに気づける、飽きる
  • 自分の考えていること自体を客観視しやすくなる
    • 事実と、自分による評価とを、分ける・整理するきっかけになる

文章として書く以外に、マインドマップのように、図的に書き出す方法も、良いかもしれません。

書き出したら、それについて考えることは一旦やめる(よう努力する)のが大事です(多分)。また同じことを考えてしまうかもしれませんが、そこで自分を過度に責めず、はいはい、程々にね…くらいにして、思考の切り替えを少しずつ練習してみて下さい。

最後に

それでも挫けそうな時が有ったら…

私の心の支えを貼っておきますね。