C言語プログラミングの覚え書き(改訳)

原文: Notes on Programming in C

Rob Pike

1989年2月21日

Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved.

Lucent Public License Version 1.02

前書き

KernighanとPlaugerによる“The Elements of Programming Style” (「プログラム書法」木村泉訳)は重要で影響力のある本です。この本にはそれだけの価値があります。しかし、その中の簡潔なルールが、本来意図されたような哲学の簡潔な表現としてではなく、よいスタイルのレシピとして受け取られているように私は時々感じます。この本が変数名は意味を持つようにつけられるべきだと言うなら、名前が使い方を説明するちょっとしたエッセイのようなものであるほうがいいということになるのでしょうか。MaximumValueUntilOverflowmaxval よりもいい名前ということになるのでしょうか。私はそうは思いません。

これから述べるものは、融通の利かないルールではなく、全体としてプログラミングの明確さという哲学を促進するような短いエッセイ集です。すべてに同意してもらおうとは思いません。これらは意見であり、意見は時とともに変わるものだからです。しかし、これらのことは、文書として書いたことはありませんでしたが、私の頭の中に長い時間をかけて蓄積してきたもので、多くの経験に基づいています。そのため、これがプログラムの詳細を計画する方法についての理解の助けになればと願っています(全体を計画する方法についてのよいエッセイを読んだことはありませんが、この文章は一部それについて書いています)。もし変わっていると思われても、問題ありません。同意できないとしても、問題ありません。しかし、なぜ同意できないかを考えていただくきっかけになれば、そちらのほうが望ましいことです。決して、私がこう言ったからこう書くということをしないでください。プログラムの中で達成したいことを最もよく表現できるとあなたが考えるようにプログラムしてください。また、それを一貫して、容赦なく行ってください。

あなたのコメントをお待ちしています。

表示の問題

プログラムは出版物のようなものです。プログラムはプログラマ自身やほかのプログラマ(それは数日後、数週間後、数年後のあなた自身かもしれません)に読まれるもので、そして最後に機械に読まれるためのものです。機械は、プログラムの見た目の美しさを気にしません。プログラムがコンパイルできれば、機械はそれで満足です。しかし、人間は美しさを気にしますし、気にするべきです。時々、やりすぎになることもあります。プリティプリントを行うプログラムは、プログラムのどうでもいい細かいところを強調するようなきれいな出力を自動的に行います。これ文章助詞すべて太字表示するのと同じぐらい馬鹿らしいことです。プログラムはAlgol-68 Reportのような見た目じゃないといけない(システムによってはそのスタイルでプログラムを編集するよう強制したりもします)と考える人は多いですが、明瞭なプログラムはそんな表示をしてもそれ以上明瞭にはなりませんし、ひどいプログラムは笑ってしまうような結果になるだけです。

もちろん、表示についての一貫した規約は見た目をわかりやすくするためには重要なものです。インデントは、最もよく知られた、最も役に立つ例でしょう。しかし、見た目がプログラムの意図より目立つようでは、表示が主体になってしまっていることになり、本末転倒です。ですから、古き良きタイプライター的出力で通すにしても、表示上の馬鹿げたやり方には気をつけましょう。装飾を避けましょう。例えば、コメントは簡潔に、バナーをつけないようにしましょう。言うべきことはプログラムの中で、簡潔に一貫性を持って言いましょう。それから次に進みましょう。

変数名

そう、変数名です。名前で重要なのは、長さではありません。重要なのは表現の明確さです。めったに使われないようなグローバル変数であれば、長い名前をつけてもいいかもしれません。例えば、maxphysaddr のように。ループ内のすべての行で使われるような配列の添字には、i よりも凝った名前は必要ありません。indexelementnumber といった名前をつけるのは、タイプ量が増える(または、エディタの助けを借りる)ことになりますし、計算の詳細よりも目立ってしまいます。変数名が非常に長くなると、何をしているのかわかりにくくなります。これは、表示の問題の一部でもあります。次の二つについて考えてみましょう。

for(i=0 to 100)
    array[i]=0
for(elementnumber=0 to 100)
    array[elementnumber]=0;

実際の例では、問題はもっとあっという間にひどいことになります。添字はただの記法です。そのように扱いましょう。

ポインタもちゃんとした記法が必要です。np が "node pointer" を指しているということが簡単に導けるような命名規約を一貫して使っていれば、npnodepointer と同じぐらい覚えやすいものになります。これについては次のエッセイで詳しく書きます。

