読者です 読者をやめる 読者になる 読者になる

中途半端に知識だけある素人プログラマを相手にしている場合ではない件について

中途半端に優秀なプログラマが「正しいプログラミングテクニック」だと妄信しがちな3つポイント - 分裂勘違い君劇場
が流行ってるみたいなので参戦。

なんだか、「中途半端に優秀なプログラマ」を相手にしているというより、「中途半端に知識だけある素人プログラマ」に対して投げられているように感じました。まあ、ノンプログラマーから見れば、「知識だけある素人」と「優秀」の違いを見極めるのは困難だという意味で、皮肉を込めて「優秀」と言ってるのかもしれないけど。

で、これをきっかけに大きく寄り道、って感じで。細かいこといろいろ考えました。

変数のスコープは狭いほど良い

広いスコープからアクセスできるようにした方が正しいケースの代表的な例は、標準入出力ストリーム(stdinやstdout)がバインドされたグローバル変数の形で実装されているシステム変数や、Rubyのprintやpメソッドだろう。(この点、JavaのSystem.out.println() は、Rubyよりもいささかブサイクな仕様だ。)

うーん、Java5以降に限定されるけど、これどうなのさ?

import static java.lang.System.out;

public class Hoge {
    public static void main(String[] args) {
        out.println("Hello static import world.");
    }
}

初期開発の早さを欲しいなら、いまどき1.4以前のJavaを使うことはないだろうから、これで十分便利だと思うけどなぁ。スタティックインポートできない世代のJDKを使う時って、よっぽど広範に使えるライブラリを書くときか、使い込まれている既存システムの保守(すでに動くので早さより慎重さが求められる)か、どっちかで、どっちにしても、EoDはそれほど重要じゃないですね。

それでも不満なら、それこそこんなユーティリティを作っておいて、

package util;
import java.io.*;

public class StdIO {
    private static final PrintStream out = System.out;
    private static final InputStream in = System.in;
    
    public static void puts(String x) {
        out.println(x);
    }
    public static void puts(Object x) {
        out.println(x.toString());
    }
    public static void printf(String format, Object... args) {
        out.format(format, args);
    }
    // もっと実装しようかな
}

こんな感じ?

import static util.StdIO.*;
import static java.lang.Math.*;

public class Hoge {
    public static void main(String[] args) {
        puts("Hello more convenient static import world.");
        puts(sqrt(2));  //doubleがオートボクシングされてprintln(Object x)に送られる。
        printf("%d / %d = %f\n", 10, 3, 10.0 / 3);  //上記同様
    }
}

これ、まるでC言語と同じです。

#include <stdio.h>
#include <math.h>

void main()
{
    /* 最初の例 */
    fputs("Hello world", stdout);
    
    /* 次の例 */
    puts("Hello world");
    printf("%f\n", sqrt(2)); /* puts(sqrt(2)); はアウトなのでprintfで代用 */
    printf("%d / %d = %f\n", 10, 3, 10.0 / 3);
}

純粋OOP言語は変数スコープが狭いと思い込むのは間違いで、もうちょっと視野を広く取らんといかん、と思いました。

なぜJavaC言語のようじゃないのか、というと、たぶん、最初からマルチスレッディングを意識しているからだと思います。マルチスレッドでグローバル変数やシングルトンの状態変更をすると、逆に、ちゃんとした同期を取る面倒が増えます。C言語のstrtkn関数は、直接変数が見えるわけではありませんが、スレッドアンセーフで、グローバル変数の書き換えと同じことが起こります。「自分のコンテキストは他人と共有しない。他人は他人のコンテキストを使う」とすれば、他人のコードが別のスレッドにあろうと、同期してなかろうと、自分のコンテキストはぜんぜん平気です。Javaインスタンスの量産を好み、そのインスタンス化のためにより一般化されたクラスを定義している理由を知るには、そのへんの事情を考えた上でないといけないような気がします。

また、軽量言語にグローバル変数(もしくはモジュールに含まれる単一インスタンス)が多いのは、インタプリタが真に並列実行されることがないという前提に立っているからでしょう。

Javaを使いたくなるような状況(真のマルチスレッドを必要とする)でグローバルな変数っぽいものの便利さを教授しようとすると、「ThreadLocal格納しておいたコンテキストを取得する静的メソッドを経由して、あらゆる層からいつでも参照できるようにする」と考える必要があります。わけがわからなくなるぐらいなら、少々面倒なほうがまだマシじゃないでしょうか?

いずれにせよ、状態の変化が起こる変数は要注意です。長寿命なオブジェクトについては、やたらと多くの場所から書き換えするべきではないでしょう。last_errorや正規表現のマッチ結果のような、短命なものなら、割り込みがないと言い切れるかぎりは、かまわないと思います。分裂勘違い君が、変数が可変かどうか、寿命が自分のコンテキストより長いかどうか、などを考慮に入れずに、「とにかく変数のスコープが広いか狭いか」でだけ論じようとしたのは、ちょっと乱暴すぎましたね。

状態変化のほとんどない変数(関数が入っていたり、定数的定義だったり、特権的アクセスでのみ書き換え可能だったり)については、使う側が責任取れる範囲で、スコープの大きい場所に変数を撒き散らしてもよいと思います。C言語だって、ヘッダファイルを小分けにしてはいるけど、「使いそうなもの全部#includeする」はOKだし。

