ハンガリアン記法考2006年10月29日 01時20分07秒

物凄く久しぶりに一人っきりでワインとか飲んだら思いっきり酔いつぶれて 2、3 時間ほど眠ったらこんどは目が冴えてしまって眠れなくなっちゃったので、これまた久しぶりに枯れた話題について愚考を重ねてみようかと思う次第。

最近、とある友人 (「主に VB」なプログラマーさん) が、「変数名には必ず型がわかるような接頭子をつける記法に慣れている」というようなことをおっしゃるので、ハンガリアン記法を原則化するコーディングルールが好きでは無いおいらはちびっと反発してしまったわけなのですが、偏見の可能性もあるので今一度整理してみようかなぁとか思ってみたわけなのですよ。

結論から言えば、結局は case by case ということになるのだろう、ということなのですが、それだけだと有意義な情報とは言えないと思うので、どういう場面で使うと効果的かというのをさらっと列挙すると大体以下の通りになるんではないかと思う。

  • コンパイラが型の違いを見抜けない言語である場合で、且つ、グローバル変数か、または関数の引数である場合。特に、C/C++ で引数の型に void *void ** などを使う必要があるケースなど、ポインタアクセス時に暗黙の reinterpret_cast (異なる型の値へのポインタとして扱おうとする型キャスト) が容易に発生しうるような言語においては有効。但し、Java や D 言語のような、オブジェクトの参照に対して型に厳密な言語においては、必ずしも有用ではない
  • 行おうとする演算によっては必ずしも型に厳密ではなくなってしまう可能性のある言語において、そのあいまい性を予防する目的で使用する場合。JavaScript における文字列と数値のあいまい性など。
  • スコープがあいまいである場合。グローバル変数なのか、関数の引数として定義した自動変数なのか、ブロックスコープ内の変数なのか、オブジェクトのメンバフィールドなのか、それらの区別が言語の文法上つかなくなる恐れがある場合。特に、変数を宣言しなくても使えるタイプの言語においては注意を要する場合があり、その注意を減らす目的において有用。
  • 汚染値チェック機構の存在しない言語において、ユーザーから入力された情報 (危険である可能性がある) と、加工済みの情報 (危険は取り除かれている) とを区別する目的で使用する場合。もっとも、これは一般的に認識されている「ハンガリアン記法」とは異なるものであり、そういう意味での異論はありそうだが、その点については後述。
  • コンパイルエラー時のエラーメッセージが難解である場合に、ハンガリアン記法を用いていればエラーの原因が特定しやすいことが予期できる場合。但し、グループ開発時のコーディングルールに含めようとする場合には、一部のベテランプログラマーとの間で折衝は否めないかもしれない。

まず、一般的になってしまっている解釈と、それに対するありがちな議論を紹介してみようと思う。

一般的な解釈

ハンガリアン記法は、一般的には、変数が保持する値の型を表す為に、変数名に決まった接頭子や接尾子を記述する記法として知られています。また、狭義には、Microsoft 社内で伝統的に用いられた記法であり、型 (または状況) と接頭子 (または接尾子) との対応が完全に固定されて定義され、統一されている (つまり、それ以外の接頭子や接尾子を「方言」として定義し運用することを禁じている) ものを指しますが (統一されていなければ企業を隔てたグループ開発で困ったことになる可能性もあるから、という考え方)、広義には同じ意図を持って用いられている接頭子および接尾子のルールはすべてハンガリアン記法の仲間である、というのが一般認識であるように思います (現実的なことを言えば各企業、職場に根付いた文化を捻じ曲げてまで統一するのは世代間の認識のずれによる誤解を生じさせるという意味でメンテナンス性を欠くし、何より言語によっては定義されている記法がそのまま使えない場合も無いとは言えないからね)。

なお、上記で示したリンク先は単に「ハンガリアン記法」でぐぐったときに一番目に表示されたリンクであり、Microsoft の公式の文書は別途存在します。

