プログラムの最近の記事

2010年10月13日

Javaでの擬似的な符号なし整数クラス

JavaにはCやC++にある符号なし整数がなく、整数は全て符号つき整数となります。
そのため、整数が最大値を超えた場合、マイナスになります。

例えば整数がlongの場合、longの最大値(9223372036854775807)に1を加算すると
ラップアラウンドして、負数の-9223372036854775808になります。


符号なしのラップアラウンドを実現をしたい場合には、ロジックで実現する必要があります。
以下は、擬似的な符号なし整数をクラスで実装した場合の例です。

クラス実装

import java.io.Serializable;
import java.math.BigInteger;

/**
* 擬似的な符号なし整数クラス
*/
public class UnsignedLong implements Serializable {

private static final long serialVersionUID = 1L;
/** longの最大値 */
private static final BigInteger LONG_MAX = new BigInteger(String.valueOf(Long.MAX_VALUE));
/** 1L */
private static final BigInteger LONG_ONE = new BigInteger(String.valueOf(1L));
/** 現在の値 */
private BigInteger value = null;

/**
* コンストラクタ(現在の値を設定)
* @param nowValue 現在の値
*/
public UnsignedLong(long value){
this.value = new BigInteger(String.valueOf(value));
}

/**
* 加算
* @param addValue 加算する値
* @return 加算後の値
*/
public long add(long addValue) {

BigInteger add = new BigInteger(String.valueOf(addValue)); // 加算する値

// 加算
value = value.add(add);

// longの最大値を超えているかどうかチェック
if(1 == value.compareTo(LONG_MAX)){
// 加算後の値がlongのMax値を超えている場合はラップアラウンド
// 例) 9223372036854775807 + 1 = 0
// 9223372036854775807 + 2 = 1
value = value.subtract(LONG_MAX);
value = value.subtract(LONG_ONE);
return value.longValue();
}

// 超えていなかった場合は加算した結果をそのまま返す
return value.longValue();

}

@Override
public String toString(){
return value.toString();
}

}

実行例

public class Hoge {

public static void main(String[] args) {

// longの最大値に1を足す(結果は-9223372036854775808)
long value = Long.MAX_VALUE + 1L;
System.out.println(value);

UnsignedLong unValue = null;
// longの最大値に1を足す(結果は0)
unValue = new UnsignedLong(Long.MAX_VALUE);
System.out.println(unValue.add(1L));

// 100に1を足す(結果は101)
unValue = new UnsignedLong(100L);
System.out.println(unValue.add(1L));

}

}


2010年1月13日

スタート地点からゴール地点に至る最短経路を求めよ

とくに理解できないのはLv0,1の人たちが開発現場でどんな仕事をしてるのかだな。ぶっちゃけ、3時間かけてこれということはコードを書く能力は0でしょ? なのに経歴書にはいろいろなプロジェクトにプログラマとして関わった経験があるようだし、こんな人でも何とか使わないといけない状況に置かれた彼らの上司がかわいそうでならない。

岡島氏のプログラマ採用の試験問題をやってみました。

試行錯誤しながら2時間半かけて一応完成しました。
時間制限のある緊張化だと、制限時間内の3時間で完成したかどうかは疑問です。
私はアルゴリズムの基礎力が無いから、力業で解決しようとする傾向があるように思いました。
あと寝る前にすると頭に血が回って寝付けないよ!

ちなみに今まで一緒に仕事をしてきた実際の職場のプログラマの方々の中で、
この問題を3時間で解ける人は、10人に1人いるかいないかだと思います。

2時間半で簡易動作の確認まではしたコード。
リファクタリングなし、見直しなし

#!/usr/local/bin/perl --
use strict;
use warnings;

my @map;
my @root;