ただし、責任のけじめとして、変数撒き散らしの波及する境界を、個々のソースファイルごとに区切っておくことができる言語じゃないと、そうは言い切れません。別のソースファイルになるということは、別の問題領域、別の責任レベルに移行するということだから。それは、#includeの有効範囲がソース単位だという点で、C言語もそうだし、ほか、多くのスクリプト言語でもそう。importかrequireしたら、そのソースファイルの中でのみ機能が増える。

Pythonで例を示しますが、まさかこんなことはしないでしょ。

__builtins__.everybody_use_this = "Copyright(c) hoge co.ltd,"

普通、共有モジュールの側でこうしておき、

COPYRIGHT = "Copyright(c) hoge co.ltd,"
# ... ほか、いろいろ定数定義

使う人の責任で、

from common import COPYRIGHT  # 変数1個だけ持ち込み
print COPYRIGHT

でもいいし、

from common import *  # 変数全部を一気に持ち込み
print COPYRIGHT

でもいいし、

import common  # 変数全部を1変数にまとめておく
print commom.COPYRIGHT

でもいいし、

import common as C  # 変数全部を自分で名前をつけた1変数にまとめておく
print C.COPYRIGHT

でもいい、というのが普通の感覚だと思います。どう書いたって、別のソースファイルにCOPYRIGHT変数が入り込むことがありません。

(変数のスコープというのは、厳密にいえば、このような「レキシカルに変数が見える/見えない」というだけの話です。そもそもは、分裂勘違い君が「スコープ」という用語を使ったことが間違っているのかも。)

ただ、そう考えると、JavaScriptは「ページ」、PHPは「リクエスト」に、それぞれ責任範囲が拡大してしまい、逆にスコープを狭めるのが難しい、困った言語環境です。

PHPでプログラムを書くと、なぜかやたらとパラメータのたらい回しや過剰なクラス設計が多くなるのは、グローバル変数/関数がファイルを超えて散らかってしまうからかもしれません。ライブラリとアプリケーション実装では、それぞれ違う変数/関数セットがグローバルスコープに見えて欲しいですね。

(追記)たとえばCodeIgniterは、$thisに長寿命インスタンスや固有のモジュールを(望めばユーザが名前をつけて)取り込むという方針を採っています。$thisはクラスで閉じるスコープだし、クラスは必ずファイルで閉じるスコープですね。スコープは普通の言語相当になりました。ちょっと変わった方法ですが、あれだけデフォルトのグローバル変数/関数を散らかしたうえ、こんどは、CGIのようなスクリプト言語のくせに、JavaのようなOOPを推奨しているPHPのあのヘタクソさをねじ伏せるためだと思えば、納得できると感じます。

JavaScriptはクロージャのネストで変数スコープを制御できる言語なんだけど、グローバル侵食の影響を最小化するように書こうとすると、こんなことになっちゃいます。

var global_var;              // この名前、他の人が使ってないか心配だなぁ
function global_function(){  // この名前、他の人が使ってないか心配だなぁ
}

(function(){
  var file_local_var;
  function file_local_function(){
  }
  ... //ここにmainな処理
}).call(this);

変数のスコープを狭くするべきかどうかはさておき、変数のスコープを狭めるための設計がなされてない言語には、特定の種類の不都合があるのは間違いありません。

プログラミング言語を極めるのが大切

プログラミング言語のスキルというのは、価値あるスキルのうちの一つにすぎないどころか、それほど優先順位の高いスキルではない。

これだけは言わせてくれ、それは違う!!

あるプログラム言語を使うと決めたんなら、その言語で普通に書かれるイデオムをすべて読み書きできる程度のスキルは、なにがなんでも、最優先で習得するべきです。

ポインタをまともに読めないC言語プログラマ、インターフェース実装を理解できないJavaプログラマPHPにないRubyの構文を無視するWebサーバ屋、クロージャなしでJavaScript/ActionScriptを書くAjax屋…などなど。

その言語で書ける高度なトリックは、徐々に学べばいいと思います。関数ポインタをインクリメントして次の処理に切り替え、なんてのは、どうでもいいです。でも、少なくとも、言語仕様と標準ライブラリの考え方だけは、きちんと勉強し終えていないとお話になりません。ある意味、極めてもらわないと困ります。

片言の日本語しか話せない人に、書評させようと思いますか? 製品マニュアル書かせますか? 文学者や詩人である必要はないけど、やりたいことやるのは、読み書きそろばんができてからじゃないとダメです。

さらに、要求仕様を実装仕様に落とし込むスキル、あるいは、実装の都合と顧客の都合を最大限に満たすような落としどころを洞察するスキルなど、より優れた美しいアーキテクチャ設計をするのに、単なるプログラミング言語のスキルよりもはるかに重要なスキルはたくさんある。

えーっと、「実装仕様に落とし込む」とか、「実装の都合」とか、「美しいアーキテクチャ設計をする」と言うからには、常識感覚というスキルとプログラムを書くスキルが両方同じレベルで必要だと思います。どちらか一方だけを指して「はるかに重要」とは言えません。まるでそれは「努力と緻密さなんかより才能とインスピレーションが大事だよ」と言ってしまう、勉強できないインチキ芸大生の詭弁に聞こえてしまいます。努力とひらめきが伴って、初めてひとつレベルアップするんじゃないでしょうか?

「すごいテクでプログラムできるオレ様カコイイ」って言ってる素人さんと、「プログラムできるイコール何でも作れる≒Lightwave使えるイコールCGアーティストになれる」って思ってる入門者は、このさい無視ね。