プログラムの可読性に関するほかの側面での場合と同じように、命名においても一貫性は重要です。ある変数に maxphysaddr という名前をつけたら、同種の変数に lowestaddress という名前をつけてはいけません。

最後になりますが、私は最短の長さで最大の情報量のある名前をつけ、残りは文脈から補完できるようにしています。例えば、グローバル変数は普通、使用時にあまり文脈がないので、名前は比較的内容がわかりやすいようなものである必要があります。このため、私はグローバル変数には maxphysaddrMaximumPhysicalAddress ではありません)という名前をつけますが、ローカルで定義して使うポインタには NodePointer ではなく np という名前をつけます。これは感覚によるところが大きいですが、感覚は明瞭さに関わってくるものです。

名前に大文字を入れることは避けています。散文調の文章に慣れた私の目には、大文字は不格好で快適に読めません。ひどい表示の仕方と同じように目障りなのです。

ポインタの使用

C はポインタが何でも指せるという点で変わっています。ポインタは切れ味の鋭い道具です。切れ味の鋭い道具というものは、うまく使うと楽しく生産的になりえますが、間違った使い方をするとひどい傷をつけます(この記事を書く数日前に、私は彫刻刀を親指に刺してしまったところです)。ポインタも例外ではありません。ポインタは危険すぎる、何か汚いものだと思われているため、学術界での評判はよくありません。しかし、私はポインタは強力な記法だと考えています。これは、ポインタは明瞭な表現をする役に立つということです。

考えてみてください。あるオブジェクトに対するポインタがあるとき、それはまさにそのオブジェクトに対する名前であって、ほかのものではありません。些細なことのようですが、次の二つの式を見てください。

np
node[i]

一つ目はノードを指していて、二つ目は同じノードを指すように評価されます(ということにします)。しかし、二つ目の形式は式です。あまり単純なものではありません。解釈するには、node が何か、i が何か、そして inode がその周りのプログラムの(おそらく明記されていない)ルールによって関連づけられているということを知る必要があります。式だけを取り出してみると、inode の有効な添字なのかを知る手がかりはありません。もちろん、望む要素を指す添字なのかもわかりません。もし ijk が全部ノードの配列の添字だとすると、簡単にうっかりミスをしてしまいます。その場合、コンパイラは助けてくれません。特に、サブルーチンに渡すときには間違いを犯しやすくなります。ポインタは単純なひとつのものですが、配列と添字は、それがセットとなるものであることを受け取るサブルーチンのほうで信用しないといけません。

オブジェクトとして評価される式は、本質的にそのオブジェクトのアドレスよりもわかりにくく間違いやすいものになります。ポインタは、正しく使うことでコードを単純にできます。例えば、

parent->link[i].type

lp->type 

です。


もし次の要素の type が必要であれば、

parent->link[++i].type

(++lp)->type

になります。

i は値が進みますが、式の残りはそのままです。ポインタの場合、進めるものはひとつしかありません。

ここでも表示の問題が絡んできます。ポインタを使って構造体の中を読み進めるのは、式を使うよりもずっと読みやすいものになります。インクの使用量も減りますし、コンパイラやコンピュータの労力も減ります。関連した問題として、ポインタの型はその正しい使い方と関係しているので、配列の添字と違ってコンパイル時の便利なエラー検出が利用できます。また、オブジェクトが構造体であれば、フィールドは型を思い出す役に立つので、次のようなものは十分に意味がわかります。

np->left

添字によって配列を使う場合、配列はきちんと選んだ名前を持つことになり、式は長くなってしまいます。

node[i].left

ここでもまた、例が大きくなれば余分な文字はどんどん厄介なものになっていきます。

だいたいの場合、もしあなたのコードに似たような複雑な式がたくさんあって、それらがデータ構造の要素として評価されるなら、ポインタを注意深く使うことですっきりさせることができます。次のコードで、

if(goleft)
     p->left=p->right->left;
else
     p->right=p->left->right;

もし p の代わりに複合式を使っていたらどんな見た目になるか考えてみてください。計算の本質を抜き出すには、時には一時変数(ここでは p)やマクロを使うことが役に立ちます。

プロシージャ名

プロシージャ名は、それが何をするかを表しているべきです。関数名は、それが何を返すかを表しているべきです。関数は式の中で使われるもので、if のようなものの中でよく使われます。そのため、適切に読めるようになっている必要があります。

