今更ながらSessionをInProcで持つのはやめましょうという話

正直なところ、

「SessionをInProcで利用してはいけない。」

というのは、誰もが知っている常識だと思っていたが、最近、仕事の中で案外そうでもない事に気づかされたので、あらためて解説します。

解説するのは以下の2点

1.なぜInProcで管理してはいけないのか。

2.どのタイミングでInProc以外にするべきなのか。

です。

まず最初に、「1.なぜInProcで管理してはいけないのか。」について解説します。

ASP.NETのプロセスは、ある条件(IISにて設定できる。経過時間だったり、特定の時間だったり、消費メモリ量だったり)に基づいて自動的に再起動するようになっています。

再起動するということは、プロセスに保持していた内容も最初からという事になります。

ということは、InProc(SessionをASP.NETのプロセス内で管理する。)だと、Sessionの内容が消去されてしまいます。

その仕組みを知らないままだと、知らないがゆえの原因不明のバグ(突然セッションの内容が消えた等・・・)に悩まされることになります。

特に開発時は発生しないためそのままリリースしてしまい、運用に入ってから突然発生して驚かされるという事になります。

そのため、SessionのモードはInProc以外(StateServerやSQLServerを利用して、プロセス外に保持するようにする。)を適用するようにしたいところです。

では続いて、「2.どのタイミングでInProc以外にするべきなのか。」について解説します。

最初に結論から述べると、

「開発の初期から。つまり最初から。」

となります。

ではなぜ最初からInProc以外にする必要があるのでしょうか。

それは、以下の2点

1.InProc以外のモードはシリアライズ可能なオブジェクトしか管理できない。

2.InProc以外のモードはSessionのEndイベントが発生しない。

について、InProcとそれ以外のモードでは動作が違うからです。

そのため、最初からInProc以外のモードを前提として開発していないと、いざ運用に入ってから

「シリアライズ可能じゃないから、セッションで管理できない。」

といった、例外が発生して、大幅な手戻り(セッションで管理しているオブジェクトがシリアライズ可能になるようにしなくてはいけない。)が発生する可能性や

「SessionのEndイベントが発生する前提で設計してしまった。」

といった事態が起きて設計の変更という手戻りが発生するが出てきます。

本来、最初からInProc以外のモードを前提としていれば、発生しない手戻りなわけです。

以上の事からも、

「運用時に利用するセッションのモードで最初から開発を行うべき。」

だという事が言えます。

今更感のあるエントリですが、あらためて解説してみました。

FacebookTwitterHatenaTumblr共有

レビューの重要性

たいてい、プロジェクトの計画をする時に、責任者にコードレビューの重要性を説くのだが、真っ先に、その工程を省いてくれるので自分の作業時間を削って、抜き打ちで勝手コードレビューをする事が多い。

抜き打ちでルール違反を発見したら、修正をお願いし、ルールを遵守する重要性を説く。

しかし、保守フェーズに入って、抜き打ちでレビューできなかったコードを見ると、ルール違反している箇所が案外ある。

もちろん、守ってくれる人もいるし、割合的にはこちらの方が多い。

しかし、守ってくれない人も少なからずいるのだ。

そういう人は、ルールを守ることも仕事の一環であるという意識が薄いため、

「自分がいる間に、見つからなければいい。」

という動機で何度諭してもルール違反をする。

以上のことからも、ちゃんとコードレビューを行う事が大切であることがわかる。

いなくなってから、違反している箇所を見つけても遅いのである。

この話は、コードレビューだけではなく、設計レビューやその他のレビューにおいても言える事である。

レビューという一見何も生産していないように見える工程を省くことが多いが、「何も生産していない」というのは大きな間違いである。

#まぁ、昔から言われている当たり前のことを、こうやってエントリする必要性があるかと言われると微妙なのだけれども。

お前らはウェブサーバか!

よく、若手に対して(私もまだまだ若いつもりですが)

「質問があったら固まってないで聞いてね。メールでいいから。

 余裕のある時に返事するから。」

と言う事があります。

でも、なかなか質問してくれません。

ちょっと気になるので、後で

「どう?」

って聞くと案の定、質問したい事がたくさんあるようです。

そこで、いろいろと回答をするついでに、

「質問があったら固まってないで聞いてね。メールでいいから。

 余裕のある時に返事するから。」

と言うのですが、相変わらず質問してくれないので、ポーリングします。

じゃないと、後で痛い目を見るのは私なので。

もう「お前らはウェブサーバか!」と突っ込みたくなります。

もちろん、わからないところは、自分で調べてから質問するべきですが、その上で、質問に来ないのは仕事をサボっているのと同義である。

よくある言い訳として、

「忙しそうだから。」

とか、

「申し訳なくて。」

とか聞きますが、

溜めるだけ溜めてからの方が、よほど困るのである。

世の中をそんなに悲観しなくてもいいよ

最近・・・というか、昔からそうなのだけれど、ニュースで様々な事件に触れる事ができる。

その過程で、「世の中はそんな事ばかりおきているのか。」と感じる事があるだろう。

