知らないと恥をかく プログラミングの常識

新人プログラマが身につけるべき 知らないと恥をかくプログラミングの常識

新人プログラマが身につけるべき 知らないと恥をかくプログラミングの常識

メールアドレスが載っていたから問題点をまとめて*1送ったら、意外と早く返信が来たものの・・・・

私の考え方は223ページ以降に記したとおりです。
一部、ご指摘のことを踏まえて、将来の版でより適切に書き直すことを検討させていただきたいと思います。
本書の出版が、他の「プログラミングの常識」に関連した書籍の内容の比較等も含めて、活発な議論のきっかけになれば幸いです。

亜阿相界 あぁそうかい。
なら議論のきっかけになるように、「一部」ではなかったことを示しておくことにするよ*2 *3


223 ページの「私の考え」はどんなものかというと、

本書ではさまざまなプログラミング言語やプログラミング環境を取り上げてきました。言語や環境によって正反対のことがあるにもかかわらず、さまざまなトピックを大胆不敵に取り上げたのには、もちろん理由や根拠があります。
・・・
本書を読んでいて、「これは間違っているんじゃないだろうか?」と思った人もいるかもしれません。
実際に、本書の記述の中には特定の環境や特定のプログラミング言語にはあてはまらないこと、言い換えれば環境やプログラミング言語によっては間違いであることが (少しは) あります。
・・・
しかしそうすると、本書のほとんどの段落で「一般的には」や「原則として」、あるいは、「多くの場合には」とか「一般に良く使われている開発環境では」などを使うことになり、・・・そこで、本書ではいちいち「一般的には」や「原則として」と断っていません。
・・・
自分の常識は世間の常識とは必ずしも一致しないという、世間一般でも当たり前でありながら忘れられがちなことを常に忘れないようにする必要があります
・・・
プログラミングを習得してある程度経験を積むと、いちいち検証せずに、それまでの自分の経験と知識で判断するようになりますが、そのようなときに独断や偏見を持ち込まないように常に気をつける必要があります (著者自戒)。

一般的で原則的な話

これと上のメールの内容を踏まえて以下をお読みください (長文注意)。

完全に間違っているもの

反論の余地は全くなく、完全に間違っている部分。

マクロ

サブルーチンの呼び出しにはオーバーヘッドがあることに触れ、そのオーバーヘッドをなくすことのできる手段としてマクロを紹介しているが、そのマクロが

#define add2val(m, n) (m + n)
サブルーチンかマクロか (P.27)

と、バグを含んでいる。
このマクロを

int result = add2val(1 << 2, 3);

のように使用すると、

int result = (1 << 2 + 3);

こう展開されるが、<< の優先順位が + の優先順位よりも低いため、7 ではなく 32 となる。
分かりやすく括弧を付けると、

int result = (1 << (2 + 3));

こう。
で、本文ではマクロのこういう危険性については、

マクロは文字列の置換で実現されているので、注意を払わないと予期しない結果になることがあります。そのような問題をマクロの副作用と言います。

サブルーチンかマクロか (P.28)

と、これだけ。具体的な例はなし*4


C や C++ のマクロについて新人に教えるとしたら、

  • 使うな
  • 別の言語だと思え
  • 関数と同じ命名規則にするな
  • とにかく使うな

かな。

C# の struct

C# の struct について、

上の例のようにメンバとして変数だけがある構造を作るとき (あとで示すメソッドがないものを定義するとき) には、実際にはクラスではなく構造体 (struct) として定義するのが常識です。そのほうがパフォーマンスの点で優れているからです。ただし、この場合は、このあとでメソッドを追加するので、クラスとして定義しました。

クラスの定義と利用 (P.32)

とあるが、そんなに単純なものではない。
というか、こういう細かい部分は新人は気にする必要なはいと言うか、気にするべきではないと思う。


内容についても、struct にすれば無条件でパフォーマンスが上がるように書かれているし、著者もそう信じているんだろうけど、そんなものではない。
フィールドのサイズが大きい場合や、数が多い場合、頻繁にボックス化・アンボックス化が発生する場合は、struct より class にした方がいい。
また、struct にメソッドが定義できないような記述もあるが、それも間違い。

独自の用語

汎用高級プログラミング言語