# マップ読み込み
my $y = 0;
my $x = 0;
my $goalY = 0;
my $goalX = 0;
my $maxY = 0;
my $maxX = 0;
open(FILE,"< map.txt") || die $!;
while (){
my @line = split(//,$_);
$x = 0;
foreach my $c (@line){
$map[$y][$x] = $c;
if($c eq 'S'){
$root[$y][$x] = 0;
}
elsif($c eq 'G'){
$root[$y][$x] = -1;
$goalY = $y;
$goalX = $x;
}
$x++;
$maxX = $x if($x > $maxX);
}
$y++;
$maxY = $y if($y > $maxY);
}
close(FILE);

# スタートからゴールまでの全探索
my $point = 0;
END_START:while(){
foreach my $y(0 .. $maxY-1){
foreach my $x(0 .. $maxX-1){
if(defined($root[$y][$x])){
if($root[$y][$x] == $point){
last END_START if(checkStoG($y-1,$x)); #上
last END_START if(checkStoG($y+1,$x)); #下
last END_START if(checkStoG($y,$x-1)); #左
last END_START if(checkStoG($y,$x+1)); #右
}
}
}
}
$point++;
last END_START if($point > 1000);
}

# ゴールからスタートまでの最短ルートを逆引き
$point++;
END_GOAL:while(){
$point--;
next if(checkGtoS($goalY-1,$goalX)); #上
next if(checkGtoS($goalY+1,$goalX)); #下
next if(checkGtoS($goalY,$goalX-1)); #左
next if(checkGtoS($goalY,$goalX+1)); #右
last END_GOAL if($point < 0);
}

# 回答表示
foreach my $y(0 .. $maxY-1){
foreach my $x(0 .. $maxX-1){
next if(!defined($map[$y][$x]));
if($map[$y][$x] eq ' ' && defined($root[$y][$x]) && $root[$y][$x] == -1){
print '$';
}
else{
print $map[$y][$x];
}
}
}

sub checkStoG{
my $y = $_[0];
my $x = $_[1];

if($map[$y][$x] eq 'G'){
# ゴールに到達
return 1;
}

if(defined($root[$y][$x])){
# 既出ルート
}
elsif($map[$y][$x] eq ' '){
# ルート候補
$root[$y][$x] = $point + 1;
}

return 0;
}


sub checkGtoS{
my $y = $_[0];
my $x = $_[1];

if(defined($root[$y][$x])){
if($root[$y][$x] == $point){
$root[$y][$x] = -1;
$goalY = $y;
$goalX = $x;
return 1;
}
}

return 0;
}


実行結果


**************************
*S* * *
* * * * ************* *
* * * ************ *
* * *
************** ***********
* *
** ***********************
* * G *
* * *********** * *
* * ******* * *
* * *
**************************

**************************
*S* * $$$$ *
*$* *$$* $************* *
*$* $$* $$************ *
*$$$$* $$$$$ *
**************$***********
* $$$$$$$$$$$$$ *
**$***********************
* $$$$$* $$$$$$$$$$$$$G *
* * $$$$*********** * *
* * ******* * *
* * *
**************************

**************************
*S                       *
*                        *
*                        *
*                        *
*                        *
*                        *
*                        *
*                     G  *
*                        *
*                        *
*                        *
**************************

**************************
*S$$$$$$$$$$$$$$$$$$$$$ *
* $ *
* $ *
* $ *
* $ *
* $ *
* $ *
* G *
* *
* *
* *
**************************


2008年5月26日

CPANからDBD::SQLiteインストール時にエラー

sudo perl -MCPAN -e 'install DBD::SQLite'
Can't call method "value" on an undefined value at /usr/share/perl5/IO/Uncompress/RawInflate.pm line 64.

いろいろさわっていたらCPANからDBD::SQLiteが入らなくなった。
環境はubuntu(8.04-server)

sudo apt-get remove libio-compress-zlib-perl
sudo apt-get install libio-compress-zlib-perl
sudo apt-get install libdbd-sqlite3-perl

で直った。
apt-get様!一生ついていきます。

開発の前に環境でよくはまるのだけど、この時間は投資になるんでしょうか。
リテラシーが低すぎて泣ける。

http://packages.ubuntu.com/ja/hardy/libdbd-sqlite3-perl

ubuntuのドキュメントの充実度はとても助かるなぁ。

2008年5月21日

VMware Server 1.0.5 + ubuntu(8.04-server) インストール手順

080521_ubu00.png

インストール準備

長いので続き以降に

続きを読む: VMware Server 1.0.5 + ubuntu(8.04-server) インストール手順

2008年5月14日

VMware ServerとcoLinuxの使用感の比較

080514ubuntu.png

比較したバージョン・OS



  • VMware Server 1.0.5 + ubuntu(8.04-server)
  • coLinux 0.6.4 + debian(sarge)

使用して差を感じた点

以下の差は純粋なVMware ServerとcoLinuxの違いではなくOSの違いも含まれていると思われます。


  • インストール

    VMware Serverの方が簡単。coLinuxはネットワークのTAPドライバの設定でブルースクリーンを連発するときがあるので設定以前に不安定さの問題があるという印象。
  • 起動速度

    coLinuxの方が速い。sambaの共有が使えるようになるまで、coLinuxが起動してから10秒ほどで使えるのに比べて、VMware Serverは2分ほどかかる。
  • samba共有速度

    VMware Serverが3倍はやい。比べた実測は下にまとめました。
  • 使いやすさ

    VMware Serverは設定・管理しやすい。coLinuxはプロ仕様というか…

VMwareはサービス化できないから毎回最小化する必要があるのが手間、
というのを聞いたことがあったのだけど、設定で問題なくサービス起動できました。

sambaを使ったデータ共有の遅さにストレスを感じていたので、そのメリットだけで開発環境を
coLinuxからVMware Serverに切り替える理由になりました。

samba共有フォルダの転送速度比較

比較方法は、samba共有フォルダにネットワークドライブを割り当てて、CrystalDiskMark 2.1を使用して計測しました。

計測種類VMware ServercoLinuxローカルドライブ
Sequential Read 56.031 MB/s 12.968 MB/s 141.609 MB/s
Sequential Write 60.743 MB/s 29.827 MB/s 126.844 MB/s
Random Read 512KB 56.633 MB/s 13.093 MB/s 48.642 MB/s
Random Write 512KB 53.640 MB/s 27.855 MB/s 56.495 MB/s
Random Read 4KB 20.074 MB/s 8.511 MB/s 0.760 MB/s
Random Write 4KB 7.718 MB/s 4.329 MB/s 2.047 MB/s

ローカルドライブでの4KBのRead/Writeが遅すぎますが、原因は不明です。
使用している体感では、Seq(Sequential Read)の値がしっくりきます。

080514speed.png

2008年4月 2日

バッチファイルとvbsを使ったOracle簡易自動バックアップ方法

バックアップ概要

・Oracleのexpによるダンプ出力 ・データは曜日による7世代管理。 ・コマンドプロンプトでは曜日の取得ができない為、vbsを起動して返り値で曜日を取得する。 ・バッチファイルには直接パスワードを記載しているので管理者ユーザ以外はバッチファイルの中身を見えないようにしておく必要があります。

環境

・Oracle ・Windows

バッチファイル内容

backup.bat
echo off
set backupPath=c:\
cscript /b %backupPath%backup.vbs
if %errorlevel%==7 set WDAY=sat
if %errorlevel%==6 set WDAY=fri
if %errorlevel%==5 set WDAY=thu
if %errorlevel%==4 set WDAY=wed
if %errorlevel%==3 set WDAY=tue
if %errorlevel%==2 set WDAY=mon
if %errorlevel%==1 set WDAY=sun

set DMPFILE=%backupPath%bk_%WDAY%.dmp
set LOGFILE=%backupPath%bk_%WDAY%.log
exp / statistics=none file=%DMPFILE% log=%LOGFILE%

backup.vbs


WScript.Quit(WeekDay(Date))

あとは、バッチファイルをWindowsのタスクか、Oracleのスケジューラにセットして終わり!

scpとシェルスクリプトを使ったmysql簡易自動バックアップ方法

バックアップ概要

・scpコマンドを利用して、別サーバにmysqlのダンプファイルをコピーする。 ・データは曜日による7世代管理

環境

・mysql ・linux ・serverFrom…バックアップ元サーバ ・serverTo…バックアップ先サーバ

SSHログイン時にパスワード無しでログインできるようにする

パスワード無しでログインできるようにする理由は、パスワードが必要な場合、 シェルスクリプトで実行するscpにてパスワードを求められてスクリプトが止まってしまう為。

・↓の記載はEnterと思いねぇ。
・作業は全てserverFromのターミナルから行います。

1.パスワードは空のSSH秘密キー・公開キーの作成する。


serverFrom$ cd ~↓
serverFrom$ mkdir .ssh ~↓
serverFrom$ chmod 700 .ssh↓
serverFrom$ ssh-keygen -t dsa↓
↓(問合せがきたら何も入力せずにEnter)
↓(問合せがきたら何も入力せずにEnter)
↓(問合せがきたら何も入力せずにEnter)
serverFrom$

2.公開キーをバックアップ先サーバに登録する。


serverFrom$ scp ~/.ssh/id_dsa.pub serverToUserName@serverTo:/home/serverToUserName↓
serverFrom$ ssh serverToUserName@serverTo↓
serverTo$ mkdir .ssh↓
serverTo$ chmod 700 .ssh/↓
serverTo$ cd .ssh/↓
serverTo$ touch authorized_keys↓
serverTo$ chmod 600 authorized_keys↓
serverTo$ cat ~/id_dsa.pub >> authorized_keys↓
serverTo$ rm ~/id_dsa.pub↓
serverTo$ exit↓

3.ログイン時にパスワードが不要になったかテストする。


serverFrom$ ssh serverToUserName@serverTo↓

パスワードが求められたら何かが失敗しています。
ちなみに私は、authorized_keysの最後のsを入力せずに失敗したことがあります。だせぇ!

シェルスクリプトを作成

#!/bin/sh

#曜日の文字列を付加させて7世代管理
WEEK=`date '+%w'`
#mysqlのダンプ出力
/usr/local/mysql/bin/mysqldump -u -p > dbhoge$WEEK.dmp

#serverToサーバにダンプをコピー
scp dbhoge$WEEK.dmp serverToUserName@serverTo:/home/

あとはシェルスクリプトを日次cronにセットして終わり!

参考にさせていただいたサイト

OpenSSHを使って,パスワードなしのログイン http://www.geocities.jp/turtle_wide/tools/sshpass.html

2007年11月19日

間違いだらけのUIにどう気付くか

RSSリーダー にlivedoor Reader(LDR)を使っているのだけど、
SleipnirプラグインのRSSリーダーに比べて断然はやく感じて、使い心地が良い。

LDR開発者のma.laさんの文章を読むと、ほとんどの開発者が「気づかない」か「仕様」で
気にしない"正しさ"を分析して実現されている。

http://ma.la/files/livedoor/seminar2006/seminar.txt

** ウェブアプリが勝ちうるためには #1 - 大量のデータをサーバーサイドで高速に処理する -- 必要なデータだけを逐次、受信、描画する -- デスクトップアプリでは代替の効かない領域

** ウェブアプリが勝ちうるためには #2
- ネットワーク(ソーシャル)連携で勝負する
-- 集合知、統計情報を利用したフィルタリング

** ウェブアプリが勝ちうるためには #3
- 正しいUIで勝負する
-- デスクトップアプリのUIは間違いだらけ

** 正しいUIってなに?
- 正しさ = 速さ

** 正しいUIってなに?
- ユーザーがタスクを実行する際に
-- 素早く
-- 迷わず
-- 正確に
- 実行できるかどうか

** 正しいUIってなに?
- 結局のところ
- 速さを見れば正しさが分かる

文章を読んでいるだけでテンションがあがります。
一から作り直したい症候群に掛かるなぁ。

私自身が、一部のサイト(たとえばLDR)を除いてはJavaScriptをOFFにして
利用をしているので、今まではJavaScriptを前提とした動作するサイトは
作らないようにしてきたのだけど、これからはJavaScriptを前提にしていても
使ってもらえるサイトを作るといったふうに意識を変えていかないとだめだな。

2007年9月28日

Oracle Application Express3.0を1週間使ってみての感想

Oracle Application Express3.0の概要

ブラウザでWebアプリを開発するOracleのフレームワーク。無償提供されている。 同じく無償提供されているOracle10gXEと組合せれば無償でWebアプリが構築可能。

Oracleサイトのデモを見るのが視覚的にはわかりやすい。

開発の向き不向き

:-)
  • 簡易なマスタメンテを作るのにかかる時間は5分ほど(システムのインストール時間は除く)
  • フレームワークのインターフェースは直感的でわかりやすい。
  • フレームワークのボタンを全て押して試行錯誤しても学習コストは1~2週間程度(PL/SQLやOracleに慣れていることが前提

XD

  • 日本語の情報サイトが少ない(海外サイトは日本より多いけどそれでも少なめ)
  • 設定を変更して開発していくのでコーディングしているといった感覚がない。
  • 設定をDBで管理するために、ソース管理がわずらわしい。手動でSQLでエクスポートしてSVNに更新する手法をとっていたけれど自動化したほうがよかった。

導入の向き不向き

:-)
  • Excel+VBAで行っている業務システムから、Webアプリのシステムに変更するのには向いている
  • 簡易なクライアント・サーバシステムの業務システムからWebアプリのシステムに変更するのには向いている

