fc2ブログ

無精で短気で傲慢なプログラマ

UNIX や web やプログラムの技術的なことを中心に。

社会人の技術レベル その2

社会人の技術レベル のつづき

へにかさんのコメント:
> ある程度枯れたソースを流用したほうが、トラブルが少ない、という理由
> かも知れません。

ごもっともです。
 - コピペによる短期的な修正量削減・リスク軽減をとるか
 - コピペをせずに中・長期的な修正量削減tと、美しい設計による保守コスト削減をとるか
という話なわけで、どちらにするかはそのときの状況判断次第です。

しかし「コピペをせずにすむ技術力を持ちつつ、状況次第ではコピペする」
ならば理解できますが、わたしの見る限り技術力がないのでコピペしかできない
人がほとんどでした。

一般論で終わってしまうのも嫌なので具体的な例を出します。DB からデータを
取得して、CSV ファイル 10個に出力するプログラムがあるとします。10 ファイルを
まとめてオープンする処理を書かせるとします。すると、

  fp1 = fopen(CSV_OUT_1, "w");
  if ( fp1 == NULL ){
    char errmsg[ERRMSG_LEN];
    sprintf(errmsg, "CSVファイルオープンエラー");
    err_fp = fopen(LOGFILE, "a");
    fprintf(err_fp, errmsg);
    fclose(err_fp);
    EXEC SQL ROLLBACK;
    EXEC SQL DISCONNECT;
    exit(1);
  }
  fp2 = fopen(CSV_OUT_2, "w");
  if ( fp2 == NULL ){
    char errmsg[ERRMSG_LEN];
    sprintf(errmsg, "CSVファイルオープンエラー");
    err_fp = fopen(LOGFILE, "a");
    fprintf(err_fp, errmsg);
    fclose(err_fp);
    EXEC SQL ROLLBACK;
    EXEC SQL DISCONNECT;
    fclose(fp1);
    exit(1);
  }
  fp3 = fopen(CSV_OUT_3, "w");
  if ( fp3 == NULL ){
    char errmsg[ERRMSG_LEN];
    sprintf(errmsg, "CSVファイルオープンエラー");
    err_fp = fopen(LOGFILE, "a");
    fprintf(err_fp, errmsg);
    fclose(err_fp);
    EXEC SQL ROLLBACK;
    EXEC SQL DISCONNECT;
    fclose(fp1);
    fclose(fp2); ← ★ここがどんどん増えていくことに注意
    exit(1);
  }
  … 以下 fp4~fp10 略。


というソースを平気で書いたりする。

おいおい、って感じですよね (ちなみに、どのファイルをオープンしようとして
エラーになったのかわからないし、errno も記録していない。ひどいのになると
いきなり exit しておしまいってのもよく見ました)。

わたしなら、

  char *csv_outfile[] = {CSV_OUT_1, CSV_OUT_2, CSV_OUT_3 ... };
  FILE *csv_fp[(csv_outfile)/sizeof(csv_outfile[0])];
  for ( i=0 ; i<sizeof(csv_outfile)/sizeof(csv_outfile[0]) ; i++ ){
    csv_fp[i] = fopen(csv_outfile[i], "w");
    if ( csv_fp[i] == NULL ){
      /* 可変長引数なログ出力関数 logging を作る */
      logging("CSVファイル [%s] オープンエラー。errno[%s]",
              csv_outfile[i], strerror(errno));
      /* わたしはこういう場合は fclose せず OS にまかせますが、まぁ一応 */
      for ( ; i>=0 ; i-- ){
        fclose(csv_fp[i]);
      }
      EXEC SQL ROLLBACK;
      EXEC SQL DISCONNECT;
      exit(1);
    }
  }


と書きます。ログファイルに

  CSVファイル[/foo/bar/hoge.csv](ほげシステム出力用CSV)] オープンエラー。
   errno[Permission Denied]