if(checksize(x))

は不親切です。checksize がエラーのときに true を返すのか、エラーでないときに true を返すのかが推測できないからです。それに対して、

if(validsize(x))

はその点を明確にしているので、将来そのルーチンを使うときに間違いが起こりにくいでしょう。

コメント

これはセンスと判断力が必要となる難しい問題です。私は、いくつかの理由から、コメントをあまり書かないようにしています。ひとつは、もしコードが明確で、よい型名や変数名を使っているなら、コード自身が説明になっているはずだからです。それに、コメントはコンパイラにチェックされないので、正しいという保証がないからです。特に、コードが変更されたあとはそうです。ミスリーディングなコメントは非常に紛らわしいものです。最後に、表示の問題です。コメントはコードをごちゃごちゃにしてしまいます。

しかし、私も時々はコメントを書きます。ほとんどの場合、その次に続くことの説明として使っています。例を挙げると、グローバル変数と型の説明(この場合だけは、大きなプログラムでは必ずコメントを書きます)、あまり見ないプロシージャや非常に重要なプロシージャの紹介、大きな計算セクションの区切りなどです。

有名な悪いコメントというものがあります。

i=i+1; /* i に 1 を足す */

そのもっと悪いやり方もあります。

/**********************************
*                                 *
*          i に 1 を足す           *
*                                 *
**********************************/

i=i+1;

笑うのは早いですよ。笑うのは、実生活で出会ってからでも遅くありません。

コメントでは、かっこいい表示を避けましょう。中心となるデータ構造の宣言のような重要なところは例外としてもいいでしょうが(データに対するコメントは、普通アルゴリズムに対するコメントよりもずっと役に立つものです)、コメントの大きな塊を避けましょう。基本的に、コメントを避けましょう。もしコメントがないと理解できないようなら、理解しやすくなるように書き直したほうがいいでしょう。ここで、次の問題が出てきます。

複雑さ

ほとんどのプログラムは複雑すぎます。つまり、問題を効率的に解くのに必要な以上に複雑だということです。なぜでしょうか。多くの場合、それは設計の悪さが原因ですが、その問題は大きすぎるのでここでは飛ばします。しかし、プログラムは細かいレベルでも複雑すぎることが多いもので、そのことについてはここで書くことができます。

ルール1 プログラムがどこで時間を使うかはわかりません。ボトルネックは驚くような場所で起こるので、ボトルネックの場所を証明できるのでなければ、適当な推測で高速化ハックを入れるのはやめましょう。

ルール2 計測しましょう。計測なしに速度のチューニングをしないでください。計測した場合でも、コードの一箇所がほかの場所に比べて圧倒的に時間がかかっているのでなければ、チューニングはやめましょう。

ルール3 かっこいいアルゴリズムは、n が小さいときには遅いものです。そして、n は普通小さいものです。かっこいいアルゴリズムは大きな定数項を持っているものです。n がよく大きな値になるとわかっているのでなければ、かっこいいことをするのはやめましょう(n が大きくなる場合でも、まずルール2 を使いましょう)。例えば、日常業務の問題では、二分木は常にスプレー木より速いものです。

ルール4 かっこいいアルゴリズムは単純なアルゴリズムよりもバグが起こりやすく、実装が難しいものです。単純なアルゴリズムと単純なデータ構造を使いましょう。

ほとんどの実用的なプログラムでは、次に挙げるリストのデータ構造があれば十分です。

  • 配列
  • 連結リスト
  • ハッシュテーブル
  • 二分木

もちろん、これらを組み合わせて複合データ構造を作る覚悟は必要です。例えば、シンボルテーブルは文字の配列の連結リストを含むハッシュテーブルとして実装されるかもしれません。

ルール5 重要なのはデータです。もし正しいデータ構造を選び、物事をうまくまとめれば、アルゴリズムはたいてい自明なものになります。プログラムの中心は、アルゴリズムではなく、データ構造です。(詳しくは「人月の神話」を参照してください)

ルール6 ルール6はありません。

データでプログラムする