XD

  • 複数テーブルへの更新・トランザクションの管理が必要なシステムには向いていない
  • フレームワークが対応していない凝ったことをしたい場合、逆にフレームワークが足かせになって開発コストがかかる


積上げグラフ実装のバッドノウハウ


  • 以下のようなゲーム機の売上げを積上げグラフを作成したいとする
  • 070928a.gif
  • このグラフは売上データとゲーム機器マスタの2テーブルで構成されるとする。
  • この場合、新しいゲーム機が増えた場合、ゲーム機マスタへゲーム機を追加するだけで対応したいが、
    グラフが要求するインターフェースは
    「2007/08 300 Wii 300 PS3 300 Xbox」である。
    たとえばDSを追加したい場合、
    「2007/08 300 Wii 300 PS3 300 Xbox 300 DS」にする必要がある。
    DBは動的に横にデータを増やすのには向いていなのに酷いですOracle
  • 副キーを設けて縦に

    「2007/08 300 Wii」
    「2007/08 300 PS3」
    「2007/08 300 Xbox」
    「2007/08 300 DS」
    といったデータで2007/08のグラフを構成したらいいのにと思う。

  • 思っても仕様は変更されないので、ゲーム機マスタが増えた際にグラフの積上げを動的に増やしたい場合は、SQLを動的に作成する必要がある。
  • SQLを動的に作成する方法

    グラフの作成。積上げ水平グラフをリージョン作成し、
    シリーズ問合せの問合せソース・タイプは「SQL問合せを戻すファンクション」にする。
    SQLは以下のように動的なSQL文字列を返すようにする。


    declare
    sqlstrHead Varchar2(32767);
    sqlstrBody Varchar2(32767);
    sqlstrFoot Varchar2(32767);
    cnt Integer;
    cursor cur_asset is select ゲーム機器ID,ゲーム機器名 from ゲーム機マスタ order by ゲーム機器ID;
    BEGIN
    cnt := 0;

    sqlstrHead := 'select null link, G.売上月 LABEL ';
    sqlstrBody := ' from ';
    sqlstrFoot := ',(select distinct 売上月 from 売上テーブル) G where ';
    for rec in cur_asset loop
    cnt := cnt + 1;
    sqlstrHead := sqlstrHead || ' ,T' || cnt ||'.売上総数 "' || rec.ゲーム機器名 || '"';
    IF cnt != 1 THEN
    sqlstrBody := sqlstrBody || ' ,';
    sqlstrFoot := sqlstrFoot || ' and ';
    END IF;
    sqlstrBody := sqlstrBody ||
    '(select 売上月,ゲーム機器ID, sum(売上数) 売上総数 from 売上テーブル' ||
    ' where ゲーム機器ID = ' || rec.ゲーム機器ID ||
    ' group by 売上月,ゲーム機器ID) T' || cnt;
    sqlstrFoot := sqlstrFoot || 'G.売上月 = T' || cnt || '.売上月(+) ';
    end loop;
    sqlstrFoot := sqlstrFoot || ' order by G.売上月';

    sqlstrHead := sqlstrHead || sqlstrBody || sqlstrFoot;
    return sqlstrHead;
    END;


  • 設定例で使用したテーブル定義(簡易例の為、Indexは不使用)

    CREATE TABLE ゲーム機マスタ
    (
    ゲーム機器ID NUMBER(5,0),
    ゲーム機器名 VARCHAR2(255)
    )

    CREATE TABLE 売上テーブル
    (
    売上月 VARCHAR2(7),
    ゲーム機器ID NUMBER(5,0),
    売上数 NUMBER(5,0)
    )