汎用高級プログラミング言語は、単に「高級プログラミング言語」あるいは「高水準プログラミング言語」と呼ばれることも多い言語で、たとえば、C 言語、C++C#FORTRANJavaVisual Basic などがあります。

プログラミング言語はどれが良いか?

Google での汎用高級プログラミング言語の検索結果

矢沢さんといい日向さんといい、自分で用語作るの好きですね。
ダメな著者の特徴なのかもしれない。

矛盾

C 言語の特徴として、

オブジェクト指向の要素がまったくない手続き型の言語ですが、そのために習得が容易

プログラミング言語はどれが良いか? (P.43)

としているにもかかわらず*5

プログラミング言語はどれが良いか? (P.48)

全然初心者向きじゃない。何をもって初心者向きとするか、ってのは難しいだろうけど、習得が簡単なら十分初心者向きだと思うんだけど。

イベントとメッセージ

イベントとメッセージは、オブジェクト指向プログラミングで使う重要な概念です。
・・・
ウィンドウベースのプログラムでは、発生したイベントのメッセージを処理することで、操作したり処理を行ったりします。これをイベント駆動型プログラミング (イベントドリブンプログラミング) と言います。

オブジェクト指向ってようするに何? (P.60)

イベントドリブンのメッセージと、OOP の概念であるメッセージを混同してないか?
イベントドリブンでのイベントとメッセージは、OOP で使う重要な概念ではないよね*6

Substring

言語によって、文字列のインデックスが 0 から始まるか 1 から始まるかという話の中で、

次に C# の文字列操作メソッドを見てみましょう。次の C# プログラムは、Substring(2, 3) によって文字列の 2 文字目から 3 文字を取り出します。

using System;

namespace CSCnslApp
{
    class Program
    {
        static void Main(string[] args)
        {
            String msg = "Hello, Dogs.";
            
            Console.WriteLine(msg.Substring(2, 3));
        }
    }
}

そのため、このプログラムを実行すると「ell」と出力されます。

ゼロか 1 か

とあるが、そんなわけない。出力されるのは「llo」だ。
こんなことを平気で書いてしまうということは、今まで本当に勘違いしていたのだと思う。
日向さん、あなたの書いたプログラム、全部見直したほうがいいかもしれませんよ?

データ型と単位

日常生活で扱う値にも、実は型があります。たとえば、酒屋で缶ビールを買うときのことを考えてみましょう。500ml の缶ビールを 350 円で買うとき、500 という値と 350 という値の性質は全く違います。我々はそのことを特に意識しているわけではありませんが、明確に区別しています。いま、500 円玉を出して、350 円の缶ビールを買ったとします (図 5.1)。そのときに、おつりとして 150ml の水を渡されたら、あなたは黙って受け取るでしょうか?

便利で怖いデータ型 (P.114)

人はそれを単位という。

さて、もし仮に今年の人口から昨年の人口を引いた人口動態を求めるつもりでうっかり次のような式を書いてしまったとしましょう。

int popMovements = popThisYear - area;

上の式は、今年の人口から面積を引いているので意味ある結果は求められません。そして、C# ではこのような場合、「型 'double' を 'int' に暗黙的に変換できません。」というエラーが報告されます。

「このような場合」ってのはダメでしょ。ためしに int 型で今年の世帯数 familiesThisYear でも追加してみましょうか?で、

int popMovements = popThisYear - familiesThisYear;

ではもちろんエラーにはならない。
C# に標準で備わっているデータ型は単位を扱うには広すぎるんだから当然なんだけど。

正の無限大、負の無限大

一般的には、多くのプログラミング言語で、数をゼロで割ると例外と呼ぶ事態が発生します。しかし、本書執筆時までのバージョンの JavaScript では結果は Infinity になります。Infinity は、例外という異常事態を避けるための工夫であるとみなすこともできます。

特殊な値 (P.134)

適当なことを言ってくれるなw
それは、JavaScript が今のところ数値を浮動小数点数として扱うからであって、他の言語でも浮動小数点数型を使えば Inf を確認できる。
それにしても、「例外と呼ぶ事態が発生します」って大仰に言ってるけど、それ逆に分かりにくくない?「例外が発生します」でいいと思う。

