技術系執筆情報

Mercurial に関する執筆は 「Mercurial の利用/執筆情報」を参照してください。

lsを読まずにプログラマを名乗るな!

lsを読まずにプログラマを名乗るな!』(ISBN:978-4-7980-3943-5) と題して、GNU coreutils 同梱の ls コマンドのソースコードを読み解く書籍を、 秀和システムから出版しました。

正誤情報や、補足説明をはじめとする各種サポート情報は、 別途サポートページから参照してください。

アセンブラで読み解くプログラムのしくみ

アセンブラで読み解くプログラムのしくみ』 と題したアセンブラの入門書を、 電子書籍形式で GIHYO DIGITAL PUBLISHING (技術評論社) から出版しました。

概要ページにもあるように、『アンティーク・アセンブラ〜Antique Assembler』 と題してウェブ媒体で公開していた連載記事を元に、 大幅な加筆・修正を加えたものです。

正誤情報

内容の不備等にお気付きの際には、 メール等でお知らせ頂ければ幸いです。 はてなのアカウントをお持ちの方は、 こちらのエントリにコメントして頂いても構いません。 twitter 経由であれば @flyingfoozy 宛てでお願いします。

第3章: もしも"if"なら

冒頭の章概要 (Summary) における記述に、 第2章のものが紛れ込んでいました。

『3.2 条件分岐』 における以下の記述に誤植がありました。

第8章: 共有ライブラリの負担

『8.1.2 OSレベルから見た「共有」ライブラリ』 における以下の記述に誤植があります。

『8.2.2 再配置情報』 における以下の記述に誤植があります。

俺のコードのどこが悪い?

俺のコードのどこが悪い?』(ISBN:978-4-7980-2918-4) と題したコードレビューの入門書を、 秀和システムから出版しました

本書は大きく分けて 「レビュー準備編」と「レビュー実践編」の2部から構成されています。

「レビュー準備編」では、 (1) レビューを効率よく進めるための事前準備、 (2) レビューで受けた指摘を次回以降で有意義に反映するための、 「失敗学」ベースの対処法、および (3) レビューに対する心理的な抵抗を低減するための考え方、 という3つの話題を柱にしています。

「レビュー実践編」では、 実際のプログラミングにおいて、 最低限どういった点に留意する必要があるのかを、 幾つかのカテゴリに分類して、 具体的なコード等を元に説明しています。

対象プログラミング言語としては C/C++ および Java を主眼に置いていますが、 ファイル操作やネットワーク通信等で、 システムコールと直結するような処理を行う場合であれば、 スクリプト系言語での開発においても参考になるのではないかと思います。

企画の経緯

備考: 以下での「プログラマ」は、 職階や職能としての「プログラマ」ではなく、 「プログラミングをする人」という広義の意味で、 あるいは「プログラミングが好きな人」というニュアンスで使用しています。

本書は元々、 プログラマ層に向けた コードリーディング(Code Reading)系書籍の企画として始まったのですが、 いざベースにするコードの候補を選択しようとすると、 著名なオープンソースソフトであっても、 何らかの架空のソースであっても:

  • 対象ソースを絞ると、取り上げる話題が限定される
  • 取り上げる話題を広げると、対象ソースの選択が難しくなる (or 手広く扱う必要がある)

といった問題に直面してしまいます。

単純に「Code Reading」の後追い企画を立ち上げよう! ということであれば、 それでも良いのかもしれませんし、 対象ソースの選択次第では、 ある程度は興味を持ってもらえるかもしれません。 しかし、 「オープンソースのコードを読んだ。良くわかった。終わり」 というだけでは、 新たな企画を立ち上げる意味がありません。

そこで企画の原点に立ち返ることにしました。

対象であるプログラマ層にとって、 オープンソース等のコードを読む理由は何だろう? と考えたのです。

「今まさに顕在化しているバグを修正するための調査」とか、 「開発案件で使用するフレームワーク/ライブラリの評価」といった、 切迫した理由でコードを読む事も多いとは思います。 しかし、 そういった生臭い(笑)ケースを除けば、 「コードを読むことで実装力の向上を図りたい」 という動機に根ざしているのではないでしょうか? (勿論、単純に「面白いから」という理由もあるでしょう)。

しかし、 巷間でも度々目にする 「オープンソースのコードを読んで実装力向上」 というお題目に関して、 実のところ私個人は半信半疑です。

確かに「オープンソースのコードを読んで実装力向上」できる人も居ます。 もしかしたら世間におけるプログラマの殆どが、 そういう人なのかもしれません。 しかし、 新入社員の研修担当やプロジェクト運営等における私自身の経験からすると、 「オープンソースのコードを読んだ。良くわかった。終わり」 で完結してしまう人が居る事も事実です。