2007年8月23日

出したものは片付ける、出していないものは片付けない

メモリリークをしているプログラムの調査を引き受けました。
10箇所ぐらいリークしているっぽいとのこと。

調べたらメモリリークが6万箇所ぐらいありました。
どんだけー(笑

そこそこできるといった評価のプログラマでも普通にメモリ関係の処理を間違うので、
もうミスといったレベルじゃなくて他の問題を感じます。
(このプログラムは特殊な環境で動作する為、IDEはメモリリークの警告を
だしてくれない&デバッグ実行ができないのでその問題が大きいと思われる)

よくあったメモリリーク例(newしているのにdeleteしていない)

  CStringArray *pArray = new CStringArray();
  pArray->Add(*(new CString("pumpkin")));
  delete pArray;

上のただしい(と思われる)例

  CStringArray *pArray = new CStringArray();
  pArray->Add("pumpkin");
  delete pArray;

よくあったへんな例(newしていないのにdeleteしている)

  CStringArray *pArray = new CStringArray();
  pArray->Add("pumpkin");
  delete pArray;
  CString strVal;
  for (int row = pArray->GetSize() - 1 ; row >= 0 ; row--) {
    if (row < 0) {
      break;
    }
    strVal = pArray->GetAt(row);
    pArray->RemoveAt(row);
    delete &strVal;
  }
  delete pArray;

上のただしい(と思われる)例

  CStringArray *pArray = new CStringArray();
  pArray->Add("pumpkin");
  pArray->RemoveAll();
  delete pArray;

2007年6月29日

駄目コード

魔法名称から魔法コードに変換するメソッドでの不具合

□魔法マスタテーブル
-----------------------------
魔法コード/魔法名称
100/ホイミ
101/ベホイミ

◆問題
魔法マスタテーブルに102/ベホマを追加して、魔法名称としてベホマを
メソッドに入力しましたが、102の魔法コードが返ってこない。

◆原因
メソッドを確認したら以下のようなロジックになっていました。

	public String getMagicCd(String magicValue){
		String result = "";
		if(magicValue == null){
			return result; 
		}

if(magicValue.equals("ホイミ")){
result = "100";
}
else if(magicValue.equals("ベホイミ")){
result = "101";
}
return result;
}

魔法マスタテーブルを参照せずに、ハードコーディングの名称で比較して
ハードコーディングのコードを返していました。
その場しのぎすぎ。

2006年12月19日

Movable Typeのエントリ投稿時の改行設定のプラグイン

○名前
・MT::Plugin::HtmlTextConvertLineBreaksTag

○ダウンロード
mt-convert-line-breaks-tag(ver0.02)

○概要
・Movable Typeのエントリ投稿時の改行設定のプラグイン

○主な機能
・改行をbrタグに置き換える(pタグは使用しません)
・tableタグ等の行では改行をbrタグに置き換えない
・preタグ・textareaタグで囲まれた行では改行をbrタグに置き換えない
・preタグで囲まれた行では&<>"'は特殊文字に置き換える
・blockquoteの開始タグと同じ行に内容がある場合はbrタグに置き換える

○インストール方法
・/mt/plugins/のフォルダに「mt-convert-line-breaks-tag.pl」スクリプトを置く

○使用方法
・インストールを行うとエントリを編集する画面の改行設定で「Tag」が表示されるので
 投稿時に「Tag」を選択する

○変換詳細

・投稿内容


ああああ
いいいい

うううう

ええええ


ああああ

いいいい

うううう

ええええ

<pre>
preタグの内部です
</pre>

<pre>preタグの内部です </pre>

<textarea>
textareaタグの内部です
</textarea>

<textarea>textareaタグの内部です</textarea>

・左:改行設定が「改行を変換する」、右:改行設定が「Tag」
061220a.gif

2006年12月13日

Tomcatのカスタムエラーページ

[問題]
web.xmlの設定でカスタムエラーページ設定したが、IEのエラーメッセージが表示されて、
用意したカスタムエラーページが表示されない。

web.xmlの設定
  <error-page>
    <error-code>400</error-code>
    <location>/error.jsp?code=400</location>
  </error-page>
  <error-page>
    <error-code>404</error-code>
    <location>/error.jsp?code=404</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error.jsp</location>
  </error-page>

[原因]
Internet Explorerの設定で、「HTTP エラーメッセージを簡易表示する」という設定の
オプションがONになっている場合、カスタムエラーページの表示byteが少ないと
Internet Explorer標準のエラーページが表示される。

[対応その1]
利用者全員にオプションをOFFにしてもらう
→非現実的

[対応その2]
エラーページを512byte以上にすると、簡易表示されず、
用意したカスタムエラーページが表示される。
エラーのステートによっては256byte以上だが512byte以上にするのが無難。

[他]
Apatchの.httaccessの設定みたいに、全アプリについて同じエラー
ページを使用したいのだけど、web.xmlはwar単位。
Tomcatではどのようにしたら全アプリに対するカスタムページの
実装できるのだろうか。

[関連]
Web屋のネタ帳:「ページが見つかりません」 - IEのHTTPエラーメッセージの簡易表示をサーバー側で無効化する方法
http://support.microsoft.com/default.aspx?scid=kb;ja;JP294807

Microsoftサポートオンライン:[HOWTO] Internet Explorer 5.x および 6.x の "HTTP エラー メッセージの簡易表示" 機能をサーバー側で無効にする方法
http://neta.ywcafe.net/000558.html

2006年12月11日

複数列副問合せ

複数列副問合せは、使いたい時に構文を忘れていて無駄な時間を
使うことが多いです。

面倒になって複数の主キーを文字列結合して無理やり
単一列の副問合せで処理しようとしたりする(笑


・事業所毎売上テーブルの売上合計が10円以上の事業所は、事業所マスタのランクにSをセット
・事業所マスタと事業所毎売上テーブルの主キーは会社コード、事業所コードとする
・DBはOracle(10g)

UPDATE
  事業所マスタ
SET
  ランク = 'S'
WHERE
  (会社コード,事業所コード) IN 
  (
    SELECT
      会社コード,事業所コード
    FROM
      事業所毎売上テーブル
    WHERE
      売上合計 >= 10
  )

コメントでikkanくんが「EXISTSとか使えそうな気がする」とのことだったので、
EXISTS版も

UPDATE
  事業所マスタ
SET
  ランク = 'S'
WHERE EXISTS
  (
    SELECT
      *
    FROM
      事業所毎売上テーブル
    WHERE
          事業所マスタ.会社コード = 事業所毎売上テーブル.会社コード(+)
      AND 事業所マスタ.事業所コード = 事業所毎売上テーブル.事業所コード(+)
      AND 売上合計 >= 10
  )

ざっと見た限りでは速度やメモリ等のコストの差はなさそうな気がしました。

2006年11月 9日

ページング機能のある検索API



ページング機能のある検索APIの実装で良い案が思いつきません



<要件>

・Webアプリから使用されるEJBのAPI。WebアプリはJava

・1ページの表示件数はWebアプリ側で設定

・検索結果が1ページの表示件数を越える場合はページング

・ページングはGoogleのようなUIをイメージ

・検索中にDBにはデータが追加される可能性がある



API


/**
* 検索条件に該当する内容を取得
* @param query 検索文字列
* @param offset 検索結果を返す開始位置
* @param viewCount 1ページの表示件数
* @return 検索条件に該当する内容のリスト(1ページ)
*/
public ArrayList search(String query, int offset, int viewCount){...}

/**
* 検索条件に該当する件数を取得
* @param query 検索文字列
* @return 検索条件に該当する件数(全件)
*/
public int searchCount(String query){...}





API使い方の想定


1.Webアプリで検索文字列が入力されて検索ボタン押下
2.API:searchで1ページ目の検索結果のhtmlを作成(offsetは0、viewCountは100)
3.API:searchCountで結果の全件を取得
4.3の結果からページングのhtmlを作成
 
5.ページングの2ページが選択される
6.API:searchで2ページ目の検索結果のhtmlを作成(offsetは100、viewCountは100)
7.API:searchCountで結果の全件を取得
8.3の結果からページングのhtmlを作成




Webアプリ側でAPIを2個つかう必要がある時点でとても違和感が。

searchのAPI一つにして、結果とページング結果をXMLで返して、Ajax使って非同期で

検索結果を表示するのがナウでヤングなのかな。

 

これでいい気がしてきたので明日試す


/**
* 検索条件に該当する内容を取得
* @param query 検索文字列
* @param offset 検索結果を返す開始位置
* @param viewCount 1ページの表示件数
* @return [0]int 検索条件に該当する件数(全件)、[1]ArrayList 検索条件に該当する内容のリスト(1ページ)
*/
public Object[] search(String query, int offset, int viewCount){...}

2006年10月 7日

×ボタンで閉じられたイベントを取得する

Webアプリでときどき要件としてあがるのが、
「ブラウザが×ボタンで閉じられたときにシステムからログアウトして」
なのですが、
「サーバ側では×ボタンのイベントは取得できないんですよ」
と説明しても伝わらないのは当然のことかと思います。

×ボタンで閉じられたイベントの取得は以下JavaScriptでできるようです。

<body onbeforeunload='
   var x = window.event.clientX;
   var y = window.event.clientY;
   alert("onbeforeunloadイベントが発生しました!" + x + "," + y + "," + document.body.clientWidth);
   if(((x > document.body.clientWidth) && (y < 0)) || window.event.altKey){
      alert("×ボタンで閉じられました!");
   };
'>

実際のサンプル
http://yuki.silk.to/img/061007a.html

ただしこのサンプルはIEでしか動作しません。
FireFoxでもwindow.eventを取得できるようにすれば動作するかもしれません。

2006年9月21日

ロック様

ロック処理をどこで設けるかについて
最適解かどうかはわからないけれど、私ならこう設計する(してきた)といった感じで。

<処理例>
1.パスワードを入力
2.おろす金額の入力画面が表示。残高は10000円だ。
3.1000円を入力
4.確定ボタンを押下
5.ありがとうございましたのお姉さん画像表示


◆設計A
・ロック箇所:2の画面表示時にデータベースの顧客残高情報に行ロック
・ロック解除:5の画面表示時。または3.~5.でキャンセル・エラーが発生した時
・2の画面表示時にロックできない場合はエラーとする。

◆設計B
・ロック箇所:4.データベース更新開始時
・ロック解除:4.データベース更新完了時
・引出後の残高が0円になる場合はエラーとする。


以前は、Aのような設計をよくやってきたけれど、今はBの設計が多いです。

処理にはそれぞれに要件があるから一概には何が良いかはわからないのだけど、
Aの場合、例えばこれが全国規模システムの在庫割当の処理だったとすると、
梅田店の上杉くんが売れ筋の商品の入力画面を起動しっぱなしで席を外してしまうと
全国でその商品の入力ができない状況が発生しそうです。


最近のアプリケーションサーバはトランザクションの機能がしっかりしていて、
連続でいろいろなデータベース・テーブルを更新する時も、アプリケーション
サーバに任せていれば考慮する箇所が少ないのが良いです。
トランザクションの伝播とか一から設計しだすと目眩がしますし。

話がずれました。
こういった設計のスキルを、ほぼ経験だけで身につけてしまっていて
改善箇所がたくさんあると思うので、スーパーエスイーならどういった設計を
するのか学びたいです。
目もくらむような美しさと実用性を備えた設計とか、そそりますねXD

2006年8月24日

HashMapのkeyにString以外を使用する方法がわかった

以下の実装でできるみたい!
・Keyとして使用したい内容のクラスを新規で作成する
・そのクラスはhashCodeとequalsを実装している必要がある

/**
* 会社コードと部署コードを保持する内部クラス
* Mapが使えるように、hashCodeとequalsをオーバーライド
*/
protected static class KeySection {
/** 会社コード */
public String cmpCd_ = null;
/** 部署コード */
public String sctCd_ = null;

public KeySection(String cmpCd, String sctCd) {
cmpCd_ = cmpCd;
sctCd_ = sctCd;
}

/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
int code = 0;
if (cmpCd_ != null) {
code += cmpCd_.hashCode();
}
if (sctCd_ != null) {
code += sctCd_.hashCode();
}
return code;
}

/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj instanceof KeySection == false) {
return false;
}
KeySection it = (KeySection)obj;
if (!cmpStr(cmpCd_, it.cmpCd_)) {
return false;
}
if (!cmpStr(sctCd_, it.sctCd_)) {
return false;
}
return true;
}