などとファイルの種類 (ほげシステム~ってところ) を出力したいがために、

  typedef struct {
    char filename[FILE_MAX]; /* ファイル名 */
    char description[256];   /* 説明用文章。エラー発生時などに使用 */
    FILE *fp;
  } csv_info_t;
  csv_into_t csv_info[] = {
    {CSV_OUT_1, "ほげシステム出力用CSV"},
    {CSV_OUT_2, "ほげ料金請求額CSV"},
    {CSV_OUT_3, "ふが料金請求額CSV"},
    ...
  };
  for ( i=0 ; i<sizeof(csv_info)/sizeof(csv_info[0]) ; i++ ){
    csv_info_t *csv_p = &csv_info[i];
    csv_p->fp = fopen(csv_p->filename, "w");
    if ( csv_p->fp == NULL ){
      logging("CSVファイル [%s](%s)オープンエラー。errno[%s]",
      csv_p->filename, csv_p->description, strerror(errno));
      ..
    }
  }


とさらにもう一段抽象化するかもしれません。

もし何かの事情があってファイルオープン処理は 10回書かざるをえないとしても、

  char *csv_outfile[] = {CSV_OUT_1, CSV_OUT_2, CSV_OUT_3 ... };
  FILE *csv_fp[(csv_outfile)/sizeof(csv_outfile[0])];
  memset(csv_fp, NULL, sizeof(csv_fp));
  csv_fp[0] = fopen(CSV_OUT_1, "w");
  if ( csv_fp[0] == NULL ){
    sprintf(errmsg, "CSVファイル [%s] オープンエラー。errno[%s]",
            CSV_OUT_1, strerror(errno));
    goto err;
  }
  csv_fp[1] = fopen(CSV_OUT_2, "w");
  if ( csv_fp[1] == NULL ){
    sprintf(errmsg, "CSVファイル [%s] オープンエラー。errno[%s]",
            CSV_OUT_2, strerror(errno));
    goto err;
  }
  (略)
  return RET_OK;

 err:
  err_fp = fopen(LOGFILE, "a"); /* ログオープンエラー処理略 */
  fprintf(err_fp, "%s\n", errmsg);
  fclose(err_fp);
  for ( i=0 ; i<(sizeof(csv_fp)/sizeof(csv_fp[0]) ; i++ ){
    if ( csv_fp[i] != NULL ){
      fclose(csv_fp[i]);
    }
  }
  EXEC SQL ROLLBACK;
  EXEC SQL DISCONNECT;
  return RET_NG;


などとエラー処理は一箇所にまとめてほしいものです。でもそれすらできません。

新人がしょーもないソースを書くのは仕方がないですが、それを指導するのは
先輩のはず。でも、その先輩にも技術力がないので指摘することができない。

かなりレベルが低い例で恐縮ですが、わたしの言うコピペというのはこういう
情けないレベルの話です。「ある程度枯れたソースを流用」云々以前の問題です。

こういう低レベルな悩みはないのであれば、へにかさんの会社 or 部署のレベルが
高いか、組み込み系全体のレベルが業務系よりは高いのかなぁと思ったりします。

まだまだ続きます。

P.S.
へにかさん、いろいろご心配頂いて申し訳ありません。とりあえず今はとても
楽しくやっております。上記のような汚いソースを書く輩がいたら、
 「こんな汚いソースは初めて見ました」
と言って (嫌な奴)、何度でも書き直させます。

スポンサーサイト



PageTop

perl+DBI プログラムの鉄則 - SQL 文の書き方

perl+DBI プログラムの鉄則 のつづき。

鉄則その4.

  my $sql =
    "SELECT ".
    "   hoge, ".
    "   fuga ".
    "  FROM table1 ".
    " WHERE foo = ?";

SQL 文の可読性を高めるため、必ずインデントを付けること。SQL はプログラムと
データベースの界面である。プログラムが正しくて、なおかつデータベースが正しく
ても、SQL 文が誤っていたら全ては台無しとなる。

しかも SQL 文は多機能なわりに短く書けるので、情報密度が高い (逆に言うと
重要な部分を読み取りづらい)。SQL 文の可読性を高めずして、プログラムの質の
向上はない。

  $dbh->prepare("SELECT hoge, fuga FROM table1 WHERE foo = ?");

などと prepare や execute の引数に直接 SQL 文を書いてはいけない。必ず一度
$sql などの変数に代入しよう。どんなに気をつけても、手で書いた SQL には
大抵ミスがあるものである。一度 SQL に代入しておけば、

  print "$sql\\n";

とすることで print デバッグができる。特に初心者には作業効率の向上に
つながるだろう。なお、「一度変数に代入した方がよい」というのは SQL に
限らずプログラム一般に言えるノウハウである。print (printf) デバッグも
できるし、ソースレベルデバッガを使っている場合でも、どんな値が入って
いるか確認しやすい。


1行につき、項目・条件は 1つだけ書く。
  my $sql =
    "SELECT ".
    "   hoge, fuga ".
    "  FROM table1 ".
    " WHERE foo = ? AND bar = ?";
と 1行に複数項目・複数条件を書かない。理由は可読性の向上に加え、コメントを付け
やすくするため。

  " WHERE NVL(foo, 0) = ?". # ほげ情報が未指定の場合は foo は NULL なので 0 に変換する
  " AND bar = ?";

などと書けるようにする (NVL は NULL なら指定された値に変換する Oracle の
関数。MySQL なら IFNULL。SQL の規格的にこういう関数があるかどうかは知らない)。

もうひとつの理由は、変更点を明確にするため。CVS や Subversion や VSS などの
バージョン管理ツールには、変更した箇所を表示する機能があるが、いずれも
行単位でしか変更点を表示できない。差分を知りたいときに

(変更前) "SELECT hoge, fuga, CASE WHEN moge=1 THEN 'a' moge=2 THEN 'b' ELSE 'c' END FROM table1 WHERE foo = ? AND bar = ? GROUP BY baz"
(変更後) "SELECT hoge, fuga, CASE WHEN moge=1 THEN 'a' moge=2 THEN 'b' ELSE 'd' END FROM table1 WHERE foo = ? AND bar = ? GROUP BY baz"

などと表示されたとしても、どこが変更されたのさっぱりわからない。複数行に分けて
書けば、どの行が変更されたのかが明確になる。
PageTop

PHP の嫌なところ

変数のタイプミス耐性がない。

つまり perl で言うところの use strict 相当の機能がない。

無精なプログラマとしては許せない。いちいちテストしないと怖くて使えないではないか。だから仕事では使わない。
PageTop

正規表現における文字クラス内のドット

今日 30分はまったこと。

Perl の正規表現で、ドットの部分を改行コードにもマッチしてほしくて
 m/[.\n]/
とし書いたが、全く意図どおりに動かなかった。

なぜなら文字クラス [~] の外にあるドットは「改行以外の任意の一文字」という意味だが、文字クラスの中にあるドットは ただのドットだから (Perl に限らず、あらゆる正規表現にあてはまる)。

よって、

if ( 'abc' =~ m/[.]/ ){
print "OK!\n";
}

はマッチしない。
PageTop

社会人の技術レベル

# 注意: ここで言う技術レベルとは、あくまでコンピュータ関連の技術的
# レベルであって、管理スキルやコミュニケーションスキルは含まない。

学生時代、fj や UNIX 関係のメーリングリストを見ていて、社会人
というのは何と技術的なレベルが高いのだろうか、こんな恐ろしい
ところでやっていけるのだろうか、と思っていた。

就職するときは少しでも技術力のある会社に行きたいと思い、1990年代
前半あたりまで UNIX (本物の UNIX ね) を作っていた会社に入ってみた。
「お前は UNIX のことを何も知らないなぁ」と呆れられながらも、少し
ずつ成長していく自分を想像したりした。

というわけでなんとなく業務系 SE になってみたものの、入社後しばらく
すると UNIX や C言語や web に関してわたしより詳しい人は、(少なくとも
同じ部署には) いないことに気づいた。

エラー処理は甘いし、命名 (変数名・プログラム名) は変だし (=物事の
本質を見極めていない)、ソースはコピペばかりで無駄に長いし、言語知識は
全然ないし、UNIX を知らない。そもそも man を見ない。かわりに見るのは
「UNIX ポケットリファレンス」。

データベースなど、それまで未経験だった部分はさすがに先輩の方が
よく知っていたが、そのうち追い抜いてしまった。

わたしは SE だったが、外注としてプロジェクトに参加していた
プログラマのレベルも低かった。なんで顧客対応や進捗管理もしなきゃ
いけない SE が、プログラミング専門でやっているプログラマに「こんな
汚いソースを書くな」と言わなきゃいけないのか。逆だろう。


でも、メーリングリストや web 界隈においては、いまだにわたしの
レベルは低い。まだまだ勉強が足らんと思う。

わたしの師匠はどこにいるんだろうか?

ゲーム系? 組み込み系? パッケージソフト系?

業務系にもいることはいるが、単にわたしの運が悪くて、まわりの人の
レベルが低すぎただけ? あるいはそんな人はどこにもいない? 青い鳥なの
だろうか?

誰か知ってたら教えてください。ほんとに。
PageTop

Perl の「裸のブロック」と next

今日 Perl で はまったこと。
foreach (1..5){
   {
     if ( $_ == 3 ){ next; }
   }
   print "$_\\n";
}

の結果は、1,2,4,5, ではなく 1,2,3,4,5 となる。

ラクダ本曰く
 「ブロックは、意味的には、1回だけ実行されるループと等価」
とのこと。知らなかった。

わたしは変数のスコープを狭くするために
{
   my $tmpbuf = &func($hoge);
   $tmpbuf =~ s/,//g;
   if ( $tmpbuf eq '' ){
     $flag = 1;
   }
}

などと裸のブロック (bare block) を多用するので、この仕様は嫌だなぁ。

ちなみに似たようなはまりやすい点として、perl では do { ... } while; 内での next・last・redo は効かない、というのがある。

ついでに言うと、C においては continue・break は裸のブロックに対して作用しない。
main(){
   int i;
   for (i=1 ; i<=5 ; i++ ){
     {
       if ( i==3 ){ continue; }
     }
     printf("%d\\n", i);
   }
}

の結果は 1,2,4,5 となる。
PageTop

flag と Boolean

flag について悩むコメントより
名前というより、データ型をBOOLEAN NOT NULLにしたら、2値ということでいいんでないでしょうか。そうでなければ、2値以外もありえる、たとえそのときは選択肢が2つしかなくとも、設計者は増える可能性を考えていた(仕様変更含め)、という認識で、データ型重視で。もちろん、『is_admin』なんて項目に、「管理者/保守ユーザ/一般ユーザ」なんて値が設定可能だったら、「なんでそんな名前やねん!」となりますが、それは単に設計者のセンスが悪かったのだと割り切ればいいんでないでしょうか。まあ、どっちにしても、なにをBOOLEANにするか?迷いどころなので、問題は解決できていないのですが、BOOLEANにしちゃったら、もう小手先の変更では取り返しがつかないというプレッシャーがあるので、よく考えた上でそうなっているはずだと、名前より重要な位置づけで考えられると思います。

なるほど、Boolean NOT NULL ですか。どこまで腹をくくれるかって話ですね。

Boolean のことをよく知らないので調べてみました。

Boolean が規格化されたのは SQL-99 なので、まだまだ使えない RDBMS が多いのが最大の欠点。各 RDBMS の対応状況は
 - PostgreSQL は Boolean に対応
 - MySQL だと tinyint (-128~127) のシノニム
 - Oracle は未対応?
 - DB2も使用不可っぽい?
てな感じ。ただ、MySQL は ENUM 型 を使って、
 ENUM('TRUE','FALSE')
 ENUM(0, 1)
 ENUM(0, 1) NOT NULL
などと型の定義ができる (Oracle や DB2 もできる?)。まぁ制約を使えばどんな RDMBS でも何とかなるんだけれども。

てな感じでしょうか。

で、仮に Boolean が使えるとして、自分が使うかどうかを考えてみました。その結果、
 - 他システムとデータのやりとりをする場合、他システムが決めた項目が 2値なら使用しない
  (3値以上になる可能性が高いから)。逆に自システム用で 2値と決定しなたら使用する。
 - レコード数が数百万を超えるなら使わないかもしれない
  (型変更に対する DB メンテナンス時間がかかるから)
という結論になりました。わたしには「2値でいくぞ」という覚悟が足らないのかもしれない。
PageTop

「flag」について悩む

2ch プログラマ板の DB 関連のスレッドで (どのスレかは忘れた)、
 「hoge_flag という項目名をつけるのであれば、とりうる値は 2種類で
  あるべき。flag は旗なんだから、上げるか下げるかのどちらかだ」
という趣旨の書き込みを見たことがある。

そのときは何となく納得して、2値の場合は hoge_flag、3値以上の場合は
hoge_code や hoge_type などと命名するようにしたが、本当に旗は上げるか
下げるかのどちらかなのだろうか。

たとえば手旗信号は
 - 真上に上げる
 - 斜め上 45度
 - 水平
 - 斜め下 45度
 - 真下に下げる
の 5種類あるように見える。

参考: 手旗信号

しかし「flag bit」という言葉もある。「flag bit」なら 2値限定、「flag」
なら 3値以上もアリ、なのだろうか。


とりあえず、仮に「2値なら hoge_flag、3値以上は hoge_code・hoge_type」と
いうルールを決めたとする。しかし最初は 2値であると思っていたが、後になって
考えると 3値以上を割り当てたくなるというのはよくある話。

ということは、最初から hoge_flag という名前を使わない方がよいのだろうか。

あるいは 3値以上になった場合、項目名を改名するのがよいだろうか。その場合、
DB 設計書・プログラム設計書・プログラム・DBのカラム名・関連する全 SQL の
すべてを改修する必要がある。はたしてそれだけの労力を費やす価値があるのだろうか。

…と、この 3年ほど項目名を決めるたびに悩んでいるような気がします。
PageTop

label 要素のススメ

「あらゆるラジオボタン・チェックボックスに label 要素をつけよう運動」を
今日から推進します。

たいていのブラウザにおいて、ラジオボタンやチェックボックスのサイズは非常に
小さいです。わずか数ドットしかない狭い領域をクリックしなきゃならないような
インタフェースは腐っています。このようなインタフェースを放置するのは利用者を
馬鹿にした行為といっても過言ではありません。

というわけで、label 要素をつければ入力が飛躍的に楽になります。


○ label 要素使用 (ラジオボタンx・チェックボックスx をクリックすると、対応するボタンにチェックが入る)






× label 要素未使用 (ラジオボタンx・チェックボックスx をクリックしても無反応)

ラジオボタン1
ラジオボタン2
ラジオボタン3
チェックボックス1
チェックボックス2

具体的には、以下のように input 要素に id を振ります。そして label for="対応するid" と
記述すれば OK です。なお、HTML 内で id の値は重複してはいけません (label 云々ではなく、
HTML の仕様)。

<input id="radio1" type="radio" name="hoge" value="1">
<label for="radio1">ラジオボタン1</label>
<input id="radio2" type="radio" name="hoge" value="2">
<label for="radio2">ラジオボタン2</label>
<input id="checkbox1" type="checkbox" name="hoge" value="1">
<label for="checkbox1">チェックボックス1</label>
<input id="checkbox2" type="checkbox" name="hoge" value="2">
<label for="checkbox2">チェックボックス2</label>

ちなみに google は、ほとんどのサービスにおいて label 要素を使用しており、
とても使いやすいです。Gmail を使っていると、ユーザインタフェースにここまで
こだわるか、と驚かされます。

Yahoo! Japan は、label 要素を使用しているサービスもあれば、
使用していないサービスもあります。がんばりましょう。


参考: フォームとアクセシビリティ
PageTop

Oracle、オープンソースソフト企業のInnobase買収

http://www.itmedia.co.jp/news/articles/0510/08/news012.html より。
米Oracleは10月7日、フィンランドのオープンソースソフト企業Innobase OYの買収を発表した。買収条件は非公開。

MySQL 的に一番嫌なのは、これにより InnoDB の開発が停滞し、現在注力している
エンタープライズ分野への進出がストップしてしまうこと、だろうか。

とりあえずいつでも PostgreSQL に切り替えられるように、MySQL の方言は極力
使わないようにしておこう。

MySQL リファレンスマニュアル: SQL-92 標準に対する MySQL 拡張機能

とはいえ、
  INSERT INTO TABLE SET col1=val1, col2=val2
はカラム名と値の対応がわかりやすいので便利だ。それと、
  INSERT INTO TABLE values (col1, col2), (col3, col4), (col5, col6);
という複数行の同時 INSERT も結構楽ちん。いずれもプログラム中で使うことは
あまりないが、マスタテーブルを更新するための SQL を手書きするようなケースでは多用する。
PageTop

MySQL と RaiseError とサーバサイド prepare

昨日から今日にかけて はまったこと。

DBI + DBD::mysql + MySQL で、RaiseError が効かない。

 use DBI;
my $dbh = DBI->connect(
'DBI:mysql:dbname', 'user', 'password',
{AutoCommit => 0, RaiseError => 1, PrintError => 0}
) || die;
my $sth = $dbh->prepare("select * from not_exist_table");
$sth->execute;


上記のように、存在しないテーブルやカラムに対して DML を発行しようとすると、
RaiseError を ON にしておけば SQL 解析の prepare で勝手に die してくれる。

エラーはこんな感じ。
  DBD::mysql::st execute failed: Table 'dbname.not_exist_table' doesn't exist at a line 5.

これは Perl における use strict のようなもので、RaiseError を ON
にするのは当然の話。

しかし、ある環境で実行すると、
  Can't call method "execute" on an undefined value at b line 6.
となってしまった。一応プログラムは異常終了しているのだが、これはテーブルが
存在しないので prepare は undef を返して終了し、undef なものに対して execute
メソッドを実行したから落ちただけのこと。prepare の時点で RaiseError により
落ちたわけではない。

RaiseError も効かないし、PrintError も効かない。それどころか
  my $sth = $dbh->prepare("select * from not_exist_table") || die "$DBI::errstr";

でも落ちない。

どうしたものかと思って調べていたら、

DBD::mysqlより引用:
Prepared statement support (server side prepare)

As of 3.0002_1, server side prepare statements are on by default
(if your server is >= 4.1.3)

To use driver emulated prepared statements, all you need to do
is set the variable mysql_emulated_prepare in the connect:

$dbh = DBI->connect( "DBI:mysql:database=test;host=localhost;mysql_emulated_prepare=1",
"", "", { RaiseError => 1, AutoCommit => 1 } );

* Note: delimiter for this param is ';'

To make sure that the 'make test' step tests whether server prepare works,
you just need to export the env variable MYSQL_SERVER_PREPARE:

export MYSQL_EMULATED_PREPARE=1


ということらしい。DBD::mysql-3.0002_1 から「サーバサイド prepare」というものが
デフォルトで有効になったとか。

というわけで、
 my $dbh = DBI->connect(
    'DBI:mysql:dbname;mysql_emulated_prepare=1', 'user', 'password',
    {AutoCommit => 0, RaiseError => 1, PrintError => 0}
   ) || die;

として解決。

で、「サーバサイド prepare」とは何かと言うと、
 MySQL Lists: mysql-ja: Prepared Statement (訳)
らしい。
  • これまでの DBD::mysql の prepare はクライアントサイドの prepare を使っており、
    速度向上にはあまり寄与していなかった。
  • DBD::mysql-3.0002_1 からサーバサイドの prepare を使うようにした。

というのはわかるが、mysql_emulated_prepare=1、つまり prepare をエミュレートするとは
どういうことか? サーバサイドの prepare はなぜ RaiseError が効かないのか?
ってのが全くわからない。

Oracle だと prepare ってのは要はカーソルオープンしっぱなしにするわけで、
つまりはサーバサイドなわけだが、MySQL の言うサーバサイド prepare ってのは
それと違う?

よくわからん人はおとなしく DBD::mysql-2.9x でも使ってなさいってことか。
PageTop

3値論理――神のいない論理

以前、DB 歴 10年目くらいの人が
 「あれ、NULL が入っている行って『カラム名 = NULL』で SELECT できるんじゃないの?」
とわたしに言った。情けない。

と嘆いたところで業界から不勉強な人が減るわけでもないので、カラムには極力 NOT NULL をつけましょう。

NULL を嫌悪する者として、いろんなところで宣伝してまわっているのが以下のページ。

世の中のカラムに NOT NULL 制約がひとつでも増えることを願います。
トップページは ミックのページ
PageTop

perl+DBI プログラムの鉄則 - fetchrow_arrayref を使え

perl+DBI プログラムの鉄則 のつつき。

鉄則その3.

   while ( my $arr_ref = $sth->fetchrow_arrayref ){
      my ($hoge, $fuga) = @$arr_ref;
      ...
   }

SELECT 時は上記のように fetchrow_arrayref を使うこと。そして必ずスカラーに
代入すること。せっかく fetchrow_arrayref を使っていても

   while ( my $arr_ref = $sth->fetchrow_arrayref ){
      print "$$arr_ref[0] $$arr_ref[1]\n";
   }

などと書いては台無しである。配列のインデックスで指定すると取得カラムの
増減に非常に弱い。そしてなりより、$$arr_ref[1] が何を意味するのか
さっぱりわからない。


fetchrow_array は使わない。複数カラムを取得する場合は問題ないが、
1カラムのみ取得する際に

   $sql = "SELECT hoge FROM table1";
   $sth->prapre($sql);
   $sth->execute;
   while ( $hoge = $sth->fetchrow_array ){
      ...
   }

とスカラーコンテキストで fetchrow_array を使ってしまうと問題が
出てくる。もしこのとき hoge が NULL であった場合、fetch 途中にも
かかわらずループが終了してしまうから。

fetchrow_hashref は使わない。

   while ( my $hash_ref = $sth->fetchrow_hashref ){
      print "hoge=[$hash_ref->{hoge}] fuga=[$hash_ref-{fuga}]\n";
   }

と書けるのは便利ではあるが、ハッシュはタイプミス耐性がない。

   print $hash_ref->{hoge};



   print $hash_ref->{moge};

とタイプミスした場合、値は undef になる。しかし DB 内の NULL は、DBI に
おいては undef として扱われるため、タイプミスと NULL の区別がつかない
(exists で調べれば区別はつくが、そんなことをわざわざやる人はほとんど
いない)。

fetchall 系は、最初は教えない。数百万・数千万・数億レコードの相手を
するようになったときに困るから。最終的にオンメモリに置くデータなら
別にかまわないが、それはメモリサイズを直感的に把握できるようになってから。


fetch というメソッドもあるが、これは fetchrow_arrayref の別名となっている。
fetch という短くてタイプしやすいメソッド名の権利を獲得したのは
fetchrow_array でもなく、fetchrow_hashref でもなく、fetchrow_arrayref
なのである。

これこそまさに fetchrow_arrayref を使うべし、という DBI 製作者のメッセージ
ではなかろうか (知らんけど)。
PageTop

perl+DBI プログラムの鉄則 - eval で例外処理

perl+DBI プログラムの鉄則 のつつき。

鉄則その2.

DB に接続したらすぐに別の関数に飛ばし、そこですべての処理を行う。
main 部分は

    my $dbh = DBI->connect(...) || die "$!";
    eval {
       ...
       $dbh->commit;
       $dbh->disconnect;
    };
    if ( $@ ){
       $dbh->rollback;
       $dbh->disconnect;
    }

とだけ書いておく。これは Java で言うところの try ~ catch に相当する。
内部で die すれば if ( $@ ) で引っかかるわけだ。なお、RaiseError を
ON にしておかないと、エラーが起こっても自動では die してくれないので
注意。disconnect の部分には、エラーが発生したことをログに記録するなどの
後始末を処理を付け加えること。

try ~ catch は何段階でもネストできるのと同様、
  eval { ... }; if ( $@ ){ ... }
もネストできる。

一部の処理でエラーを無視したい場合がある。例えば INSERT の際に一意制約が
発生しても処理を続行したい場合は、

  my $sql =
      "INSERT INTO table2 ( ".
      "  col1, ".
      "  col2 ".
      ") VALUES (".
      "  ?. ".
      "  ? ".
      ")";
  eval {
    my $sth = $dbh->prepare($sql);
    $sth->bind_param(1, $col1_value);
    $sth->bind_param(2, $col2_value);
  };
  if ( $@ ){
    if (  DBI::errstr !~ m/ORA-00001/ ){
       print "一意制約が発生したけど続行します。\n";
    } else {
       die "$@";
    }
  }

などとする (ORA-00001 ってのは Oracle の場合)。一意制約ではなかった場合、
再度 die しているのがポイント。この die は、上位の eval (サンプルの
プログラムでは main 部分の eval) があれば、そこで引っかかってくれる。
PageTop

perl+DBI プログラムの鉄則 - RaiseError と AutoCommit

perl+DBI プログラムの鉄則 のつつき。

鉄則その 1.

    my $dbh = DBI->connect($dsn, $user, $password,
         {RaiseError => 1, PrintError => 0, AutoCommit => 0 });

connect 時にするべきこと。

RaiseError は ON にする。そうすれば DB の処理でエラーが発生した
ときに勝手に die してくれる。いちいちエラーチェックをする
必要もなくなるし、エラーチェック漏れも起こりえない。楽できるのに、
なおかつ質も向上する。プログラマたるもの、怠惰でなくてはならない。

デフォルトでは PrintError が ON で RaiseError を OFF になっている。
RaiseError を ON にするとエラー発生時にエラーメッセージがダブって
表示されてしまうため、PrintError は OFF にする。


AutoCommit は OFF にする。コミットは、すべての処理が成功した場合に
最後に一度だけ行うもの (基本的には)。DBI のデフォルトは AutoCommit が
ON なので、INSERT・UPDATE するたびに毎回コミットしてしまう。必ず
OFF にすること。

MySQL の MyISAM と InnoDB

MySQL で、トランザクションの機能がない MyISAM 型を使っていた場合は
AutoCommit を OFF にしても意味がない。トランザクション機能を持つ
InnoDB を使うこと。どうしてもパフォーマンスが必要な場合のみ
MyISAM の使用を検討すればよい。


MySQL は MyISAM がデフォルトなためか、MySQL で育った人は、トラン
ザクションを知らない、または軽視している人が多そうな気がする (わたしは
Oracle で育って PostgreSQL にちょっと手を出した人間なので、信じられない)。

調べてみると、初期の MySQL は速度重視・トランザクションなんて不要、
という立場だったらしく、現在の MySQL のドキュメントもかなり速度を
重視したスタンスとなっている。その分、トランザクションがなくても
実装でなんとかなる、的なことが書いてある。

しかしエンタープライズ用途ではトランザクションがないと話にならないので、
MySQL も InnoDB を開発し、選択肢を増やしたわけである。

MyISAM と InnoDB の利点と欠点を知った上で比較検討し、その結果 MyISAM を
使うなら何も言わないが、InnoDB のことを知らない人はすぐに勉強すること。
まずは InnoDB を使って、パフォーマンスが悪いときに MyISAM の使用を
検討するべきである。
PageTop

perl+DBI プログラムの鉄則

68user 的 perl+DBI プログラムのルール。初心者に教育するときはこのサンプルプログラムを渡すこと。重視すべきなのは、バグの入り込まないコーディングスタイルであることと、可読性を重視すること。速度はその後でよい。

まずはサンプルプログラム。解説は後ほど。


#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect($dsn, $user, $password,
                       {RaiseError => 1, PrintError => 0, AutoCommit => 0 })
   || die "$!";
