新・日々録 by TRASH BOX@Eel このページをアンテナに追加 RSSフィード

2016-05-13

書籍購入:『Unix考古学』

私のように待ち望んでいた(そしていつの間にか忘れてしまっていた)人は意外といるはず。

Unix考古学 Truth of the Legend

Unix考古学 Truth of the Legend

ただし私は「UNIX USER」誌の読者ではなく*1、連載の数年後に『ホゲゆに』→SIMH→「UNIX USER」誌のバックナンバー、という順序で辿りついた口だ。同じような人はいるのだろうか?

本書は、UNIX誕生前夜(1960年代半ば)からバークレーでのBSD開発終了(1990年代前半)までの、UnixUNIXAT&TコミュニティBSDARPANET)の歴史について、分散している様々な資料をもとに再構築して解説した本だ。

様々な資料――当時の論文や、当事者たちが後日に書籍やインタビューで語った話などを探し出して丹念に洗い出し、結びつけと分析を行い、推測する。文字通り、発掘作業を伴わない、文献調査による考古学だといえるだろう。

しかもこれ、元ネタは10年以上前の「UNIX USER」誌での連載をまとめたものなのだが、当時の連載内容から色々と変更されているだけでなく、3分の1ぐらい新たに書き下ろされている。この10年分の最新の研究成果を反映したかのようだ。

連載時にはSIMHを使ったUNIX v7の実習があったが、本書からは外されている。サポートサイト(Github)に当時の校正前原稿その他がアップされており、それらを参照するかたちになる。

書籍購入:『Winnyの技術』

P2Pネタの技術調査にあたり「そういえばWinnyの解説本が出ていたな」と思い当たったので購入。

Winnyの技術

Winnyの技術

直近で知りたかったのは、「通信相手との接続確立の方法」と「TCPベースでのNAT越え」の実例だったのだが、Winnyではその辺を巧妙に回避していたのだった。

うーん、「インターネットに直接接続しているコンピュータや、NAT越しにポートフォワーディングしているコンピュータを含む、多数のコンピュータで構成されたメッシュネットワーク」を前提としているのか。ポートフォワーディングせずにNAT越しにインターネットと接続しているコンピュータ同士が直接P2Pすることはなくて、必ずメッシュネットワーク上の「直接接続しているコンピュータ」ないし「ポートフォワーディングしているコンピュータ」を経由するようにしている。

この方式って、「常時接続・常時稼動」のコンピュータを前提としているようだ。

モバイル機器だとちょっと難しくて、というのも、いくらバッテリーが進化しようとも、バッテリー容量の問題は付いて回るだろうから、常時接続・常時稼動は厳しい。契約形態によっては、通信料金の問題も発生するだろうし。

まあ、ともかく、興味深く面白い本だ。

*1:連載時はコの業界にはいなかったし、プログラミングとも無縁だった。

2016-05-08

ブライアン・カーニハンは写経したか?

ちょっと前の記事だけと、少し気になったので書いておく。

要約するのが面倒なので、内容については上記記事を参照してもらうとして……以下、上記記事を読んでいるという前提で、色々と省きながら書く。

上記記事では、プログラミングの上達にて効果的な手段として「写経」を挙げている。といっても、単なる写経ではなく:

そして筆者自身も、新しいことを勉強するのに、サンプルプログラムをとってきて改造するのをやめて、サンプルプログラムを一度印刷し、一言一句、上から下まで写経すると、驚くほどコードへの理解が深まることに気づきました。

なぜかというと、写経というのは、「なにも考えなくて良い」と言われるものの、人間ですから考えてしまいます。しかも、一言一句、記号のひとつひとつまで、意味を噛み締めながら入力していきます。

すると、サラッとサンプルプログラムを眺めているだけでは気づかなかった細かい法則や作法、サンプルプログラムを制作した製作者の意図などといったものがどことなくつかめてくるようになるのです。

もちろん、最初は意味がわからないなあ、と思って写しても構いません。

それでも、打ち込んでいくと、打ち間違いをしても、「あれ、これは少し変だぞ」と気づくようになります。

「さっきまではこの順番で丸括弧が来ていたのに、ここだけカギ括弧になっていて変だぞ」などということに、本能的に気づくのです。

https://wirelesswire.jp/2016/04/52633/

――正直なところ、個人的見解だが、これを「写経」といってよいものか疑問に思うところだが……。

ここで思い出したのが、K&RのKであるブライアン・カーニハンが『言語設計者たちが考えること (THEORY/IN/PRACTICE)』にて自身のプログラミング言語学習法について述べていたくだり(以下、同書初版第1刷のP.126より):

■言語を新たに学ぶ際には、どのようにするのですか?

Brian:言語を新たに学ぶには、行いたいことと近い内容の例を探し出し、それを使って学習していくのが最も近道になると考えています。例をコピーし、ニーズに合うよう変更した後、それを動作させることで知識を広げていくのです。このようにしてさまざまな言語を触っていると、頭の中が混乱し、ある言語から他の言語に頭を切り替える際に時間がかかるようになります。思い出そうとしている言語が大昔に学んだC言語と似ていない場合、特に時間がかかります。このため、優れたコンパイラが誤った構造や怪しげな構造の警告を出してくれるのは、学習する上でありがたいものとなります。こういった点でC++Javaといった強い型システムを持つ言語も学習に役立ちますし、標準への厳格な準拠を強制するオプションも良いものと言えます。

さて、先のブログ記事での学習法(仮に「写経」式学習法とでも呼ぼう)とカーニハンの学習法(こちらはカーニハン式学習法と呼ぶことにする)は、対立しているのだろうか? 私には、若干のスタンスの違いはあれど、どちらも全く同じことをやっているようにしか思えない(効率の差は別として……)。