public static boolean cmpStr(String s1, String s2) {
if (s1 == s2) {
return true;
}
if (s1 == null) {
return false;
}
return s1.equals(s2);
}
}

public void testHashMap(){
HashMap sectionNameMap = new HashMap();
KeySection key1 = new KeySection("会社コード1","部署コード1");
KeySection key2 = new KeySection("会社コード1","部署コード2");
KeySection key3 = new KeySection("会社コード1","部署コード3");

sectionNameMap.put(key1, "部署コード1の名前");
sectionNameMap.put(key2, "部署コード2の名前");
sectionNameMap.put(key3, "部署コード3の名前");

KeySection whereKey = new KeySection("会社コード1","部署コード2");
String sectionName = (String)sectionNameMap.get(whereKey);

System.out.println(sectionName);
}

2006年8月23日

HashMapのkeyにStringの配列を使用する方法がわからない

HashMapのkeyにStringの配列を使用したかったのだけどやり方がわかりませんでした。

やりかったことは、こんな感じ


public void testHashMap(){
HashMap sectionNameMap = new HashMap();
String[] key1 = new String[2];
String[] key2 = new String[2];
String[] key3 = new String[2];

key1[0] = "会社コード1";
key2[0] = "会社コード1";
key3[0] = "会社コード1";
key1[1] = "部署コード1";
key2[1] = "部署コード2";
key3[1] = "部署コード3";

sectionNameMap.put(key1, "部署コード1の名前");
sectionNameMap.put(key2, "部署コード2の名前");
sectionNameMap.put(key3, "部署コード3の名前");

String[] whereKey = new String[2];
key1[0] = "会社コード1";
key2[1] = "部署コード2";
String sectionName = (String)sectionNameMap.get(whereKey);
System.out.println(sectionName);
}


