私信:CS にも過渡現象論みたいな位置づけの理論があると良いのかも?みたいな…
この記事は、この方のつぶやき
あと、前のツイートとは関係ないですが、副作用さえ無ければいいという考え方、妹さえいればいい。という発想と同じな気がするんですよね、手段が目的化してるというか。。。
— も~ひずむ (@yuwki0131) 2017年11月14日
に対する、@pokarim さんの、
— ぽかりむ (@pokarim) 2017年11月14日
Immutability にこだわる方向にも限界があると思いますね。
— ぽかりむ (@pokarim) 2017年11月14日
immutability の重視とか副作用の排除の目的はなんだったのかというと、やっぱりモジュール性の向上というのが考えられると思う。
— ぽかりむ (@pokarim) 2017年11月14日
この辺りのツイートに、私がこう、ぽやん…
.oO(…この辺りは、電気工学の過渡(現象)と定常、みたいに理論を二本立てにするのが良さそう、みたいに思っています…) https://t.co/jAa5gALzos
— hkoba (@hkoba) 2017年11月15日
先日の過渡現象と定常の話にぴったりな話題を話しておられたので…
— hkoba (@hkoba) 2017年11月17日
この表計算の、再計算が走ってセルが順次書き換わっていく過程と、
全ての計算が完了して全セルの値が確定した状態の関係が、
過渡現象と、定常状態の関係に似ているなぁ、という話なのです。
(他に効率の観点もありますが https://t.co/g1VClYp3uo
と感想をぶら下げて始まった対話の、お返事です。
ぽかりむさんへ
私の考えがどんなものか、なぜその考えに至ったか(特に Immutability についての考え方の由来)、それに基づいて行動した結果どんな経験を得られたか、少しお話させて下さい。(結論は無いです)
まず私の考えを改めて文章にしますと、
『電気工学では、定常状態の理論と、過渡現象の理論を別立てに分けている。この結果、それぞれ定常状態の理論は単純で初等数学でも理解しうるものになり、過渡現象の理論は複雑な現象を誤魔化さずに記述し切ることが可能になっている』
『同様の分け方が、プログラミングや計算機科学の世界にも、有って良いのではないか。何故なら、理論とはモデル化であり、人の理解を助けるためののものであるから。一つのモデルで最終状態と途中の変化を両方記述しようとして、かえってモデルが複雑化して理解しづらくなったら、本末転倒』
『特に、Immutable な世界と、State の Mutation を取り扱う世界に関して、理論を分けて持つことが、同様のメリットを生むのではないか』
です。
なぜこの考えに至ったか
源流らしきものは2つありました。
一つ目は25年ほど前、電気工学科の(落第寸前の)学生だった頃のこと。OPアンプの負帰還増幅回路の動作を習う時に、教授から示唆された仮説です。雑でおぼろげな記憶ですが、
『そもそも人間は時間変化する現象を(言葉だけで)イメージ化して理解することが苦手なのだ。負帰還増幅回路を理解しようとする時も、「入力が増えて、増幅された出力が出て、それが遅延されて入力に入って…」のように時間変化に沿って理解しようとすると、かえって難しくなるのだ。それよりも、「- 端子と + 端子の入力の値が最終的に一致するように動く」というゴール状態から理解する方が優しい』
といったものでした。(当時は、ほえ〜、そういうものか〜、たしかに逐次で追うのは大変そうだな〜、位の受け止め方でした) 最終状態の不変式で理解するのが楽だ、という考え方だったのかもしれません。
もう一つは院の輪講で、確か…タネンバウムの分散システム関連の書籍を学んだ際に触れた、Immutability の効能の話です。
(分散)並列計算をまともに(?)機能させるためには、レース状態の入り込む個所を減らす・無くすことが大事で、そのためには(並列システム間でやり取りされる)public/published な値を全て Immutable とするのが最も単純で効果的である…
みたいな議論だった気がします(用語や論旨は脳内で補っているので雑です。書籍にそう書いてあったかどうか自体、ちょっと怪しいです。輪講の中での議論で出た話題だったかもしれません)。値は Immutable にした上で、更新はディレクトリ(インデックス)上の参照の差し替えで表現する、分散ファイルシステムなどでオーソドックスなやり方の話です。(同時期にデータベースの授業でトランザクションの話も聞いていたので、その影響もありそうです)
どんな経験が得られたか
この 20年ほど、仕事で私がデータ保存を伴うプログラムを作る時は、原則として Immutable なレコードの追記型ストア+その上のインデックス、という構成を取ってきました。(と言っても、誇れるほどの仕事の幅はないのですが…) インデックスの貼り方に頭を使う必要はあるものの、大事な一次データが絶対に消えない(最悪人力でサルベージできるし、インデックスもログからリビルド出来る)安心感は、大きかったと感じています。ですから、(システムから観察できる published な) 値を Immutable にすることのメリットは大である、と考えます。
それに対して過渡的な現象、状態の変化の過程をどう記述するべきかですが、変化の記述にまで Immutability を追求すべきかどうかについては、正直、そんなに拘らなくても良いのでは?と感じております。(最初に挙げた、published な値を Immutable にすることの、実用的なメリットと比べて、です) マルチスレッドプログラムで言えば、スレッドのスタックに乗っているprivate で一時的な値と、そこから(共有メモリやチャネルを使って) 他のスレッドから見えるように公開した値では、扱いを変えてよいのでは、という考えです。
もちろん、プログラムのモジュール性を向上させるために一時的な値であっても破壊を避ける、読みやすさを助けるために変数の使い回しを避ける、という話はあるでしょう。しかし、いずれにせよ、マシンのレジスタは使いまわされているし、L1, L2.. キャッシュも使いまわしてこそ性能が出るものです。命令のスケジューリングと記憶域の使い回しを、人の書くプログラムから完全に捨象せんとする努力が、(尊いことでは有るけれども) メリットだけなのか (デメリットはないのか?) という、立ち止まって振り返ることも大事なのではないか、そのように考えています。
間接的なヒントとして…10年前の私は SQL を書くのが嫌いで、ORM を使うべきだと考えていました。その後、複雑なクエリを ORM に吐かせることに疲れ、かつ SQL を書く力も上がった今では、 ORM を使うより SQL を書くほうが手堅い、と感じるようになっています。
結論なしの尻切れトンボですが、あまりお待たせするのも申し訳ないので、このくらいで終わりにします。ぽかりむさんの考えとは違うところもありそうですが、何かのヒントにでもして頂ければ幸いです。ではでは〜♪
SSLのクライアント証明書の扱いについて誤解を正して貰えて有難かった話
SSLのクライアント証明書を使って接続相手の認証まで行いたいケースで、 クライアント証明書が self signed certificate だと危ないのでは?という疑問を呟いたら @satoh_fumiyasu
さんに突っ込んでもらえて、私の誤解を正してもらえました。…誤解したままだったら…と考えると、肝が冷えます。さとうさん、有難うございました。
.oO(…SSLのクライアント証明書って、オレオレにしちゃったら、認証として全く意味が無くなると思うのだけど、違うのかしら?…)
— hkoba (@hkoba) 2017年10月4日
違いますね。
— ふみやす@シェルまおう(自称でない)🚲 (@satoh_fumiyasu) 2017年10月5日
むむむ?どんな使い方だと、オレオレなクライアント証明書でも安全になるのでしょう?
— hkoba (@hkoba) 2017年10月5日
サーバーへのログインユーザ名にクライアント証明書の CN を使うというケースだと、他の人が勝手に同じ CN でオレオレ証明書を作って接続してきても、通ってしまうのでは、と思うのです…
当然、条件はあります。要は相手が示した証明書の署名が、自分の信頼している CA の証明書で発行したもの(かつ期限内、CRL などに載ってない)であればいいわけですから。オレオレ証明書でも同じです。
— ふみやす@シェルまおう(自称でない)🚲 (@satoh_fumiyasu) 2017年10月5日
認証する側が自己署名証明書を信頼している (信頼する CA 証明書として設定してある) なら大丈夫だったかと。実装や証明書に付与した何かしらの制約(constraints)に依るかも。
— ふみやす@シェルまおう(自称でない)🚲 (@satoh_fumiyasu) 2017年10月5日
なるほど、該当 self-signed cert 自体をサーバー側に信頼する CA として登録する、と…
— hkoba (@hkoba) 2017年10月5日
確かにそこまですれば、別人が勝手に同じ CN の証明書で繋げてきても拒否できますね。
ありがとうございます~
その後、実際に postgresql で試してみた所、CNだけ同じの成り済ましクライアント証明書で接続した時はこんなエラーが出る、と確認できました。
psql: SSLエラー: tlsv1 alert unknown ca
じこひはん
クライアント側も CA として扱う、という発想が出てこなかったのが、自分の頭の硬い所だったなぁと。お互いがお互いを Certificate Authority として信頼する…おお、peer to peer だ。どうも私の発想はサーバーに偏りすぎてたようです…
ssh 先で sudo 実行するコマンドに渡すワンライナーを zsh に quote させる
いつもの通り、ツッコミ歓迎です。
その quote, zsh に任せると幸せ…かも??
笹田さんのこのツイを見て、おっと zsh 宣伝チャンス、と。
なんで a b 消えちゃうのん?
— _ko1 (@_ko1) 2017年9月29日
$ sh -c "echo a b; echo c d"
a b
c d
$ ssh host sh -c "echo a b; echo c d"
c d
ssh host -t sudo ... ってやりたかったので、それがいかんのですよね、残念ながら
— _ko1 (@_ko1) 2017年9月29日
zsh には変数展開時に結果を自動で quote してくれる (q)
, (qq)
, (q-)
...
という機能があります。
この機能を使って、 ssh 先の sudo に渡すスクリプトの quote を試みます。
(ローカル側は zsh ですが、 リモート側は /bin/sh で OK なのが味噌です)
まず最初の echo の例
% cmds=( "echo a b" "echo c d" ) % ssh -t localhost sh -x -c ${(qqj/&&/)cmds} + echo a b a b + echo c d c d Connection to localhost closed. %
- ssh に渡す
${(qqj/&&/)cmds}
は以下の2つの組み合わせ(qq)
による自動 quote(j/&&/)
はjoin("&&")
ssh に渡される実際のコマンドラインを知りたい場合は以下のようにすると良いです。
% print -R ssh -t localhost sh -x -c "${(qqj/&&/)cmds}" ssh -t localhost sh -x -c 'echo a b&&echo c d' % print -l ssh -t localhost sh -x -c "${(qqj/&&/)cmds}" ssh -t localhost sh -x -c 'echo a b&&echo c d' %
-R
は escape の抑制-l
は引数を一行ずつ出力する
ssh 先の sudo に渡すワンライナーを zsh に quote させる
次は以下の二行を一度の ssh に渡すよう、quote してみます。
sudo ruby -e 'puts "Hello uid=#{Process.uid}"' sudo ruby -e 'puts "Again uid=#{Process.uid}"'
上記の2つのワンライナーを配列に格納するには、
各々を '...'
で quote する必要が有るので、
cmds=( 'sudo ruby -e '\''puts "Hello uid=#{Process.uid}"'\''' 'sudo ruby -e '\''puts "Again uid=#{Process.uid}"'\''' )
これを手で入力すると大変ですが、幸い zsh には現在の入力を丸々
一本の文字列へと quote するコマンド M-'
quote-line
が用意されているので、これを使いましょう。
範囲指定版の M-"
quote-region もあります。
出来た配列 $cmds
を使って ssh してみます。
% ssh -t localhost sh -x -c "${(qqj/&&/)cmds}" + sudo ruby -e 'puts "Hello uid=#{Process.uid}"' [sudo] hkoba のパスワード: Hello uid=0 + sudo ruby -e 'puts "Again uid=#{Process.uid}"' Again uid=0 Connection to localhost closed.
quote-line を zsh の関数として書いてみる
使い捨ての作業なら quote-line で一行ずつ quote しても良いのですが、 スクリプト化したい時には不便です。引数それぞれを quote して繋げた文字列を 返す関数を定義してみましょう。
% function zquote { print -R "${(@qq)argv}" } % zquote sudo ruby -e 'puts "Hello uid=#{Process.uid}"' 'sudo' 'ruby' '-e' 'puts "Hello uid=#{Process.uid}"' %
… (q-)
の方が、読みやすい出力になって良いですね。
% function zquote { print -R "${(@q-)argv}" } % zquote sudo ruby -e 'puts "Hello uid=#{Process.uid}"' sudo ruby -e 'puts "Hello uid=#{Process.uid}"' %
これを使うと、先の例はこう書き直すことが出来ます。
cmds=( "$(zquote sudo ruby -e 'puts "Hello uid=#{Process.uid}"')" "$(zquote sudo ruby -e 'puts "Again uid=#{Process.uid}"')" )
zquote の出力を一本の文字列にする必要が有るので、 "$(zquote ...)"
のように呼び出し全体を ".."
で囲っています。
…面倒臭くなってきたので、この辺で!あとは皆さん、ご自由に!
…なお、もっと複雑なケースの時は私は tcl + sshcomm を使います。 が、その話はまたいつか…
参考
"--name <SPC> value" style options considered harmful for end-user scripting.
追記20170914朝JST: 以下の議論では簡単のため --name value
に話を絞り -o VALUE
形式への言及を省略したが、後者においても辞書が必要となる点は変わらない。自明とは思うが念の為…
20170914昼JST: タイトルtypo 修正 ><
ストーリー
- 以前あなたはプログラム A を書いた。それは職場で同僚たちが日々使用している。今のバージョンにはオプションが 5 個ほどある。オプションは今後も増え続けるだろう。
- 同僚たちも、あなた程ではないが、省力化のための簡単なスクリプト程度は好んで書く人たちである。
- あなたの書いた A は、同僚の書いたスクリプト B(それは例えば日々の仕事を定期実行するためのバッチや、よく渡すオプションを予めまとめたラッパースクリプトである)から呼び出されるようになった。
- 同僚のスクリプト B を起動する時に、そこからあなたの A へ、いつもと違うオプションを渡したくなる時がある。だから B は殆どのオプションをあなたの A に素通しで渡したい。
- ただし、 B 独自のオプションも持ちたい。
- 同僚たちは (B だけでなく) 元々 A のユーザーでもあるので、あなたの A のマニュアルを読んでいる。そこにオプションが
--name value
(name と value の間のスペースに注意。--name=value
ではない)の形式で書かれている場合、ユーザーのメンタルモデルにはオプションが--name value
形式で刻まれる。だから B も、その仕様に従うだろう。- 何より、職場の Wiki に日々書かれるコピペ用のコード例が
--name value
スタイルで書かれるだろう。
- 何より、職場の Wiki に日々書かれるコピペ用のコード例が
“–name value” オプションの切り出し処理は辞書を要求する
- あなたの A が、オプションを
--name value
形式で受け取る場合、--name
に続く次の引数が、オプションの value か、あるいは無関係な別の引数であるかは、 name 次第となる。つまり任意の引数列からオプション列を切り出すには、 オプションの名前を網羅した 辞書 を用意する必要が有る。 - つまり同僚のスクリプト B にも、あなたのプログラム A の全オプションに関する辞書が必要になってしまう。
辞書で対応付けを作った箇所には、変更のバケツリレーも付いてくる
- 同僚のスクリプト B に対して、誰かが更に別のラッパースクリプト C を書く時もある。 C にも A のオプション辞書が必要になる。 D, E, F… 止まらない保証はない。
- あなたが元のプログラム A を改良してオプションを追加したら? スクリプト B と C のオプション辞書にも改良が必要になる。オプションが増減する度に、この改良(?改良なのか?)の連鎖が起こる。これバケツリレーだ、 WWII の空襲記録で見たこと有るやつだ…
誰かが、この無限の連鎖を止めなければならない…誰かが…
一案としては、 A にオプションの辞書一覧を機械可読な形式で出力する機能を用意する。 B を書く人には、それを読み込んで処理する機能を書いてもらう。これなら処理を書くのは一度きりで済む。 ただし…ちょっとスクリプトを書けるようになり始めた位の人に、これを要求するのは、 ハードルが高すぎる。職場全体で少しでもスクリプトを書く人数が増えて欲しい時には、 ハードルを少しでも下げたい。
もっと簡単に書けて、処理も軽く、漏れがなく、一度書けば済む…そんな、貧者のアプローチがないか?
どうすればよい?
既に書かれた 他人のプログラムは、諦める とする。自分たちがこれから書く、職場限定のプログラムに限り、 オプションの与え方に一律の制限を課す(標準化する)。例えば:
このように制限をすれば、辞書抜きで機械的にオプション列を切り出すプログラムを書けるようになる。辞書と異なり、この処理は一度書けばずっと使える。プログラム A にオプションが増えても、 B のオプション切り出し部分に変更は必要ない。 (もちろん、 B 自身のオプション名と被った場合は問題だが、少なくとも切り出しプログラムの問題ではなくなる)
規格化・標準化は自動化・省力化の友
諸君、私は自動化が好きだ。愛していると言ってもいい。
自動化出来るか否かに比べれば、使い方が流行のプログラムに似ているかなど、どうでも良い。
オプションを --name value
のように書きたいばかりにあなたを辞書変更の無限地獄へ巻き込もうとする者たちからは、出来るだけ離れていよう。
多分、連中は雇用対策でそうしているのだろう。我々の人生はもっと有益なことに使うべきだ。
オチ
ラッパーから2個以上のコマンドを呼ぶ時、困るよね…段々辞書が欲しくなるよね…
--
を書いてもらう?う〜ん…
『実行可能なモジュール』設計パターンについて…あるいはサブコマンドを持つコマンドを私はどう作るか
『実行可能なモジュール』と私が勝手に呼んでいる、ある種の設計パターン/コーディングイディオムについて、 私なりの意見を整理しておこうと思います。
(この設計パターンは Perl 以外の言語でもよく見かけるので、既に名前が付いているのでは?と予想しています。 教えて頂けるとありがたいです)
pm に shbang と unless caller を書く
unless caller
Perl スクリプトで、ファイルの最後にこんなコードを見たことが有る人は、いるでしょうか?
unless (caller) { ...何らかの処理... }
この unless (caller) {...}
のブロックは、このファイルをコマンドとして直接起動した時だけ
呼ばれる処理を記述したものです。私が初めてこの種の書き方に触れたのは 1996頃の
Perl/Tk の文脈
で、
MainLoop unless caller;
と書いて
- このスクリプト自体が起動された時は、
Tk::MainLoop()
を呼び出す - それ以外のケース、例えば上記スクリプトを別プログラムからクリップボード経由で直接 eval したり
do
などでロードした時は、何もしない。
という動作を実現するために使われていました。
myscript.pl の代わりに MyScript.pm
さて、この unless caller
というイディオムは、 Tk に限らず一般の Perl スクリプトでも役に立ちます。
例えばスクリプトを書く時、 myscript.pl
の代わりに MyScript.pm
という名前にして package
文も書いて、
正当なモジュールとしてロードできるようにします。その上で、最後に unless caller
で、コマンドとして
起動された時の処理を書くのです。
例えば以下のように書きます。
#!/usr/bin/env perl package MyScript; ... unless (caller) { my @opts; push @opts, split /=/, shift(), 2 while @ARGV and $ARGV[0] =~ /=/; # XXX:手抜き my $app = MyScript->new(@opts); $app->main(@ARGV); } 1;
すると、この MyScript.pm
は (chmodして) 直接コマンド行ツールとして起動するだけでなく、
モジュールとしてロードし、一部のメソッドだけを呼び出すことも出来るようになります。
# コマンドとして起動し、 MyScript->new(x=>100,y=>100)->main('foo','bar') を呼ぶ % ./MyScript.pm x=100 y=100 foo bar # モジュールとしてロードして new し、メソッド foo() を呼ぶ % perl -I. -MMyScript -le 'print MyScript->new->foo'
サブコマンドをメソッドに対応付ける
先の例では unless caller
時には MyScript->new->main
を呼ぶように決め打ちしてありました。
ここを
- posix style の long option
--name=value
の列をnew()
の引数にする。--name
のみなら--name=1
として扱う。--debug
みたいに。
- 次に来た引数をサブコマンドの名前に使う。
という動作にすればどうでしょう? こんなイメージです。
# 何らかのテキストファイルをパースして、DB にロードする % ./MyScript.pm --dbname=foo.db import journal.tsv # 上記 DB から特定の条件で検索をする % ./MyScript.pm --dbname=foo.db list_accounts
早速、これを実現する unless caller
ブロックを書いてみましょう。
parse_opts()
は後で定義することにします。
unless (caller) { my @opts = parse_opts(\@ARGV); my $self = __PACKAGE__->new(@opts); my $cmd = shift @ARGV || "help"; my $method = "cmd_$cmd"; # サブコマンドのメソッド名は cmd_... で始めることにする。 $self->can($method) or die "No such subcommand: $cmd"; $self->$method(@ARGV); }
- ここでサブコマンドのメソッド名に接頭辞
cmd_
を付けることにしたのは、 例えばimport
というメソッド名が Perl にとって特別な意味の有るメソッド名で、 これと被ると予期せぬ面倒を生みかねないからです。
発展:任意のメソッドをサブコマンドとして試せるようにする
先の unless caller
ブロックで呼び出せるのは cmd_...
で始まる名前のメソッドだけでした。
これを任意のメソッドまで呼べるように拡張すれば、内部的なメソッドも CLI から
簡単に呼び出して試せるようになります。特に Perl は REPL が弱いので、
これを使えば好きなメソッドを shell のヒストリ・エディタ上で反復的に試せるようになり、
REPL の弱さを補うことが出来ます。
ただし、普通のメソッドは結果を Perl のスタックに返すだけで画面には何も出しませんから、メソッドの結果を
出力する機能も作る必要があります。また戻り値は undef や []
, {}
... を含みますから、
出力時はシリアライザーを通したほうが良さそうです。
以上を考えた unless caller
ブロックは、例えばこんな感じでしょうか>
use Data::Dumper; unless (caller) { my @opts = parse_opts(\@ARGV); my $self = __PACKAGE__->new(@opts); my $cmd = shift @ARGV || "help"; # cmd_ で始まるメソッドがあるなら、そちらをそのまま呼び出す。 if (my $sub = $self->can("cmd_$cmd")) { $sub->($self, @ARGV); } # それ以外でも、メソッドがあるなら、デバッグ目的で実行できるようにしておく。 elsif ($sub = $self->can($cmd)) { my @res = $sub->($self, @ARGV); print Data::Dumper->new(\@res)->Dump; } else { die "No such subcommand: $cmd"; } }
もちろん、もっと強化できる所はあります。
@res
の内容に応じて終了コードを設定すると、シェルスクリプトから使う時に便利になります。- スカラーコンテキストとリストコンテキストをオプションで使い分けられると嬉しい人もいるでしょう。
- 出力時のシリアライザーを JSON にする手もあります
- 引数の文字列が
{...}
,[...]
の形式の時に JSON としてデコードする手も、ありえます。
この辺りに興味の有る方は MOP4Import::Declare の
MOP4Import::Base::CLI_JSON
もどうぞ…
この設計パターンの使いどころ
あまり有効でないケース
先に、このパターンがあまり有効でない状況を挙げます。
- 開発するプログラムの仕様が十分に確定しており、設計に時間を掛ける余裕が有る。
- 開発者が十分に足りていて、モジュールを分割するほど、手分けして並列で開発を進められる。
- 作ったけど使わない、という可能性を考えなくて済む。
この記事で挙げたパターンは複数のサブコマンドを一個のスクリプトファイルに書くので、 複数人で並行開発することは困難だろうからです。
有効に働くケース
逆に、
- 何を作れば『ビジネス上の要求』を満たせるか分からない、探索的な開発をする必要が有る。
- 顧客が要求仕様をまとめられず、どんなコマンドを何個作ることになるか、全然予測できない。
- それを作っても使うか分からない、業務に投入してみないと何も言えない時。
という状況下で、それでも前進しないと駄目な時には、以下のメリットがあります。
- 最初から OOP 用のクラス・モジュールとして書いているので、いつでも継承してメソッドの挙動を変える、など自由自在に
OOP の技法を投入できる。
- もし初手をコマンドとして書き後からクラスへ括り出す場合だと、そこでクラスの命名で悩む時間を取られる。
- サブコマンドを増やし放題。メソッドへ括り出し放題。あらゆる無茶振りを一旦受け止めるための、汚れ役クラスとも言える。
- CLI ベースで作って、後から Web 経由で (Fat な) Model として使う、という手も有る。
- モジュールになっているので、テストも書きやすい。
とは言え、これで作ったものは巨大な一枚 pm になりがちなので、機会を見つけて整理をするのが大事、ではあります。 (…吐血…)
なんでこれ書いたのか
songmu さんの blog 記事
私も UNIX 哲学は好きで、概ね同意できる、と思いつつ…
自分が Perl で仕事のツールを書く時によく使うスタイルの話も書いておいたほうが、誰かの役に立つかもな〜、と思ったからでした。
おまけ
parse_opts()
の例です(既存のコードを解説用に簡略化したものなので、動作は未検証です)>
sub parse_opts { my ($list, $result) = @_; $result //= []; while (@$list and my ($n, $v) = $list->[0] =~ m{^--$ | ^(?:--? ([\w:\-\.]+) (?: =(.*))?)$}xs) { shift @$list; last unless defined $n; push @$result, $n, $v // 1; } wantarray ? @$result : $result; }
更新履歴
業務向け情報共有サービスに私が求めるもの
Markdown Night 2017 Summer - connpass で色んな話を聞けて刺激を受けたものの、その後の懇親会で自分の問題意識・欲求がいまいち上手く伝わらない・伝えられないことに気づいたので、自分なりに整理(?)しておきます。
TL; DR. 社内マニュアル向けシステムには、情報の構造化と新陳代謝を支援して欲しい
10年以上前から Wikiの一つを チーム内マニュアル書きに使ってきたものの、長年不満を感じている。 Wiki も色々有るし、更にもっと良さげな情報共有サービスも色々出てきたので、 実は既にこの問題は解決されているのかもしれないけれど… とにかく自分の問題意識を書き下しておきたい。
オーソドックスな Wiki の特性
- ページはページの名前で識別されリンクされる (例外: tcler’s wiki)
- ページの追加は、既存のページに(新ページへの)リンクを書くことで始まる
- 既存ページと新ページの間には、単にリンクが貼られている以上の(システム的)関係は無い(例えば「このページは元ページの子ページである」という情報は、システム内には記録されない。 (例外: redmine )
- ページの並び、という概念も存在しない。ゆえに、親ページ上に並んだリンクの順番はあっても、それに基づいて子ページ間に「前へ←、→次へ」のようなページャを配置することは、手作業になる (…sphinx ベースの wiki ってあるのかしら?)
- ページ内については wiki 記法で章立て・構造化をすることが出来るが、これが検索の単位と連動するとは限らない。 (例外: mediawiki)
要するに、Wiki で書かれたページ群は、(Wiki 奉行でも配して特別な注意と労力を払わない限り) ランダムに更新され成長してゆくグラフ・ネットワーク に近い構造に至る。
ページ群がランダムグラフに至ると、誰がどう困るか
- 新人教育で、教える側が困る。
- 指導用の教材は一本道に整理したいが、ページャのリンクを手書きはやってられない。
- 教わる新人が困る
- 読むべき資料が一本道の木構造なら、要する努力も見積もれるが、グラフでは底なし。
- ベテランも日々困る
- 検索をセクションで部分木に絞り込めないので、全マニュアルから各人が案件ごとに書いたアンチョコが無限にヒットする。が、どれが正しいか、今のオススメはどれか、今見ているページが古くないか…全くわからない。
- 検索が弱いので、重複した内容の、けど少しずつ違うマニュアルを各人が好きに書くことになり、余計に検索を悪化させる。
- 事例を既存のどのページにぶら下げるかべきか、どんな名前のページにするべきか、新しい事例ほど迷って時間を食う。けど、書き残さないとあとで困るので、とりあえずの場所にぶち込むことに。そして整理されない。Drag & Drop で移動したい…
- 更新のタイムラインを見る暇があれば、誰かがマニュアルを書き換えたことは把握できる。が、後で実際にマニュアルを見ている時に、それが参照するに値するのか、判断する基準がない。(例えば業務システム revision X のリリースより前なのか後なのか…) いつ頃誰が書いた記述かわからない(要するに git blame が欲しい)
- 新機能リリース時に、マニュアルのどこを改訂すればよいのか。自分で書いたマニュアルは改訂すれば良いとして、それに基づいて皆が書いたアンチョコは古いまま。どうするか。(機能とドキュメントの対応関係がシステム化されていない)
理想を言えば php.net (docbook) や amon2 (sphinx) のような一本道の 「本」 として構成された公式マニュアルをオンラインで作ることが出来て、かつ、その特定の章に絞った検索が出来て欲しい。それとは別に、その本という枠組みに縛られずに自由にページを足す機能は欲しいが、未分類なページを似たもの同士集めて分類を育てていく支援も欲しい。タグは…破綻させない仕組みがあれば…。重複マニュアルをどう見つけるか…。そして何より、部分的に古くなったマニュアルを、どう見つけて改定していくか…
おまけ
曰く「…多分、この内部構造のため、脳は(情報が)木構造にマップされる階層構造を持つ場合、一番効率よく働く…」 pic.twitter.com/HWzmvfa79A
— hkoba (@hkoba) 2016年2月21日
メモ:mod_fastcgi で FastCgiExternalServer 使いたい時は FastCgiWrapper を Off にしないとダメぽい
Apache2 の mod_fastcgi で 初めてFastCgiExternalServer を使おうとしたらハマったのでメモ。ツッコミ歓迎です。
TL;DR FastCgiWrapper を Off にしないと、他を正しく設定しても 404 Not Found にされてしまう(ぽい)。
プロセスを起動するか、外部で起動したプロセスに繋げるかの判定フラグ
content_handler()
を見ると fr->dynamic
が肝らしい
fr->dynamic を設定する箇所
create_fcgi_request()
のここによれば、 fs == NULL
で判定している。
ファイルが有っても fs が NULL になってた…なんで?
create_fcgi_request()
のこの箇所が fs
を設定している
fcgi_util_fs_get_by_id(const char *ePath, uid_t uid, gid_t gid)
は何してる?
ソースのコメントに曰く>
Find a FastCGI server with a matching fs_path, and if fcgi_wrapper is enabled with matching uid and gid.
fcgi_wrapper (suexec の fcgi 版)が有効になっていると uid/gid 検査が走って、それが常に偽なので NULL が返る。
結局、将来 ~user
でユーザ権限 suexec な fcgi を使いたくなった時のため…と思って FcgiWrapper を On にしてたのが
引き金でしたと。
かんそう
- 外部プロセス型の FastCGI したいときは nginx の方が楽だと思った(こなみ
- gist-it.appspot.com - Embed files from a github repository like a gist便利ね…あとはてブロ普通に script タグ書けるの便利ね。