そもそも「ソースコードを読み、内容を理解する」とは、どういうことだろうか?

プログラムは2つの姿をもつ。それは「静的構造」と「動的構造」だ。静的構造はソースコードであり、ソースコードによって表現された「プログラムの構造」だ。一方の動的構造は、プログラム実行時の実際の計算の進行(≒実行時の挙動、振る舞い)だ。

動的構造は、人間の目には見えない。プログラムの構造によっては、例えばデバッガを使ってステップ実行することで、動的構造に非常に近い環境を再現することが可能だ*1。だが現実のソフトウェアでは、例えばマルチスレッド・マルチプロセス割り込みなどに起因する「タイミングの問題」など、その方法では再現できない動的構造がある。何よりもソフトウェアの大きさが、動的構造の全て(≒全ての分岐パス)を理解することを許さない。

プログラムを理解する」とは、最終的には「プログラムの動的構造を高い精度で推測できるようになる」ということだ。

つまり、「ソースコードを読み、内容を理解する」とは、「ソースコードという静的構造を理解した上で、静的構造から動的構造を高い精度で推測できるようになる」ということを意味する。

(余談だが、「ヒトの頭では『大規模プログラム』の動的構造を全て理解するのは無理だから、静的構造と動的構造が極力一致するようにして、静的構造(≒ソースコード)から動的構造(≒プログラムの動作)が分かるようにしようぜ」というのが、構造化プログラミングにおけるGOTO文をめぐる話の核心だ*2

静的構造から動的構造を推測するためには、まず、静的構造を正しく理解できなくてはならない。つまり、ソースコードという記号の羅列を、意味のあるものとして解釈できなくてはならない。その上で、静的構造が動的構造にどのようにマッピングされるのか、判断できるようにならなくてはならない。

このように書くと、「ソースコードを読み、内容を理解する」という作業は、あたかも「まず静的構造を理解し、次に動的構造を推測する」という風にキレイに順序化されるものであるかのように見える。

しかし実際のところ、「静的構造の理解」と「動的構造の推測」は、混在した作業だ。静的構造の理解は、使用している言語・ライブラリフレームワークについての土地勘がないと、途端に難易度が跳ね上がる。また、静的構造だけ*3から動的構造を推測することも難しい。現実には、ある初見のプログラムを理解しようとする時、ソースコードを読む作業だけでなく、「ソースコードを読むための技術」で述べられているように動的解析*4を併用することが多い。そして興味深いことに、動的解析の結果を通じて、言語・ライブラリフレームワークリファレンスからはいまいち分からなかった挙動が判明し、静的構造の理解が曖昧だった部分が正しく理解できるようになることがある。

私見だが、「写経」式学習法は「静的構造の理解」先行型のスタイルだ。一方のカーニハン式学習法は、動的解析をやや優先するスタイルだ。スタイルの違いはあるが、しかしどちらも「静的構造の理解」と動的解析を併用している。「写経」式学習法では、ディープラーニングの技術セミナーでの例のように、「写経プログラム実行、ソースコード解説」のような順序で進めている。また、カーニハンが「静的構造の理解」を決して疎かにしてはいないことは、先のインタビューの後半にてコンパイラの警告や強い型システムについて言及していることから推測できる。

個人的に、両者のスタンスの違いがどこから生じているのか、なんとも妄想をかき立てられるものだ。

というのも、カーニハン式学習法のようなアプローチは、例えばかつてAT&Tベル研究所で採用されていた形跡があるのだ。以下、『UNIXカーネルの設計』の序文より:

本書の内容と構成は1983年から1984年にかけてAT&Tベル研究所での教育コース用に準備した資料から生まれてきた。コースはソースプログラムを読むことを目的としていたのだが、ここで、ひとたびシステムの概念とアルゴリズムが理解されているとプログラムの理解は簡単になるという事実を痛感した。UNIXの簡潔さと美しさを少しでも反映するように、アルゴリズムの記述はできるだけ簡潔にした。したがって、本書はシステムの内容の各行を解説したものではなく、それぞれのアルゴリズムの全体的な流れを説明したものである。

UNIXカーネルの設計』はカーネル本なので、背景というか前提条件は大分違うのだが、しかしUNIXカーネルソースコードを読むにあたり、動的解析の代わりに「大まかな全体の流れ」の解説を優先させた上で、その後に「ソースコードを読んで理解する」の段階に進んでいる。(公正な比較ではないことを承知した上で書くが)「写経」式学習法とは逆のアプローチだ。

私は、両者のスタンスの違いは、歴史的経緯によるものが大きいと考えている。

  1. 「便利で手軽で高品質なデバッガ」の普及による、実装スタイルの変化
  2. 「単純で十分に理解されている部品を組み合わせた」時代の終焉
  3. より安全な高水準言語の普及
  4. 宣言的な記述(抽象的な記述)の増加

1番目は、簡単にデバッガを使える環境が整ったことや、そのような環境しか知らないプログラマが増加したことで、実装時に「静的構造の美しさ」とでもいう部分に注力する割合が変化したのではないか、その結果として静的構造を理解する能力にレベル差が生じているのではないか、という仮説だ。現在のアプリケーションソフトウェア開発では、ソースレベル・デバッガの存在は当たり前であるし、大抵は統合開発環境に組み込まれている。デバッガの品質は高く、デバッガ再現する動的構造と実際の動的構造の差異は非常に小さい。このような環境では、デバッグ実行で動作を検証することが非常に容易だ。だから、自身が構築した静的構造に少々微妙な点があったとしても、デバッグ実行で確かめてしまえばよい。しかしデバッグ環境が整っていない場合、プログラムに問題が起きた際に即座に頼れるものはソースコードだけだ。だから、実装の際には静的構造の妥当性や美しさといった要素――すなわち「ソースコードをキレイに記述すること」に注力することで、予めバグを作りこまないように予防すると同時に、後で不具合報告が届いた際にソースコードのみから「静的構造の理解」と「動的構造の推測」を容易に行えるように準備するようになる。これら両者の違いが、プログラムの静的構造に対する姿勢(≒どの程度重視するか?)に影響を与えているのではないか?

2番目は「本の虫: MITがSICPを教えなくなった理由」で述べられている内容と同じだ。かつては、シンプルな言語仕様とスリムな標準ライブラリを用いていた。そのため、開発に用いる言語とライブラリは自分自身の手足も同然だった。この時代を経ている人は、ライブラリをつっつき回す時代になってからプログラミングを学び始めた人よりも、言語・ライブラリフレームワーク・その他ツールを体に覚えさせようとする傾向にあるのではないか、その結果知らず知らずのうちに静的構造を注視するようになっているのではないか?

3番目は、2番目とも関係している。例えば私はC言語が主戦場な人なのだが、C言語使いにとって「プログラムが動作し、意図した結果が得られる」という事実は気休めにもならない。Cプログラマは「それは本当に正しいプログラムか? 偶然、それっぽくいい感じに動いているだけではないか?」と常に疑いを抱く――たとえコードの見た目が正しそうで、問題なく動作しているステートメントでも、警告オプションを高くしてコンパイルした時に警告されないか確かめるし、警告が出なかった場合でも「言語仕様上の問題はないか? 単にこのコンパイラで警告が出なかっただけではないか?」と心配するものだ(だから言語仕様上の微妙な問題に詳しくなるし、コンパイラの「標準への厳格な準拠を強制するオプション」の有無を気にする)。それほど注意深くないと、自身の足を撃ちぬいてしまう。そういった、モダンな言語よりも危険で落とし穴のある言語の経験が長ければ長いほど、ソースコードを注意深く観察する癖が付いているのではないか? 安全な言語を使う機会が増えたことで、観察力が少し落ちているのではないか?

そして4番目。言語・ライブラリフレームワークの高水準化が進み、以前よりも宣言的・抽象的な記述がなされたコードが増えている。実のところ、宣言的な記述から実際の挙動や動作を理解することは難しい(≒理解に必要な知識が多いので、理解できるようになるまでのハードルが上がっている)。非常に乱暴な言い方だが、Java入門文書の当初にて「public static void main(String[] args)」を「呪文」として扱うことに似たスタンスで見慣れぬシンタックスに相対して、「詳細は分からないが、とりあえず目的とした結果を得ている」という水準で済ます癖が付いている人が増えているのではないか? 1〜3番目とハードウェアリソースの向上により、それでなんとかなってしまうケースが増えているのではないか?

言語やライブラリが「手足も同然」から「ままならぬ代物」と化し、コードの記述がより抽象化された昨今では、「静的構造の理解」の難易度が上昇している。かつての、動的解析をやや優先するスタイルにおける「静的構造の理解」への取り組みは、実は徒手空拳も同然であり、今となってはなにかしらの武器が必要なのかもしれない。

また、かつては静的構造を注視し、ソースコードを注意深く観察するタイプだった人でも、1〜4に挙げた環境の変化より、静的構造に取り組む姿勢が軟化しつつある可能性はあるだろう。

そう考えると、「写経」式学習法のように「静的構造の理解」を先行させるスタイルでのプログラミング学習は、現状に合致したものなのだろうか。

*1:実のところデバッガは、プログラムの動的構造を巧妙に再現するツールにすぎない(デバッグ実行中の動的構造は、プログラム本来の動的構造ではない)。この辺は、古い本だが『デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language)』を参照。