期待された結果は「部署コード2の名前」が出力されることだったのだけど、
実際はkey2とwhereKeyが異なるObjectになるので、sectionNameにはnullが設定されます。


10分程考えてわからなかったので、
こんなはしたないコードにしてしまいました。
明らかに基礎が足りていません。基礎の本を読んで勉強しなければ。

public void testHashMap(){

HashMap sectionNameMap = new HashMap();
final String splitStr = "\n";

String key1 = "会社コード1" + splitStr + "部署コード1";
String key2 = "会社コード1" + splitStr + "部署コード2";
String key3 = "会社コード1" + splitStr + "部署コード3";

sectionNameMap.put(key1, "部署コード1の名前");
sectionNameMap.put(key2, "部署コード2の名前");
sectionNameMap.put(key3, "部署コード3の名前");

String whereKey = "会社コード1" + splitStr + "部署コード2";
String sectionName = (String)sectionNameMap.get(whereKey);
System.out.println(sectionName);
}

2006年4月25日

JavaScriptやCSSの更新時にキャッシュから読ませない

おそらく架空のストーリ

プログラマ「プログラムを更新しました」
テスター「いきなりエラーがでますよ!動作確認したんですか!」
プログラマ「え、私のところでは動くけど…、キャッシュクリアしました?」
テスター「キャッシュをクリアしたらエラーがでなくなりました」
テスター「でも、ユーザ全員にキャッシュをクリアさせるの?」

今日、JavaScriptやCSSの更新時にキャッシュから読ませない方法を知りました。
先人達は偉大すぎます。


<link href="common.css" rel="stylesheet" type="text/css">

この記述だとサーバ側の「common.css」を更新しても、
クライアント側のキャッシュの「common.css」が使用されて、
サーバ側の変更が反映されない可能性があります。

これをcssやjsにクエリー文字列を付加することで、
「common.css」ではなく、「common.css?20060425」でキャッシュさせます。

<link href="common.css?20060425" rel="stylesheet" type="text/css">
<script src="common.js?20060425" language="javascript" type="text/javascript"></script>

cssやjsを更新した際はクエリ文字列を変更します(20060425→20060426)

<link href="common.css?20060426" rel="stylesheet" type="text/css">
<script src="common.js?20060426" language="javascript" type="text/javascript"></script>