アルゴリズムや、アルゴリズムの細かいところは、たくさんの if 文のようなもので書くよりも、データとして書いたほうが、効率よく強力に記号化することができることがよくあります。その理由は、手元の仕事の複雑さが独立した細かい部分の組み合わせによるものであれば、記号化できるからです。典型的な例としては、パージングテーブルというものがあります。プログラミング言語の文法を、定型のかなり単純なコードによって解釈できる形に記号化したものです。この手のやり方としては有限状態機械が特に柔軟に使えますが、何らかの抽象的な入力を「パージング」して何らかの独立した「アクション」列にするようなプログラムであれば、どんなものであってもデータ駆動アルゴリズムにすることでいい結果が得られるでしょう。

このような設計のおそらく最も興味深い側面は、テーブルがほかのプログラムによって生成されることもあるということです。典型的な例でいうと、パーザジェネレータがあります。もう少し身近な例としては、OSがI/Oリクエストを適切なデバイスドライバに割り当てるテーブルによって動いている場合、マシンに接続されたデバイスについての記述を読み込んで対応するテーブルを表示するようなプログラムで「設定」できるでしょう。

データ駆動プログラムが(少なくとも初心者の間で)一般的でない理由のひとつは、Pascalの独裁です。Pascalは、その作者同様、コードとデータを分割することを固く信じています。そのため、(少なくとも元の形では)初期化されたデータを作ることができません。これは、プログラム内蔵方式の原則を定義したチューリングフォン・ノイマンの理論に真っ向から喧嘩を売っています。コードとデータは同じものです。少なくとも、同じにすることができます。そうでなければ、コンパイラがどうして動くのか説明できないでしょう。(関数型言語はI/Oに関して同じような問題を抱えています)

関数ポインタ

Pascalの独裁の結果には、初心者が関数ポインタを使わないということもあります(Pascal では関数を値に持つ変数を使えません)。複雑さを記号化するために関数ポインタを使うことには、いくつかの面白い性質があります。

複雑さの一部は、ポインタの指すルーチンに渡されます。ルーチンは、同じように呼び出されるルーチンセットのひとつとなるので、何かしらの標準プロトコルに従う必要があります。しかし、それより重要なのは、ルーチンが自分の責任範囲のことしかしないということです。複雑さは分散されることになります。

このプロトコルという考え方は、同じような使われ方をする関数は同じようなふるまいをしなければならないというものです。このことが、ドキュメントの記述やテスト、プログラムの発展をやりやすくしています。さらには、プログラムをネットワーク越しに動かすこともやりやすくなります。プロトコルは、リモートプロシージャコールとしても記号化できるのです。

私の主張は、オブジェクト指向プログラミングの核心となるものは、関数ポインタを明確に使うことだというものです。データについて実行したい操作セットがあり、それらの操作に対して適用したいデータ型のセットがあるなら、プログラムをまとめる一番簡単な方法は、それぞれの型に対して関数ポインタのグループを使うというものです。これは、一言で言うと、クラスとメソッドを定義するということです。もちろん、オブジェクト指向言語にはそれ以上のものがあります。きれいな構文、派生型など。しかし、概念的には、ほとんど変わるところはありません。

データ駆動プログラムと関数ポインタを組み合わせると、びっくりするほど表現力のある書き方ができるようになり、私の経験では、意外な喜びをもたらしてくれることもよくありました。特別なオブジェクト指向言語なしでも、余計な手間なしでオブジェクト指向のいいところの90%を手に入れることができ、結果についても自分でコントロールしやすくなります。これほどお勧めできる実装スタイルはありません。この方法で構築してきたプログラムは、かなりの開発を重ねてもうまく生き残っています。もっとゆるいやり方の場合よりも、ずっといい結果です。きっと、この手法に求められる規律は、長い目で見ると割に合うということでしょう。

インクルードファイル

単純なルールです。インクルードファイルはインクルードファイルを決してインクルードしてはいけません。その代わりに、どのファイルを先にインクルードするべきかが(コメントで、または暗黙的に)言明されていれば、どのファイルをインクルードするべきかという問題はユーザ(プログラマ)に押しつけられますが、ある意味では扱いやすく、組み立て方によって多重インクルードを避けることができるようになります。多重インクルードはシステムプログラミングの癌です。ひとつの C のソースファイルをコンパイルするのに、5 回以上もインクルードされるファイルがあることは珍しいことではありません。この面から言うと、Unix/usr/include/sys はひどいものです。