*2:さらに余談だが、「静的構造も、大きすぎると理解できなくなるから、そこんとこ工夫でカバーしようぜ」というが、構造化プログラミングにおける分割統治とトップダウン手法の話だ。プログラミング言語の上に抽象層を構築することで、プログラムの各層の大きさを「ヒトの頭で理解できる大きさ」に抑えられるようにすることと、うまく抽象化することで「本当に必要になるまで上下階層のコードを読まなくても済む」という状態にすることの重要性を説いている。

*3:強調に注意。

*4:例えばデバッガ上で動かしてみるとか、printf(3)(≒トレースログ)を仕込んで動かすとか。

2016-04-25

誰も正しいコメントの書き方を知らない

「コメントの書き方」にもある種の理論・思想があり、理論・思想にもとづく「型」があると思っているのだが、「『コメントの書き方』についてそこまで突っ込んだ理解をしている人は驚くほど少ない」と考えておいたほうが無難だろう。

Code Kataは既出だが、Comment Kataは無い。しかし「型」はある。見落とされているだけだ。

――と、下記エントリとその周辺を見ていて思った。

それはおそらく、コードは動作するが、コメントは動作しないからだろう。コードは主役であり、コメントはパセリだ。ともなれば、我々はコードの品質向上については気にかけるが(なんたって主役なんだから!)、コメントについてはなおざりになりがちであるし、時にはおざなりにしてしまうこともある。

なおざりになりがちだからこそ、新人は先輩/教育担当から「コメントを書け!」としか言われない。で、書き方に悩んだ挙句、ソースコードの右端にアセンブラのごとくステートメントごとに細かくコメントを書いて提出すると、そこで初めて「ここは○○にして、そこは××で……」と個々の案件について具体的な注文が返ってくる。だが往々にして「なぜ○○にするべきか?」という理由は明かされないし、その背後にあるだろう理論・思想はもっと見えてこない。なぜならば、「コメントについて明確な理論・思想をもち、それらにもとづく「型」を明示できる」という水準に達している人が非常に少ないからだ。明確な体系を持たないから、経験と勘にもとづいて個別に指摘するしかできないのだ。明確な体系があるならば、それを提示すれば済むし、体系にもとづく「型」があるのならば、まずは「型」に沿ったテンプレートを使わせるところから訓練を始めてもよい話なのだ。非効率な話だと、常々思う。