「common.css?20060425」はキャッシュされていますが、
「common.css?20060426」はキャッシュされていないので、
キャッシュは使用されず、サーバ側の「common.css」が実行されます。

クエリ文字列の内容は特に処理に影響は与えないので、
何を付加してもかまいませんが、日付やバージョンをつけるのが
管理しやすいです。

2006年4月19日

bashからsqlplusへの引数渡しと戻り値取得



・bashからsqlplusへ引数を渡して戻り値を取得します。

・sqlplusの終了で「quit 文字列」としたらエラーになるので

中間ファイルを経由させました。

・中間ファイル経由の方法はかなり無理やりな気がします。

もう少し良い方法が知りたいです。

#!/bin/sh

#

TMP_FILE="./hoge.tmp"



# sqplus処理

function printSqlplus {

  ${ORACLE_HOME}/bin/sqlplus -s scott/tiger >>EOF < /dev/null

  SET HEADING OFF

  SET ECHO OFF

  SET FEEDBACK OFF

  SET PAGESIZE 0

  SET TRIMSPOOL ON

  SET TERMOUT OFF

  SET LINESIZE 1000

  SPOOL ${TMP_FILE};

  SELECT 'Hello ${1}' FROM DUAL;

  SPOOL OFF

  quit

EOF

  ret=${?}

  if [ ${ret} -ne 0 ] ; then

    return 1

  fi

  return 0

}

# sqlplus起動

echo "-------------------------------------------"

printSqlplus "Oracle"

ret=${?}

if [ ${ret} -ne 0 ] ; then

  echo "エラー"

  exit 1

fi



cat ${TMP_FILE}

rm -f ${TMP_FILE}

exit 0





bashでの確認問合せ



正規表現を用いている為、yes/noはYやNでの入力も可能です。

#!/bin/sh

#



while /bin/true

do

  echo "-------------------------------------------"

  echo "よろしいですか?(yes/no)"

  echo -n " => "

  read ans

  echo "-------------------------------------------"

  case ${ans} in

    [Yy]|[Yy][Ee][Ss])

      echo "yesが押されました"

      continue ;;

    [Nn]|[Nn][Oo])

      echo "noが押されました"

      continue ;;

    *) 

      echo "キャンセルされました。"

      exit 0;;

  esac

done





2006年3月28日

ポリモルフィズム

http://www.hyuki.com/yukiwiki/wiki.cgi?%A5%DD%A5%EA%A5%E2%A5%EB%A5%D5%A5%A3%A5%BA%A5%E0#i1

オブジェクト指向言語で型によるswitch文が出てきたらクラス設計が間違っているかもしれないとうたがってみるとよい。

良いアドバイスで感動しました。

2006年2月12日

Suggest機能



Google Suggestの機能が面白かったので、

Rhapsody of the Ragnarok Online.にSuggest機能を付けました。



Suggestイメージ



入力変換中のイベントの取得方法については、下のような数行のJavaScriptで

取得ができて、



<script type="text/javascript">

function suggest(){

  // ここにイベントを書けば変換中の文字も取得できる



  setTimeout(suggest, 100);

}

setTimeout(suggest, 100);

</script>




自作のSuggest機能として、このサンプルページまではできたのですが、

結果をレイヤーで出力して、そのレイヤー内で矢印キーやEnterキーのイベントを

取得する方法がわかりませんでした。



結局、suggest.js - 入力補完ライブラリを使わせていただいて、

このような感じ(モンスタ名検索)の実装となりました。



ライブラリを少しカスタマイズさせてもらって、

漢字・読み(ひらがな)・読み(カナ)のどれかがヒット

すれば候補にあがるようにしています。





私にとっては楽しくてしかたがないユーザインタフェースなのですが、

実装後に1日につき60000件くらいの検索は実行されているのにもかかわらず、

まったく反響がないので、独りよがりの機能だったのかもしれません。

普段ユーザインタフェースを勉強しているのに生かされていないのかもXD

2006年2月 4日

onFocusに対応した画像ボタンのロールオーバ



ブラウザ上で動作するシステム(Webアプリ)の作成がほぼ完了し、

デザイン会社さんが作成してきたデザインを組み込んでいたのですが、

どうも上手く動かない現象がでて困っていました。



◆デザイン要件

・通常はボタンは灰色とする

・ボタンにフォーカスが合わさったときに画像を青にする

・ボタンにマウスが合わさったときに画像をオレンジにする

・ボタンがマウスでクリックされたときに画像を黒くする

・IEのみで動作すればよい



ボタンにフォーカスが合わさったときに画像を青にするというのがくせもので、

フォーカスをボタンに合わせて青にした後に、マウスをボタンに合わせてオレンジにして、

マウスを外したときに画像を青に戻す必要があるのに、灰色に戻ってしまったり

(最後のフォーカスを記憶しておく必要がある)

ボタン2にフォーカスを合わせてボタン1をマウスクリックしたときにボタン2の画像が

おかしくなったり、いろいろデザインに不具合があって、もやもやしたので、いちから

デザインを組み直して調べてみたら、デザイン会社さんで使われているソフト(Dreamweaver)で

出力しているJavaScriptの不具合という結論に落ち着きました。

フォーカスに対応していないJavaScriptなのに無理矢理フォーカスで使用しているのが

一番の原因ではないかと推測するのですが。