この「読む→なるほど!→終わり」でサイクルが閉じてしまう違和感は、 ある程度キャリアを積んだ人が、 「オブジェクト指向」とか「デザインパターン」 に手を出した時にも良く目にします。

例えるなら、 大きな歯車はあるのに、 それらの間を繋ぐ小さな歯車がごっそり抜けていることで、 全然噛み合っていない感じ、 とでも言えば良いのでしょうか?

ではその「繋ぐための小さな歯車」とは何だろう?と考えると、 「神は細部に宿る」ではありませんが、 重箱の隅を突く様な些細な、 しかし重要な事柄の積み重ねへのこだわりではないかと思うのです。

ならば、 そういったこだわりの対象となる「細部」に関するまとまった解説があるのか? というと、寡聞にして私はそういったものを見たことがありません。

勿論、 個々の細かい話自身は、 世の中に広く出回っています。 しかしそれは、 別の大きな主題があった上での従属的なものであることが殆どです。 そのため、 相対的に扱いは小さなものになってしまい、 結果として見過ごされがちになります。

であれば、 そういった細かい話を、 「細かくて見過ごされがちな話」という括りでまとめてみるのは、 それはそれで意義のあることではないでしょうか?

また、 そういった「細かくて見過ごされがちな話」 が重要になる(or 目に付く)のはどういう局面かと言うと、 私自身の経験から言えば、 もうコードレビューをおいて他には無いと思うのです。

幸い、 上記の様な方針に関して、 担当編集氏/出版社の理解を得ることが出来たことで、 無事本書を出版する運びとなりました。

微力ではあるかもしれませんが、 本書が多くの人の役に立つことを願う一方で、 『お前の「細かい話」はなっちょらん! もっと素晴らしい「細かい話」をしてやる!』という方の登場や、 本書に書かれているような事が全てのプログラマにとって常識化することで、 一日でも早く本書が無用となる日が来ることを願って止みません。

正誤情報

内容の不備等にお気付きの際には、 メール等でお知らせ頂ければ幸いです。 はてなのアカウントをお持ちの方は、 こちらのエントリにコメントして頂いても構いません。 twitter 経由であれば @flyingfoozy 宛てでお願いします。

全般

「強い型付け」/「弱い型付け」

本書では、 いわゆる「type stricted」な事を指して 「強い型付け」という表現を用いています (※ 変数(=参照元)に対する型の要否)。

しかし、 「型付け」の強弱と言った場合は、 参照先のデータにおける型情報の有無を指す、 との読者からの指摘がありました。

そこで、 本書における「強い型付け」という表記は「静的型付け」を、 「弱い型付け」という表記は「動的型付け」を指すものと考えてください。

42 ページ

コラム「条件判定の評価順序」は:

論理和演算における式の評価順序は、 言語仕様上「コンパイラ依存」

であることを前提に記述していますが、 C99 言語仕様(JIS X3010 2003/12/20 版)を確認したところ、 式の評価順序に関して以下のように記されていました (規格ドキュメントの参照は、 「JIS 検索」画面で、規格番号である "X3010" を検索してください)。

(関数呼び出しの ()、&&、||、?: およびコンマ演算子に対して) 後で規定する場合を除いて、 部分式の評価順序及び副作用が生じる順序は未規定とする。
〜 JIS X3010 プログラム言語C - 6.5 式 〜

そして、 上記記述において例外とされる「関数呼び出しの()」「&&」「||」「?:」 「コンマ演算子」に関する「評価順序および副作用が生じる順序」は、 「記述順序」と規定されています。

1990 年代初頭に C 言語を習い憶えた当時の 「演算の評価順序は未定義」という記憶から、 てっきり「論理演算における評価順序もコンパイラ依存」だと思い込んでいたのですが、 論理演算(+ 幾つかのケース )における評価順序は、 言語仕様において保証されています。

C99 以前の仕様に関しては、 正式な文書を確認できなかったのですが、 歴史的経緯に関する注記等が特に無いことから、 少なくとも ANSI 等で標準化された時点では、 既にこのような仕様であったものと思われます( 追記:「論理演算の評価順序は、 ANSI 標準化以前から保証されていた」との指摘がありました)。

なお、上記で「例外」扱いされていない演算子を用いる式(代入文含む)では、 式を構成する各部分式の評価順序はコンパイラ依存となりますので、 変わらず注意が必要です。

59 ページ

コラム「enum を持たない言語」において:

Java の場合、言語仕様として enum に相当する定数定義機能を持ちません

と記述してありますが、 実際には、 2004/09/30 にリリースされた J2SE 5.0(所謂 "1.5.0")から、 「型保証された列挙」として enum 相当の機能が言語仕様に導入されています。

言い訳になりますが、 1.4.x 系から 1.5.x 系への移行の過渡期に Java を用いた開発から離れてしまった、 という個人的事情から、 すっかり失念しておりました。