(「『リーダブルコード』でも読め」と手渡されるほうが、よっぽど親切だ)

また例えば、手元の和書にて、コメントの書き方について「型」といえるほど偏執的に書かれているのは『CODE COMPLETE 第2版 下 完全なプログラミングを目指して』の第32章「読めばわかるコード」だけだ。1章まるごと、50ページほどコメントの書き方について具体例付きで論じている。

他に『プログラミング作法』『Code Craft ~エクセレントなコードを書くための実践的技法~』『リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)』を持っているし、『プログラム書法 第2版』も読んだことがある。どれも良い本ではあるが、こと「コメントの書き方」に限定するなら、どれも『CODE COMPLETE 第2版 下』には一歩及ばない。『リーダブルコード』や『Code Craft』はコメントの良し悪しに関する理論・思想についてしっかり記述されていて、結構いい線いっているのだが(さらに言えば『リーダブルコード』は安価なので薦めやすいのだが)、しかしまだ少し簡潔すぎる。「型」といえるほど具体的な記述は少ない。

和書を見渡しても、これらの本ぐらいだろうか? 「コメントの書き方」に限っていえば、体系的かつ具体的な記述がなされた文書は少ない。

情報が少なく、また優先順位が一段低いとなれば、良いコメントの書き方の技術に詳しい人は少なくなる。ほぼ確実に、「良いコードの書き方の技術に詳しい人」よりも少ないだろう。

そもそも「コメントが無くても読めるようなプログラムを書け」という格言の意味を、どれだけの人が正しく理解できているだろうか?

「コメントが無くても読めるようなプログラム」は「コメントの無いソースファイル」を意味しない。「(ソースファイルにコメントを書かずに済むように、ありとあらゆる合法的かつ妥当な手法を駆使した結果)(そのコードを共有する文化圏の基準で)最小限のコメントのみが、メンテナンスしやすいスタイルで記述されているソースファイル」のことだ。

コメントの量を減らすには、まず「コメントすべきでないもの」を取り除き、次に「コメントの代替となる何か」に置き換えることになる。この結果、ソースファイルには「コメントすべきもので、かつ代替となるものが存在しないもの」のみがコメントとして残ることとなる。

では「コメントすべきでないもの」から見ていこう。

コメント以前に、ソースファイルはas is、つまり「現在の姿」を映し出すものでなくてはならない。ソースファイルの構成要素はソースコードとコメントだ。コメントも「現在の姿」を映し出すものでなくてはならない。

つまり、ソースファイルの内容とは無関係のコメントは書いてはならない。先に挙げた記事でいうなら、「その場しのぎの処理」は(文章表現は別として)セーフだろうが*1「仕方がない」はアウトだ。それはアンタの感想にすぎないだろう。

また、変更履歴のような「過去の姿」に関するコメント、特に「// ○○対応のため追加(2016-04-25:eel3)」のようなコメントも書いてはならない。この手のコメントは、驚くほど早く劣化する。下手すると次のコミットにて邪魔な過去の遺物と化してしまうだろう。そういう情報はソースファイル中に書くのではなく、バージョン管理システムコミット・ログや、プロジェクト管理ツールのチケットへのコメントなど、履歴の関する情報を管理するための適切なツールを用いて記録するべきだ。個人的には、分散バージョン管理システム(というかGit)の普及で、ようやく撲滅できる環境が整ってきたと考えている*2

「コメントすべきでないもの」は、実はこれだけだ。世の中で「コメントすべきでない」とされているもの*3の大半は、実際には「『コメントの代替となる何か』に置き換える」ことが可能なコメントに該当する。

鍵となるのはDRYDon't Repeat Yourself)だ。コメントを書くことで、ソースファイル中の特定のソースコードとの間に「記述の二重化」が発生するのなら、そのコメントを削除できないか、一度は検討するべきだろう。

なぜならば、「記述の二重化」が発生することによって、ソースコードを変更する際に、対となっているコメントも変更して同期させる必要性が生じるからだ。2ヶ所メンテするよりも1ヶ所の方が楽だし、もう片一方を変更し忘れることもない。

この時、コメントの代替――すなわち対となるソースコードの表現、特にステートメントの表現を見直すことは、鉄板ネタだ。世の中の大概の「コメントを減らすべき」という主張は、ステートメントの表現の見直しを指している。

というのも、ソースコードの構成要素をあえて「式とステートメント」と「データ構造」に分けるとすると、「式とステートメント」についての変更は頻繁に発生する。なので、もしステートメントAに密着したコメントaが存在するなら、Aを変更する際にaも変更しなくてはならない。そんな作業を何度も繰り返すのは無駄以外の何者でもないのだ。

かくして、ありとあらゆる手段でステートメントの可読性を向上させることで、ステートメントに密着したコメントを取り除こうということになる。適切な名前・適切な順序・シンプルなアルゴリズムの採用……典型的な「良いコードの書き方の技術」だ。

コメントの書き方でよく言われる「HowよりWhat、WhatよりWhy」的なアドバイス*4でいえば、ステートメントに密着したコメントは「How」に該当する。ステートメントの可読性を向上させることで解読補助用のコメントが減るため、「How」のコメントも減る。