#ifdef を使ってファイルが 2 回読まれないようにする小手先のテクニックがありますが、実際には正しく運用されないものです。#ifdef はファイル自身の中にあり、インクルードする側にあるのではありません。結果として、何千行もの不要なコードが字句解析器に渡されることになってしまいます。これは、(良いコンパイラの場合)最も負荷の高いフェーズになってしまいます。

単純なルールに従いましょう。

「C言語でプログラミングする際の覚書」の誤訳箇所

ここでは、C言語でプログラミングする際の覚書の誤訳を列挙します。

参考として、私の翻訳はC言語プログラミングの覚え書き(改訳)にあります。

What follows is ...

×従うべきは

○これから述べるのは

"What follows" で「続くもの」という意味です。ここでの「続く」というのは、現在の文章に続く、つまり「以下に述べること」です。

But they've been accumulating in my head, if not on paper until now, for a long time, ...

×しかし、私の意見は頭のなかにしばらくあったものをまとめたものであり、長らく文章として公開してきませんでした。

○しかし、これらのことは、文書として書いたことはありませんでしたが、私の頭の中に長い時間をかけて蓄積してきたもので、…

"if not on paper until now" これは、"in my head" との対比です。「頭の中にはあったが、文書にしたことはない」という意味です。また、"for a long time" は "they've been accumulating in my head" にかかります。

I've yet to see a good essay on how to plan the whole thing,

×これまでプログラム全体の計画に関しての良い文章は読んだことがありますが、

○全体を計画する方法についてのよいエッセイを読んだことはありませんが、

"yet to" は「まだ…していない」という意味です。文意が完全に逆になっています。

a clear program is not made any clearer by such presentation

×明瞭なプログラムはそのような見た目で成されるものではなく

○明瞭なプログラムはそんな表示をしてもそれ以上明瞭にはなりませんし

"not made any clearer" は「さらに明瞭にはならない」ということです。

... is more to type (or calls upon your text editor)

×タイプ量(あるいはエディタ内で呼ばれる回数)が増え

○タイプ量が増える(または、エディタの助けを求める)ことになりますし

"call (up)on" は「頼る」という意味です。

if you consistently use a naming convention from which np means ``node pointer'' is easily derived

×どの np が "node pointer" を意味しているかがすぐに分かる命名規則を一貫して使っていれば

○np が "node pointer" を指しているということが簡単に導けるような命名規約を一貫して使っていれば

"from which" の "which" は "naming convention" を指しています。

As in all other aspects of readable programming, consistency is important in naming.

×プログラムの可読性に関していえば、命名において一貫性は重要です。

○プログラムの可読性に関するほかの側面での場合と同じように、命名においても一貫性は重要です。

元の翻訳では "As in all other aspects" が訳されていません。

I prefer minimum-length but maximum-information names

×私は最短の名前ではなく最も情報量がある名前を好み

○私は最短の長さで最大の情報量のある名前を好み

「最短であるが、しかし最大の情報量を持つ」という意味です。

They jangle like bad typography.

×悪い印刷のように目障りなのです。

○ひどい表示の仕方と同じように目障りなのです。

最初は、全篇を通して "typography" が「印刷」と訳されていたと思うのですが、その名残でしょうか。

Pointers are sharp tools

×ポインタは賢い道具で

○ポインタは切れ味の鋭い道具で

文字通りの意味です。「賢い」では、あとの「使い方を間違えると…」とつながりません。

If we want the next element's type

×もし次の要素の型が必要な場合は

○もし次の要素の type が必要であれば

原文で type がコード用フォントになっていませんが、文脈から構造体の要素名であることが明らかです。

less effort is expended by the compiler and computer

×コンパイラやコンピュータが展開する労力も減ります

コンパイラやコンピュータの費やす労力も減ります

"expend"(費やす)を "expand"(展開する)と間違えたのでしょう。

which allows some helpful compile-time error checking that array indices cannot share

×これでコンパイル時に配列のインデックスが適切で無い旨のエラー検出が可能になります。

○配列の添字と違ってコンパイル時の便利なエラー検出が利用できます。

"array indices cannot share" の "share" は、「同じく持つ」という意味です。配列の添字ではエラー検出ができないということです。意味が取れておらず、つじつま合わせになっています。

As a rule

×ルールとして

○だいたいの場合

熟語です。

expressions that evaluate to elements of a data structure

×データ構造の要素を評価するような…データ構造

○データ構造の要素として評価される式

ここでの "evaluate to" は、「評価されることによって…になる」という意味です。

Consider what ... would look like using a compound expression for p