◆調査で作成した対応版(サンプルページ

 まだ不具合が残っています。不具合が何かがわかった方はテスターの才能があります。



<SCRIPT TYPE="text/javascript">

<!--

var nowDownId = null;

var lastFocusId = null;



function myOnFocus(obj){

  objId = obj.getAttribute('objid');

  img = obj.getAttribute('objsrc');

  if(nowDownId != objId){

    document.getElementById(objId).src = img + '_f.gif';

    nowDownId = null;

    lastFocusId = objId;

  }

}



function myOnBlur(obj){

  objId = obj.getAttribute('objid');

  img = obj.getAttribute('objsrc');

  document.getElementById(objId).src = img + '.gif';

  lastFocusId = null;

}



function myOnMouseDown(obj){

  objId = obj.getAttribute('objid');

  img = obj.getAttribute('objsrc');

  document.getElementById(objId).src = img + '_c.gif';

  nowDownId = objId;

  lastFocusId = objId;

}



function myOnMouseOut(obj){

  objId = obj.getAttribute('objid');

  img = obj.getAttribute('objsrc');

  if(lastFocusId != objId){

    document.getElementById(objId).src = img + '.gif';

  }

  else{

    document.getElementById(objId).src = img + '_f.gif';

    nowDownId = null;

  }

}



function myOnMouseOver(obj){

  objId = obj.getAttribute('objid');

  img = obj.getAttribute('objsrc');

  document.getElementById(objId).src = img + '_o.gif';

}



// -->

</SCRIPT>


<a href="#" tabindex="2" objid="btn01" objsrc="btn01"

onFocus ="myOnFocus(this)"

onBlur ="myOnBlur(this)"

onMouseDown ="myOnMouseDown(this)"

onMouseUp ="myOnMouseOver(this)"

onMouseOver ="myOnMouseOver(this)"

onMouseOut ="myOnMouseOut(this)"

onKeyPress ="myOnMouseDown(this)"

>

<img src="btn01.gif" alt="death" name="btn01" width="80" height="15" border="0" id="btn01"></a>



汚い組み方になった上にFirefoxだとエラーになって、まだ不具合が残っています_n○



追記:

一晩寝てから見直したらFirefoxでのエラーは修正することができました。

JavaScriptはブラウザで動作が異なるし、デバッグしにくいし

ノミで彫刻を作るような技と匠の世界で大変な言語だなぁと思いました。

Javaが建築士だとしたら、JavaScriptは宮大工、って感じ。

2006年1月23日

チェックボックスの個数が可変の時に対応する



チェックボックスの個数が可変の時に対応した

JavaScriptの制御を試していました。




// JavaScript部



var count = 0;

obj=document.form.check;

for (var i=0;i<obj.length ; i++){

  if(obj[i].checked){

    count+;

  }

}



window.alert(count + '件、選択されています。');



// HTML部



<input type="checkbox" name="check" value="北海道">

<input type="checkbox" name="check" value="東京">

<input type="checkbox" name="check" value="大阪">





この組み方だと、チェックボックスが2つ以上だと問題なく動くけど、



<input type="checkbox" name="check" value="北海道">



こんな感じでチェックボックスが1つだと配列にはならないので

obj[i].checked

のコードでNaNになってしまって、うまく動きませんでした。



結局

<input type="checkbox" name="check" value="北海道">

<input type="hidden" name="check" value="dummy">

こんな感じで非表示のダミーチェックボックスを挿入するようにして

チェックボックスが必ず2件以上の配列になる状態にして

正常動作するようにしたのですが、ちょっと力技のような気がします。



Webで調べてみましたが、これといった解決方法を見つけることができませんでした。

王道の組み方はどういった組み方なんだろう。



またよい組み方を見つけたらこのエントリーか別エントリーで

追記します。





ふといつもこの文章を書いているMovableTypeで同じ制御していた箇所が

あったと思い出して、JavaScript部を探してみたら、下のような処理でした。




var count = 0;

var obj = document.form.check;

if (!obj) return 0;

if (obj.type && obj.type == 'hidden') return 1;

if (obj.value && obj.checked){

  count++;

}

else{

  for (i=0; i<obj.length; i++)

    if (obj[i].checked) count++;

}

return count;





なるほど、「if (obj.value && obj.checked)」でチェックボックスが1件の時は分岐させているのか。

けっこうベタな組み方でした。

ちょっとしょんぼり。

2005年12月24日

籠の中の鳥はいついつ出やる

勉強&趣味の為にJSPのプログラムを書き書き

Webページより可変の行の入力を処理したいときに
どう処理したらいいのか試していました。

可変な行というのはWebショッピングにある買い物カゴみたいに
買いものをした内容によって買えば買うほど行が増える
・雑誌   1冊
・雑誌   1冊
・コミック 1冊
こんな感じの画面のことです。

◆最初に思いついた方法
 1.処理時にJavaScriptで複数行を1行に結合して送信。※例)雑誌,1<>雑誌,1<>コミック,1<>
 2.受信した箇所で分解して処理

// 複数商品の入力内容を一行に結合する(JavaScript)

function SetText()
{
   document.form.setText.value = "";
   var str;
   for(var i=1; i<<%= inputLine %>+1; i++){
      var itemNameX = eval("document.form.itemName" + i);
      var itemCountX = eval("document.form.itemCount" + i);
      itemNameX.value = itemNameX.value.replace("\t","");
      itemNameX.value = itemNameX.value.replace(";","");
      itemCountX.value = itemCountX.value.replace("\t","");
      itemCountX.value = itemCountX.value.replace(";","");
      document.form.setText.value = document.form.setText.value + itemNameX.value + ";" + itemCountX.value + ";" + "\t";
   }
}

// 結合された入力内容を分解してArrayListにセットする(Java)

String inputSetText = request.getParameter("SetText");
ArrayList infoList = new ArrayList();

int splitPs = 0;
int splitPe = 0;

for(int i=0; i <inputLine; i++){
   String[] info = new String[2];
   for(int y=0; y <2; y++){
      splitPs = splitPe;
      splitPe = inputSetText.indexOf(";",splitPs);
      if(splitPs + 1 < splitPe){
         info[y] = inputSetText.substring(splitPs,splitPe);
         splitPe++;
      }else{
         info[y] = "";
      }
   }
   infoList.add(info);
   splitPe = inputSetText.indexOf("\t",splitPe);
   splitPe++;
}

◆次に思いついた方法
 1.受信した箇所でパラメータ名を変数にして処理

// 画面の入力内容をArrayListにセットする(Java)

ArrayList infoList = new ArrayList();

for(int i=1; i <inputLine + 1; i++){
   String[] info = new String[2];
   info[0] = request.getParameter("itemName" + i);
   info[1] = request.getParameter("itemCount" + i);
   infoList.add(info);
}

後者のほうがシンプルでだんぜん良さそうだ。

JSPの知識が低いのでやりたいことがなかなかできませんが、
意図通り動くがどうか試行錯誤するのも楽しいし、思い通りに動いた
時の楽しさはゼルダの謎を解いた時のピロリロリロリンという音を
聞いたときのような爽快感です。
だんだんと把握していく過程はとても面白いですね。