null
  • C/C++ では null は値ゼロと同じです。また、C/C++ では大文字の NULL は、値 0、または、文字 '\0'、あるいは (void *)0 を表します。
  • C#Java では、参照型がオブジェクトを指していないことを表します (C#Java では、null はゼロではありません)。
  • JavaScript では null は「値を持っていない」ことを表す値です (JavaScript の場合、null はゼロではありません)。
特殊な値 (P.135)

C では (void*) 0 は NULL としてありうるけど、C++ ではこれは違法だし、'\0' は C でもダメなんじゃ*7
それに、値ゼロと同じという表現もどうだろう?ヌルポインタは整数の 0 と同じビットパターンでなければならないというわけじゃないし。


追記:
コメントいただいたように、

  • C89 では NULL は 0 か ( (void*)0) とすべき(全体を括弧で囲む必要アリ)
  • '\0' はヌルポインタとして有効なので、C99 や C++ のように NULL が処理系定義の場合有効
  • 「null は値ゼロと同じ」という表現に間違いはなさそう
Nothing、this、void

Infinity や null と同じところで、Nothing や this、void といったものについても説明があるけど、基本、これらは値ではない。

より良いコード?いやいや

グローバル変数を使用していたコードをローカル変数を使用する方法に改良して、さらに最終的には「より良いコード」としてひどいコードを紹介している。

グローバル変数をローカル変数に置き換えたプログラムは、かなり良くなりましたが、現在のオブジェクト指向プログラミングの技術を容易に利用できる環境では、必ずしも優れたコードとは言えません。長方形を扱うこのプログラムを、今後どんどん拡張していくつもりなら、たとえば次のようなコードを書くことを考えましょう。

// 
// Rrectangle.cpp
// 
#include <iostream>
#include <cmath>

using namespace std;

class Rectangle
{
private:
    int x1, y1, x2, y2;
    
public:
    Rectangle()    {}
    Rectangle(Rectangle *rect)
    {
        x1 = rect->x1;
        y1 = rect->y1;
    }
    Rectangle(int x, int y)
    {
        x1 = x;
        y1 = y;
    }
    
    void getTopLeft()
    {
        cout << "左上のX座標>";
        cin >> x1;
        cout << "左上のY座標>";
        cin >> y1;
    }
    
    void getBottomRight()
    {
        cout << "右下のX座標>";
        cin >> x2;
        cout << "右下のY座標>";
        cin >> y2;
    }
    
    double getArea()
    {
        return abs(x1-x2) * abs(y1-y2);
    }
    
    double getDiaglen()
    {
        return sqrt( (double) ((x1-x2) * (x1-x2) + (y1-y2) * (y1-y2)) );
    }
    
    void printRectInfo()
    {
        cout << "(" << x1 << "," << y1 << ")-("
             << x2 << "," << y2 << ")の長方形" << endl;
    }
    
    void printArea()
    {
        cout << "面積=" << getArea() << endl;
    }
    
    void printDiaglen()
    {
        cout << "対角線の長さ=" << getDiaglen() << endl;
    }
};

int main()
{
    // ローカル変数
    Rectangle *rect1 = new Rectangle();
    
    rect1->getTopLeft();
    rect1->getBottomRight();
    
    rect1->printRectInfo();
    rect1->printArea();
    rect1->printDiaglen();
    
    cout << "新しい長方形" << endl;
    Rectangle *rect2 = new Rectangle( rect1 );
    rect2->getBottomRight();
    
    rect2->printRectInfo();
    rect2->printArea();
    rect2->printDiaglen();
    
    return 0;
}
グローバル変数とクラス変数/ローカル変数 (P.146)