日本語の正式な文書は見つけられなかったのですが、軽く紹介している文書を見つけることはできました。

こちらの記述はいわゆる「広義の」ハンガリアン記法のようです。

ありがちな議論

D 言語のコーディングスタイルガイドにおいて、ハンガリアン記法を否定する記述があったことに対する反論。ハンガリアン記法の利点と欠点について、それなりによくまとめられている記事だと思います。

で、当の D 言語のサイトを覗いてみると、以下の記述を見つけることができます。

(※訳注:「なぜダメなの?理由は?」という質問を随所で見かけるので、勝手ながら訳者が注釈を添えておくことにします。訳者自身は必ずしもハンガリアン記法=ダメと一概には言えないと考えていますが、一般には、以下のような理由が考えられるようです。(1)型の名前を変数名に埋め込むのは単純に無駄である。コンパイラは型をわかっているのだから、間違えて使ってもエラーですぐに気づけるはず。(2)コンパイラは型名とハンガリアン変数名の一貫性をチェックしないので、人間がチェックしないといけない。ミスって間違えた名前をつけたり開発途中に型を変えたいときなどに、混乱の元&めんどくさい。(3)C/C++コンパイラの型システムでは区別できない情報を変数名にのせてバグを減らす「正しいハンガリアン」すら、D言語ではほとんどの場合不要。記法で注意するよりも、typedefを使ってコンパイラにチェックさせるべき。(4)そもそも本家Microsoftですら.NETになってからはハンガリアン記法をやめている。etc, etc。実際のところWalter氏がどう考えておられるのかはわかりませんが。)

これらの主張は、あらかた正当であるように思います。多くの場合、型の正当性チェックはコンパイラに任せるべきであるという思想は理に適っています。反面、コンパイラはハンガリアン記法のチェックまでは行わない為、「ハンガリアン記法上のミスが出る可能性」は余計なリスクとして計上する必要があります。型変更時の不便については両サイト共に指摘するところですね。

そして、「C/C++ では必要とされるケースであっても D では多くの場合不要」という説明が出てきます。おいらとしてはここの部分が最も重要であるように思います。要するに、D 開発者の Walter 氏としては、「言語仕様上ハンガリアン記法が不要なように作っているんだから、D ではハンガリアン記法を使わない文化を定着させようぜ」ということなのであって、必ずしもプログラミングにおけるあらゆる場面においてハンガリアン記法的な手法を否定しているわけでは無いのではないか、ということです。

「本来の」ハンガリアン記法

先の D スタイルにおける釈明中に、「正しいハンガリアン」として、以下のサイトへのリンクが示されていました。

この記事は、プログラマーを志す方であれば、是非、ご一読いただきたいと思います。特に、「間違ったコードを間違いに見せる」には以降の記述については非常に重要であります (汚染値チェックの話題も含めて)。

そもそも、「わかりやすいプログラム」とは、どういったものなのでしょうか? 変数が持つ値の型が判別しやすいことが、本当に「わかりやすいプログラム」として、必要な要件なのでしょうか?

本当に必要なことは、変数であれ、あるいは関数であれ、「わかりやすい名前」をつけることであるように思います。たとえば最近の風潮としては、「変数名に略称を使用すべきではない」というものがあります。

time_t dt;      /* DaTe かもしれないし、DateTime かもしれないし、
                 * あるいは Delayed Time の略かもしれない */

time_t present_datetime;    /* 「現在日時」を扱う変数であることが
                             * 確実に理解できる */

また、文脈を重視した名前の設定方法というものもありえます。boolean な値であることを示すために b と前置きするよりも、if 文で使用する値であることを意識させるために、前置詞としての is_can_ で前置きした方が、よりプログラムの意図が理解しやすくなります。