でも、ちょっと待った。大事な事を忘れている。

何も起きていない平穏な日々は報道されない。

という事だ。

そして、昔は平穏な日々の一幕とされていたちょっとした出来事もネガティブなニュースとして報道されるようになったと言うことだ。

ちょっと自分に置き換えて考えてみればわかるだろう。

自分が買った商品に買っただけの価値があったとき、何事もなく使えたとき、あなたは積極的に人に情報を公開するか?

たいてい情報を公開する時は、何らかの不都合、不具合があったときが多いのでは?

だから、報道に惑わされて世の中を悲観しなくてもいい。

プロジェクトを推進する上で「みんなで決めましょう」は悪

プロジェクトを推進する上で、いろんな事を決めなくてはいけないが、その際

「みんなで決める。」

なんてことはしてはいけない。

違ったベクトルを持った人間同士が集うプロジェクトにおいて、そんなことをしていたらいつまでたっても決まらないし、決まっても指針のはっきりしないものになる。

とはいえ、意見を聞くなと言っているわけではない。

意見を聞かないような船頭もまた悪である。

意見は大いに聞けば良い。

ただ、最終的に船頭たる人間が信じる方針や指針に照らし合わせ、そして意見を吟味して決定する必要があるという事である。

もちろん、その際、船頭が自分自身の判断を信用できないようではいけない。

船頭はぶれないで、自信を持って決定し続ける必要があるのだ。

船頭にとって、プロジェクトの運営とは大小さまざまな決定の連続なのだ。

ぜひ、船頭たる人間には自信を持ってプロジェクトを遂行してもらいたい。

マイクロソフトからキーボードが届いた

とりあえず、交換してもらえることになってよかった。

NAL-6295の舌先三寸 – マイクロソフトのハードウェアサポート後日談

キーボードが届いた。

先に、新しいキーボードが届いてから、故障したキーボードを送れば良い点は評価できる。

全体的に、サポートに繋がってからの対応は迅速だったが、サポートのワークフローやサポート情報の告知の仕方、そこにたどり着くまでのインタフェースに問題がある。

いずれ、解消されるだろう。

拡張メソッドを使ってみる

拡張メソッドを使ってみたかったので、ちょっと遊んでみます。

以前、LINQを利用して、読みやすいコードを書くサンプルとして以下のようなコードを提示していました。

class 在庫計
{
public int 数量計 { get; set; }
public int 金額計 { get; set; }
}
public class サンプル
{
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, bool> 集計対象 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
var 在庫計情報 = 集計(在庫リスト,集計対象);
System.Windows.Forms.MessageBox.Show(string.Format("数量計 = {0},金額計 = {1} ", 在庫計情報.数量計, 在庫計情報.金額計));
}
private 在庫計 集計(在庫[] 在庫リスト,Func<在庫,bool> 集計対象)
{
在庫計 在庫計情報 = new 在庫計();
在庫計情報.数量計 = 在庫リスト
.Where(集計対象)
.Sum(x => x.数量);
在庫計情報.金額計 = 在庫リスト
.Where(集計対象)
.Sum(x => x.金額);
return 在庫計情報;
}
}

集計メソッドで在庫リストにある数量と金額の合計を作成するわけですが、集計メソッドに在庫リストと抽出条件を引数で渡しているだけなので、特に在庫リストに結びついた処理であるといった情報はありません。

そこで拡張メソッドを利用したサンプルを書いてみます。

class 在庫計
{
public int 数量計 { get; set; }
public int 金額計 { get; set; }
}
class サンプル
{
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, Boolean> 抽出条件 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
var 在庫計情報 = 在庫リスト.集計(抽出条件);
System.Windows.Forms.MessageBox.Show(string.Format("数量計 = {0},金額計 = {1} ", 在庫計情報.数量計, 在庫計情報.金額計));
}
}
static class 在庫処理拡張サンプル
{
public static 在庫計 集計(this 在庫[] 在庫リスト, Func<在庫, Boolean> 抽出条件)
{
在庫計 在庫計情報 = new 在庫計();
var result = from 在庫情報 in 在庫リスト
where 抽出条件(在庫情報)
select 在庫情報;
在庫計情報.数量計 = result.Sum(x => x.数量);
在庫計情報.金額計 = result.Sum(x => x.金額);
return 在庫計情報;
}
}

この例の場合、在庫リスト.集計とコーディングできるため、「在庫リストの集計」と読むことができて、より文脈に沿ったコードを書くことができるようになりました。

まぁ、在庫リストなんて在庫の配列があるかといえば、だいたいにおいて在庫テーブルにアクセスするクラスがあって、そこに集計メソッドがあれば済む話なので拡張メソッドを利用する事は無いし、あまり多用するものでもないのですが、今回の例みたいに

「特定の配列に処理を追加したい。」

なんてときには便利かもしれません。

LINQのGroupJoinを使ってみる

LINQに普通のJoinのほかにGroupJoinという機能がありますので、ちょっと使って遊んでみました。

通常のJoinの場合