突っ込みどころとしては、

  • (全体的に) なにこの Java (もしくは C#) もどき
  • Rrectangle.cpp なんて、変な名前つけるんですね
  • ヘッダと実装ファイルに分離しないの?
  • Pimpl イディオム使わないの?
  • なんでコピーコンストラクタじゃないの?
  • なんで初期化リスト使わないの?
  • なんで explicit なし? Rectangle rect3 = rect1; とかすごくキモイんですけど
  • Rectangle(Rectangle*) でなんで全部コピーしないの?
  • なんで戻り値型が void なのに get なんて名前つけるの?
  • なんでメンバ関数で cin とか cout 使ってメンバ変数設定してるの?
  • なんで abs に int 渡してるの?
  • なんで C++ 形式のキャスト演算子を使わないの?
  • なんで出力に operator<< を使わないの?
  • なんで printArea やら printDiaglen やら、外部に出せるものをメンバ関数にしてるの?
  • // ローカル変数 ってコメントに、どんな意味があるの?
  • delete し忘れてる・・・なんで auto_ptr 使わないの?いや、そもそも new する必要ないよね

「長方形を扱うこのプログラムを、今後どんどん拡張していくつもりなら、たとえば次のようなコードを書くことを考えましょう。」だって?
冗談はよしてくれ。

式と文

「area = PI * r * r;」のようなものは、式であると同時に、代入を行うので、これもステートメントです。
・・・
たとえば、次の式は、まず、1 に 3 を加えた値 4 を決定します。次に、その値を x に代入して x の値を決定します。

x = 1 + 3;
プログラムの要素 (P.151)

えっと、この人、インタプリタの本も出してましたよね。
式と文の違いが分からないとか、ちょっと致命的な感じがするんですけど。しかも、代入を行うから文?なら a = b = c とかどう説明するんだ。

さて、ここで次のような式があるものとします。

x = 3 + 5 * 7;
プログラムの要素 (P.152)

だから、それは文*8

優先順位と括弧
x = 3 + 5 * 7;

これが実行されると、結果は 3 + (5 * 7)=3+35=38 となります。これは、乗算 (*) 演算子の優先順位が、加算 (+) 演算子の優先順位よりも高い、ということを知っていれば簡単に理解できます。もし、この優先順位を変更して先に 3+5 を実行したいときには「x = (3 + 5) * 7;」にします。() の優先順位は (ほとんど常に) 最も高いからです。
演算子の優先順位ということを聞いたことがあれば、ここまではよいでしょう。

プログラムの要素 (P.152)

良くないよ!優先順位を強制するための括弧は演算子じゃないよ!
つながりをみると C か JavaScript の話だと思っていい。てことは、関数呼び出しの () 演算子を優先順位変更の演算子であると勘違いしたんでしょうね。
細かいことだけど、「先に 3+5 を実行」ってのもちょっと引っかかる。「先に 3+5 を評価」じゃね?どうでもいいか。
・・・本当にこの人、インタプリタの本書けるの?


追記:
コメントいただいたように、C 言語や C++ では () は単項演算子らしい。
JavaJavaScript とは違うのか・・・


更に追記:
コメントいただいたように、やっぱりC 言語や C++ でも () は演算子じゃないらしい。
あとで確認してみる。確認した。やっぱり演算子じゃない。

SQL

問い合わせ (データの検索や取得) に SQL (Structured Query Language、構造化問い合わせ言語) を利用するデータベースがよく使われます。

データの保存 (P.169)

SQL は何かの略じゃないんだってば。

これを BLOB (Binary Large OBject、バイナリラージオブジェクト) と言います。

データの保存 (P.170)

それも違うんだって!

最適化

プログラムは使える限りの最適化技術を使ってできるだけ高速化するべきです。

見かけの速さの重要性 (P.187)

全てをアセンブリで書けということですね、わかります。
最適化の話は新人にすべきなのかどうか、というのは置いといたとしても、最適化のやり方*9が載っていないとはどういうことだ。


で、最適化の手法として、

  • 関数のマクロによる置換
  • 別モジュールにして実行時チェックをはずしてコンパイル
  • SSE や SSE2 の使用
  • 整数に変換して計算後、再変換
  • 並列処理
  • アラインメントの調整

を挙げた後に、

スクリプト言語やマクロ言語などでは、高速化の方法は限定されます。できることは、小さいサイズの変数を使うとか、アルゴリズムを改良する程度です。

細かいことにこだわろう (P.192)

って、アルゴリズムの改良は真っ先に考慮すべきものだろw
何その「アルゴリズムを変えたってそんなにパフォーマンス向上しないよ」みたいなニュアンス。
そもそも、「ユーザーのマシンは遅い(P.185)」のに、「現在のコンピュータには、多くの場合、マルチコアの CPU が搭載されています(P.191)」って、都合いいな。並列処理言いたかっただけちゃうんかと。

それはデバッグとは言わない

最も良いデバッグの方法は、デバッグしないで済むプログラムを設計して作ることです。矛盾しているように感じるかもしれませんが、これが真理です。

デバッグの技法 (P.194)

矛盾しているように感じるんじゃなくて、矛盾してるんだよ。
デバッグとは、「バグを取り除くこと」だから、はじめからバグがなければ、それは (どれ?) デバッグとは言わない。
「バグを作らないためには、コードを書かないことだ」から連想したんだとは思うけど、こっちは矛盾してない。

例外オブジェクト

例外を処理すると、例外オブジェクトは消滅したものとみなされます。

エラーリカバリーの重要性 (P.198)

例外オブジェクトは処理したからと言って消滅するわけじゃないでしょ。
GC のある言語なら、GC に回収されるまでメモリ上に存在し続けるし、C++ で「値で投げて参照で受け取れ」ってのは catch を抜けたからって例外オブジェクトが消滅するわけじゃないことからきてるんだし。
しかも、「例外を処理する」なんていってるけど、再スローはどう説明するんだろうか。
無理に独自の解説をしようとして失敗している例なんだろうな。

構文?

C# の場合の例外処理のコードは、基本的には、try と catch を使った次のような構文です。

// 例外処理の例
try
{
    ( 例外が発生する可能性のあるコード )
}
catch (例外クラス e)
{
    ( 例外クラスの例外が発生したときに実行するコード )
}
エラーリカバリーの重要性 (P.198-199)

それは構文とは言わないんじゃない?基本的じゃない場合にはなにか別の構文でも用意されてるの?
インタプリタの本書いてる人なんだからさ、用語くらいきちんと使おうよ。
例外クラスの例外って意味も分かりにくいし。


ちなみに Java だと例外処理の構文は、

TryStatement:
  try Block Catches
  try Block Catchesopt Finally

以下略の try 文を使用する。C# の言語仕様は知らないんだけど、似たようなもんじゃないの?

VC++ しか使ってなさそうなのに、そんなことがよく言える

コンパイラによっては、事実上、無意味な警告を報告するものがあります。たとえば、Microsoft の Visual C++ では、strcpy() や strcat() のような標準的な文字列関数を C/C++ソースコードの中で使うと警告が出ます。Microsoft は、セキュリティ上の理由で strcpy() の代わりに strcpy_s() を使い、strcat() の代わりに strcat_s() を使うことを推奨しています。しかし、現時点で、このような非標準の警告は現在も将来も Microsoft の開発ツールだけに頼っている開発者には意味がある可能性がありますが、一般的には無意味な警告です。無意味な警告は無視するほうがよいでしょう。・・・以下自重

警告されたらどうするか (P.206)

こんなことを新人に教えるのか?本気でやめてくれ。こういうときこそマクロの出番でしょうが。
C++ なら _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES を ON にするだとか、そもそも C の文字列関数を使わずに std::basic_string 使うだとか、やりようは他にもあるだろうし。


というかですね、あなたの書くプログラム、なんか VC++ 以外ではコンパイルしたことなさそうに見えるんですが。

間違っているとは言い切れないもの

著者の考え

こんなことを言ってしまっては身も蓋もないのだけれど・・・正直、著者の考えが気に食わない。
・・・そこで終わってしまっては本当に身も蓋もないので、ちょっと掘り下げる。


まず、最初と最後で言ってることが変わっている所。

本書の内容は、原則的にプログラミング言語の種類には関係ありません。基本的には、あらゆる種類のプログラミング言語のプログラムに共通することを取り上げます。ただし、特に特定の種類のプログラミング言語に当てはまることがある場合は、そのことを示して解説します

はじめに (P.3)

本書ではさまざまなプログラミング言語やプログラミング環境を取り上げてきました。言語や環境によって正反対のことがあるにもかかわらず、さまざまなトピックを大胆不敵に取り上げたのには、もちろん理由や根拠があります。
・・・
本書を読んでいて、「これは間違っているんじゃないだろうか?」と思った人もいるかもしれません。
実際に、本書の記述の中には特定の環境や特定のプログラミング言語にはあてはまらないこと、言い換えれば環境やプログラミング言語によっては間違いであることが (少しは) あります。
・・・
しかしそうすると、本書のほとんどの段落で「一般的には」や「原則として」、あるいは、「多くの場合には」とか「一般に良く使われている開発環境では」などを使うことになり、・・・そこで、本書ではいちいち「一般的には」や「原則として」と断っていません。
・・・
自分の常識は世間の常識とは必ずしも一致しないという、世間一般でも当たり前でありながら忘れられがちなことを常に忘れないようにする必要があります
・・・
プログラミングを習得してある程度経験を積むと、いちいち検証せずに、それまでの自分の経験と知識で判断するようになりますが、そのようなときに独断や偏見を持ち込まないように常に気をつける必要があります (著者自戒)。

一般的で原則的な話 (P.223-225)

「はじめに」では「あらゆる種類のプログラミング言語に共通することを取り上げる」とし、「特定の種類のプログラミング言語に当てはまる場合はそのことを示す」とも言っているのに、一番最後では「本書の記述の中には特定の環境や特定のプログラミング言語にはあてはまらないことがある」としている。
正直、都合が良すぎる。223 ページ以降は、何か指摘があった際の「逃げ」にも見える。
つか、検証しろよw


そして、もう一点。

ひとつのプログラミング言語をマスターすれば、他のプログラミング言語を習得するのは容易です。プログラミングで使われる概念はプログラミング言語の種類に関係なくほとんど同じなので、他のプログラミング言語をマスターするときには、自分が既に習得した言語と、これから習得しようとしている言語の違いに焦点を当てて学習すると、新しい言語を比較的容易に習得することができます。

プログラミング言語はどれが良いか?

何回か言ってるけど、ひとつの言語が出来るからって、他の言語の理解が容易になるとは思えない*10
実際に、C++JavaC# のように使った人がいますしねぇ*11
それに、プログラミングで使われる概念って何のことを言っているんだろうか。
それにそれに、どの辺のレベルに達したら「マスターした」とか「習得した」とか言えるんだろうか。
未だにどの言語も胸を張って「マスターした」なんて言えないんだけれども。

構造化へのアプローチ?

たとえば、Java のような言語で、初歩の学習段階ではひとつのクラスのひとつの main() にプログラムコード全体を記述することがよくあります。
・・・
また、main() の中のプログラムコードをコンストラクタに入れることも検討するべきです。
すると、次のようなプログラムになります。

public class javaarray {

    // 配列の内容を出力するメソッド
    void printArray(int a[]) {
        for (int i=0; i<a.length; i++)
            System.out.print(a[i] + " ");
        System.out.println();
    }
    
    // コンストラクタ
    javaarray() {
        // 配列を宣言する
        int data[] = new int[10];
        
        // 配列を初期化する
        for (int i=0; i<data.length; i++)
            data[i] = i + 1;
        
        // 配列の内容を出力する
        printArray(data);
        
        // 配列の内容を変更する
        data[1] = 11;
        data[5] = 55;
        
        // 変更した配列の内容を出力する
        printArray(data);
    }
    
    public static void main(String[] args) {
        javaarray ja = new javaarray();
    }
}

main() の中のプログラムコードをコンストラクタに入れ、javaarray クラスのオブジェクトを生成して実行することによって、static 変数 (静的変数) などを使わないで済むようになります。

構造化へのアプローチ (P.20-21)

・・・立派な Java プログラムですね。
突っ込みどころとしては

  • なんでクラス名が JavaArray じゃないんだ
  • Arrays.toString 使え
  • main の引数では String[] args なのにそのほかの部分ではなんで 型 変数名[] 形式?
  • main に全部突っ込むのはダメの典型例だけど、コンストラクタに全部突っ込むのもダメの典型例
  • static 変数などを使わないで済むの意味不明

なんというか、C と Java を足して 2 で割ったような感じ。

入れ子関数と匿名関数では、匿名関数の方が適切な場合がある?

プログラミング言語によっては、関数の中に関数を入れ子状態で定義できるものがあります。たとえば JavaScript では、次のように関数の中に関数を記述することができます。

function distance(x1, y1, x2, y2) {
    function square(x) { return x*x }
    var dx = x2 - x1;
    var dy = y2 - y1;
    return Math.sqrt(square(dx) + square(dy));
}

・・・
プログラミング言語によっては、匿名関数という名前のない関数を使用できる場合もあります。次の例は JavaScript の Function コンストラクタというものを使い、匿名関数を定義して f という名前の変数に保存して利用する例です。

function distance(x1, y1, x2, y2) {
    var f = Function("x", "return x*x");
    var dx = x2 - x1;
    var dy = y2 - y1;
    return Math.sqrt(f(dx) + f(dy));
}

・・・
プログラムの構造化という観点からは、入れ子の関数より、匿名関数のほうが適切である場合があります。

入れ子関数と匿名関数 (P.22-23)

・・・なんで?
function リテラル使えば入れ子関数も匿名関数も、違いなんて分からない感じなのに。
そもそも、プログラムの構造化という観点って何?

クラスの設計の理論ってなに?

クラスの設計は理論的には単純です

クラスの定義と利用 (P.37)

その理論、kwsk
まさか、名詞抽出法じゃないよね^^;

完全なオブジェクト指向言語

C# の特徴は、なんといっても完全なオブジェクト指向プログラミング言語であるという点でしょう。たとえば、C# では組み込みデータ型でさえすべてオブジェクトです (C# のデータ型は、.NET Framework というオブジェクト指向の暮らすライブラリの構造体と呼ぶもののエイリアス (別名) です)。また、オブジェクト指向プログラミング言語としてゼロから設計されたために、オブジェクト指向プログラミングとして一貫しています。

プログラミング言語はどれが良いか? (P.45)

class と structt を分けている時点で完全とは言えないような・・・?
ジェネリクスオブジェクト指向の要素じゃないし、ラムダ式もオブジェクト指向の要素じゃないし・・・


そしてプログラム。

// 
// hello.cs
// 
using System;

namespace CSCnslApp
{
    class Program
    {
        static void Main(string[] args)
        {
            String msg = "Hello, C#";
            
            Console.WriteLine(msg);
        }
    }
}
プログラミング言語はどれが良いか? (P.45)

プログラムの中で、string と String を使っているのは何か深い理由でもあるのだろうか?

名前

アンダースコアについて、

特に理由がなければ、名前の先頭にアンダースコアを使うのは避けるほうがよいでしょう。コンパイル時に名前の先頭に 1 個または複数個のアンダースコアを自動的に付けるコンパイラがよくあるからです。

名前の付け方 (P.69)

えーっと?処理系に予約されてるとか、そういうことを言ってるのかな?
それにしたって特定の言語の話なんだから、「はじめに」に従うならそれを示しておくべき。

for (int i=0, x=0; i < n; i++)
    x += 1;              // x = x + 1 と同じ
名前の付け方 (P.69)

何この無意味なプログラムw
この後に i, j, k, ... の由来として FORTRAN の暗黙の型宣言を取り上げているけど、確か他にも説はあったはず。それだけを取り上げるのはいかがなものかと。


さらにここでは、

なお、複数の同じ種類の名前を区別するために、アルファベットの名前のあとに数字をつなげることがよくあります。たとえば、複数の文字 (Character) を扱うプログラムでは、c0、c1、c2、あるいは、ch1、ch2、ch3 のようにしてそれぞれの文字を区別します。また、複数の長方形 (Rectangle) のオブジェクトを扱うプログラムでは、rect1、rect2、rect3 のようにしてそれぞれのオブジェクトを区別します。

名前の付け方 (P.71)

・・・止めて。
サンプルでは、そりゃ数字使うしかないような場合もあるけど、普通のプログラムだったら何かしら適切な名前があるはず。それか配列とかコレクションにするのが正しいか。
この人、サンプルしか弄ったことないんじゃないの?それを常識だと思ったら大間違いですよ。

言語を選ばないライブラリでも作る気ですか?
  • 一般に、変数名の先頭は小文字にします。
  • 関数やメソッドの名前では、[動詞] + [名詞] の形式にすることがあり、その場合、動詞の先頭は小文字にして、名詞の先頭は大文字にします。たとえば、getValue() とか、setName() のような名前が使われます。
  • オブジェクト指向プログラミングでは、クラス名の先頭は大文字にして、オブジェクト名の先頭は小文字にします。
  • オブジェクト指向プログラミングでは、インタフェースの名前の先頭文字を大文字の I にします。
名前の付け方 (P.71)

とあるけど、変数については Prolog は大文字から始まるし、メソッド名は .NET 系の言語は大文字から始まるし、クラス名は C++ の標準ライブラリは小文字から始まるし、インターフェイス名は Java の標準ライブラリは I から始まらない。
使える文字も言語によってまちまちで、それによって例えば Ruby とか Scheme では 真偽値を返すメソッドは ? で終わるけど、他の言語は isXxx のような形式を使うなどなど。
ということで、名前の付け方については言語を超越して言えることは、「分かりやすい名前を付ける」ことくらいなんじゃないかな。あぁでも変数名の長さが制限されてる言語だとそれすら言えないのか。


あ、そうそう。上ではクラス名の先頭を大文字にするといっておきながら、この本の Java のクラス名はことごとく小文字から始まっている*12という矛盾。

コメントアウトして残しておく

コードを変更するときにもコメントを活用することが出来ます。・・・以下自重

コメント (P.91)

コメントアウトして残しておくよりも、バージョン管理システムを使ったほうがいいと思う。
編集中のコードにノイズものらないし、完璧に元に戻すことも出来る。
コメントアウトよりよっぽど便利じゃないか。

より精度の高い計算が行いたい場合

さらに高精度の実数計算を行いたいときには、FORTRAN のような科学技術計算用のプログラミング言語を使うか、倍長精度浮動少数点数型のような高い精度の実数型を使います。

整数と実数 (P.125)

それもそうだけど、Java の標準ライブラリの BigDecimal や Commons の Fraction のようなものを使うという選択肢もあるはず。

スマートなコード

いかにもベテランが使いそうな、スマートな (賢そうな) コードの書き方というものがあります。スマートなコードを書くと気分がよいのは事実ですが、ときにはあえて拙く見える書き方をする必要がある場合があります

スマートなコード (P.164)

スマートなコードの定義もそこそこにスマートなコードの批判をするとは、スマートじゃないな。

C++ 言語で配列を宣言して、その要素を初期化するとき、一般的には次のようにするでしょう。

int a[20];

for (int i = 0; i < 20; i++)
    a[i] = i;

しかし、次のように書くこともできます。

int a[20];

for (int i = 0; i < 20; a[i++] = i)
    ;

上の例の for ステートメントの次の行の ; (セミコロン) は飾りではなくて、必要な「何もしない」ステートメントです。この ; を for ステートメントの最後に移動して次のように書けば、さらにプロらしく見えます。
・・・アホくさいので省略

スマートなコード (P.165)

どうも、日向さんの「プロらしさ」は世間一般の「プロらしさ」とはずれているようなきがする。
このコードでプロらしさを出すとしたら、

  • 20というマジックナンバーに名前を付ける
  • 後置ではなく前置のインクリメントを使用する
  • 配列の変わりに std::vector 等を使用する

とか、そういう方向なんじゃないの?


かなり長くなったけど、これでも一応、セオリーよりはマシなほうだと思う。
ただ、セオリーは「間違ってるところしかなかった」といえるほど、どこが間違ってるかなんて気にせずに全部間違ってると思って読むことは出来るんだけど、この本は正しいことも書いてある分、たちは悪いかも。
日本人には、こういう「入門書の次に読む本」は書かないほうがいいのかもね。
Java なら Effective JavaC++ なら C++ in Depth シリーズ、JavaScript ならサイ本と、やっぱり言語別に「次の本」を読めばいいと思う。


あわせて読みたい筆者には幻滅しました

*1:途中消えちゃってた部分があったのが残念・・・

*2:『他の「プログラミングの常識」に関連した書籍の内容比較も含めて』って、Clean Code とかとの比較をお望みなのか?

*3:将来の版か・・・一部の書き直しじゃすまないと思うけどどうだろう。指摘事項を修正したら、もはや別の本でしょこれ

*4:ある意味本に載ってるのは具体的な例だけど、この本の読者層を考えると気付かないと思う

*5:C 言語の習得が容易かどうかは気になるところだけど・・・C 言語分からない!

*6:ちょっと自信ない

*7:C には詳しくないからわかんない

*8:式文?式文は式じゃなくて文だから同じ

*9:まず計測。で、最適化。で、計測して本当に速くなったか確かめる

*10:例外はいるけど、あくまで例外

*11:昔の自分とか!

*12:それだけじゃなくてクラス名に大文字が使われていない