×p の複合的な使い方をしているこのコードが何をしているかを考えてみましょう

○もし p の代わりに複合式を使っていたらどんな見た目になるか考えてみてください

「p の代わりに複合式(例えば、node[i] など)を使ったらゴチャゴチャした見た目になる」ということです。元の訳では意味不明です。

Sometimes it's worth a temporary variable (here p) or a macro to distill the calculation.

×時には一時変数(この場合は p )を使用したり、計算の本質を抜き出すマクロを使用する価値があります。

○計算の本質を抜き出すには、時には一時変数(ここでは p)やマクロを使うことが役に立ちます。

「一時変数」と「マクロ」は並列です。

Procedure names should reflect what they do; function names should reflect what they return.

×プロシージャ名はそれが何をするかを反映すべきです。つまり関数名はそれが何を返すかを反映すべきです。

○プロシージャ名は、それが何をするかを表しているべきです。関数名は、それが何を返すかを表しているべきです。

原文にない「つまり」があるために、実際にはない論理的関係があるかのように見えてしまいます。

A delicate matter, requiring taste and judgement.

×慎重に、経験と判断をもって書く必要があります。

○これはセンスと判断力が必要となる難しい問題です。

コメントの「書き方」ではなく、「コメントを書くこと(書くかどうかを含め)」全体に対するものです。

a symbol table might be implemented ...

×シンボルテーブルは…として実装されているでしょう

○シンボルテーブルは…として実装されるかもしれません

"might" はかなり弱い意味です。

Algorithms, or details of algorithms, can often be encoded compactly, efficiently and expressively as data rather than, say, as lots of if statements.

×アルゴリズム、つまりアルゴリズムの細かな部分は、しばしばデータという簡潔で、効率的で、表現豊かな形に記号化されます。それは、たとえば、多くのif文という形ではありません。

アルゴリズムや、アルゴリズムの細かいところは、たくさんの if 文のようなもので書くよりも、データとして書いたほうが、効率よく強力に記号化することができることがよくあります。

元の訳は、日本語としてよくわからないものになっています。

A classic example of this is parsing tables, which encode the grammar of a programming language in a form interpretable by a fixed, fairly simple piece of code.

×古典的な例で言えば、表のパースです。これはプログラミング言語の文法を、定形のかなり単純なコード片によって説明可能な形式に記号化することです。

○典型的な例としては、パージングテーブルというものがあります。プログラミング言語の文法を、定型のかなり単純なコードによって解釈できる形に記号化したものです。

"parsing tables" は全体でひとつの名詞です。動詞-目的語ではありません。また、"interpretable" はコードによって「解釈」できる、つまり読み込んでそれに従って動作できるという意味で、「説明」ではありません。

Finite state machines are particularly amenable to this form of attack

×特にこのような問題に取り組むときには有限状態機械が採用されていますが

○この手のやり方としては有限状態機械が特に柔軟に使えますが

"amenable" は「従順な」、つまり「柔軟に使いやすい」ということです。

can be constructed profitably as a data-driven algorithm

×生産的な形としてデータ駆動のアルゴリズムになります

○データ駆動アルゴリズムにすることでいい結果が得られるでしょう

"profitably" は、「利益が得られるような形で」というニュアンスです。

One of the reasons data-driven programs are not common, at least among beginners, is the tyranny of Pascal.

×データ駆動プログラムが一般的でない理由の一つは、少なくとも初心者においては、Pascalによる圧政でしょう。

○データ駆動プログラムが(少なくとも初心者の間で)一般的でない理由のひとつは、Pascalの独裁です。

"at least among beginners" は前にかかります。

This flies in the face of the theories of Turing and von Neumann

×このことはチューリングフォン・ノイマンの理論の前にはたち消えてしまいます

○これはチューリングフォン・ノイマンの理論に真っ向から喧嘩を売っています

"fly in the face of" は「真っ向から対立する」です。これは私も調べたところなのですが、「ここはたぶん熟語じゃないか」というセンスが必要です。適当に訳をでっち上げるのはよくありません。

I cannot recommend an implementation style more highly

×より高度な次元の実装方法を推奨することはできません

○これほどお勧めできる実装スタイルはありません

"highly recommend" という組み合わせはよく聞くものですが、"recommend ... more highly" はその比較級です。「それ以上お勧めできない」、つまり「とてもお勧めできる」ということです。