ステートメントについての「What」は、基本的に要約コメントだ。関連するステートメントの塊について、何をしているのか、ドメインに着目して書く。要約コメントの典型例が「ファイルのヘッダ・コメント」と「関数メソッドのヘッダ・コメント」で、要約コメントの中では最も多く書かれている。関数メソッドが長くなった場合、中身のフェースごとに1〜2行ずつ要約コメントを足すことがあるが、稀な部類に入る。何を行っているかぱっと見では分かりにくいステートメントの塊について、ステートメントの可読性向上だけでは対応できない場合に、要約コメントを足すこともある。

関数メソッドのヘッダ・コメントには濃淡がある。古きよきJavaのgetterやsetterのようなルーチンは、名前から役割が明白であるし、中身もシンプル極まりないので、ヘッダ・コメントは書かれない。モジュール/クラス内部でのみ用いられるルーチンでは、概要を述べた1行コメントで済ますことも多い。外部に公開されるルーチンには、もう少し詳しい情報を提示するかもしれない。不特定多数の人が利用するライブラリともなると、さらに厳しくなる。

特に外部公開インタフェースに関しては、「コメントではなくテストコードやサンプルコードで」という主張もあるが、それでは不十分であるケースもある点は押さえておくべきだろう。例えばシステムコール由来のエラーが発生した際のルーチンの振る舞いについて、テストコードやサンプルコードにて、どのように読者に伝えればよいのだろうか?

思い起こしてほしい。我々はUnixシステムコールを叩くとき、サンプルコードだけ読むだろうか? 否。manとサンプルコードの双方を読むはずだ。Unixmanは、不特定多数が利用するライブラリのドキュメントの典型例だといえる(その良し悪しは別として)。

外部公開インタフェースの解説について、テストコードやサンプルコードで済ますことが可能なのは、そのコードの作者と利用者の双方に「暗黙の共通認識」が存在するケースであることが多い。

これは外部公開インタフェースに限らず、例えばモジュール/クラス内部でのみ用いられるルーチンのヘッダ・コメントでも同様だ。そのソースファイルの読者が限られていて、共に共通するバックグラウンドを持っている場合、内部ルーチンのヘッダ・コメントは1行コメントであることが多いし、時にはヘッダ・コメントが書かれないこともある。一方、不特定の人に読まれ、メンテナンスされていくことを前提としたソースファイルの場合、内部ルーチンであってもそこそこ詳細なヘッダ・コメントが書かれることがある。

ステートメントについての「Why」は、意外な要素や注意すべき処理などの「意図(Why)」を説明するコメントだ。基本的には、シンプルなアルゴリズムを採用し、トリッキーなコードは避けるべきであるが、そこそこの大きさのアプリケーションを書いていると、どうしても他の読者の不意を突いてしまう部分が1〜2ヶ所は出てきてしまうものだ。チーム・レビューで他のメンバーからより良い代替案が出ることも多いのだが、どうにもならない場合には、注意を促すコメントを残すことになる。

ステートメントについてのコメントの重要度は「How < What < Why」だ。実際のコメントの分量は、多くの比較的マシな環境では「How < Why < What」だろう。ソースファイルの作者と読者に共通するバックグラウンドがあり、暗黙知によるコメントの省略が可能な環境では、コメントの分量は「How < What < Why」に近づいていくだろう。

では「データ構造」についてはどうだろうか? データ構造は、一度適切な構造が採用されてしまえば、あまり変更は発生しない。データ構造に変更が発生する際は、関連するソースコードを巻き込んで大々的に実装し直す必要があることが多い。例え適切なインタフェース抽象化することでモジュール/クラス外部への変更の波及を抑えられたとしても、モジュール/クラス内部はアレコレ手を入れなくてはならないだろう。つまり、データ構造に変更が発生した時点で、コメントによる「記述の二重化」とか悠長なことを言っている暇など無くなってしまう。どうせ大掛かりに変更することになるのだから、実はあまり二重化の弊害について気にする必要はない。

むしろ現実には、データやデータ構造についてのコメントは足りないことが多い。

プログラム書法』に曰く、「データの割り付けかたについての解説をつけよう」である。

プログラムに解説をつけるためにも、もっとも効果的な方法の一つは、単にデータの割り付けかたをくわしく説明する、というものである。おもな変数について、その値としてはどんなものが可能かを示し、それが変って行くようすを説明すれば、それだけでプログラムの解説は、ずいぶん進んだといってよい。

この指針に従うと、例えばクラスや構造体によって抽象化されたデータ構造の中身や、インスタンス変数のような生存期間の長いデータについて、HowやWhat(ときにはWhy)レベルのコメントが足されることになる。

ステートメントとは異なり、データ構造(または生存期間の長いデータ)については、HowやWhatのコメントを書いても許される側面がある。というのも、データやデータ構造の変化の様子は、システムやモジュール/クラスのライフサイクルにもとづく「計算の動的構造」をつまびらかにしなくては分からない要素だ。調べるためには、データやデータ構造を参照・変更する全てのステートメントを時系列順に確認する必要がある。はっきりいって、非常に面倒だ。しかしコメントで記述されていれば(そしてそれが正しいなら)、ステートメントを確認する必要はなくなる。

つまり、データやデータ構造については、例えHowレベルの内容であっても、実質的にはWhatレベルの要約コメントとして働くのである。

データやデータ構造についてのコメントの量は、モジュール/クラスの粒度や切り分け方、または使用する言語によって変化しやすい。

例えばモジュール/クラスの粒度が小さく、単一のコンテクストのみ扱うシンプルなものであるなら、その内部のデータ・データ構造についてのコメントは少なくなるだろう。しかし粒度が大きく、複数のコンテクストを抱えたモジュール/クラスでは、データ・データ構造についてのコメントは多くなるだろう。