var 商品リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター" },
new { 商品番号 = "B", 商品名 = "安全ハサミ" },
new { 商品番号 = "C", 商品名 = "ラジオペンチ"}};
var 在庫リスト = new[] { new { 商品番号 = "A", 数量 = 30 ,LotNo = 1} ,
new { 商品番号 = "A", 数量 = 40 ,LotNo = 2} ,
new { 商品番号 = "A", 数量 = 50 ,LotNo = 3} ,
new { 商品番号 = "A", 数量 = 60 ,LotNo = 4} ,
new { 商品番号 = "A", 数量 = 70 ,LotNo = 5} ,
new { 商品番号 = "A", 数量 = 90 ,LotNo = 6} ,
new { 商品番号 = "A", 数量 = 80 ,LotNo = 7} ,
new {商品番号 = "C",数量 = 69,LotNo = 8}};
var 商品別LOTNO別リスト = from 商品 in 商品リスト
join 在庫 in 在庫リスト
on 商品.商品番号 equals 在庫.商品番号
select new
{
商品番号 = 商品.商品番号,
商品名 = 商品.商品名,
LOTNO = 在庫.LOTNO,
数量 = 在庫.数量
};

というように二つの匿名型の配列を商品番号で結合したときの結果は

var 商品別LOTNO別リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 30 ,LotNo = 1} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 40 ,LotNo = 2} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 50 ,LotNo = 3} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 60 ,LotNo = 4} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 70 ,LotNo = 5} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 90 ,LotNo = 6} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 80 ,LotNo = 7} ,
new {商品番号 = "C",, 商品名 = "ラジオペンチ", 数量 = 69,LotNo = 8}};

というように、SQLでINNER JOINをする時と同じような結果になります。

つまり、商品リストの商品番号に対して在庫リスト中に同じ商品番号があれば、それだけオブジェクトが作られます。

もちろん、対象の商品番号が無かったオブジェクトは作られません。

大して、

var 商品リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター" },
new { 商品番号 = "B", 商品名 = "安全ハサミ" },
new { 商品番号 = "C", 商品名 = "ラジオペンチ"}};
var 在庫リスト = new[] { new { 商品番号 = "A", 数量 = 30 ,LotNo = 1} ,
new { 商品番号 = "A", 数量 = 40 ,LotNo = 2} ,
new { 商品番号 = "A", 数量 = 50 ,LotNo = 3} ,
new { 商品番号 = "A", 数量 = 60 ,LotNo = 4} ,
new { 商品番号 = "A", 数量 = 70 ,LotNo = 5} ,
new { 商品番号 = "A", 数量 = 90 ,LotNo = 6} ,
new { 商品番号 = "A", 数量 = 80 ,LotNo = 7} ,
new {商品番号 = "C",数量 = 69,LotNo = 8}};
var 商品別在庫リスト = from 商品 in 商品リスト
join 在庫 in 在庫リスト
on 商品.商品番号 equals 在庫.商品番号 into 商品別在庫
select new
{   商品番号 = 商品.商品番号,
商品名 = 商品.商品名,
数量 = 商品別在庫.Sum(x => x.数量)
};
foreach (var 商品別在庫 in 商品別在庫リスト)
{
Console.WriteLine(string.Format("商品番号:{0} 商品名:{1} 在庫数量:{2}",
商品別在庫.商品番号,
商品別在庫.商品名,
商品別在庫.数量));
}

Join … Intoという構文で上記のようにGroupJoinをすると、

var 商品別在庫リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター" ,数量 = 420},
new { 商品番号 = "B", 商品名 = "安全ハサミ" ,数量 = 0},
new { 商品番号 = "C", 商品名 = "ラジオペンチ",数量 = 69}};

というように、商品リストの商品番号と同じ商品番号の在庫は集約されて結合されますので、商品番号単位に在庫リストが作成されることになり、商品番号別の数量の合計を出力する事が可能です。

また、在庫リストに対象の商品番号が無いオブジェクトも作られます。

諦める前に後悔したほうが良い

「後悔先に立たず」

という言葉があるように、後悔したところですでに過ぎてしまったものを無かったことにはできないが、だからといってすぐに諦める必要は無い。

ここは一旦、後悔しておくのだ。

後悔するということは、自分の過去の行動を否定することである。

これが何を意味するのかというと、「改善するためのタイミング」になるということだ。

自分の過去の行動を否定しないでただ諦めてしまうだけであれば、その後、何も産み出すことは無いが、後悔することで「改善するためのタイミング」を作れば、その後の自分にとって何らかのプラスになるはずである。

たとえば、

「学生時代、もっと勉強しておけばよかった。」

と後悔することで、「勉強が足りなかった。」と自分の過去を否定し、「今からでも勉強したら良いのでは?」というタイミングができる。

もし、

「学生時代、勉強してなかったんだから仕方ないよね。」

と諦めてしまうと、そこで何も産まないで終わってしまうのである。

以上の理由で、諦めてしまう前に一度後悔する事を、お勧めします。

まぁ、適用できない後悔もたくさんありますが・・・。