Maybe that's it:

×以上です。

○きっとこういうことなのでしょう。

これは ":" に続くものに対しての文です。

by construction

×ビルド時に

○組み立て方によって

「組み立て方を工夫することで(多重インクルードを避けられる)」程度の意味です。

but it's usually done wrong in practice

×普通は間違った結果となります

○実際には正しく運用されないものです

"in practice" 実際にやってみると、"done wrong" 悪いやり方で行われる、という意味です。元の訳ではなぜ「間違った結果」になるのかわかりません。

善意のひどい訳について

2014/10/14 追記: 補足記事を書きました。なぜ誤訳指摘をしたか


ぼくは、ずっと昔から「ひどい翻訳」というものに憤りを感じてきた。

以前、別の記事に書いたこともある。

統計学を拓いた異才たちのようなひどい翻訳を見るたびに、どうして世の中からはこの手の悲劇がなくならないのかとため息が出る。


この前、またひどい翻訳を目にする機会があった。

C言語でプログラミングする際の覚書


ちょっと原文と比較すると致命的な誤訳がいくつも見つかる、最低クラスの翻訳だ。

やれやれと思いながら、翻訳のひどさを嘆くコメントをはてブに残して、ツイッターに流した。



日常の一コマだ。


ここでちょっといつもと違う流れになったのは、訳者の方からリプライをもらったということ。



そういえば今はネット時代なんだから、誤訳指摘が訳者の目にもとまるというのは十分ありうることだ。


その後リプライを通して、誤訳を 2箇所直してもらった。

普通の流れなら、ここで一件落着、みんなハッピーというところだ。


でも違う、そうじゃない。

誤訳は 2箇所 どころじゃない。

ちゃんと指摘すると、何箇所になるかわからないぐらいあるんだ。


統計学を拓いた異才たちを読んだとき、あまりに翻訳がひどいので原書(おすすめです)を買って読んで、和訳の誤訳箇所に付箋を貼ろうとしたことがある。

しかし、最初の数ページまで見たところで付箋だらけになってしまい、不毛な気持ちになってあきらめた。


でも今回は、量が少ないこともあるので、「誤訳の全指摘」をやってみることにした。

ひどい翻訳というものにはどれだけの誤訳があるものかを示したかったし、自分でも興味があった。


自分でも全文を翻訳してみて、それと比べながら指摘箇所をまとめた。

すると、誤訳は(指摘済みの2箇所のほかに)30箇所あった。


誤訳指摘は別記事にまとめた。

「C言語でプログラミングする際の覚書」の誤訳箇所


ちなみに、ぼくの参考訳はこちら。

C言語プログラミングの覚え書き(改訳)


今回の件で、ひどい翻訳というものについて真剣に考えることになった。

これまで、ひどい翻訳を見ると、無意識のうちにそれを何らかのモラルの欠如によるものだと思っていた。

ろくに英語を勉強していない人が身の程知らずに翻訳しているか、手抜きで翻訳を見直しもしないとか。

だから、ひどい翻訳を見るといつも怒りを感じていた。

しかし、今回の翻訳を出した id:ymotongpooさんは明らかに紳士的な人で、人間としても技術者としてもすばらしい人のようだった。

ツイッターで話してみると真面目に考えて訳されたようで、調べてみると外資系企業で普段から英語を使って仕事をされているらしい。*1

マニュアルの翻訳や書籍の翻訳もされているようだ。


でも、背景はともかく、例の翻訳はあまりにもひどい。

コードの質が、誰が書いたかではなくどう書かれているかで判断されるように、翻訳の質も、誰が訳したかではなくどう訳されているかで判断するべきだと思う。


例の翻訳も、ぼくの翻訳も、さらっと読んだ感じの印象はあまり変わらないかもしれない。

翻訳の質というのは、それほど重要じゃないのかもしれない。

それでもぼくは、誤訳の多い翻訳はよくないと思っている。

理由は主に二つ。

  1. 文章をきちんと読むという習慣を阻害する。
  2. 著作物のアイデンティティの問題。


一つ目の「文章をきちんと読むという習慣を阻害する」というのは、そういう誤訳だらけの文章を読んでいると、一字一句をちゃんと理解して読もうとするとつまずいてしまうからだ。

例えば、元の翻訳の次の部分。

if(goleft)
     p->left=p->right->left;
else
     p->right=p->left->right;