Fileクラス、CSVパーサ・クラス、vectorのような動的配列クラス、これら3つを用いたデータファイル読み込みクラス――という風に4つに分かれているなら、それぞれの中身は比較的シンプルだろう。しかしこの4つの処理を全て自前で行おうとする単一のクラスは複雑だ。内部のデータ・データ構造のコメントも多くなるだろう。つまり、そういうことだ。

ステートメントもデータ・データ構造も、HowやWhatのコメントについては、コメント以外の他のメディア代替される余地がある。例えば、独自の通信プロトコルについて、仕様書が整備されているなら、わざわざ詳細をコメントに書く必要はないだろう。参照すべきドキュメントについてコメントするだけで十分だ。もちろん、仕様書が無いのならコメントでプロトコル形式(もしくはデータの例)を書いておくべきだろう。

ここまでの議論を元に、ソースファイルのコメントを削ったとしよう。後には次のようなコメントが残ることになる。

  • 「ファイルのヘッダ・コメント(What)」
  • 関数メソッドのヘッダ・コメント(What)」
  • ステートメントに関するコメント(What/Why)」
  • データ・データ構造に関するコメント(What/Why)」

さて、ここからさらにコメントを削ることは可能だろうか?

ここからは、使用する言語の抽象度・そのソースファイルの性質・作成者と読者の間の「暗黙知」の問題となってくる。

例えば、解くべき問題に合致している抽象度の言語を用いている場合、コメントを削りやすい傾向にある。タブ区切りファイルの第2フィールドの数値を加算した結果を求めるAWKスクリプトなら、ファイルのヘッダ・コメント1行と、処理するレコードのフォーマット(もしくはレコードの例)についてのコメント1行で十分だろう。しかし、C言語で自前で解析するというのなら、いくつかの関数を定義・実装することになり、各関数のヘッダ・コメントが1行ずつ必要となるだろう。

ソースファイルの性質の影響は、例えば「自社開発アプリケーションコンポーネントの一部で、概ね固定されたメンバーで管理しているソースファイル」なのか、「内製ライブラリで、ライブラリ開発部隊がメンテしていて、社内の複数のプロジェクトにて組み込まれるソースファイル」なのかによって、公開インタフェース(CやC++でいうヘッダファイル)のコメントの書き方は異なるし、内部ルーチンのコメントのスタイルも違ってくるだろう。メンテナンスする人が限られていて、メンバーも固定されがちで、外部への露出が少ないならば、コメントは簡素となる。一方で、不特定多数の人に公開されるライブラリのソースファイルでは、問い合わせを減らすためにドキュメントを充実させる傾向にあるので、コメントをドキュメント代わりにしている環境では、コメントも充実した内容(≒API仕様文書)となる。

ソースファイルの性質の件は、ソースファイル作成者と読者の間の「暗黙知」の問題と絡んでくる。メンバーがある程度固定された環境では、明示的・暗黙的にかかわらず、例えば「○○の場合は××のスタイルで書け!」のようなお作法があったりする。この環境に適応すると、ソースコード中の「××のスタイル」を目にした時に「ああ、ここは○○なのだな」と逆引きが容易になる。

名前の付け方やインタフェース定義の流儀など、明快かつ合理的な作法(型)が存在し、皆が作法に従っているなら、コメントするまでもなく暗黙のうちに理解できてしまう部分が生じるだろう。*5

暗黙知」が許されるか否かや、どこまで許されるかという問題は、当然ながら環境に依存する。メンバーが固定されていて、レベル差がそれほど大きくなく、書かれたコードがメンバー同士での利用に留まるなら、暗黙知にもとづいてコメントを削ってもあまり問題にはならないだろう。むしろメンバー的には冗長性が排されるので歓迎されるかもしれない。なぜなら、彼らからすれば「当たり前の常識」なのだから、わざわざ書くほどのことではないからだ。しかし、不特定多数の人が読むなど、どうしても読者のレベル差が広くなり、それに対処する必要があるのなら、逆に「暗黙知」をコメントで提示することになるかもしれない。

コメントの少ないソースファイルについて、個人的に良い例だと思っているのが、以下の『プログラミング作法』のサンプルコードだ。

PythonRubyなどからすれば低水準なCやC++を使っている、という側面はあるが……お仕事で書くコードで、メンバーが少数かつ固定された面子で、外部への公開をあまり考えなくてよいのなら、ここまでコメントを削っても許されるだろうと思う。

一方、私は普段、不特定の人が使用するライブラリの開発に携わることが多い。なので、コメントは冗長気味に書く癖がついている。以下のコードが、その雰囲気を表している(英語は間違っているので注意)。

(大御所のコードと比較すると、自分のコードの下手さ加減がよく分かる)

もちろん、自分用の小ツールなら、コメントは少ない(以下、Go言語)。

時には細かいコメントがほとんどないソースファイル(以下、Perl)もあるが……実は、暗黙のうちに「SNTPのパケットフォーマットの知識」を読者に要求している。

コメントを減らすことは、ある水準までは技術の問題だ。しかし、ある時点から次第に技術ではなく環境・文化の問題となっていく。

(本当に開発者のためになる)仕様書が存在し、しっかりメンテナンスされている環境ならば、開発対象についての高水準の記述について、コメントで書かれることはない。仕様書を参照すれば済むからだ。しかし、そうではない環境では、仕様書の代わりにコメントで記述されるかもしれない。

不特定多数の人が利用するライブラリを開発していて、API仕様書を書かないのならば、API仕様をコメントで記述することになるだろう。もしかしたら、JavaDocDoxygenを利用して、コメントからAPI仕様書を生成しているかもしれない。一方で、少人数で開発中のコンポーネントの一部で、作者も使用者もチーム・メンバーで、特に外部に公開することもないのなら、インタフェース部分のコメントは簡素となるだろう。それで困る人はいない。