eval {
   &mainwork();
   $dbh->commit;
   $dbh->disconnect;
};
if ( $@ ){
   $dbh->rollback;
   $dbh->disconnect;
}

exit 0;

#--------------------------
sub mainwork {
  my $sql =
    "SELECT ".
    "  hoge, ".
    "  fuga ".
    " FROM table1 ".
    " WHERE foo = ?";

  my $sth = $dbh->prepare($sql);
  $sth->bind_param(1, $hogehoge);
  $sth->execute;

  while ( my $arr_ref = $sth->fetchrow_arrayref ){
    my ($hoge, $fuga) = @arr_ref;
    print "hoge=[$hoge] fuga=[$fuga]\n";

    my $sql =
      "UPDATE table2 SET ".
      "  hoge = ? ".
      " WHERE fuga = ?";

    my $sth = $dbh->prepare($sql);
    $sth->bind_param(1, $hoge);
    $sth->bind_param(2, $fuga);
    my $rows = $sth->execute;

    # 正常時は、ここで 1行だけ更新されるものとする。
    if ( $rows != 1 ){
      die "更新行数が異常! rows[$rows] sql[$sql] hoge[$hoge] fuga[$fuga]";
    }
  }
}
PageTop

長すぎる部分が勝手に隠れるテーブル

Gmail の吐いた HTML と 2時間も格闘して得た技。

http://x68000.q-e-d.net/~68user/bugnote/bugindex.php?projectid=1

ウィンドウサイズを小さくすると、「要約」の列のみが勝手に隠れる。
その他の列は隠れない。Firefox・IE6 で確認。
PageTop

はじめての blog

blog とかいうものに手を出してみた結果、
fc2 のシステムはダメだということがわかった。

> 希望パスワード (できるだけ類推されにくいパスワードにしてください。)
> (4~10文字 半角英数小文字) 例:aaaa

最小 4文字は少なすぎ。最大 10文字も少なすぎ。
なぜ英大文字や記号が使えないのか。ダメすぎ。

> ブログタイトル (ブログ上で表示するブログのタイトルです。)
> (全角1~40文字)

なぜ全角なのか。本当は 68user's blog にしたかったが、
全角英数字を入力するのは嫌なので、仕方なくあきらめる。

まぁアカウントを作ろうとしたものの、重すぎて作れなかった seesaa
よりマシかもしれない。

というわけでひとつよろしく。
PageTop