if (!bAccess){      /* アクセスがない場合?
                     * それとも、アクセスできない場合? */

if (!can_access){   /* アクセスできない場合 */

しかしそうした反面、慣習において許された特定のケースにおいてのみ、記号的、魔術的な変数名が容認されているのも事実です。

/* i は integer の i ? それとも iterator の i ?
 * 分からないけど、でもよく見かけるよね */
for (i = 0; i < MAX_LENGTH; i++){

かつては、コンパイラが型を理解できないケースがあったために、プログラマーが型を意識してコードを書く必要がありました。もしも、コンパイラがどんな状況においても型を理解することができるのであれば、プログラマーは、必ずしも型ばかりを意識してプログラムを組む必要はなくなるはずです。そうしたときに、人間であるプログラマーとしては、コンパイラが理解できないもっと抽象的な (そして実際的な) 概念における状況依存性について意識を配ることができるようになるはずであり、実際そうすべきであると思うのです。そして、それこそが、本来プログラマーが「わかりやすいプログラム」を書く上で重要な要素であるということに気付くべきです。

実際的な話

まとめは冒頭に書いてしまったので、以下は余談です。ハンガリアン記法に対する誤解は、ハンガリアン記法が「状況を意識するための記法」であることを理解できずにいたことに由来するように思います。「ハンガリアン記法」という言葉をわざわざ引き合いに出さずとも、多くの場合、無機質な「型」ばかりに囚われてプログラミングを行うことは、決して効率的ではありません。

/* 解は単精度に丸められる */
fCircum = dRadius * 2.0 * MATH_PI;

// C++ ならば明示的キャストを記述すべき
// (そうしないと多分警告になるはず)
circum = static_cast<float>(radius * 2.0 * MathUtil::PI);

# Perl の場合、そもそも気にする必要は無い
$circum = $radius * 2 * pi;

従って、俗世的なハンガリアン記法を好むプログラマーを見つけると、「型にばかり囚われているひよっ子 (もしくは旧式) プログラマーだ」という偏見が生まれることが、自称ハッカーたちの間で度々見られるような気がします。

反面、いわゆる Microsoft 的なハンガリアン記法の「見た目」だけを忌み嫌う勢力もあります (Unix 方面に特に多い)。こうした方々は、形式は違えど結局は似たような記法を利用していたりすることがあります。例えば C++ においては、クラス名は型名なのだからということで接尾子に _t を用いたりとか、

class CMonkeyHead;      // VC++ プログラマーによく見かける記法
class MonkeyHeadClass;  // 狭義のハンガリアン記法における本来の書式
class MonkeyHead;       // Pascal 記法 (型名は first upper case で)
class monkeyhead_t;     // Unix 系 C 言語出身で typedef 好きだとこうなる

クラスのメンバフィールドを表すのに、接尾子として下線のみ _ を用いたりとかするわけです。

string m_first_name;    // ハンガリアン記法:
                        // これは VC++ プログラマーにも普及している
string first_name_;     // Unix 界隈ではよく見かける記法
string first_name;      // ちなみにおいらはどっちも書かない
                        // 大抵はそれで困らないし

結局のところ、現場を見てきた感触としては、各個人に確たる理論があるわけでもなく、ただフィーリングとして、ハンガリアン記法に賛同したり、拒絶したりしているのではないかなぁと思います。あとは慣れですかね。

ただ、コーディング規約がしっかりと定められているような職場ではそれに従う必要があるわけで、そういう場面で「いやおいらはそれは気に入らないからそうしないよ」というわけにはいかないのも事実であり (提案することは悪くないとは思いますが、その代わり責任は持つべきでしょう)、フットワークが重要な IT 業界の職人的プログラマーにしてみれば、それこそ case by case で対応できなきゃいかんのでしょうね。

まぁ、最近すっかり Perl に毒されちゃっているおいらとしては、「なんでも指向」でええやんとか思わなくもないわけなんですけんども。。。

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※投稿には管理者が設定した質問に答える必要があります。

名前:
メールアドレス:
URL:
次の質問に答えてください:
おいらがやっている会社の名前をひらがな4文字で。

コメント:

トラックバック

このエントリのトラックバックURL: http://harapeko.asablo.jp/blog/2006/10/29/578486/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。