さて、あなたはどの環境の人ですか? 相手はどの環境ですか? で、あなたの批評は、相手の環境に合致してますか?

*1:システムの肝の部分のコードを覗いた人への警告にはなるので。とはいえ、おそらく私でも「もうちょっとなんとかならんかったものか……」とため息をつくと思う。

*2バージョン管理システムが無い場合、ChangeLogのような「別のファイル」とdiff(≒ソースの差分を参照できる何か。patchでも、ソース一式のアーカイブでもよい)で履歴を管理すべきだが、手動での管理が面倒なため、差分の粒度が大きくなってしまいがちだ。CSVSubversionのような集中型バージョン管理システムの場合、運用など諸々の都合でコミットの粒度が大きくなってしまうことも多い。Gitのように比較的気兼ねなくコミットできる環境が整ったことで、細かな変更単位でコミットしてログを残すことが可能となってきたように思う。

*3:私自身の過去の記事での記述も含む。

*4:これ、『リーダブルコード』では若干嫌われている気がしないでもない(P.68)のだが、使ってしまうことにする。

*5:これが、アセンブラBASICの頃から時が止まっているかのようなクソな作法を強制される環境だと地獄なのだが……。

2016-04-22

netcatによるTCPでのファイル転送は2パターンある

タイトル名の通りのことを今更ながら実感したので、記録を残しておく。

netcat(nc(1))を使って、TCPで簡易的にファイルを転送する方法は2パターンある。ファイルを送る側のコンピュータでlistenする方法と、ファイルを受け取る側のコンピュータでlistenする方法だ。

ファイル送信側がlistenする方法

まず、ファイル送信側にて、netcatでポートをlisten状態で開いておく。この時、送信したいファイルをnetcatにリダイレクトしておく(≒標準入力経由でnetcatに流し込む)。

# ファイル送信側:192.0.2.11
listen_port=10002
nc -l $listen_port <file

次に、ファイル受信側から送信側にnetcatで接続する。送信ファイルの内容が標準出力経由で垂れ流されるので、リダイレクトしてファイルに流し込むようにしておく。

# ファイル受信側:192.0.2.12
target_ip=192.0.2.11
target_port=10002
nc $target_ip $target_port >file

ファイル受信側がlistenする方法

まず、ファイル受信側にて、netcatでポートをlisten状態で開いておく。この時、netcatの標準出力をファイルにリダイレクトしておく(≒標準出力経由で受け取った内容をファイルに書き込む)。

# ファイル受信側:192.0.2.12
listen_port=10002
nc -l $listen_port >file

次に、ファイル送信側からにnetcatで接続する。この時、netcatの標準入力送信するファイルをリダイレクトしておく(≒送信データを標準入力経由でnetcatに流し込む)。

# ファイル送信側:192.0.2.11受信側
target_ip=192.0.2.12
target_port=10002
nc $target_ip $target_port <file

どちらを使うか?

正直、どちらを使っても問題ない気がする。

ただ、netcatは悪用も可能なツールなので、「可能なら、できる限り『クライアント』マシンとして使いたい」というコンピュータではlistenしないようにした方がよいと思う。

例えば、開発PCがWindowsで、実験用のターゲットPCがMac OS XLinuxだとする。開発PCからターゲットPCにファイルを転送する場合は「ファイル受信側がlistenする」方法を、その逆の場合は「ファイル送信側がlistenする」方法を使うとよいのではないか?

というのも、Windowsファイアウォールデフォルト設定では、あるアプリケーションが初めてlisten状態でポートを開こうとした時に、通信を許可するか否かの確認ダイアログが表示される。他のファイアウォール・ソフトでも、同様の機能を持つものは多いはずだ。

netcatは悪用可能なので、できればlisten状態でポートが開かれたことを捕捉できるようにしておいた方がよい。しかしnetcatでlisten状態でポートが開くことが常態化していると、ファイアウォールの設定で許可されているために、意図せぬタイミングでnetcatがlisten状態でポートを開いた際にユーザに何も通知されない可能性がある。まあ、この辺はファイアウォール・ソフトの種類や設定に依存するし、あとnetcatは有名といえば有名なのでプロ仕様(!)のウイルスの類では狙われない*1気もするのだが……。

Windowsにnetcatが入っているのは、正直なところ少々特殊な状況だ。なので、この程度は気を使っておいたほうがよい気がする。気にしすぎかもしれないが。

*1:おそらくもっと別の、より巧妙で気づかれにくい方法を用いるはず。

2016-04-18

イヤホン聞き比べ:Panasonic RP-HJE150とRP-HJE122Aの音はどう違うか?

なぜかセットで名前が挙がることの多い、Panasonicのカナル型イヤホンRP-HJE150とRP-HJE122A。両者を実際に聞き比べたレビューがなさそうなので、試しにやってみた。

RP-HJE150といえば、1000円以内で買えて、価格の割には音が良いと評判なイヤホンだ。

パナソニック カナル型イヤホン ブラック RP-HJE150-K

パナソニック カナル型イヤホン ブラック RP-HJE150-K

この記事を書いている時点でのAmazon価格は664円。かなり安い。

似たような製品に、同じくPanasonicのRP-HJE122AというAmazon限定モデルがある。

Amazon価格は580円で、RP-HJE150より84円安くなっている。100円ショップの高価格帯イヤホンが500円(税別)なので、それに毛が生えたぐらいの価格だ。

この2つ、製品名(型番)も仕様も異なるのにAmazonのページが共通で、「通常モデル」を選択すればRP-HJE150が、「Amazon.co.jp限定」を選択すればRP-HJE122Aが買えるという、少々鬼畜な状況になっている。今は分からないが、少なくとも過去には間違えて購入した人がいたようだ。RP-HJE150をリピート購入しようとしてRP-HJE122Aを購入してしまうも、製品が違うことに気づかずに「音が違うぞ! 劣化した!」と怒りの声を上げたらしき人の痕跡もちらりと見うけられる。