p の複合的な使い方をしているこのコードが何をしているかを考えてみましょう。

ここでの「このコード」はただの例だ。

「何をしているか」を考えてもしょうがない。

でも、「きちんと読む」ということをしようとしてしまうと、ここで引っかかってしまう。

ちなみに、ぼくの翻訳は次のようなものだ。

次のコードで、もし p の代わりに複合式を使っていたらどんな見た目になるか考えてみてください。


二つ目の「著作物のアイデンティティの問題」というのは、「その訳文は、そのタイトルに値するのか?」ということ。

統計学を拓いた異才たち」を読んだ人は、"The Lady Tasting Tea"と同じ本を読んだと言えるのか?

C言語でプログラミングする際の覚書」を読んだ人は、"Notes on Programming in C"を読んだと言えるのか?

どちらも、訳文の意味の通らないところを読んで、読者が「この著者の書く文章はわかりにくいな」と思ってしまうかもしれない。

ひどい翻訳というのは、機械翻訳よりはずっとましかもしれないが、そういう点で危険だ。

機械翻訳なら、原著者がわかりにくいことを書いたとは思わないだろう。


いろいろ悪く書いたけれど、それはあくまで訳文に対するもので、人格攻撃ではない。

翻訳というシステムがもっとうまく動くようになってくれればいいと願っている。

原文を読めばいいと言う人もいるかもしれないが、外国語を読むのには時間がかかる。

ぼくは速いほうだとは自負しているけれど、それでも日本語に比べるとだいぶ遅くなる。


技術翻訳は残念なものが多いが、文芸翻訳は平均して質が高いと感じる。

翻訳で読んだ本には、すばらしいものがいくつもある。

ここではお気に入りを二つ挙げる。



どちらも翻訳でも原文でも読んだけれど、誤訳はほとんどなかった。*2

何百ページもある本をほとんど誤訳なしに訳すのだから、考えてみるとすごいことだ。


技術翻訳も、専業の翻訳者との連携でできたらいいんじゃないかと思う。

専門知識のない翻訳者でも、技術文書の翻訳で助けになれるところは多いはずだ。

例えば、

a naming convention from which np means ``node pointer'' is easily derived.

の "which" が関係代名詞であること、

parsing tables, which encode the grammar of a programming language

の "parsing tables" が動詞-目的語構造ではなく全体でひとつの名詞句であることを読み取るのに、プログラミングの知識は必要ない。

それは英語自体から読み取れる。


英語(外国語)の構文を間違えずに読むというのは、ひとつの能力だ。

翻訳には、分野の専門知識と同じぐらい、外国語を読む能力が必要だ。

(その能力のない)技術者が単独で翻訳をすると、分野の専門知識を何も知らない翻訳者が単独で翻訳するのと同じぐらい悲惨なことになる。

しかし、専門知識のない翻訳者が単独で技術書を翻訳することはあまりないのに、英語(外国語)の読めない技術者が単独で技術書を翻訳するというのは、ずっとよくあることだ。

「翻訳」ということの本質的な困難さが認識されて、後者が前者と同じぐらいひどいことだという認識が広まってほしいと思っている。


ところで、id:ymotongpoo さんが「すごいErlangゆかいに学ぼう!」を翻訳されたときはGitHubのプライベートリポジトリなどでされたそうだ。



この書籍はまだ拝見していないが(そのうち読んでみたい)、多くのレビュアーによって質の高いものになっていることと思う。

こういう体制が普及していけば、例えば専業翻訳家との連携のようなこともやりやすくなって、統計学を拓いた異才たちのような「英語の読めない専門家によるひどい翻訳がそのまま書籍になってしまう」という悲劇が起こらなくなるんじゃないかと期待している。


後記1 この記事の中では「統計学を拓いた異才たち」を非常に悪く言っているが、この訳書はそれに値するものだと思っている。仕事になるのであれば、その数百箇所にのぼるであろう誤訳を全指摘したいところだ。

後記2 「じゃあお前は何を翻訳したんだ」と言われるかもしれない。ぼくはこれまで翻訳はあまりしていない。そのことはこの記事の主張とは関係ないと思うが、いま少しずつ手をつけているものはある。公開できるところまで行ければいいと思う。

*1:エンジニアとして有名な方のようだが、ぼくはアンテナが低いせいか知らなかった。

*2:突き合わせて読んだわけではないけれど、大きな誤訳があれば読めばわかる。