「過渡期においては、このような手法があったのだ」 という参考になれば幸いです。

103 ページ

コード 11.3 には、以下の間違いがあります。

    floatsum = data[i].fvalue * 3.5; /* 不動小数点数型の計算 */
    floatsum = data[i].fvalue * 3.5; /* 浮動小数点数型の計算 */

104 ページ

コード 11.4 での集約例は、 「実施するとすれば、このような方法になる」あるいは 「このようにすれば、やれないわけではない」ということを示すためのものです。

「このような集約をすべきである」 と主張するものではありません

メリット/デメリットを踏まえた上で、 各自の事情に応じて採用の要否を判断してください (この記述に限ったことではありませんが....)。

187 〜 188 ページ

※ 以下の記述における realloc() 対象領域は、 一定以上のサイズのものを想定します

本書では、realloc() による領域拡張に関して:

一時的にせよ「以前の領域」(size)+「新規の領域」 (newsize)分のメモリが必要になる

という記述がありますが:

  1. realloc() 対象領域のアドレス高位側に隣接する領域が未使用で、 且つ
  2. 要求される領域拡張分以上の空きがある

という条件を満たす場合には、 おそらく全ての realloc() 実装で、 隣接領域を使用した領域拡張を行うと思われますので、 本書で記述したような問題は発生しません。

隣接領域を使用した領域拡張を期待する場合、 状況に応じて、 「既存の未使用領域をまとめて1つの大きな領域」にしたり、 「OS から現行領域の後ろに新たにメモリを獲得して現行領域を拡張」 したりしますが、 いずれにしても 「realloc() 対象領域のアドレス高位側の領域が未使用」 でなければなりませんので、 基本的に realloc() が連続的に実施されることを保証する必要があります。

なお、 ソースの字面上で連続的に実施されていても、 複数スレッドでメモリ獲得/解放要求が並走するような場合は 「連続実施」性が損なわれる一方で、 ソースの字面上は対象領域外のメモリ獲得/解放処理が混在していても、 実質的に「連続実施」性が維持される場合もあります。

一般的な malloc 実装では、 一定サイズ以下 (※ システム毎に異なります) の領域に関しては、 「事前に確保した大きな領域から切り出して割り当てる」 という方式を採用しています。 このようなメモリ管理方式は「チャンク」(chunk) ないし「スラブ」(slab) と呼ばれます。

このような管理対象下にある領域に対する malloc/realloc を実施した場合は、 事前に確保した領域からの切り出しで領域が手当てされることから、 (一定サイズ以上の) 対象領域のアドレス高位側の領域の使用状態は変更されず、 realloc() の「連続実施」性を損ねません。

但し、 「事前に確保した大きな領域」を使い切った場合には、 当然割り当て要求に応えるために新たな領域の確保を実施します。 この際に、 「事前に確保した大きな領域」として、 対象領域のアドレス高位側を使用する可能性がありますから、 「小さいサイズの malloc/realloc であれば混在していても大丈夫」と決め付けるのは避けた方が良いでしょう。

211 ページ

コード 27.3 には、以下の間違いがあります。

  • open() システムコールの成否判定は、負値との比較
    if(fd = open(filename, ....)){ return -1; }
    fd = open(filename, ....);
    if(fd < 0){ return -1; }

コード 27.4 には、以下の間違いがあります。

  • open() システムコールの成否判定は、負値との比較
  • result のスペルミス
    if(fd = open(filename, ....)){ reslt = errno; goto notopened; }
    fd = open(filename, ....);
    if(fd < 0){ result = errno; goto notopened; }

290 ページ

コラム「カバレッジの確認手段」において:

タイミング依存の状況が発生する確率が 1/100 であっても、 裏を返せば100回実行すれば1回発生する、ということですから....

という記述がありますが、 発生頻度が 1/100 = 0.01 の事象に対して 100 回の試行を繰り返した場合、 その事象が発生しない確率は、 おおよそ 37% となります。

(1 - 0.01)100 = 0.3660.....

目当ての事象を 99% 程度の確率で発生させる= 発生しない確率を 1% 程度にするには、 おおよそ 460 回程度の試行が必要とされます。

(0.99)n 0.1
n log0.99(0.01)
log10(0.01)/log10(0.99)
(-2)/(-0.004364...)
458

C/C++プログラマのためのDTrace入門

C/C++プログラマのためのDTrace入門』 と題して、 ユーザプログラムにおける DTrace の利用に関する入門記事を、 技術評論社 gihyo.jp において掲載中です。

エンジニアのための「失敗学」のススメ

エンジニアのための「失敗学」のススメ』 と題して、 失敗学に関する入門記事を、 技術評論社 gihyo.jp において掲載中です。