ただ、この2つを実際に聞き比べて、具体的に音の何がどう違うのか書いている記事が見当たらない。探し方が悪いだけかもしれないが、「違う」とだけ書かれても、その「違い」がなんなのか分からなくて困るのだ。

仕様の違い

聞き比べる前に、まずは両者の仕様から。

製品名RP-HJE150RP-HJE122A
形式密閉型ダイナミック・ステレオインサイドホン密閉型ダイナミック・ステレオインサイドホン
ドライバーユニット直径10.7 mm9 mm
インピーダンス16 Ω16 Ω
音圧感度100 dB/mW96 dB/mW
最大入力200 mW (IEC)200 mW (IEC)
再生周波数帯域6 Hz - 23 kHz20 Hz - 20 kHz
コード長さ*約 1.2 m約 1.1 m
プラグの種類ステレオミニプラグ(直径 3.5 mm)ステレオミニプラグ(直径 3.5 mm)
プラグの型ストレート型L型
プラグの金メッキありなし
質量(コードを含む)約 11 g約 11 g
質量(コードを除く)約 3 g約 3 g
付属品イヤーピース(XS、S 各2個)イヤーピース(XS、M 各2個)
標準のイヤーピースMS

価格が価格だけに、両者ともBAではなくダイナミック型だ。というか、この価格でBAだったら大騒ぎだろう。

ドライバーユニットの直径は、RP-HJE150の方が大きい。仕様からは、RP-HJE150の方が低音が出るのではないかと推測できる。

音圧感度も、RP-HJE150の方が大きい。プレーヤーの音量を変えずにイヤホンを差し替えた場合、おそらくRP-HJE150の方が音が大きいはずだ。

再生周波数帯域はRP-HJE150の方が広いが、これが音に影響してくるか否かは不明。一般的にいわれている可聴域は20Hz〜20kHzだが、この範囲はRP-HJE122Aもカバーしている。可聴域の範囲外の音(超音波)のヒトへの影響は、まだ研究中の分野だ。

プラグはどちらもステレオミニプラグだが、RP-HJE150はストレート型で金メッキあり、RP-HJE122AはL型で金メッキなし、という違いがある。金メッキの有無は、音質の差云々よりも、経年劣化による酸化(それによるノイズの発生*1)への影響が大きい気がする。

地味に違いがあるのが、購入時点で取り付けられているイヤーピースの大きさ。RP-HJE150のMサイズにたいして、RP-HJE122AではSサイズだ。世の中にはイヤーピースに無頓着な人も多く、そういう人たちは購入時のイヤーピースのままイヤホンを使おうとする。イヤーピースは意外と音に影響を与える部品だ。「RP-HJE150とRP-HJE122Aとで音が違う」という言説の中には、イヤーピースの大きさの違いに起因するものも含まれているのではないだろうか?

聞き比べ:評価環境

RP-HJE150とRP-HJE122Aとの聞き比べは、次の条件で実施した。

  • 音源としてiPod classic(第6世代)を使用。
    • イコライザーはOFF(フラット状態)
  • RP-HJE150、RP-HJE122A両者とも、イヤーピースをMサイズに統一。
  • 比較対象として、以下のイヤホンを使用。

聞き比べ:音の共通

RP-HJE150とRP-HJE122A、どちらも価格の割には良い音を出している。とはいえ、価格が価格だけに、過度の期待は厳禁だ。

音の傾向は、両者とも同じ。フラットというか、低音域から高音域まで比較的バランスのとれた出方をしている。あと、音がセンター寄りに鳴っている。

音質は、XYY-16よりも遥かに良い。XYY-16よりも音はこもっていないし、細かい音も出ている。だがATH-CK330Mには遥かに及ばない。ATH-CK330Mの方が解像度が高く、より自然な音が出ている。

高音域の解像度があまり良くなくて、例えばハイハットやシンバルの音が潰れている。それっぽい音はするが、ハイハット本来の音ではない。

聞き比べ:音の違い

音の違いよりも前に、まず音の大きさが違う。音圧感度の差による影響が、素人でも分かるぐらいに表れている。音源のボリュームを固定した状態でイヤホンを差し替えると、RP-HJE150にて適切な音量だったならRP-HJE122Aでは小さく聞こえるし、RP-HJE122Aて適切な音量だったならRP-HJE150では音が少々大きすぎる感じになる。

音質(解像度やこもり)はRP-HJE150の方が良い。聞き比べると、RP-HJE122Aの方がこもっているような音がするし、高音寄りの音が軽い(安っぽい)感じがする。劇的に違う、というわけではないのだが、実際に聞き比べると、素人でも「うーん、これはちょっと差があるのでは?」と疑問に思う程度には、音質に差がある。

低音域は、RP-HJE150の方が音が出ている。同じ曲で聞き比べると、バスドラムのアタック音はRP-HJE150の方が良く聞こえる。

個人的感想

実売価格の差がたった84円である点を考慮すると、私ならRP-HJE150を買うと思う。

どちらも価格の割には良い音を出すが、実売価格が664円と580円だという点は押さえておくべきだろう。価格差が2〜3倍以上の、2,000〜3,000円台のイヤホンの音質には及ばない。というか、及んじゃったらPanasonicが商売に困るでしょ、上位モデルが売れなくて。

*1:古い音楽プレーヤー/イヤホンで時に発生する、イヤホンジャックに指したプラグを回転させると、場所によって音がキレイに出たりノイズがのったりするアレのこと。