最近プログラムを書くようになった人なら、昔のマイコンやパーソナルコンピュータのプログラムで、いわゆる「forの空回し」を使ってるのを見たら驚くと思います。
for(i=0;i<10000;i++){;} みたいな記述をしているわけですが、なぜこんな書き方が必要かというと、制御チップ等へのアクセスで「nミリ秒待つ」必要があるのに、当時のコンピュータにはsleep(100)みたいな機能が無いため、そのように書くしか手段が無かったのです。
ええと、最も衝撃的かどうかは分かりませんが、かなり酷いコードを見ました。(それはJavaで書かれていました)
- for (int i = 0; i < 40; i++)
- {
- if (i == 0)
- fieldA = decoder.getNextField();
- if (i == 1)
- fieldB = decoder.getNextField();
- if (i == 2)
- fieldC = decoder.getNextField();
- // etc. etc.
- if (i == 39)
- fieldX = decoder.getNextField();
- }
あらゆる「知恵」が欠如しています。各fieldを個別に取得しそれぞれを個別のオブジェクトに割り当てており、fieldのナンバリングはハードコードされており、「if-else-if」構文の代わりに「if」を単に繰り返しています。しかしこのループはそもそも彼らが達成したかった目的に対し完全に不要でした。以下のように書いても挙動は全く同じだったはずです:
- fieldA = decoder.getNextField();
- fieldB = decoder.getNextField();
- fieldC = decoder.getNextField();
- // etc. etc.
- fieldX = decoder.getNextField();
ええと、最も衝撃的かどうかは分かりませんが、かなり酷いコードを見ました。(それはJavaで書かれていました)
- for (int i = 0; i < 40; i++)
- {
- if (i == 0)
- fieldA = decoder.getNextField();
- if (i == 1)
- fieldB = decoder.getNextField();
- if (i == 2)
- fieldC = decoder.getNextField();
- // etc. etc.
- if (i == 39)
- fieldX = decoder.getNextField();
- }
あらゆる「知恵」が欠如しています。各fieldを個別に取得しそれぞれを個別のオブジェクトに割り当てており、fieldのナンバリングはハードコードされており、「if-else-if」構文の代わりに「if」を単に繰り返しています。しかしこのループはそもそも彼らが達成したかった目的に対し完全に不要でした。以下のように書いても挙動は全く同じだったはずです:
- fieldA = decoder.getNextField();
- fieldB = decoder.getNextField();
- fieldC = decoder.getNextField();
- // etc. etc.
- fieldX = decoder.getNextField();
回答者:Avrom Finkelstein, ソフトウェア開発者
GNU glibcのソースコードに、本当にクールなコードを見つけました。ヘッダファイルのstring.hにあるstrlen関数です。
もしstrlenを実装してくれと言われたら、私は多分こんな感じのコードを書くでしょう。
- size_t strlen (const char *str)
- {
- const char *p;
- if (str == NULL)
- return 0;
- p = str;
- while (*p != '\0')
- p++;
- return p - str;
- }
全く悪くありません。きちんと機能しますし、あなたが期待する程度の複雑さでしょう。
しかしglibcを書いた奴らときたら。かっこよすぎます!
ちょっとその前に、ソースコード全体はこちらにあります。読みやすくするためにコメントを削ったりインデントを変えたりしていますので、実際のコードはリンク先を確認してください。
で、この関数は文字列を先頭から1文字ずつ検索して、longのワード長の境界にある文字を探すところから始まります。もし途中で文字列の終了を検知したら関数が終了して長さを返します。
- for (char_ptr = str;
- ((unsigned long int) char_ptr
- & (sizeof (longword) - 1)) != 0;
- ++char_ptr)
GNU glibcのソースコードに、本当にクールなコードを見つけました。ヘッダファイルのstring.hにあるstrlen関数です。
もしstrlenを実装してくれと言われたら、私は多分こんな感じのコードを書くでしょう。
- size_t strlen (const char *str)
- {
- const char *p;
- if (str == NULL)
- return 0;
- p = str;
- while (*p != '\0')
- p++;
- return p - str;
- }
全く悪くありません。きちんと機能しますし、あなたが期待する程度の複雑さでしょう。
しかしglibcを書いた奴らときたら。かっこよすぎます!
ちょっとその前に、ソースコード全体はこちらにあります。読みやすくするためにコメントを削ったりインデントを変えたりしていますので、実際のコードはリンク先を確認してください。
で、この関数は文字列を先頭から1文字ずつ検索して、longのワード長の境界にある文字を探すところから始まります。もし途中で文字列の終了を検知したら関数が終了して長さを返します。
- for (char_ptr = str;
- ((unsigned long int) char_ptr
- & (sizeof (longword) - 1)) != 0;
- ++char_ptr)
- if (*char_ptr == '\0')
- return char_ptr - str;
そして(まだズボンを降ろすのは早いですよ)、そのポイントから始まる文字列をunsigned long intの配列にキャストします。
- longword_ptr = (unsigned long int *) char_ptr;
これでこの関数は、システムのアーキテクチャにより、4文字か8文字の単位でループを回せるようになりました。文字列の終わりに遭遇した時に、long intの途中にNULLがあることを検知できるようにするため、いくつかのマジックナンバーを持っています。
- // 32 bit architecture
- himagic = 0x80808080L;
- lomagic = 0x01010101L;
- // 64 bit architecture
- if (sizeof (longword) > 4) {
- himagic = ((himagic << 16) << 16) | himagic;
- lomagic = ((lomagic << 16) << 16) | lomagic;
- }
- // Something is wrong
- if (sizeof (longword) > 8)
- abort ();
ここまでできたら、メインループが始まります。
- for (;;) {
- longword = *longword_ptr++;
各ループにおいて、この関数は文字の組をlong変数にコピーして、文字列のポインタを進めます。その後にマジックナンバーを適用します。もしビット演算の結果が0以外であれば、最後のループのどこかにNULLがあることを示します(誤爆の可能性もあります)。
- if (((longword - lomagic) & ~longword & himagic) != 0)
もしNULLがあると思われる場合、キャストした文字列をcharの配列に戻して0の値を検索します。
- // note we have to subtract 1 because the pointer is 1 step
- // too far ahead
- const char *cp = (const char *) (longword_ptr - 1);
- // now just check which char is == NULL
- if (cp[0] == 0)
- return cp - str;
- if (cp[1] == 0)
- return cp - str + 1;
- if (cp[2] == 0)
- return cp - str + 2;
- if (cp[3] == 0)
- return cp - str + 3;
- // 64 bit architecture
- if (sizeof (longword) > 4) {
- if (cp[4] == 0)
- return cp - str + 4;
- if (cp[5] == 0)
- return cp - str + 5;
- if (cp[6] == 0)
- return cp - str + 6;
- if (cp[7] == 0)
- return cp - str + 7;
- }
もしNULLがあれば、この文字列は終了で、ちょっと補正をして正しい長さを返します。もしなければ誤爆だったことになり、ループを継続します。
こんなことを思いつくプログラマがどれだけいるのかわかりませんが、こんなコードがあるから私もgit gud(*)しちゃうんですよね。
訳注 Git Gud - オンラインゲームで上級者が初心者に向かって上手くなれと罵倒するとき等に使う俗語。Get Good。「上達しろよ」
追記:これをテストしてみましょう!
どうやら「これ意味あるの?」という質問が多いようです。本当にパフォーマンスが向上する価値があるのか曖昧です。コメント欄で皆さんと話していて、パフォーマンスが明らかに向上するかどうか疑問に思えてきました。直感的には、64ビットCPUならば、複雑なコードのほうが単純なコードよりも8倍高速なはずですが、どのくらい長い文字列になれば実際にその差に気づくのでしょうか? こうした最適化は、最新のハード上で意味があるのでしょうか?
コメント欄で最初にベンチマークを計ったのはブルース・ウェディングさんです。彼のコメントにあるテストの結果はMSVCコンパイラを使ってあなたも確認できます。彼によると長い文字列でも単純な関数と複雑な関数に有意な差がみられず、短い文字列では複雑な関数の方が遅い結果になりました。ブルースさんはこの実験で、長い文字列を268435455文字、短い文字列を255文字と定義しました。彼のコードはgithubで公開されています strlentest.cpp。
ブルースさんがgithubに公開したコードでは、単純なstrlenが間違って2度呼ばれているとの指摘がコメント欄で別の人からありましたので、彼のコードを私のマシンで実行し (Debian 9, Intel i7, g++)、以下の結果を得ました。
- GLib Optimized: 292.271 Seconds
- Simple Implementation Hand Written: 177.293 Seconds
- Built-In strlen: 169.546 Seconds
- Short String GLib Optimized: 0.0456911 Seconds
- Short String Simple Implementation Hand Written: 0.0320349 Seconds
- Short String Built-In strlen: 0.0302996 Seconds
これはglibの実装が単純な実装よりも実際には遅いということを示しています。しかしブルースさんはテストの中でランダムな文字列を生成しており、これがstrlen関数のトータル実行時間に含まれてしまっています。文字列生成処理を削除し、各関数を同じstaticな文字列(内容は何でもよく、長さだけが重要です)に対して走らせると、次のような結果を得られます。
- GLib Optimized: 0.730014 Seconds
- Simple Implementation Hand Written: 5.42762 Seconds
- Built-In strlen: 9.14e-07 Seconds
- Short String GLib Optimized: 0.000105536 Seconds
- Short String Simple Implementation Hand Written: 0.000755128 Seconds
- Short String Built-In strlen: 2.521e-06 Seconds
私が期待していたような結果が出ているようです。
ブルースの直後にリック・メイヤーさんもベンチマークの結果を投稿してくださりました。リックさんは単純な関数と、string.hのstrlenを直接比較しました。私のマシンを使い、前述のブルースさんのテストと同じ条件で実行したところ、次のような結果を得られました。
- len=1, strlen=1.447e-06 sec, myStrLen=1.081e-06 sec, ratio=0.747063
- len=2, strlen=1.159e-06 sec, myStrLen=1.136e-06 sec, ratio=0.980155
- len=4, strlen=1.126e-06 sec, myStrLen=1.378e-06 sec, ratio=1.2238
- len=8, strlen=1.123e-06 sec, myStrLen=1.61e-06 sec, ratio=1.43366
- len=16, strlen=1.504e-06 sec, myStrLen=1.998e-06 sec, ratio=1.32846
- len=32, strlen=1.364e-06 sec, myStrLen=3.201e-06 sec, ratio=2.34677
- len=64, strlen=1.805e-06 sec, myStrLen=4.312e-06 sec, ratio=2.38892
- len=128, strlen=1.964e-06 sec, myStrLen=6.648e-06 sec, ratio=3.38493
- len=256, strlen=2.267e-06 sec, myStrLen=1.1491e-05 sec, ratio=5.06881
- len=512, strlen=2.526e-06 sec, myStrLen=2.0979e-05 sec, ratio=8.30523
- len=1024, strlen=3.514e-06 sec, myStrLen=4.0012e-05 sec, ratio=11.3865
- len=2048, strlen=4.647e-06 sec, myStrLen=0.000102821 sec, ratio=22.1263
- len=4096, strlen=7.632e-06 sec, myStrLen=0.000185926 sec, ratio=24.3614
- len=8192, strlen=1.3739e-05 sec, myStrLen=0.000328272 sec, ratio=23.8934
- len=16384, strlen=2.535e-05 sec, myStrLen=0.00068922 sec, ratio=27.1882
- len=32768, strlen=5.3857e-05 sec, myStrLen=0.00133392 sec, ratio=24.7679
- len=65536, strlen=0.000156777 sec, myStrLen=0.0026164 sec, ratio=16.6887
- len=131072, strlen=0.000310634 sec, myStrLen=0.00526008 sec, ratio=16.9334
- len=262144, strlen=0.000651425 sec, myStrLen=0.0148742 sec, ratio=22.8333
- len=524288, strlen=0.00240398 sec, myStrLen=0.0218377 sec, ratio=9.08398
- len=1048576, strlen=0.00320764 sec, myStrLen=0.0489684 sec, ratio=15.2662
- len=2097152, strlen=0.00746779 sec, myStrLen=0.0852466 sec, ratio=11.4152
- len=4194304, strlen=0.0145624 sec, myStrLen=0.156973 sec, ratio=10.7793
- len=8388608, strlen=0.0527272 sec, myStrLen=0.332842 sec, ratio=6.31253
- len=16777216, strlen=0.127556 sec, myStrLen=0.675704 sec, ratio=5.29732
- len=33554432, strlen=0.261963 sec, myStrLen=1.36771 sec, ratio=5.221
- len=67108864, strlen=0.519335 sec, myStrLen=2.98975 sec, ratio=5.75688
- len=134217728, strlen=1.03679 sec, myStrLen=5.58891 sec, ratio=5.39057
- len=268435456, strlen=2.12669 sec, myStrLen=11.1648 sec, ratio=5.24987
- len=536870912, strlen=4.26715 sec, myStrLen=23.3579 sec, ratio=5.4739
これもまた、glibcの実装が単純なstrlen関数よりも速いことを示しているようです。リックさんのテストでは文字列の長さを2倍ずつ増やし、glibcがだんだん有利になっていく様子が示されています。
リックさんもソースコードをgithubに公開されていますので、見てみたい人はぜひチェックしてみてください。
strlen performance comparison by Rick Meyer
彼のテストケースでは興味深いことに、コンパイラの最適化によって正しく計測できなくなることを避けるためにトリックが必要であることを示しています。
そして最後に私も自分でテストしました。glibc版のstrlenと、自作の簡単バージョンを使って実装しました。万全を期すため、私の手持ちのC標準関数にあるstrlenも含めました。このテストでは、8バイトから4MBまで文字列を2倍にしていき、各関数のパフォーマンスの平均を取りました。時間は1000回繰り返して平均を取った値になっています。ちょっと華を添えるため、少しズレた構造体を使って文字列バッファをワード境界から外すということもやっています。
- struct offset { char nudge[1]; char buffer[BUF_LEN]; } offset;
- char *buffer = offset.buffer;
要するに、glibcは初期サーチとメインループの両方を実行しなければならないということです。この実験の結果は以下のようになりました。
- Beginning test: simple
- Buffer at address 0x7fff860a7741
- [len = 8B] avg 0.00000s over 1000 iterations
- [len = 16B] avg 0.00000s over 1000 iterations
- [len = 32B] avg 0.00000s over 1000 iterations
- [len = 64B] avg 0.00000s over 1000 iterations
- [len = 128B] avg 0.00000s over 1000 iterations
- [len = 256B] avg 0.00000s over 1000 iterations
- [len = 512B] avg 0.00000s over 1000 iterations
- [len = 1KB] avg 0.00000s over 1000 iterations
- [len = 2KB] avg 0.00001s over 1000 iterations
- [len = 4KB] avg 0.00002s over 1000 iterations
- [len = 8KB] avg 0.00005s over 1000 iterations
- [len = 16KB] avg 0.00009s over 1000 iterations
- [len = 32KB] avg 0.00016s over 1000 iterations
- [len = 64KB] avg 0.00032s over 1000 iterations
- [len = 128KB] avg 0.00066s over 1000 iterations
- [len = 256KB] avg 0.00133s over 1000 iterations
- [len = 512KB] avg 0.00258s over 1000 iterations
- [len = 1MB] avg 0.00484s over 1000 iterations
- [len = 2MB] avg 0.01002s over 1000 iterations
- [len = 4MB] avg 0.02001s over 1000 iterations
- Beginning test: glibc
- Buffer at address 0x7fff860a7741
- [len = 8B] avg 0.00000s over 1000 iterations
- [len = 16B] avg 0.00000s over 1000 iterations
- [len = 32B] avg 0.00000s over 1000 iterations
- [len = 64B] avg 0.00000s over 1000 iterations
- [len = 128B] avg 0.00000s over 1000 iterations
- [len = 256B] avg 0.00000s over 1000 iterations
- [len = 512B] avg 0.00000s over 1000 iterations
- [len = 1KB] avg 0.00000s over 1000 iterations
- [len = 2KB] avg 0.00000s over 1000 iterations
- [len = 4KB] avg 0.00000s over 1000 iterations
- [len = 8KB] avg 0.00001s over 1000 iterations
- [len = 16KB] avg 0.00001s over 1000 iterations
- [len = 32KB] avg 0.00002s over 1000 iterations
- [len = 64KB] avg 0.00004s over 1000 iterations
- [len = 128KB] avg 0.00008s over 1000 iterations
- [len = 256KB] avg 0.00016s over 1000 iterations
- [len = 512KB] avg 0.00033s over 1000 iterations
- [len = 1MB] avg 0.00061s over 1000 iterations
- [len = 2MB] avg 0.00121s over 1000 iterations
- [len = 4MB] avg 0.00255s over 1000 iterations
- Beginning test: string.h
- Buffer at address 0x7fff860a7741
- [len = 8B] avg 0.00000s over 1000 iterations
- [len = 16B] avg 0.00000s over 1000 iterations
- [len = 32B] avg 0.00000s over 1000 iterations
- [len = 64B] avg 0.00000s over 1000 iterations
- [len = 128B] avg 0.00000s over 1000 iterations
- [len = 256B] avg 0.00000s over 1000 iterations
- [len = 512B] avg 0.00000s over 1000 iterations
- [len = 1KB] avg 0.00000s over 1000 iterations
- [len = 2KB] avg 0.00000s over 1000 iterations
- [len = 4KB] avg 0.00000s over 1000 iterations
- [len = 8KB] avg 0.00000s over 1000 iterations
- [len = 16KB] avg 0.00000s over 1000 iterations
- [len = 32KB] avg 0.00000s over 1000 iterations
- [len = 64KB] avg 0.00000s over 1000 iterations
- [len = 128KB] avg 0.00001s over 1000 iterations
- [len = 256KB] avg 0.00001s over 1000 iterations
- [len = 512KB] avg 0.00003s over 1000 iterations
- [len = 1MB] avg 0.00006s over 1000 iterations
- [len = 2MB] avg 0.00015s over 1000 iterations
- [len = 4MB] avg 0.00034s over 1000 iterations
折れ線グラフに視覚化しました。X軸は文字列の長さ、Y軸は秒です。
このグラフを見る限りglibc版のパフォーマンスが大きく異なることが明らかにわかります。この傾向はgcc、clang、ccなどでコンパイルしても同様です。
私のコードもgithubのこちらにあります。Benchmarking glibc's strlen function
結論。はい! 文字列が十分に長ければ、複雑な関数には意味があります。
コンピュータサイエンス学部のラボでのことです。
私たちはC++でループについて習いました。テストとして、教員は以下のような問題を出しました。
次のパターンを出力せよ。
全員コードを書き始め、全力を尽くしました(この課題は当時の私たちにはまだ高度過ぎました)。私の友達は全く手も足も出ませんでしたが、何とかいいスコアを取ろうと考えました。
どうしたと思います?
彼はこんな感じのコードを書きました。
- #include<iostream.h>
- void show();
- int main();
- {
- char ch='A';
- int i,n;
- /* 何かをやり遂げようと頑張って書いたコード。*/
- show(); //この関数をこっそり呼び出す。
- for(i=0;i<7;i++)
- {
- for(int j=0;j<12;j++)
- {
- /* 何かを出力しようとしている
- さらに無駄なコード。*/
- }
- }
- return 0;
- }
- /* 最後に100行ほどの空行がありますが、
- 皆さんはスクロールしなくてもよいです。*/
- void show()
- {
- cout<< " ABCDEFGFEDCBA\n";
- cout<< " ABCDEF FEDCBA\n";
- cout<< " ABCDE EDCBA\n";
- cout<< " ABCD
コンピュータサイエンス学部のラボでのことです。
私たちはC++でループについて習いました。テストとして、教員は以下のような問題を出しました。
次のパターンを出力せよ。
全員コードを書き始め、全力を尽くしました(この課題は当時の私たちにはまだ高度過ぎました)。私の友達は全く手も足も出ませんでしたが、何とかいいスコアを取ろうと考えました。
どうしたと思います?
彼はこんな感じのコードを書きました。
- #include<iostream.h>
- void show();
- int main();
- {
- char ch='A';
- int i,n;
- /* 何かをやり遂げようと頑張って書いたコード。*/
- show(); //この関数をこっそり呼び出す。
- for(i=0;i<7;i++)
- {
- for(int j=0;j<12;j++)
- {
- /* 何かを出力しようとしている
- さらに無駄なコード。*/
- }
- }
- return 0;
- }
- /* 最後に100行ほどの空行がありますが、
- 皆さんはスクロールしなくてもよいです。*/
- void show()
- {
- cout<< " ABCDEFGFEDCBA\n";
- cout<< " ABCDEF FEDCBA\n";
- cout<< " ABCDE EDCBA\n";
- cout<< " ABCD DCBA\n";
- cout<< " ABC CBA\n";
- cout<< " AB BA\n";
- cout<< " A A\n";
- }
教員がチェックしに来て、コードをチラ見し、動かしてみてと言われました。
これがその出力です。
正しい出力を見た教員は、彼に満点を与えました。望ましい結果が得られたので、教員はもうコードの正しさなんかどうでもいいようでした。
私は「おいなんてアイデアマンなんだ!」みたいに思いました。
編集1:
私がここで言及した友人にこの回答を見せました。私がこの件を正確に覚えていたことに驚いていました。彼のおかげで私はQuoraで有名になったと思われてしまったようで、何か奢るよう言われてしまいました。
仕事で分かりやすいコードを書くってのがプロという意味では、これから申し上げる私の回答は失格かもしれませんが、優れたアルゴリズムを構築して少しでも効率的に処理されることを目指すところでしょうか。
すいません、自画自賛のようです恐縮ですが、具体例としては自分のことを書きますね。
長い社会人経験の中でプログラミングはそんなしてないんですが、一応小学生の頃からプログラミングをかじってました。
以前、仕事でとある業種の最大手のとある部署に少しだけ常駐しまして、業務で組んでるプログラムが動かなくて困っているということで私が手を入れました。
基幹系ではなく分析系の領域だったものでプログラムの内容を自由に手を入れて見直すことができたのですが、元はうろ覚えですが3000行ほどあって走らすと一日かかるような処理でした。エラーが出るし時間がかかって確認もままならないしとプロジェクトはリカバリーに行き詰まってました。
もともと何時間も掛かる処理にいろいろ手を入れたらもっと時間がかかるようになり、いろんな制約があってデバッグもままならない状況のようでして、私が入り抜本的に見直しました。
1週間ほどかけて、お客さんがやってること、やりたかったこと、実現したいことをしっかり再整理し、インプットとアウトプットと出てきたアウトプットの後続での利用方法を固めました。
そしてプログラミングに取り掛かり、じゃあこれでいいですね、と半日
仕事で分かりやすいコードを書くってのがプロという意味では、これから申し上げる私の回答は失格かもしれませんが、優れたアルゴリズムを構築して少しでも効率的に処理されることを目指すところでしょうか。
すいません、自画自賛のようです恐縮ですが、具体例としては自分のことを書きますね。
長い社会人経験の中でプログラミングはそんなしてないんですが、一応小学生の頃からプログラミングをかじってました。
以前、仕事でとある業種の最大手のとある部署に少しだけ常駐しまして、業務で組んでるプログラムが動かなくて困っているということで私が手を入れました。
基幹系ではなく分析系の領域だったものでプログラムの内容を自由に手を入れて見直すことができたのですが、元はうろ覚えですが3000行ほどあって走らすと一日かかるような処理でした。エラーが出るし時間がかかって確認もままならないしとプロジェクトはリカバリーに行き詰まってました。
もともと何時間も掛かる処理にいろいろ手を入れたらもっと時間がかかるようになり、いろんな制約があってデバッグもままならない状況のようでして、私が入り抜本的に見直しました。
1週間ほどかけて、お客さんがやってること、やりたかったこと、実現したいことをしっかり再整理し、インプットとアウトプットと出てきたアウトプットの後続での利用方法を固めました。
そしてプログラミングに取り掛かり、じゃあこれでいいですね、と半日で作ったコードは50行くらいでした。関係者全員がびっくりしていました。
これもうろ覚えですが、直積を多用して最初にデータを取り込むテーブルを一気に作り、3段階のネスト構造でやりたい処理を探索的に一気にさばくような処理ステップだったと思います。
元のコードは一つ一つ処理を行ってたんですけど、色々な経緯があるようで完全にスパゲッティ状態でしたが、私のコードは大枠2つの処理だけで完結できました。後処理なんかも入れて最終的には100行くらいはあった気がしますが、メインは本当にシンプルでした。
そして時間ですね。処理も5分くらいで終わったんですよね。普通なら1日かけても終わらないやつが。
その道30年の重鎮プログラマーさんやお客さんが半信半疑でプログラムをレビューするんですが、そんなにレビューするところもないんですよね。
でもプログラムを見るだけだと何をやってるかが理解できないみたいで、「今回やりたいことはこういうことですよね、ならこういう処理で実現できますよね」と何度か説明して納得してもらいました。
変なことはせずにやりたいことに対して最短距離で処理ができるような作りにした記憶があります。その後のメンテややりたいことのための改修が楽になったようです。そりゃ3000行も見ないでいいですからね。
最初のコードは下請けベンダーさんがコツコツ作ったらしいんですが、なんの処理かとかの丁寧なコメントはあるものの、通して読んでもプログラム全体で何をやるためのどういう処理かイマイチわからず。
でも一つ一つちゃんとコメントしてあるし、たぶん日本のいわゆるSIerってこういうのが評価されるんだろうなと思いましたけど、考え込まれて洗練された良いコードのほうが最終的にわかりやすいと思うんですよね。処理から目的も見えるし。
まあ、今の日本だと基本的にそうはなっていないのかなと思います。平凡に合わせないといけないので。
準備はいいですか?
歴史上最も洗練されたソフトウェアは、私たちが名前も知らない人々によって構成されたチームによって書かれました。
それは、コンピュータワームです。このワームはおそらく2005年から2010年の間に書かれました。
このワームは非常に複雑かつ洗練されているので、このワームの最も表面的な概要しか説明できません。
このワームは最初はUSBドライブに潜んでいます。USBがその辺に転がっているのを見つけたり、郵便物として受け取ったりした人は、中に何が入っているのか興味を持つでしょう。このUSBがWindows PCに差し込まれた時、ユーザーが気づかないうちに、ワームは自動的に実行され、自身をPCに複製します。ワームが自らを実行する方法は少なくとも3つあります。もし1つの方法で上手くいかなければ、他の方法を試みます。当時、これらの方法のうち少なくとも2つは全く新しいものであり、その2つともWindowsに潜んでいた2つの別のバグを悪用したもので、このワームが出現するまで、バグの存在には誰も気づきませんでした。
このワームは、PCで実行されると管理者権限によるアクセスを試みます。ウイルス対策ソフトウェアがインストールされているかはこのワームには関係ありません。なぜなら、このワームはほとんどのウイルス対策ソフトウェアを回避して侵入できるからです。 次に、実行中のWindowsのバージョンに応じ
準備はいいですか?
歴史上最も洗練されたソフトウェアは、私たちが名前も知らない人々によって構成されたチームによって書かれました。
それは、コンピュータワームです。このワームはおそらく2005年から2010年の間に書かれました。
このワームは非常に複雑かつ洗練されているので、このワームの最も表面的な概要しか説明できません。
このワームは最初はUSBドライブに潜んでいます。USBがその辺に転がっているのを見つけたり、郵便物として受け取ったりした人は、中に何が入っているのか興味を持つでしょう。このUSBがWindows PCに差し込まれた時、ユーザーが気づかないうちに、ワームは自動的に実行され、自身をPCに複製します。ワームが自らを実行する方法は少なくとも3つあります。もし1つの方法で上手くいかなければ、他の方法を試みます。当時、これらの方法のうち少なくとも2つは全く新しいものであり、その2つともWindowsに潜んでいた2つの別のバグを悪用したもので、このワームが出現するまで、バグの存在には誰も気づきませんでした。
このワームは、PCで実行されると管理者権限によるアクセスを試みます。ウイルス対策ソフトウェアがインストールされているかはこのワームには関係ありません。なぜなら、このワームはほとんどのウイルス対策ソフトウェアを回避して侵入できるからです。 次に、実行中のWindowsのバージョンに応じて、ワームはPCの管理者アクセス権限を得るために、当時は知られていなかった2つの方法のうちの1つを試みます。このワームが公になるまで、誰もWindowsの隠されたこの2つのバグを知りませんでした。
この時点で、ワームはオペレーティングシステムに侵入したことによって活動を隠すことが可能になり、ウィルス対策ソフトウェアもワームの存在を検出できなくなります。ワームはPCにこっそりと同化するため、たとえワームの存在するディスク上の場所を調べたとしても、何も見つかりません。このワームは非常に巧妙に隠れるため、このワームがインターネット上で1年以上にわたって実行されていても、世界中のどのセキュリティ企業もワームの存在にさえ気付きませんでした。
ワームは、次にインターネットへの接続が可能か確認します。もし可能なら、http://www.mypremierfutbol.comまたはhttp://www.todaysfutbol.comのいずれかへアクセスを試みます。当時、サイトのサーバーはマレーシアとデンマークにありました。ワームは暗号化されたリンクを開き、サーバーに新しいPCの管理者権限を獲得したことを知らせます。すると、ワームは自動的に自身を最新バージョンへ更新します。
この時点で、ワームはユーザーが接続した他のUSBメモリーへ自身をコピーします。ワームは、巧妙に設計された偽のディスクドライバーをインストールして自身をコピーします。このドライバーはRealtek社によってデジタル署名されていました。つまり、ワームの作者達は、この台湾の大企業のセキュリティが最も厳しい場所に侵入し、Realtek社が気づかないまま、この会社の所有する秘密鍵を盗むことに成功していたのです。
その後、このドライバーを書いた人達は、台湾の別の大企業であるJMicron社から取得した秘密鍵によるデジタル署名を始めました。この時も、ワームの作者はJMicron社に知られずに、会社の最もセキュリティの厳しい場所に侵入し、会社が所有する最も安全とされている鍵を盗む方法を考えだしました。
このワームは洗練されています。
ここまでの出来事は、このワームの序章にも過ぎません。
この時点で、このワームは最近発見されたWindowsの2つのバグを悪用します。1つのバグはネットワークプリンタに関連し、もう1つのバグはネットワークファイルに関連しています。ワームはこのバグを利用して、ローカルネットワークを経由して、施設内の他のすべてのコンピュータに自身をインストールします。
次に、このワームは、大規模な産業用機械を自動化するために、ドイツのシーメンス社が設計した、特殊な制御ソフトウェアを標的に活動します。ご想像の通り、このワームは、産業用コントローラーのプログラマブルロジックデバイスへ自身を複製するために、知られていない別のバグを悪用します。ワームはコントローラーに一度侵入すると、永久にそこに居続けます。こうなると、PCを交換したり感染後の駆除を行っても、ワームを駆除することはできません。
ワームは、特定2社の産業用電動モーターをチェックします。企業の1社はイランにあり、もう1社はフィンランドにあります。ワームが標的とする特定のモーターは、可変周波数ドライブと呼ばれています。このドライブは、産業用遠心分離機の運転に使用されています。遠心分離機は、多くの種類の化学物質を精製することができます。例としてウランです。
この時点で、ワームは遠心分離機を完全に制御しているため、思いのままに何でも実行できます。
すべてをシャットダウンすることも可能です。あるいは遠心分離機をすべて破壊することも可能です。最大速度を超えて稼働させれば、遠心分離機は爆弾のように爆発して粉々になり、その付近にいる人たちは殺されます。
しかし、このような事態は起こりません。なぜなら、この洗練されたワームには別の計画があるからです。
ワームは施設内すべての遠心分離機がコントロール可能になると、活動を停止します。
数日が経過します。または数週間または数秒の場合もあるかもしれません。
ワームは、適切な時が来たと判断すると自身を静かに起動します。 遠心分離機がウランを精製している間に、ワームはランダムに遠心分離機を選びます。そして、ワームは遠心分離機をロックするので、人間が何らかの異変に気付いても、遠心分離機を停止することはできません。
そして、ワームは遠心分離機を密かに回転させ始めます。それはごく小さな異変であり、人間が気になるほどの大きな異変ではありません。回転速度がわずかに速い、わずかに遅いというだけのことです。安全とされるパラメータから少し誤差がある程度です。
それと同時にワームは、遠心分離機内のガス圧を増加させます。遠心分離機内のガスは、UF6と呼ばれています。これは取り扱いが非常に厄介な物質です。ワームはそのUF6ガスの圧力を、安全とされるパラメータから少し誤差がある程度に変更します。そのため、遠心分離機が回転している間、遠心分離機内のUF6ガスが固形化する可能性は十分に小さいのです。
遠心分離機にとって、運転速度が速すぎるのも、遅すぎるのも、また固形化も好ましい条件ではありません。
このワームは、隠された最後の罠を1つ仕掛けています。 それは、まさに天才的な悪の企みです。
ワームはコンピュータ画面に、遠心分離機が正常に稼働していた時にキャプチャした21秒間のデータ記録を切り出して表示します。
しかもワームはその記録を、ループ処理で繰り返して何度も再生します。
その結果、コンピュータの画面に表示される遠心分離機に関するすべてのデータは、私たち人間にとっては正常なものに見えます。
しかし、実際にはワームによって作成された偽の記録です。
あなたがこの巨大な産業施設でウラン濃縮を行う責任者であると想定してください。一見、すべてが正常に稼働しているように見えます。もしかしたら、いくつかのモーターの音に小さな異変があるかもしれません。しかし、コンピュータに表示されたすべての数値は、それらのモーターが設計された通りに稼働していることを示しています。
そして、遠心分離機は次から次へとランダムに壊れ始めます。多くの場合、遠心分離機は静かに故障することになりますが、稀に故障して大事故となる場合もあるでしょう。ウラン生成物が発生しますが、ウランは精製されている必要があります。有効に利用するには精製が足りていません。
もしあなたがこのウランの濃縮施設を稼働していたらどうしますか?何度チェックしても、すべてがオフライン状態になった理由を理解できないでしょう。必要だと思うなら、施設内すべてのPCを置き換えることもできます。
しかし遠心分離機は、故障したと思えたのに正常な状態に戻ることもあります。そしてあなたにはその理由を知る方法がありません。
最終的に約1000基の遠心分離機が故障するかオフライン状態になります。
あなたは少し狂いそうになって、なぜ設計通りに動作していなかったのか理解しようと努めるでしょう。
これこそが、まさに起こった出来事です。
あなたはすべての問題が、コンピュータワームが原因となっていたとは想像することさえできないでしょう。このワームは、莫大な資金とリソースを備えた極秘チームの人々によって書かれた、歴史上最も凶悪かつ巧妙なコンピュータワームです。その目的は、誰にも気づかれることなく、あらゆるデジタル防衛システムへ侵入し、国の核爆弾プログラムを破壊することです。
1つのソフトウェアがこれらまでに挙げた1つでもできるとしたら、それは小さな奇跡といえるでしょう。
1つのソフトウェアが全てを可能にしたら、それは…
Stuxnet(スタックスネット)ワームは、歴史上これまでに書かれた中で最も洗練されたソフトウェアに違いありません。
By John Byrd
Gigantic SoftwareのCEO、Segaのディレクター、Electronic ArtsのManager、Harvard 91年卒(Electronic Arts)
コメントに謎の文字列が書いてあって、書いた人に聞いたら「おまじない」といわれたこと w
これは間違いなく1990年前後のMacintosh II用のフルカラービデオカードのファームウェアです。
Macintosh II
背景
日本のとあるメーカがMacintosh II用のフルカラービデオカードを開発していて、ファームウェアの開発に手も足もでず話が回ってきました。ファームウェアの開発は初めてでしたが、Macintosh IIを既に持っていたこともあり技術的に飛躍するチャンスと思い敢えて引き受けました。
Macintosh IIのマザーボード
Apple Display Card 24AC
NuBusに挿すビデオカードです。Macintosh IIは初めてカラー表示をサポートし、多分個人が購入できるコンピュータとしては初めてのフルカラー(1千6百万色)表示可能な機種でした。マルチモニターも初めてサポートしました。
個人で買えると言ってもシステム一式で約2百万円しましたが。
256色しか表示できないと人の顔はこのようになります。
モニターがない!
ハードとソフトの同時開発の難しさは当然でApple側のドキュメントも未整備なのもしかたがないのですが、最初の大きな壁はデバッグのためにモニターが使えないことでした。
Macは起動直後にハードディスク、キーボート等の様々なハードウェアの初期設定をおこないますが、最初にビデオカードの初期化が必要です。フォームウェアの仕事の多くは、ビデオカードを初期化する
これは間違いなく1990年前後のMacintosh II用のフルカラービデオカードのファームウェアです。
Macintosh II
背景
日本のとあるメーカがMacintosh II用のフルカラービデオカードを開発していて、ファームウェアの開発に手も足もでず話が回ってきました。ファームウェアの開発は初めてでしたが、Macintosh IIを既に持っていたこともあり技術的に飛躍するチャンスと思い敢えて引き受けました。
Macintosh IIのマザーボード
Apple Display Card 24AC
NuBusに挿すビデオカードです。Macintosh IIは初めてカラー表示をサポートし、多分個人が購入できるコンピュータとしては初めてのフルカラー(1千6百万色)表示可能な機種でした。マルチモニターも初めてサポートしました。
個人で買えると言ってもシステム一式で約2百万円しましたが。
256色しか表示できないと人の顔はこのようになります。
モニターがない!
ハードとソフトの同時開発の難しさは当然でApple側のドキュメントも未整備なのもしかたがないのですが、最初の大きな壁はデバッグのためにモニターが使えないことでした。
Macは起動直後にハードディスク、キーボート等の様々なハードウェアの初期設定をおこないますが、最初にビデオカードの初期化が必要です。フォームウェアの仕事の多くは、ビデオカードを初期化することで、初期化しないとモニターには何も表示されません。
デバッグのための何か表示するとか、デバッガを使ったりすることができず「視界ゼロ」のプログラミングとなりました。
ハードウェア技術者が、ある状態になったらランプを表示するなど協力してくれて、何とかモニターにMacのディスクトップが表示できるようになりました。
初めてみたフルカラー
一旦表示できるようになると、プライマリモニターとしてApple純正のビデオカードを使い、我々のカードはセカンダリーモニターとして「視界ゼロ」状態は解消し、比較的順調に開発が進んでいきました。システム起動後ビデオカードはユーザに選択にしたがって色数を変える機能が必要で、ファームウェアは白黒、16色、256色、フルカラーとOSからの指示にしたがってビデオボードを制御します。
大きな障害もなく開発は進みました。勉強になったのは色数を切り替える途中で不快な色の画面にならないように、フルカラー ->全面グレー->256色と遷移させることで、全面グレーだとフルカラーでも256でも画面は同じになるので、グレーの時にビデオカードの色モードを遷移させます。単に色数を変える間の一瞬に、そこまで気を使っているAppleの設計に驚きました。
色数の切り替えが成功して、初めてフルカラーの画面をみたとき、ハードウェア技術者とともに、美しさに感激してしばらく画面に見入っていました。
プライマリモニターでデバッガを表示しない
セカンダリーモニターとして完璧に動作しビデオカード用のUIも開発し、いよいよプライマリモニターとして単独で動作し始め若干の違和感があったものの、頂上は見えたと安心していました。
突然「違和感」の正体がわかりました。
当時のMacには開発者用に起動直後にデバッガ(ブートROMに組み込まれたもの)を表示することができましたが、我々のカードではデデバッガを表示しないのです。
何日か悩んだ末にAppleのサンプルコードのコメントにヒントを見つけました。
互換性維持のためにCPUは最初に24ビットアドレスモードで起動し、その後32ビットアドレスモードに切り替わることが分かりました。
ビデオカードが24ビットアドレスに対応していなかったので、ビデオカードのハードの改修(ASICの作り直し)するはめになりソフトウェア側も再度「視界ゼロ」プログラミングからのサイクルが始まりました。
1サイクル目の経験がありチームワークも良くなっているので、比較的短時間で製品を完成に持ち込みリリースすることができました。
バケツから色が漏れる
プロジェクトが終了して暫くしてからメーカから連絡がありました。
「Adobe Photoshopのバケツツールを使うと色が漏れる。ハードウェアの問題とは思われない」
バケツツール(塗りつぶしツール)でポイントした領域の境界を無視し、全画面が同じ色になってしまいました。
ファームウェアはOSの指示にしたがって色数の変更をするだけでピクセルのデータは全てOSが処理するので、ファームウェアとは考えられないのですが、OSの動作は他の人では解析できないので調査に出向きました。
ビデオRAM上で1ピクセルのデータは数のように4バイトのデータで表現します。
αチャネルは当時は未使用領域でした。
例えば左側の色のピクセルをデバッガでみると16進で右側のように表示されます。
Photoshopの内部動作を追うことは叶わないので、手掛かりはなかなか見つからなかったのですが、正常の動作するビデオカードと開発したビデオカードか必ず何かの違いがあるはず。Apple純正のものビデオRAMを表示させたり、開発したカードのビデオRAMを表示させたりしてRGBのデータがどのように違いがあるかモニターに顔を近づけて調べてのですが違いがわからず袋小路にはまっていました。
仕方がないので食事をとったり休憩をとったりして一旦リラックスして作業場に戻り、ぼんやりと開発したボードのビデオRAMの表示を見ていたら何となく全体の濃淡が正常なカードのものと違うことに気がつきました。
あっ、αチャネルが違う!
開発カードはαチャネルがFFに設定していました。Photoshopがバケツツールの処理で、αチャネルが00という前提で一時的にフラグとして利用しているとすると問題の辻褄があうことに気がつきました。
問題はビデオカードですが、Appleの仕様ではαチャネルが00であることを明示していないのでビデオカードのバグとは言い切れないのですが、ハード担当者の説明しハードを修正を依頼しました。
ハードの修正には時間が必要で、確信があったのですが待っている間はやはり不安でした。数日して問題が解決したとの連絡を受けほっとしました。
アセンブラで約6000行、Apple Pascalで約2000行ぐらいの開発ですが、10ヶ月程度のプロジェクトで自信にもなり技術資産にもなりました。
大変に思い出深いプロジェクトです。
質が高いというか、なんというか。
私にとって、プログラムソースも、夏目漱石の物語も、同一だろう?
というのが持論です。
開発業なんて半分以上は他人のソースの読解が仕事のようなもので。もう30年読書をしているようなものです。
作者にそれなりに個性があり、物語を書いているのです。
他のプログラマに笑われますが、私は流用元のソース解析とかしていると、怒ったり笑ったり感動したりします。何が面白いんだ???とよく言われますがw
それぞれのソースに大事にしているところ、だからおれはこーしたいんだぁー。
みたいなものが読み取れるとですね。作ってる時の苦労が偲ばれたり。
小規模開発くらい(3人くらい?)で作ったプログラムであれば、リーダー格のひとがこうやって作って、一人はなんとかくらいついてるけど、瓦解させてる一人は新人かなぁ?くらいは読み取れるようになります。
ベテランさんが、必ずしも質が高いソースを書くとは限りませんし、国文学的に書く人もいれば、流行にのって書く人もいればと、バラバラです。
でも概ね物語が破綻してないな。というのが多いです。
そういう意味では、やはりベテランさんのほうが質が高いと思います。バグだらけでもね。
バグが沢山あっても、それは校正がぬるかっただけで、修正すればいい。という話であり、例えば新人さんが作った小学生の作文なのか?くらいの起承転結ができてない物語のバグを修正するのとはちょっと比較のしよ
質が高いというか、なんというか。
私にとって、プログラムソースも、夏目漱石の物語も、同一だろう?
というのが持論です。
開発業なんて半分以上は他人のソースの読解が仕事のようなもので。もう30年読書をしているようなものです。
作者にそれなりに個性があり、物語を書いているのです。
他のプログラマに笑われますが、私は流用元のソース解析とかしていると、怒ったり笑ったり感動したりします。何が面白いんだ???とよく言われますがw
それぞれのソースに大事にしているところ、だからおれはこーしたいんだぁー。
みたいなものが読み取れるとですね。作ってる時の苦労が偲ばれたり。
小規模開発くらい(3人くらい?)で作ったプログラムであれば、リーダー格のひとがこうやって作って、一人はなんとかくらいついてるけど、瓦解させてる一人は新人かなぁ?くらいは読み取れるようになります。
ベテランさんが、必ずしも質が高いソースを書くとは限りませんし、国文学的に書く人もいれば、流行にのって書く人もいればと、バラバラです。
でも概ね物語が破綻してないな。というのが多いです。
そういう意味では、やはりベテランさんのほうが質が高いと思います。バグだらけでもね。
バグが沢山あっても、それは校正がぬるかっただけで、修正すればいい。という話であり、例えば新人さんが作った小学生の作文なのか?くらいの起承転結ができてない物語のバグを修正するのとはちょっと比較のしようがないほどの開きがあります。
ただ、物語にも、太宰は好きとか人によって好みがあるじゃないですか。
なのでベテランさんが書いた物語としては破綻してないけど、好みによっては読みにくく感じたり、理解しがたかったりするのは、それはしょうがないかなと。
万人にウケる物語はそうそう書けませんと思いますよ。
私は素人で、学生時代から独学でプログラムを書いてきて、プロが書いたものを見る機会はなかったのですが、別の部署に移動した同僚が以前に書いたサブルーチンを他の言語に移植する必要があり、彼が書いたソースコードを見せてもらうことにしました。
彼はエンジニアですが以前は本職のプログラマーでした。ファイルを開けてみてまず驚いたのはコメントの量です(追記:ご指摘いただきました通り、コード自体がその内容を説明するのが望ましく、コメントの量は最小限にするべきです)。
冒頭部は知的所有権の所属やdisclaimerなどで、まあこれは雛形通りでしょうが、そのあと関数の仕様、バージョン、アップデートの履歴、依存関係が簡潔にまとめてありそれからコードが始まります。
コード自体はまあ30行ぐらいの物でしたが、その後にコメントアウトされたコードがさらに100行ぐらいあって、それはそのサブルーチンの動作を確認するためのテストコードでした。このテストコードの部分をコピペして実行し、上のサブルーチンをを呼び出しその挙動を体験しながら学んだり、想定されたパラメータ範囲内で問題なく動いていることを引き継いだ人が確認できるようになっていました。
私は後で自分が見た時に何をやっていたのか思い出せるようにコメントを付けるように気をつけてはいますが、プロが書いたコードというものは次にそのソースコードを編集する人は全く別人であることが想定
私は素人で、学生時代から独学でプログラムを書いてきて、プロが書いたものを見る機会はなかったのですが、別の部署に移動した同僚が以前に書いたサブルーチンを他の言語に移植する必要があり、彼が書いたソースコードを見せてもらうことにしました。
彼はエンジニアですが以前は本職のプログラマーでした。ファイルを開けてみてまず驚いたのはコメントの量です(追記:ご指摘いただきました通り、コード自体がその内容を説明するのが望ましく、コメントの量は最小限にするべきです)。
冒頭部は知的所有権の所属やdisclaimerなどで、まあこれは雛形通りでしょうが、そのあと関数の仕様、バージョン、アップデートの履歴、依存関係が簡潔にまとめてありそれからコードが始まります。
コード自体はまあ30行ぐらいの物でしたが、その後にコメントアウトされたコードがさらに100行ぐらいあって、それはそのサブルーチンの動作を確認するためのテストコードでした。このテストコードの部分をコピペして実行し、上のサブルーチンをを呼び出しその挙動を体験しながら学んだり、想定されたパラメータ範囲内で問題なく動いていることを引き継いだ人が確認できるようになっていました。
私は後で自分が見た時に何をやっていたのか思い出せるようにコメントを付けるように気をつけてはいますが、プロが書いたコードというものは次にそのソースコードを編集する人は全く別人であることが想定されており、それでも出来るだけスムーズに引き継げるように書かれています。
これです。
以下のソースコードをメモ帳に貼り付けて、htmlで保存すればテトリスが動きます。
- <body onKeyDown=K=event.keyCode><script>X=[Z=[B=A=12]];h=e=K=t=P=0;function Y()
- {C=[d=K-38];c=0;for(i=4;i--*K;K-13?c+=!Z[h+p+d]:c-=!Z[h+(C[i]=p*A-Math.round(p/
- A)*145)])p=B[i];!t|c+4?c-4?0:h+=d:B=C;for(f=K=i=0;i<4;f+=Z[A+p])X[p=h+B[i++]]=1
- if(e=!e){if(f|B){for(l=228;i--;)Z[h+B[i]]=k=1;for(B=[[-7,-20,6,17,-9,3,6][t=++t
- %7]-4,0,1,t-6?-A:-1];l--;h=5)if(l%A)l-=l%A*!Z[l];else for(P+=k++,j=l+=A;--j>A;)
- Z[j]=Z[j-A]}h+=A}for(i=S="";i<240;X[i]=Z[i]|=++i%A<2|i>228)i%A?0:S+="<br>",S+=X
- [i]?"■":"_";document.body.innerHTML=S+P;Z[5]||setTimeout(Y,99-P)}Y()</script></bo
これです。
以下のソースコードをメモ帳に貼り付けて、htmlで保存すればテトリスが動きます。
- <body onKeyDown=K=event.keyCode><script>X=[Z=[B=A=12]];h=e=K=t=P=0;function Y()
- {C=[d=K-38];c=0;for(i=4;i--*K;K-13?c+=!Z[h+p+d]:c-=!Z[h+(C[i]=p*A-Math.round(p/
- A)*145)])p=B[i];!t|c+4?c-4?0:h+=d:B=C;for(f=K=i=0;i<4;f+=Z[A+p])X[p=h+B[i++]]=1
- if(e=!e){if(f|B){for(l=228;i--;)Z[h+B[i]]=k=1;for(B=[[-7,-20,6,17,-9,3,6][t=++t
- %7]-4,0,1,t-6?-A:-1];l--;h=5)if(l%A)l-=l%A*!Z[l];else for(P+=k++,j=l+=A;--j>A;)
- Z[j]=Z[j-A]}h+=A}for(i=S="";i<240;X[i]=Z[i]|=++i%A<2|i>228)i%A?0:S+="<br>",S+=X
- [i]?"■":"_";document.body.innerHTML=S+P;Z[5]||setTimeout(Y,99-P)}Y()</script></body>
[参照]
私はかつて12,500行で書かれたSQL文を見たことがあります。
1文で12,500行です。驚愕です。
このSQLはそのシステムで必要なあらゆるデータをこの1文だけで全て取得できるようになっていました。なんでこんな設計にしたのか、全く理解できませんでした。今でもできていません。
もっと驚愕したのは、そのSQLをそれほど苦労もなく不具合修正、機能追加して書き換えていく先輩の存在です。なんでそんなに修正箇所が簡単にわかるんだと。
(なおそのシステムは自社で開発したものではなく、他者がメンテ放棄したシステムの保守を自社で引き受けたものです。なんでそんなシステムのお守りを引き受ける気になったことにも驚愕です)
役立つというよりかは、古典と言うべきですが。
Unixバージョン6の、カーネルの奥深いところに、プロセスとプロセスの切り替えを制御する処理があります。これはC言語の演算子の優先順位と、UNIXの同期ルーチンの動作に強く依存しています。これは視点によっては、非常にエレガントであると見ることもできれば、最悪であると見ることもできます。この異常な文の直前には、このコメントがあります。
- /* You are not expected to understand this. */
あなたに理解してもらおうとは思ってません。
これは嫌味ではなく、続くコードが本当に複雑なので、必要がなければ無理に理解しなくてもよく、読み飛ばしても恥ずかしくないということです。
このコメントはその後、コンピューター業界の伝説
となり、これを記念するギークなボタンまであります。大勢の皆さんからのご要望に応え、こちらにその文を引用します。
- /*
- 2231 * If the new process paused because it was
- 2232 * swapped out, set the stack level to the last call
- 3333 * to savu(u_ssav). This means that the return
- 2235 * actually returns from the l
脚注
役立つというよりかは、古典と言うべきですが。
Unixバージョン6の、カーネルの奥深いところに、プロセスとプロセスの切り替えを制御する処理があります。これはC言語の演算子の優先順位と、UNIXの同期ルーチンの動作に強く依存しています。これは視点によっては、非常にエレガントであると見ることもできれば、最悪であると見ることもできます。この異常な文の直前には、このコメントがあります。
- /* You are not expected to understand this. */
あなたに理解してもらおうとは思ってません。
これは嫌味ではなく、続くコードが本当に複雑なので、必要がなければ無理に理解しなくてもよく、読み飛ばしても恥ずかしくないということです。
このコメントはその後、コンピューター業界の伝説
となり、これを記念するギークなボタンまであります。大勢の皆さんからのご要望に応え、こちらにその文を引用します。
- /*
- 2231 * If the new process paused because it was
- 2232 * swapped out, set the stack level to the last call
- 3333 * to savu(u_ssav). This means that the return
- 2235 * actually returns from the last routine which did
- 2236 * the savu.
- 2237 *
- 2238 * You are not expected to understand this.
- 2239 */
- 2240 if(rp->p_flag&SSWAP) {
- 2241 rp->p_flag =& ~SSWAP;
- 2242 aretu(u.u_ssav);
- 2243 }
もし新しいプロセスが、スワップアウトされたために、ポーズ状態に移行した場合、最後にsavu(u_saav)しているところにスタックレベルを設定します。これはつまり、returnは実際には最後にsavuを呼び出したところに戻るということです。
あなたに理解してもらおうとは思ってません。
大変ご好評につき、以下に私のコード解釈を述べます。
- まず、この条件文がクリティカルセクション(訳注:原文ではcritical region)で実行されないのは、1970年代のC言語は同期プリミティブをネイティブにサポートしておらず、セマフォでロックやアンロックをするために関数を呼び出すのはコストがかかりすぎるためです。つまりこのコードは、プロセスのタイムスライスによって中断される恐れがあります。これが奇妙さの原因の1つです。
- この変数はプロセスの状態を表すビット列を表しているようです。2240行目では、C言語の慣例に倣い、ビット列の中の1ビットだけをマスクして比較しています。結果が0でなければ式が評価されます。Cコンパイラはこの条件文全体をアトミックな1命令(
rp if SSWAP true
)にコンパイルします。 - 2241行目では、元のマスクを反転することにより、スワップのビット(SSWAP)をクリアします。
=&
代入演算子は、コンパイラが1命令に最適化することを除けば、rp->p_flag = rp->p_flag & ~SSWAP
と等価です。=&
演算子は1970年のC言語の表記であり、今の規格では&=
になっています。また2240行目で既に分岐の結果が確定していますので、クリティカルセクションが不要であることにもご注意ください。 - 2242行目では、カーネルレベルの魔法の関数(恐らくアセンブラ)を呼び出しています。スタック上に置かれたリターンアドレスを、プロセスが事前に保存していた値に改竄します。つまり、スタックの交換と、保存されていたアドレスへの復帰を、1度の関数呼び出しで行えます。
脚注
Haskellです。今でも驚きが続いています。
最初は値が変更不可であるということに。そんなのでプログラム書けるのか?と思いましたが、書けるし、書けるどころか今では値が変更できる言語に恐怖を感じるほどに仕上がりました。
おかげでたまにJavaを書くときはfinalな変数だらけになります。私の中でJavaはやりたいことに対して記述が冗長すぎる言語になってしまいました。なのでJavaで動かしたいプログラムはもっぱらKotlinで書くようにして、valのシンプルさに酔いしれています。
ループがないのにも度肝を抜かれましたね。どうかしたらifすら無くなる。それに遅延評価のおかげでプログラムした順番に評価されるのはむしろマレ。おかしい、僕らはプログラムの構造は
- 順次処理
- 分岐
- 繰り返し
しかない、と教わらなかったか?このどれもがHaskellには無い!
Haskellからの驚き、次はプログラミングの世界がとても深淵なことを教えてくれたことです。Haskellのライブラリは、Monadに代表されるように、抽象的で学術的なものが非常に多い気がします。ApplicativeやMonadに相当する概念は、Javaでは表現しようがありません。
抽象的な分、分からないととことん分からず、辛い思いをすることが多いです。でもいつの間にか理解出来るようになっていたりして、自分の成長を感じさせてくれる言語です。
次に、Haskel
Haskellです。今でも驚きが続いています。
最初は値が変更不可であるということに。そんなのでプログラム書けるのか?と思いましたが、書けるし、書けるどころか今では値が変更できる言語に恐怖を感じるほどに仕上がりました。
おかげでたまにJavaを書くときはfinalな変数だらけになります。私の中でJavaはやりたいことに対して記述が冗長すぎる言語になってしまいました。なのでJavaで動かしたいプログラムはもっぱらKotlinで書くようにして、valのシンプルさに酔いしれています。
ループがないのにも度肝を抜かれましたね。どうかしたらifすら無くなる。それに遅延評価のおかげでプログラムした順番に評価されるのはむしろマレ。おかしい、僕らはプログラムの構造は
- 順次処理
- 分岐
- 繰り返し
しかない、と教わらなかったか?このどれもがHaskellには無い!
Haskellからの驚き、次はプログラミングの世界がとても深淵なことを教えてくれたことです。Haskellのライブラリは、Monadに代表されるように、抽象的で学術的なものが非常に多い気がします。ApplicativeやMonadに相当する概念は、Javaでは表現しようがありません。
抽象的な分、分からないととことん分からず、辛い思いをすることが多いです。でもいつの間にか理解出来るようになっていたりして、自分の成長を感じさせてくれる言語です。
次に、Haskell製WebアプリケーションフレームワークのYesod。全てのレイヤに型安全を持ち込むというコンセプトに驚愕し、それを実現するための数々のHaskellの機能に狂喜しました。ただ、ライブラリ間の依存が簡単に壊れることと、ビルド時間の長さには本当に泣かされました。
次にパッケージマネージャ兼ビルドツールであるstack。これまで書いてきた中で最も大きな驚きと喜びをもたらしてくれました。なにせあれほど苦労して何度挫折したか知れないYesodを使ったWebアプリを、コマンド一発できれいにビルドしてくれるのですから。
stackの登場は、Haskellを「真に実用的な言語にした」「学習する甲斐ある言語にした」「人にすすめることの出来る言語にした」など、多くの意味でパラダイムシフトと言ってもいいほどの変化をもたらしました。パッケージマネージャの重要さをこれほど強く実感したことはありませんでした。stackの開発と依存関係を管理して下さっている方々の方向には足を向けて寝られません。
最近の言語は言語自体に加えてパッケージマネージャが付いてくることが多いですよね。node.jsとnpm、Rustとcargoなど。本当に良い時代になったものです。
なんか最後は言語自体とは違う方向になってしまいましたが、こんなところです。
「C言語でint型変数2個を互いに入れ替えるマクロはどのように書きますか?に対する大島 芳樹 (Yoshiki Ohshima) さんの回答」で見た do ~ while (0) が衝撃的でした。
ここに、数十億ドル規模の帝国を崩壊させ、3D ゲームを大衆に普及させた 1 つのラインがあります。
90 年代に世界を変えた画期的な 3D ゲーム、Doom の C ソース コードから引用。Carmack のコメントも独創的で、彼が書いたのではなく、3D 作業にこれまで使われていなかった既存の公式を利用したのだと思います。
i = 0x5f3759df - ( i >> 1 ); // 一体何なんだ?
リアルタイム 3D モデリングでベクトルまでの距離を決定するための一般的な計算である近似逆平方根を計算する、一見魔法のような方法です。通常、アイザック ニュートンの元の近似式を実行するには、計算集約型の浮動小数点演算が必要です。
たとえば、1089 の平方根はいくらでしょうか。30 と推測し、それを二乗して小さいことを確認し、1 を加えてもう一度試します。最終的に 33 であることがわかります。これはコンピューターにとっては骨の折れる作業です。
この関数は、1 つの複雑な 3D ディスプレイをレンダリングするために何百万回も呼び出される必要があります。これを 1 秒間に 30 回実行すると、速度と効率がいかに重要であるかがわかります。
私は 90 年代に Silicon Graphics (SGI) でエンジニア (MTS) として働いていました。絶頂期の SGI はウォール街やハリウッドの寵児
ここに、数十億ドル規模の帝国を崩壊させ、3D ゲームを大衆に普及させた 1 つのラインがあります。
90 年代に世界を変えた画期的な 3D ゲーム、Doom の C ソース コードから引用。Carmack のコメントも独創的で、彼が書いたのではなく、3D 作業にこれまで使われていなかった既存の公式を利用したのだと思います。
i = 0x5f3759df - ( i >> 1 ); // 一体何なんだ?
リアルタイム 3D モデリングでベクトルまでの距離を決定するための一般的な計算である近似逆平方根を計算する、一見魔法のような方法です。通常、アイザック ニュートンの元の近似式を実行するには、計算集約型の浮動小数点演算が必要です。
たとえば、1089 の平方根はいくらでしょうか。30 と推測し、それを二乗して小さいことを確認し、1 を加えてもう一度試します。最終的に 33 であることがわかります。これはコンピューターにとっては骨の折れる作業です。
この関数は、1 つの複雑な 3D ディスプレイをレンダリングするために何百万回も呼び出される必要があります。これを 1 秒間に 30 回実行すると、速度と効率がいかに重要であるかがわかります。
私は 90 年代に Silicon Graphics (SGI) でエンジニア (MTS) として働いていました。絶頂期の SGI はウォール街やハリウッドの寵児でした。当時彼らが作成したジュラシック パークやターミネーターの素晴らしいグラフィックスを思い出してください。
この 1 行の C コードは、SGI 独自のカスタム浮動小数点シリコン チップと同じことをソフトウェアで実行しました。SGI は一夜にして最大のビジネス上の優位性を失いました。正確さは劣っていましたが、ほとんどのユーザーの目を欺くには十分でした。
私たちは、John D. Carmack (id Software) のソース コードのライセンス コピーを使用して、カスタム UNIX OS である IRIX で実行される Doom のバージョンを実装しました。私たちは Doom を何時間もプレイしました (QA テスト!)。それは啓示でした。それまで、SGI は 3D ベクター グラフィックスの浮動小数点計算を行うために高価なカスタム チップに依存していました。
この単純なコーディング ハックがすべてを変え、実質的に SGI を廃業に追い込みました。市販のハードウェアを搭載した安価な PC で、突然、10 万ドル以上のワークステーションと同じ 3D グラフィックスが実行できるようになりました。SGI は方向転換してサーバー ビジネスに注力する必要がありましたが、結局失敗に終わりました。
SGI の OpenGL は、当時から現在まで生き続けている唯一の真の遺産です。
その 1 行のコードの影響は過大評価できません。
Quora 英語版
謙虚、尊敬、そして信頼です。
このような質問はLarry Wallの「三大美徳」やそれに影響された回答が多いだろうなと思いましたが案の定ですね。それを一概に否定するものではないですが、この発言は「本来は悪いことをよいことのように言う」というジョークというか諧謔であるという前提を理解しておく必要があります。そして、その3つの美徳そのものではなく、それがもたらすもののことまで考えないといけないと私は思っています。
例えば傲慢こそ美徳だみたいなことを真に受けてか、無意味に傲慢なってしまうという人をいろいろ見てきました(自分もそうだったという面もあるかもしれない)。言うまでもなく傲慢そのものは美徳でもなんでもないし、必要なものでもありません。時としてはtoxic cultureの源泉として忌避され、あるいは糾弾されることもあります。
実際のところの「三大美徳」における傲慢(hubris)とは、「The quality that makes you write (and maintain) programs that other people won't want to say bad things about.」であり、つまり誰も悪く言うことのないようなコードを書くことになる資質、つまりは「これを作ったらすごいことになるぞ」という思い上がりや自信のようなものと解釈するべきでしょう。
こういったふう
謙虚、尊敬、そして信頼です。
このような質問はLarry Wallの「三大美徳」やそれに影響された回答が多いだろうなと思いましたが案の定ですね。それを一概に否定するものではないですが、この発言は「本来は悪いことをよいことのように言う」というジョークというか諧謔であるという前提を理解しておく必要があります。そして、その3つの美徳そのものではなく、それがもたらすもののことまで考えないといけないと私は思っています。
例えば傲慢こそ美徳だみたいなことを真に受けてか、無意味に傲慢なってしまうという人をいろいろ見てきました(自分もそうだったという面もあるかもしれない)。言うまでもなく傲慢そのものは美徳でもなんでもないし、必要なものでもありません。時としてはtoxic cultureの源泉として忌避され、あるいは糾弾されることもあります。
実際のところの「三大美徳」における傲慢(hubris)とは、「The quality that makes you write (and maintain) programs that other people won't want to say bad things about.」であり、つまり誰も悪く言うことのないようなコードを書くことになる資質、つまりは「これを作ったらすごいことになるぞ」という思い上がりや自信のようなものと解釈するべきでしょう。
こういったふうにLarry Wallの「三大美德」というものは言ってみれば「取り扱い注意」なものであって、深く考察や解説をすることなしに取り上げるのはいかがなものかなあ、と思います。
そこで私がかわりに挙げたいのが、書籍『Team Geek』で挙げられている3つの要素、すなわち謙虚さ(humility)、尊敬(respect)、そして信頼(trust)です。書籍では頭文字をくっつけてHRTと書き、「ハート」と読むことを提唱しています。
- 謙虚さとは、自分が正しくないかもしれないことを理解しておくことです。開発においては、間違った前提や思い込みから進んでしまうことはよくあります。その場合に自分の当初の前提に固執することなく状況を理解して方針転換できる柔軟性のことをこの単語で表現しています
- 尊敬とは、会う人を人間として尊重しコミュニケーションを取ることを意味します。機能を提案したりするといきなり罵倒したり馬鹿にしたような返事を返す人、いますよね。それをやめようということです。もちろん相手の言うことにいつでもそのまま従うということではないですが、どのような文脈があり、何を考えてなされた提案なのかをきちんとコミュニケーションを通じて理解しようとするべきだ、という態度のことです
- 信頼とは、相手に悪意などは仮定せず基本的には善意を仮定すること、また信頼に足る相手には積極的に移譲することです。もちろん誰でも彼でも信頼するということはありませんが、何にでも疑い深くなり、防衛的に相手の考えを否定することは避けるということです。
これはまあ端的に言えば、無駄にとっつきにくくなったりするのをやめようという話ではあります。しかし本での著者の主張は、この方針はチーム全体としての機能性を高め、結果として総合的な生産性が高くなるというものでもあります。
『Team Geek』は会社組織で働く技術者のための本なので、この方法論もまたチームプレイヤーとしての技術者としての方針ではあります。なので一人で開発するだけのプログラマには当てはまらないように感じられるかもしれませんが、現実のソフトウェア開発では一人でコードを書くだけで完結するというものは少ないですし、会社などではないオープンソースでの活動であってもコミュニティがあるものなので、この方法論は有意義であると言えるんじゃないかと私は思っています。
私自身がこの方針を正しく実践できているかというと道半ばかと思いますが、大事な指針だと思います。
C言語のプログラムなんですが。
最初から最後まで、初期化していないポインタを配列として使いまくってました。
つまり、スタック領域を破壊しまくりながら動いていたわけです。
こんなクソミソなプログラムを本に載せて出版し、印税もらってるとは。
で、あとがきで「たまに動かなくなるので、私より詳しい人教えて」とか書かれてました。
なんだかんだ学生時代に書いていたプログラムが一番難しかった気がします。
修士の時はタンパク質の立体構造予測の研究をしていました。最終的にはフラグメントアッセンブリという手法と水和自由エネルギー
を組み込んだ独自の評価関数を使用したモンテカルロ法を作りました。どういうことかというと、タンパク質のアミノ酸配列の一部を切り取ってきた部分配列には通常すでに既知の構造がProtein Data Bank
というデータベースに登録されていて、ターゲットタンパク質の様々な部分配列に対する既知のフラグメントを集めてき置換しながらスコアを評価して最適な構造を探していくという方法です。General overview on structure prediction of twilight-zone proteins
水和自由エネルギー計算のプログラムは共同研究先から頂いたものでTinker
という定番のOSS分子力学パッケージをベースに作っていました。そこで私もこのTinkerをベースにフラグメントアッセンブリのプログラムを開発していろいろ実験しました。実際にはフラグメントは二面角 のデータの集合でタンパク質の構造をxyz表示から二面角に直してフラグメントのデータに置換し、またxyzに戻して通常の力場で最適化してからスコアを計算みたいな感じで作りました。ここら辺の変換はTinkerにいろいろ実装さ脚注
なんだかんだ学生時代に書いていたプログラムが一番難しかった気がします。
修士の時はタンパク質の立体構造予測の研究をしていました。最終的にはフラグメントアッセンブリという手法と水和自由エネルギー
を組み込んだ独自の評価関数を使用したモンテカルロ法を作りました。どういうことかというと、タンパク質のアミノ酸配列の一部を切り取ってきた部分配列には通常すでに既知の構造がProtein Data Bank
というデータベースに登録されていて、ターゲットタンパク質の様々な部分配列に対する既知のフラグメントを集めてき置換しながらスコアを評価して最適な構造を探していくという方法です。General overview on structure prediction of twilight-zone proteins
水和自由エネルギー計算のプログラムは共同研究先から頂いたものでTinker
という定番のOSS分子力学パッケージをベースに作っていました。そこで私もこのTinkerをベースにフラグメントアッセンブリのプログラムを開発していろいろ実験しました。実際にはフラグメントは二面角 のデータの集合でタンパク質の構造をxyz表示から二面角に直してフラグメントのデータに置換し、またxyzに戻して通常の力場で最適化してからスコアを計算みたいな感じで作りました。ここら辺の変換はTinkerにいろいろ実装されていましたのでそれらをうまく活用しました。なお、当時のTinkerはFortran77で書かれており、私はFortran95でいつも書いていたので両者をうまく混在させる方法とかをいろいろ調べた記憶があります。(もう覚えていませんがw)
最新のTinkerはFortran95で実装されていてOpenMPで並列化とかもされていていろいろ新しくなっているようですね。また分子計算やりたいなーw
脚注
全てのことに理由を付けられることを目指す。私はこれでやってました。
コードレビューはもちろん品質を保つための工程ではありますが、個人の学びという観点では「なぜ、そうしたのか」ということを自他共に確認するというプロセスでもあるとおもいます。
自分はなぜここで、この書き方を選んだのか、他の人はどうしてここでこういう風にしたのか?他にも方法はあったろうに、なぜ?
これを指摘されて答えたり、考えたり、逆に、質問して説明を受けたり。こういう工程を繰り返すことで「なんとなく」による作業を減らせるようになっていく(成果物についても同じように洗練されていく)という側面もあるとおもいます。
なので、「自分はなんでこうしたのか」を他人に説明できるくらいきちんと理由を持って作業できるようになる、を目指しておけば、普通にコードレビューを繰り返すのと同じような効果はあるでしょう。これはやりようによっては一人でも出来るトレーニングです。また、きちんと自分なりの理由をもって手を動かすことで、将来他の人と「やりかた」について議論になった際に「自分のこれはこう言う理由で良くないのか」と、指摘を受けてもきちんと理由付きで納得できるので、失敗すら学びに変えることができるようになります。
「なんとなく、なんだかよく分からないけど前に似たものを作った人がそうしてたから~」みたいな仕事をせずに、時間と力の許す範囲で「なぜ?」をきちんと
全てのことに理由を付けられることを目指す。私はこれでやってました。
コードレビューはもちろん品質を保つための工程ではありますが、個人の学びという観点では「なぜ、そうしたのか」ということを自他共に確認するというプロセスでもあるとおもいます。
自分はなぜここで、この書き方を選んだのか、他の人はどうしてここでこういう風にしたのか?他にも方法はあったろうに、なぜ?
これを指摘されて答えたり、考えたり、逆に、質問して説明を受けたり。こういう工程を繰り返すことで「なんとなく」による作業を減らせるようになっていく(成果物についても同じように洗練されていく)という側面もあるとおもいます。
なので、「自分はなんでこうしたのか」を他人に説明できるくらいきちんと理由を持って作業できるようになる、を目指しておけば、普通にコードレビューを繰り返すのと同じような効果はあるでしょう。これはやりようによっては一人でも出来るトレーニングです。また、きちんと自分なりの理由をもって手を動かすことで、将来他の人と「やりかた」について議論になった際に「自分のこれはこう言う理由で良くないのか」と、指摘を受けてもきちんと理由付きで納得できるので、失敗すら学びに変えることができるようになります。
「なんとなく、なんだかよく分からないけど前に似たものを作った人がそうしてたから~」みたいな仕事をせずに、時間と力の許す範囲で「なぜ?」をきちんと突き詰める癖を付けていけば、環境に恵まれなくても伸びていくチャンスは掴めるのではないかとおもいます。
ありますあります。
一番驚いたのは、フレームワーク内に生SQLでとんでもなく複雑なコードが書かれていたことです。
フレームワーク中心でコードを書いてきた僕には全然理解できないのですが本人曰く、「フレームワークのクエリビルダを使うのは面倒くせえ生 SQLで複雑なものをガンガン書いた方が手っ取り早い」から生 SQL 書いたという風に聞きました。
チームで開発してるからできるだけみんながわかるようにクエリビルダーで書いて欲しいんですけどまあ仕方ないですよね。
あります。
しかし、プログラミングというのは物作りのなかの一部:「実装」の仕事であって、本当にしたい仕事のすべてではなく、部分なのです。プログラムの制作をゴールとされている方は、おそらく別の価値観をお持ちだと思います。
私の代表作は Synchro PRIMO という、交流信号解析方法とそのプログラムです。しかしこれはプログラミングテクニックのたまものではなく、その上位に存在する問題を解いた結果、あるアルゴリズムが浮かび、そのプログラムがうまく実装できただけの話です。成果としてこんなシーンを捕らえることができました。 なので成功作なのでしょう。
GUI ・グラフィック・スムーススクロール・内部の計算はマルチスレッドです。ある「特殊な方法」で同期をとっているなど・・・プログラミング手法は満載です。この例ではMFC や C++ライブラリ、QtのようなGUIサポートは一切使わず、Windows のNative API のみで作っています。カーネルに近いところは時間が経っても変わらないことをみこんで、一見生産性は低いですが、多くのOSバージョンで普通に動きます。その理由を説明するには、さらに深いところを説明しないといけないのですが、まあ、専門領域でのプロ・プログラマとしての感覚であり、一般的なアマチュアの方ににおすすめできる方
あります。
しかし、プログラミングというのは物作りのなかの一部:「実装」の仕事であって、本当にしたい仕事のすべてではなく、部分なのです。プログラムの制作をゴールとされている方は、おそらく別の価値観をお持ちだと思います。
私の代表作は Synchro PRIMO という、交流信号解析方法とそのプログラムです。しかしこれはプログラミングテクニックのたまものではなく、その上位に存在する問題を解いた結果、あるアルゴリズムが浮かび、そのプログラムがうまく実装できただけの話です。成果としてこんなシーンを捕らえることができました。 なので成功作なのでしょう。
GUI ・グラフィック・スムーススクロール・内部の計算はマルチスレッドです。ある「特殊な方法」で同期をとっているなど・・・プログラミング手法は満載です。この例ではMFC や C++ライブラリ、QtのようなGUIサポートは一切使わず、Windows のNative API のみで作っています。カーネルに近いところは時間が経っても変わらないことをみこんで、一見生産性は低いですが、多くのOSバージョンで普通に動きます。その理由を説明するには、さらに深いところを説明しないといけないのですが、まあ、専門領域でのプロ・プログラマとしての感覚であり、一般的なアマチュアの方ににおすすめできる方法ではありません。
現職では、電波天文学で使用されるCASAというソフト開発をやっていますが、こちらは、ある意味、まったく別の文化圏です。第一線の電波天文学者のもとめるソフトとは「観測機器」であって、ソフトウェアは陰のサポータであるべし、と思っています。
さて、私はプログラマーがこれを行うのを見たことはありません。しかし、私はこのゲームをたくさんプレイしました。
このコンピュータには、すべてに 1 KB のメモリがありました。つまり、そのスペースには 1024 文字を保存できます。ただし、コンピュータを操作するために一部が必要であり、すべてを使用することはできませんでした。ある人がこのコンピュータで優れたチェス ゲームを作成しました。また、1 つの例を挙げると、画面にはテキストのみが表示され、グラフィックスは表示されませんでした。画面には 24 行のテキストと各行に 32 文字を表示できます。画面を埋めるには、1024 バイトのうち 768 バイトが必要です。
こんな感じです。私にとっては、これはただただ驚きです。実は、コンピューター AI (実際には AI ではありません) は、まったく悪くありませんでした。チェス盤をメモリに保存する必要はなく、常に再作成できますが、すべてのキャラクターがどのようにプレイするか、ナイトがどのようにジャンプするか、各駒の位置を知っておく必要があります。
これを見て頭が爆発しそうです。理解も解明もできません。本当に賢い人がいるものです。
Quora 英語版
さて、私はプログラマーがこれを行うのを見たことはありません。しかし、私はこのゲームをたくさんプレイしました。
このコンピュータには、すべてに 1 KB のメモリがありました。つまり、そのスペースには 1024 文字を保存できます。ただし、コンピュータを操作するために一部が必要であり、すべてを使用することはできませんでした。ある人がこのコンピュータで優れたチェス ゲームを作成しました。また、1 つの例を挙げると、画面にはテキストのみが表示され、グラフィックスは表示されませんでした。画面には 24 行のテキストと各行に 32 文字を表示できます。画面を埋めるには、1024 バイトのうち 768 バイトが必要です。
こんな感じです。私にとっては、これはただただ驚きです。実は、コンピューター AI (実際には AI ではありません) は、まったく悪くありませんでした。チェス盤をメモリに保存する必要はなく、常に再作成できますが、すべてのキャラクターがどのようにプレイするか、ナイトがどのようにジャンプするか、各駒の位置を知っておく必要があります。
これを見て頭が爆発しそうです。理解も解明もできません。本当に賢い人がいるものです。
Quora 英語版
個人的にプロのプログラマは最高なんて事考えて仕事していません。
最高のコードが書けるのは基本的には自分が書きたいコードを書いている時だけだと思います。
普通のプロ(業務っていう意味で)プログラマは自分は然程興味のないお客さんに言われた事を実現するためのコードを書いています。
でお客さんの無理難題を聞くために途中に入れたくもない条件分岐が山ほど増えるのが普通です。
そこをなるべくスパゲティコード(今も通じるんですかね?)にならないようにしていくのがプロの見せ所です。
質問者さんが言うのはおそらく普通の人なら長くなるコードをびっくりする方法で短く書いたりとかそういう事を想像してるかもしれませんが、そういう事ばかり考えている新人さんは実際には現場では使い物になりません。
現場で必要とされる能力は「納期に間に合わせろやゴラァ!!!」に対して誠実に仕事ができる人です。
美しいコードを書ける人なんて現場には必要ありません。
・・・と、現実を書きました 汗
実際の所美しい、格好いいコードは魅力的ですが、あまりに短く実現すると他の人に理解できないコードになる可能性があります。
新人さんが良くブログやツイッターで格好いいコードを書いたのに上司に直させられたみたいなこと言ってますが、まぁ気持ちはわかりますが周りの実力に合わせるのも実力者の腕の見せ所だと思いますので(とフォローする
まずは、Lisp1.5です。歳がバレますね。
昔は今のコンピュータから見たら信じられない程スペックが低いので、ガベコレするような言語などというものを考えてそれを実装するということが信じられませんでした。さらに、整数に1を足すという処理だけでも新しくセルを確保して云々とか。当時は処理系が計算途中でいきなり「ちょっとゴミ集めするので待ってください」とメッセージが出てガベコレしてました。
最近ではHaskellでしょうか。とにかく、自分が出会ったことのないパラダイムの言語を学びたいと思って四苦八苦してるのですが。これは頭が固くなった私には手強い。
驚きポイントは普通と違ってるかもしれません。実は「モナド則のようなものを自動的に保障するなど難しいのでは?」と思っていたら、「モナドを作るときは、作者が責任をもってモナド則を満たすように作らなければいけない」ということを知って「その手があったか!」というところに驚きました。
イラッまでにはいきませんが、そんなに会社を信じるなよ!、老害の言葉を真に受けるなよ!、意味のない指示をクソ真面目るにやるなよ!、ちっとは反発しろよ!と呆れる事はありますね。
一般的には、時系列で記述するコードを空間域に展開したコードです。書いたのは私です。
前状態保持フラグなんて、それを使おうって考えた時点でクソコードです。
それは置いといて、時系列コードをダラダラ書いた物は読みたくないですね。順序が必要なら、ぶった切ってルーチン化させて番号振って、上位でコントロールしたいです。
前の会社の話ですが、ある日同期のコードを見ることになりました。Cで書かれた数値計算コードでしたが、すべての関数の冒頭で、その関数で使う変数が定義されていました。普段は分かりやすくするため、使うところの直前で定義するので、とても不思議に思いました。私と同じ20代の人でしたが、コードは間違いなくおじさんの臭いがしていました。
結論まで飛ばすと、その彼は大学の教授のコードから学んで、同じように書いていたとのことでした。そして、その教授は昔のコンパイラーをそのまま使っていたようで、あのコンパイラーでは関数の途中で変数を定義出来なかったらしいです。
なるほど。通りでおじさん臭がしていました。
現代的なコンパイラーにして、最適化をコンパイラーに任せてもらっただけでコードの可読性がだいぶ改善しました。
これは延々と円周率を計算し続けるプログラムです。
- <!DOCTYPE html><html><head><meta charset="UTF-8"><title>円周率 = 3.</title></head><body><p id="pi"></p><script>
- var f=Math.floor,p=document.getElementById('pi'),a=[],b,c,d=0,e=100000,i=0,j,k,l;function g(){b=0;
- for(j=i+17;j>i;--j){b=(20*e+b)*j;a[j]=b%(j+j+1);b=f(b/(j+j+1));}for(j=i;j>0;j-=17){a[j]+=b*j;b=0;
- for(k=j;k>j-17;--k){l=k+k+1;c=f(a[k]/l)*e+f(b/l)*k;b=b%l*k+a[k]%l*e;a[k]=b%l;b=c+f(b/l);}}d=d*e+b;
- if(i){p.innerHTML+=('00000'+d).slice(-11,-6)+(i%20?'':'<br>\n');d%=10*e}i+=17;setTimeout(g,0);}g();</script></body></html>
下記のリンクで公開されています。
※最近、上記のリンクが切れたようです。以下のリンク先を試し
これは延々と円周率を計算し続けるプログラムです。
- <!DOCTYPE html><html><head><meta charset="UTF-8"><title>円周率 = 3.</title></head><body><p id="pi"></p><script>
- var f=Math.floor,p=document.getElementById('pi'),a=[],b,c,d=0,e=100000,i=0,j,k,l;function g(){b=0;
- for(j=i+17;j>i;--j){b=(20*e+b)*j;a[j]=b%(j+j+1);b=f(b/(j+j+1));}for(j=i;j>0;j-=17){a[j]+=b*j;b=0;
- for(k=j;k>j-17;--k){l=k+k+1;c=f(a[k]/l)*e+f(b/l)*k;b=b%l*k+a[k]%l*e;a[k]=b%l;b=c+f(b/l);}}d=d*e+b;
- if(i){p.innerHTML+=('00000'+d).slice(-11,-6)+(i%20?'':'<br>\n');d%=10*e}i+=17;setTimeout(g,0);}g();</script></body></html>
下記のリンクで公開されています。
※最近、上記のリンクが切れたようです。以下のリンク先を試してみて下さい。
初めて見た時、順々に小数点以下の桁が増えていく計算方法があるのかと驚きました。この計算方法は1995年に発見されたそうで、Spigot algorithmと呼ばれています。詳しくは以下のリンクを参照してみてください。
http://www.cs.williams.edu/~heeringa/classes/cs135/s15/readings/spigot.pdf
http://円周率.jp/program/spigot.html
計算した桁数とかかった時間とを表示するように少し改造してみました。
3つの願いを4つにしてくれというのが私の願いだ … 聞いてもらうぞ by アヴドゥル
といわんばかりにずるっこい手を使います。
1.プログラマの三大美徳「怠惰」「短気」「傲慢」 by Larry Wall
やっぱりこれでしょう。説明省略。私は Perl というスタンドの使い手です。
2.抽象化能力・メタ認知能力
三大美徳が重要とはいえ、抽象化やメタ認知の能力がなければ実際にレバレッジできません。
3.情熱
プログラミングというスタンドは情熱という生命エネルギーが実体化したものです。
以上
2000行あった関数ですね。
あるプロジェクトに支援へ行くよう命じられて、どうもウェブアプリケーションの性能が悪く起動して30分ほどで応答しなくなり原因がわからないと言われました。
とりあえずソースコードを見て、チェックしようと。
原因と思われるクラスのコードを見たら、行数が2000行。関数が1個!
「どうしてそうなった」
その言葉自然と出ました。
開発したのは下請けの中国の会社だそうで北京に電話して、なんでこうなったの?と聞いたら、フローチャートが送られてきて、そのチャート一枚=関数1個だと思ったらしいです。
既存のコードは捨てて、徹夜で朝までかかって1から書きなおしたら普通に動きました。
めでたしめでたし。
・怠惰
何よりも重要な資質は怠惰だと思っています。いわゆる「手を抜くための工夫」を考えられる人。怠惰でない人は、「手でやった方が早いじゃないか」とプログラムを組まない傾向にあります。
・新しもの好き
とはいえ、新しい技術はいくらでも出てきます。そういうのにアンテナを伸ばせるのも重要な資質だと思います。
・null
三つの要素の入れ物があって、二つしか思いつかないなら、プログラマならnullを入れておきます
わずか3ヶ月で、保守性の高いソースに意識が向いているのは素晴らしい!
いくつか、やってほしいことをお伝えしますね。
一つ目、本を読もう。
「リーダブルコード」や「レガシーコードからの脱却」などは読んでおくと、よろしいかと思います。
二つ目、静的解析ツールに頼ろう。
StyleCop、CheckStyle、FindBugs、PMD、AdLintなど、自分が使っている言語に合わせて探してみてください。「こう書くのがいい」という点を教えてくれます。
三つ目、プロジェクト内の規約やガイドをよく読もう。
プロジェクト内で用意されている規約やガイドには、こう書いて欲しいという願いや、ノウハウが詰まっていることが多いものです。
四つ目、文化がなくても、お願いしてみよう。
後輩に頼られることを嫌う先輩は少ないものです。コードレビューをする文化がないからといって、やってもらえないとは限らないです。
よきエンジニアたらんことを!