2013年8月30日金曜日

なつがおわりまーす

とか言いつつ今日は暑いです。

えー、8月も間もなく終わり、前回のエントリからほぼ3週間ですか。思いの外サボってしまいました…。

そんな時期もありますよね(白目)。ボチボチいきます…。

2013年8月10日土曜日

古くなったログファイルを削除する処理をC#+LINQで。

何かしら業務で利用するシステムであれば、大抵何かあったときの障害解析などに備え、ログファイルが作られることになります。で、そのログファイルは作られっぱなしだとそのうちディスクを圧迫するので、ある一定のルールの下で削除されていくことになるのが一般的だと思います。

その「削除されるルール」は、当然すべてきれいさっぱり消してしまっては、いざという時に役に立たないし、またシステムによってログの残し方の要件が違ったりで、設計や実装も結構厄介なもんです。

ここでは、いくつかログの残し方ルールをあげて、古いログファイルを削除する処理をC#+LINQを使って書いてみようと思います。

ログファイルの構成前提


前提として、ログファイルは以下のような形で残されていることとします。

  • ログフォルダにはログファイルのみが存在する。
  • ログファイルのファイル名は「log_yyyy-MM-dd_nn.log」とする。ただし、yyyyは西暦の4ケタ。MMは月の2ケタ。ddは日の2ケタ。nnはシーケンシャル番号の2ケタ。
  • ログファイルは以下のタイミングで新たに生成される。ただし、同一日のログファイルが存在しない場合はシーケンシャル番号は「0」。存在する場合はシーケンシャル番号の最大+1になるようにする。
    • 日が変わった以降最初のログ出力時。
    • 現在のログファイルが所定のサイズ以上になった時。
    • サービスの起動時。
そしてログファイル削除クラスとしてスタティックな「DeleteOldLogFiles」クラスを作ることとします。

バリエーション1:指定ファイル数のみ残して古いものを削除


指定された数のより新しいファイルを残し、それ以外のものを削除する仕様として処理を考えます。この場合、ファイル名を降順でソートして、新しいほうから所定の数は何もせず、それ以降はファイルを削除する。という処理を考えてみました。

ForEachメソッドを使いたいので、IEnumerableを一旦Listに変換しています。ま、しょうがない。

public static void ByCounts(string path, int keepCount)
{
  Directory.GetFiles(path)
    .OrderByDescending(f => f)
    .Skip(keepCount)
    .ToList()
    .ForEach(f => File.Delete(f));
}

ま、シンプルですよね。

バリエーション2:指定ファイルサイズに収まるようにして古いものを削除


バリエーション1の変形ですが、新しいほうからファイルサイズを足して行って、所定のサイズ以下のうちはスキップし、所定のサイズを超えた以降のファイルを削除する処理とするとシンプルに書けますね。

public static void BySize(string path, long size)
{
  long totalSize = 0;

  Directory.GetFiles(path)
    .OrderByDescending(f => f)
    .SkipWhile(f => (totalSize += new FileInfo(f).Length) < size)
    .ToList()
    .ForEach(f => File.Delete(f));
}


バリエーション3:日付が古いものを削除


ファイル名から日付部分を抜き出して、指定日数より以前のものは削除する仕様とします。このメソッドの引数「path」と「days」はまあいいとして、「startPos」はファイル名中の日付文字列の開始位置を、「dateForm」は日付文字列の解析フォームを渡します。

public static void ByDays(string path, int days, int startPos, string dateForm)
{
  var target = DateTime.Today.AddDays(-days);

  Directory.GetFiles(path)
    .Where(f => DateTime.ParseExact(
      Path.GetFileName(f).Substring(startPos, dateForm.Length),
      dateForm,
      System.Globalization.DateTimeFormatInfo.InvariantInfo) < target)
    .ToList()
    .ForEach(f => File.Delete(f));
}

今回のケースでログファイルの保持日数を3日とすると、ログファイル名は「log_yyyy-MM-dd_nn.log」なので、「startPos」は4、「dateForm」は「yyyy-MM-dd」となるので、以下のようにこのメソッドを呼び出すことになります。

DeleteOldLogFiles.ByDays(logFolderPath, 3, 4, "yyyy-MM-dd");


2013年8月1日木曜日

今週のマガジン

『あひるの空』が凄かった。なんだこれ?数週間のモヤモヤを一気に晴らす10ページ。一瞬何が起きたのかわからないくらいの一気。いやー。スゲー。
しかし、「どこに負けるか」をバラしておいて、「敗戦までのキセキ」を描くんですか。是非描ききってほしいです。

『ベイビーステップ』も暗い話にならなくて良かった。とはいえバッドエンドの地雷は埋まったままなので、ここからどう展開するのか楽しみ。

2013年7月27日土曜日

今ハズセナイ連載中漫画 (2013年夏版)

今楽しみにしている、連載中漫画リスト。春からいくつかの作品が完結しましたね。ってことで最新版に更新。
  • 週刊少年マガジン
    • あひるの空/日向武史
    • ベイビーステップ/勝木光
    • アゲイン!!/久保ミツロウ
    • 七つの大罪/鈴木央
    • 波打際のむろみさん/名島啓二
    • 我妻さんは俺のヨメ/蔵石ユウ・西木田景志
  • 週刊少年サンデー
    • 神のみぞ知るセカイ/若木民喜
    • マギ/大高忍
    • 銀の匙 Silver Spoon/荒川弘
    • 絶対可憐チルドレン/椎名高志
    • ハヤテのごとく!/畑健二郎
    • 最後は?ストレート!!/寒川一之
  • ヤングマガジン
    • センゴク一統記/宮下英樹
    • 雪にツバサ/高橋しん
    • エイト/楠みちはる
    • 8♀1♂/咲香里
  • ビックコミックスピリッツ
    • アイアムアヒーロー/花沢健吾
    • とめはねっ!/河合克敏
    • くーねるまるた/高尾じんぐ
    • あさひなぐ/こざき亜衣
    • 王様達のヴァイキング/さだやす
  • ビックコミックスペリオール
    • 人生画力対決/西原理恵子
  • モーニング
    • GIANT KILLING/ツジトモ・網本将也
    • 宇宙兄弟/小山宙哉
    • グラゼニ/森高夕次・アダチ ケイジ
    • ひらけ駒!/南Q太
    • 鬼灯の冷徹/江口夏美
    • ピアノの森/一色まこと
    • ライスショルダー/なかいま強
  • イブニング
    • 少女ファイト/日本橋ヨヲコ
    • いとしのムーコ/みずしな孝之
    • ADAMAS/皆川亮二
    • プロチチ/逢坂えみ子
    • Eから弾きな。/佐々木拓丸
  • アフタヌーン
    • ヒストリエ/岩明均
    • ヴィンランド・サガ/幸村誠
  • ゲッサン
    • MIX/あだち充
    • アオイホノオ/島本和彦
  • グランドジャンプ
    • 瞬きのソーニャ/弓月光
    • ぼくの体はツーアウト/よしたに
  • その他
    • もやしもん/石川雅之
    • 3月のライオン/羽海野チカ
    • ましろのおと/羅川真里茂
    • 白衣のカノジョ/日坂水柯
    • kiss×sis/ぢたま某
    • よつばと!/あずまきよひこ
    • WxY/マドカマチコ
完結してリストから削除されたのは、多分この4つ。
  • ジャポニカの歩き方/西山優里子
  • アントルメティエ/早川光・きたがわ翔
  • こどものじかん/私屋カヲル
  • 純潔のマリア/石川雅之
「ジャポニカの歩き方」は、以前のエントリで最終回についてちょっと書きましたね。「純潔のマリア」は終わったらしいんですが、単行本待ちです。2巻の終わりから後1冊でどうラストに持っていくのか楽しみ。

「こどものじかん」も小学校を卒業して終わりましたね。ただ、あのラストはいーのかしら?こうこうせいだよね…。しかも見た目変わらないってのがどうもなー。ともあれ、周りにお勧めできるかどうかはともかく、楽しく読めて考えることも多かったです。イイ漫画でした。

あとは「アントルメティエ」。これはなー。ちょっと残念なラストでした。面白かったと思うんだけどなー。


今回はこんな感じです。その他、リストに挙げてないけど楽しみにしているのも多いですし、最近始まったばかりで期待しているのもいっぱいあります。なんかしら、大化けして楽しみに育ってくれるモノが出てきてくれるとさらにうれしいなー。

FxCop カスタムルールの作成(配置と実行)

FxCop カスタムルールの作成(ビルドまで)」で作成したルールを実際に動かします。

本当はデバッガで動作を追っかけたいところですが、VS2012Expressではデバッガで外部ツール(プログラム)を実行させるのは難しいんですかね。残念ですがデバッガの使用はあきらめます。

ちなみに、Express以外の有償のエディションであれば、プロジェクトのプロパティで「デバッグ」を開き、「開始動作」を「外部プログラムの開始」に、「外部プログラムの開始」に「FxCopCmd.exe」へのパスを、「コマンドライン引数」に「/f:」に続いて静的分析したいアセンブリのフルパスを、さらに「/c」のスイッチを付けてコンソールに結果を出力させるようにしておくことで、作成したルールをデバッグできる「はず」です。

デバッグ時の注意点としては、VisualStudioもデバッグしたいルールアセンブリを読み込んでしまうので、修正して再配置するときは、VisualStudioを一度終了させる必要があります。面倒ですが仕方ない。

さて、ビルドしたルールアセンブリは、Program FilesのVSのフォルダの、「Team Tools\Static Analysis Tools\FxCop\Rules」のフォルダにコピーします。デバッグする場合は"pdb"ファイルも一緒にコピーします。

作成したルールアセンブリが、正しく動作するかどうかを確認するテスト用アセンブリも作っておきます。VS2012Expressで新規プロジェクトを作成し、クラスライブラリを作ります。名前は「DataTableRuleDummy」とでもします。

テスト用アセンブリには一つクラスを作ります。ルールがすべて引っかかるように、以下のようにしてみました。

public class Dummy
{
  private DataTable table;

  public Dummy(DataTable table)
  {
    this.table = table;
  }

  public int SumOfQuantity
  {
    get
    {
      int sum = 0;
      foreach (DataRow row in table.Rows)
      {
        sum += (int)row["qty"];
      }
      return sum;
    }
  }

  public int FirstOrder
  {
    get { return (int)table.Rows[0]["order"]; }
  }

  public void SetDone()
  {
    foreach (var row in table.Rows.Cast<DataRow>().Where(r => (int)r["f"] == 0))
    {
      row["f"] = 1;
      row["doneAt"] = DateTime.Now;
    }
  }
}

で、テスト用のアセンブリもビルドしておきます。

実行はここではFxCopのコマンド版で試してみます。GUI版やVisualStudioビルド統合版も多分動くはず。

コマンドプロンプトを起動し、以下のように入力します。(もちろん、実際は改行なしです。)

>"C:\Program Files\Microsoft Visual Studio 11.0\Team Tools\
Static Analysis Tools\FxCop\FxCopCmd.exe" /f:D:\Projects\Da
taTableRuleDummy\bin\Debug\DataTableRuleDummy.dll /c

これで実行してみると以下の結果となりました。



4つのルールがすべて出力されています。続いて、テスト用アセンブリを以下のように修正してビルド。その後もう一度FxCopを実行させてみます。

public class Dummy
{
  private DataTable table;

  public Dummy(DataTable table)
  {
    this.table = table;
  }

  public int SumOfQuantity
  {
    get { return table.AsEnumerable().Sum(r => r.Field<int>("qty")); }
  }

  public int FirstOrder
  {
    get { return table.AsEnumerable().First().Field<int>("order"); }
  }

  public void SetDone()
  {
    foreach (var row in table.Select("flag=0"))
    {
      row.SetField("flag", 1);
      row.SetField("doneAt", DateTime.Now);
    }
  }
}


DataTable関連の警告が出なくなりましたね。

と、とてもとても時間がかかりましたが、古式ゆかしきDataSetも、工夫次第でそれなりに使えるということを言いたかったのです。

あと、会社やプロジェクト単位でコーディングスタンダードを用意している場合、コードレビューでスタンダードに適合しているかをチェックするなんてのはやってらんないので、このようにFxCopのカスタムルールを一度作ってしまえば、スタンダードに適合しているか否かのチェックをFxCopに任せられるので、あとがずいぶん楽になります。このために参考になる方もいるのではないかなぁ。と思いました。

2013年7月26日金曜日

FxCop カスタムルールの作成(ビルドまで)

ADO.netのDataSetをLINQableに書くために (本題) ~ FxCopカスタムルールを作成する。」の続きです。今度こそカスタムルールを作ります。ただ、手元にVS2012Expressしかないので、これで作ります。

前エントリで書いたとおり、アセンブリを静的分析し、以下のパターンに適合する場合は警告を出すように作ります。

イマイチ好みじゃないずいぶんマシ
foeach構文でDataTable.Rowsプロパティを使う。DataTable.AsEnumerable拡張メソッドまたはDataTable.Selectメソッドを使う。
LINQを使うために、DataTable.RowsプロパティにEnumerable.Cast<DataRow>拡張メソッドを使う。DataTable.AsEnumerable拡張メソッドまたはDataTable.Selectメソッドを使う。
DataRowCollectionクラスのインデクサを使う。DataTable.AsEnumerable拡張メソッドまたはDataTable.Selectメソッドを使う。(インデクサは使わない。)
DataRowクラスのインデクサを使う。DataRow.Field拡張メソッドDataRow.SetField拡張メソッドを使う。

VS2012Express(今回はDesktopを使いました。)を開き、新規プロジェクトを作ります。「クラスライブラリ」を選択し、プロジェクト名は「DataTableRules」とでもしておきましょう。


作成したプロジェクトに、FxCopのアセンブリを参照するようにします。プロジェクトの「参照設定」を右クリックし、「参照の追加」を選択します。

プロジェクト外の、GACにもない外部アセンブリを参照するので、「参照」ボタンをクリックします。
Program FilesのVSのフォルダの、「Team Tools\Static Analysis Tools\FxCop」にある2つのアセンブリファイル、「FxCopSdk.dll」と「Microsoft.Cci.dll」を選択して追加します。


作成したプロジェクトには、警告メッセージなどを管理するためのXMLリソースが必要になります。
ここでは「CustomRules.xml」というファイルをプロジェクトに追加します。プロジェクトを右クリックして「新規作成」-「追加」-「新しい項目」を選び、XMLファイルをプロジェクトに追加します。


  追加したXMLファイルはリソースとして扱います。ソリューションエクスプローラーで追加したXMLファイルを選び、プロパティで「ビルドアクション」を「埋め込まれたリソース」に変更します。


XMLの中身の編集は後でやります。

さて、前述の警告出力パターンですが、どうすれば検知できるかを考えてみます。

まず、「foeach構文でDataTable.Rowsプロパティを使う。」ですが、これは「DataTable.Rows.GetEnumerable」メソッドが使われたときに警告を出す。と考えればよさそう。

「DataTable.RowsプロパティにEnumerable.Cast<DataRow>拡張メソッドを使う。」は、そのまんま「Enumerable.Cast<DataRow>」静的メソッドが使われたときに警告を出すでオケ。

「DataRowCollectionクラスのインデクサを使う。」はインデクサの呼び出しですが、CLI的には「DataRowCollection.get_Item」メソッドが使われた時。という扱いになるようです。

「DataRowクラスのインデクサを使う。」も同様に、「DataRow.get_Item」または「DataRow.set_Item」メソッドが使われた時。となるようです。

これら4つのルールすべてにおいて、メソッドの呼び出しを確認することで検知可能ということになります。そこで、まずは基本となる抽象クラスを作ります。

プロジェクト作成時に作られるプレースホルダのクラス「Class1.cs」を使いましょう。まずはファイル名を「BaseDataTableRule.cs」に修正、クラス名もそれに合わせて変更します。内容は「すべてのメソッドを確認する」的な内容になっている…と思われます。なにせドキュメントが少なく、いろいろ試してうまくいっているだけなので、定かじゃないところも残ってます。

using Microsoft.FxCop.Sdk;

namespace DataTableRules
{
  public abstract class BaseDataTableRule : BaseIntrospectionRule
  {
    protected BaseDataTableRule(string name) : base(
      name, "DataTableRules.CustomRules", typeof(BaseDataTableRule).Assembly) {}

    public override ProblemCollection Check(Member member)
    {
      var m = member as Method;
      if (m != null)
        Visit(m);

      return Problems;
    }
  }
}

抽象クラスを作ったら、1ルールにつき1クラスとなるように、4つのクラスを作り抽象クラスから派生させます。まず「foeach構文でDataTable.Rowsプロパティを使う。」を検出するルール。「NoUseGetEnumeratorMethodToDataTableRows」としました。長いけどこのクラスを誰か人に使ってもらうわけではないので、今回は気にしない。名前がわかりやすいほうが重要。

using System;
using Microsoft.FxCop.Sdk;

namespace DataTableRules
{
  public class NoUseGetEnumeratorMethodToDataTableRows : BaseDataTableRule
  {
    public NoUseGetEnumeratorMethodToDataTableRows()
      : base("NoUseGetEnumeratorMethodToDataTableRows") {}

    public override void VisitMethodCall(MethodCall call)
    {
      var binding = call.Callee as MemberBinding;
      var target = binding.TargetObject;
      var member = binding.BoundMember;

      if (member.Name.Name == "GetEnumerator" && 
          target.Type.FullName == "System.Data.DataRowCollection")
      {
        Problems.Add(new Problem(GetResolution()));
      }

      base.VisitMethodCall(call);
    }
  }
}

2つめのクラスは「DataTable.RowsプロパティにEnumerable.Cast<DataRow>拡張メソッドを使う。」これを「NoUseCastMethodToDataTableRows」クラスとして実装します。(18行目と19行目に分けているのは、Web上で表示させるのに横長になりすぎるためで、本来分ける必要はありません。)

using System;
using Microsoft.FxCop.Sdk;

namespace DataTableRules
{
  public class NoUseCastMethodToDataTableRows : BaseDataTableRule
  {
    public NoUseCastMethodToDataTableRows()
      : base("NoUseCastMethodToDataTableRows") {}

    public override void VisitMethodCall(MethodCall call)
    {
      var binding = call.Callee as MemberBinding;
      var member = binding.BoundMember;

      if (member.FullName == 
        "System.Linq.Enumerable.Cast<System.Data.DataRow>"
        + "(System.Collections.IEnumerable)")
      {
        Problems.Add(new Problem(GetResolution()));
      }

      base.VisitMethodCall(call);
    }
  }
}

そして3つめのクラスは「DataRowCollectionクラスのインデクサを使う。」を「NoUseIndexerToDataTableRows」クラスとして実装します。

using System;
using Microsoft.FxCop.Sdk;

namespace DataTableRules
{
  public class NoUseIndexerToDataTableRows : BaseDataTableRule
  {
    public NoUseIndexerToDataTableRows()
      : base("NoUseIndexerToDataTableRows") {}

    public override void VisitMethodCall(MethodCall call)
    {
      var binding = call.Callee as MemberBinding;
      var member = binding.BoundMember;

      if (member.FullName.StartsWith(
         "System.Data.DataRowCollection.get_Item(", StringComparison.Ordinal))
      {
        Problems.Add(new Problem(GetResolution()));
      }

      base.VisitMethodCall(call);
    }
  }
}

そして4つめ。「DataRowクラスのインデクサを使う。」を「NoUseIndexerToDataRow」として実装します。

using System;
using Microsoft.FxCop.Sdk;

namespace DataTableRules
{
  public class NoUseIndexerToDataRow : BaseDataTableRule
  {
    public NoUseIndexerToDataRow() : base("NoUseIndexerToDataRow") {}

    public override void VisitMethodCall(MethodCall call)
    {
      var binding = call.Callee as MemberBinding;
      var member = binding.BoundMember;

      if (member.FullName.StartsWith(
            "System.Data.DataRow.get_Item(", StringComparison.Ordinal) ||
          member.FullName.StartsWith(
            "System.Data.DataRow.set_Item(", StringComparison.Ordinal))
      {
        Problems.Add(new Problem(GetResolution()));
      }

      base.VisitMethodCall(call);
    }
  }
}

あとはXMLリソースの中身の編集です。長いですが、こんな感じ。(適宜改行してます。)
<?xml version="1.0" encoding="utf-8" ?>
<Rules FriendlyName="データテーブルの使い方の規則">
<Rule TypeName="NoUseCastMethodToDataTableRows"
      Category="My.DataTable" CheckId="DT0001">
<Name>DataTable.RowsにCast&lt;DataRow&gt;()拡張メソッドを使用しません</Name>
<Description>可読性の向上のため、DataTable.RowsにCast&lt;DataRow&gt;拡張メソッド
を使用しません。</Description>
<Url />
<Resolution>DataTable.RowsにCast&lt;DataRow&gt;拡張メソッドが使用されています。
さらなる条件の絞り込みや、ソートが必要であればDataTable.Select()メソッドを、
そうでなければDataTable.AsEnumerable()拡張メソッドを使用してください。
</Resolution>
<Email />
<MessageLevel Certainty="80">Warning</MessageLevel>
<FixCategories>NonBreaking</FixCategories>
<Owner />
</Rule>
<Rule TypeName="NoUseGetEnumeratorMethodToDataTableRows"
      Category="My.DataTable" CheckId="DT0002">
<Name>foreach構文でDataTable.Rowsを使用した列挙を行いません</Name>
<Description>LINQとの親和性向上のため、DataTable.Rows.GetEnumerable()メソッドを
使用しません。</Description>
<Url />
<Resolution>DataTable.Rows.GetEnumerable()が使用されています。さらなる条件の
絞り込みや、ソートが必要であればDataTable.Select()メソッドを、そうでなければ
DataTable.AsEnumerable()拡張メソッドを使用してください。</Resolution>
<Email />
<MessageLevel Certainty="80">Warning</MessageLevel>
<FixCategories>NonBreaking</FixCategories>
<Owner />
</Rule>
<Rule TypeName="NoUseIndexerToDataTableRows"
      Category="My.DataTable" CheckId="DT0003">
<Name>DataTable.Rowsのインデクサを使用しません</Name>
<Description>可読性の向上のため、DataTable.Rowsのインデクサを使用しません。
</Description>
<Url />
<Resolution>DataTable.Rowsのインデクサが使用されています。テーブル内の各行を列挙
する場合には、DataTable.Select()メソッド、またはDataTable.AsEnumerable()拡張
メソッドを使用してforeach構文で列挙してください。 先頭行のみ取り出す場合には、
DataTable.AsEnumerable()拡張メソッドと、Enumerable.First()を使用して一時変数に
代入後、使用してください。
      例:var firstRow = dt.AsEnumerable().First();</Resolution>
<Email />
<MessageLevel Certainty="80">Warning</MessageLevel>
<FixCategories>NonBreaking</FixCategories>
<Owner />
</Rule>
<Rule TypeName="NoUseIndexerToDataRow"
      Category="My.DataTable" CheckId="DT0004">
<Name>DataRowのインデクサを使用しません</Name>
<Description>可読性の向上のため、DataRowのインデクサを使用しません。
</Description>
<Url />
<Resolution>DataRowのインデクサが使用されています。テーブル内の各行のカラムに値
を設定、または取得する場合は、DataRow.SetField&lt;T&gt;()拡張メソッド、および
DataRow.Field&lt;T&gt;()拡張メソッドを使用してください。</Resolution>
<Email />
<MessageLevel Certainty="80">Warning</MessageLevel>
<FixCategories>NonBreaking</FixCategories>
<Owner />
</Rule>
</Rules>

ふぅ。これで準備完了。のはず。後はビルド~配置~デバッグ。です。

が、もう限界。眠い。またまた続きにします。しくしく。いい加減終わらせたかったけどなぁ。

2013年7月18日木曜日

今週のマガジン

『ベイビーステップ』の完全に予想外な展開にびっくり。今後どう展開していくのか…。
あとは『我妻さんは俺のヨメ』がどんどん面白くなってきてます。今後さらに期待したい。

2013年7月12日金曜日

VS2012ExpressでFxCop(コマンドライン版)を実行する。

今僕がいじっているPCには、VisualStudio 2012 Express (VS Express) のWebとDesktopがインストールされていますが、FxCopのコマンドライン版はすでに入っていました。



最初から入っていたかどうかは謎ですが…。深く追求はしないことにして、こいつが動くかどうかを確認しておきます。

コマンドプロンプトを起動し、FxCopCmd.exeを実行します。/f オプションで分析対象のアセンブリをフルパスで、また、結果をコンソールに出力させるため、/c オプションも付けて実行。



あれ?途中までうまくいったと思ったらエラーが出た。「Xslファイルが無ぇ」と来たか。

確かにエラーメッセージを見ると、上のエクスプローラで示したフォルダに、「Xml」フォルダが無いとおかしいのかな?

うーん。そんなもんなのか。でも探せばどっかにあるんじゃないか。と思ったら果たしてありました。このリンク先がいつまであるかも謎ですが、とりあえず貼っておく。

https://trac.openstreetmap.org/browser/applications/utils/Srtm2Osm/trunk/lib/Microsoft%20FxCop%201.36/Xml/VSConsoleOutput.xsl?rev=6526

謎のTracサイトですが、このXslはそれっぽい。リンク先のページの下のほうにある、「Download in other formats / Original Format」のリンクから、「VSConsoleOutput.xsl」ファイルをダウンロードし、FxCopのフォルダに「Xml」サブフォルダを作ってそこにコピー。

その後さっきと同じコマンドを実行した結果。





うん。これでOK。特にSDK+FxCopを別途インストールしなくとも、コマンドラインのFxCopならExpressでも動作させられるようですね。…Xslファイルを一つ謎のサイトから持ってきたことを除けば、まぁ、問題はないと言えるような気がしなくもないかな。



…で、ここまでできるならビルドにFxCopも統合して、FxCopのメッセージをビルド時にワーニングとして出すようにできるんじゃないかと思い、いろいろ試行錯誤してみましたが、今のところできて無いです。MsBuildのフォルダにはFxCop用のターゲットファイルもあったし、.csproj のファイルに<CodeAnalysisTarget>を書けばいけるんじゃないかと思ったんですが、甘かった。

どなたかVS2012Expressで、FxCopをビルドに統合できた方。もしいたら教えていたいただきたいなー。なんて。

2013年7月10日水曜日

今週のイブニング

今週も色々面白かった。中でも出色は「さよならタマちゃん」最終回でした。

この漫画、連載開始当初はまるで期待せずに読んでたんですが、途中から格段に面白くなってきて、ここしばらくはとても楽しみにしていました。絵も好みです。

で、今回の最終回。電車のなかで読んだんですが、感情の起伏が激しくて、泣きそうになるのを我慢しながら読んでました。

しかし、漫画読んで泣くなんて久しぶり。記憶にある限りだと、「赤ちゃんと僕」以来。多分。

イブニングで25回の連載。ということは、ほぼ一年連載してたんですね。お疲れ様でした。

さて。次はどんな漫画を読ませてもらえるのか。楽しみに待ちまーす。

2013年7月8日月曜日

ADO.netのDataSetをLINQableに書くために (本題) ~ FxCopカスタムルールを作成する。

ADO.netのDataSetをLINQableに書くために (前段)」では、『型無しデータセットを使う場合は、コードのメンテナンシビリティを保つためにルールを制定する』こと、そして、『開発にあたってのルールを強制する方法を考えた時、「FxCop」と「StyleCop」の2つのツールが有用』と書きました。

その後いくつかのエントリで、FxCopとStyleCopについて書いてみて、いよいよカスタムルールを作ってみようと思います。

今回はFxCopのカスタムルールを作ります。StyleCopではなくFxCopを選んだのは、いくつかのカスタムルールを生成する方法を説明したサイトを見てみて、こちらのほうが作るに容易そうだ。という点と、FxCopで十分要求する内容が作れそうだ。と考えたことによります。

FxCopのカスタムルールの生成方法について、以下の2つのサイトを参考にさせていただきました。ありがとうございます。


さて、過去いろんなプロジェクトのソースを眺めてきましたが、DataSetを使ったコードで、大変気になるのが以下のようなケース。これらは、DataSetの各種拡張メソッド等を使うことで、だいぶキモチワルサが緩和されると思っています。


イマイチ好みじゃないずいぶんマシ
foeach構文でDataTable.Rowsプロパティを使う。DataTable.AsEnumerable拡張メソッドまたはDataTable.Selectメソッドを使う。
LINQを使うために、DataTable.Rows拡張プロパティにEnumerable.Cast<DataRow>拡張メソッドを使う。DataTable.AsEnumerable拡張メソッドまたはDataTable.Selectメソッドを使う。
DataRowCollectionクラスのインデクサを使う。DataTable.AsEnumerable拡張メソッドまたはDataTable.Selectメソッドを使う。(インデクサは使わない。)
DataRowクラスのインデクサを使う。DataRow.Field拡張メソッドDataRow.SetField拡張メソッドを使う。

たとえば、よくあるこんなコード(イマイチな例その1)。
foreach (DataRow row in table.Rows)
{
  sum += (int)row["order"];
  row["flag"] = 0;
  row["doneAt"] = DateTime.Now;
}

これをこう変えることで、後にリファクタリングするときにLINQを利用しやすくしたり、あるいはコンパイラによる型のチェックをフォローしてもらえるようになるメリットがあります。
foreach (var row in table.AsEnumerable())
{
  sum += row.Field<int>("order");
  row.SetField("flag", 0);
  row.SetField("doneAt", DateTime.Now);
}

あるいはよく見かけるのは、SELECTで1行のみにヒットするようにしたときのコード(イマイチな例その2)。
data.order = (int)table.Rows[0]["order"];
data.name = (string)table.Rows[0]["name"];
data.flag = (int)table.Rows[0]["flag"];

これはこう書くとまだすっきりします。
var firstRow = table.AsEnumerable().First();

data.order = firstRow.Field<int>("order");
data.name = firstRow.Field<string>("name");
data.flag = firstRow.Field<int>("flag");

また、DataTable.Rowsプロパティ(DataRowCollectionクラス)は、IEnumerableは実装しますが、IEnumerable<DataRow>を実装しないので、そのままではLINQの各種メソッドを使えません。

Enumerable.Cast<DataRow>を使ってLINQの各種メソッドに繋ぐこともできますが、専用の拡張メソッドAsEnumerableを使ったほうがわかりやすいと思います。あるいは、さらにWhereやOrderByで絞り込みや並べ替えが必要なら、DataTable.Selectメソッドのほうが高速なケースが多いです。(Selectメソッドの名前が気に入らないですが、仕方ないと思ってます。)

というわけで、上にあげた4種類の「イマイチ好みじゃない」コードを見つけた時に、コンパイル時点で警告を出すFxCopのカスタムルールを作ろうと思います。

えーと、続きます。

2013年7月6日土曜日

今週のモーニング

「カレチ」が最終回でした。どう考えてもグッドエンドにはなりそうにない展開でしたが、なんとか鬱にならずに済みましたかね。

面白かったです。お疲れ様でした。

さて、次はどんな漫画を読ませてもらえるのかが気になりますね。

2013年6月23日日曜日

テキストファイルを行番号付きでコンソールに出力する処理をLINQで。

タイトルの通り、テキストファイルを読み取ってコンソールに出力。その時に行番号を先頭に表示させる処理を考えてみます。

SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。」と同様に、TextReader/TextWriterをLINQで使いやすくする拡張メソッドを用意しておきます。
public static IEnumerable<string> GetLineEnumerator(this TextReader tr)
{
  string s;
  while ((s = tr.ReadLine()) != null)
  {
    yield return s;
  }
}

public static void WriteLines(
          this TextWriter tw, IEnumerable<string> values)
{
  foreach (var line in values)
  {
    tw.WriteLine(line);
  }
}

で、出力時に行番号を付ける方法を考えます。LINQっぽく考えれば、行番号は1から順番の数字が必要となるので、Enumerable.Rangeが使えそう。そして、行番号と入力した各ラインをそれぞれ順次送り出せればよいので、Enumerable.Zipで纏めるのがよさそう。

ってことで、前述の2つの拡張メソッドも使い、こんなコードになりました。
static void Main(string[] args)
{
  using(var reader = new StreamReader(args[0]))
  {
    Console.Out.WriteLines(Enumerable.Range(1, int.MaxValue)
      .Zip(reader.GetLineEnumerator(), Tuple.Create)
      .Select(x => string.Format("{0}:{1}", x.Item1, x.Item2)));
  }
}

SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。

もともとは「ラムダ式を利用したリファクタリングの例 その2」で扱ったネタです。

こんな要求に対する処理を書いていました。
  • Shift-JISのCSVファイルを入力し、UTF-8のCSVファイルを出力する。
  • 入力したCSVの各カラムは、固定長で前後に空白が入る可能性があり、その空白は除去して出力する。
  • 各行の先頭のカラムはIDになっていて、特定のIDのみ出力対象とする。
以前書いた記事を見直してみて、String.Joinを使うことでよりシンプルなコードにできることに気がつきました。だいぶ今さらですが。

まずはやっぱり拡張メソッドを、でも今回は2つだけ用意して使います。

1つめは、TextReaderクラスから各行ごとの読み込みを列挙可能なものにする拡張メソッド。
public static IEnumerable<string> GetLineEnumerator(this TextReader tr)
{
  string s;
  while ((s = tr.ReadLine()) != null)
  {
    yield return s;
  }
}

2つめは、TextWriterクラスに列挙可能な文字列配列等を渡して、各行ごとに出力する拡張メソッド。
public static void WriteLines(
          this TextWriter tw, IEnumerable<string> values)
{
  foreach (var line in values)
  {
    tw.WriteLine(line);
  }
}

そのうえで、inFileが入力ファイルのパス、outFileが出力ファイルのパスとして、絞り込みの条件を「先頭カラムが奇数」とすると、以下のコードで要求が満たせますね。
using (var sr = new StreamReader(inFile, Encoding.GetEncoding("shift-jis")))
using (var sw = new StreamWriter(outFile))
{
  sw.WriteLines(sr.GetLineEnumerator()
    .Select(line => line.Split(','))
    .Where(items => int.Parse(items[0]) % 2 != 0)
    .Select(items => string.Join(",", items.Select(item => item.Trim()))));
}

2013年6月22日土曜日

個人的に無効にしたいFxCopとStyleCopのルール

多人数で開発する場合に特に有効と考えられるFxCopとStyleCopですが、個人的にはどうしても無効にしたいルールがいくつかあります。そこら辺を簡単に。

FxCop


FxCopでは、受け入れがたいものはあまり多くありません。でもプロジェクトの内容によりますが、以下は無効にすることが多いです。
Globalizationの2つについては、いずれも文字列の操作時なんかに余計なパラメータをセットすることを要求されます。これらはかなり細かい国際化をする場合じゃなければ必要になりませんし、必要無ければコードを汚しているようにしか感じられません。どうしても必要になったときにONにすればよいでしょ。

Designの2つについて、まずアセンブリの厳密名については、.net 1.x の頃ならともかく、GACに登録するようなものでなければ、厳密名は必要ないと個人的には認識しています。

また、CA1020は「アセンブリ内に同じ名前空間内にクラスやenum等の『型』を5つ以上含めるべき。」という内容ですが、使う側からすると、Usingをたくさん指定しなくちゃいけなくなって厄介なのはわかりますが、名前空間のUsing指定で拡張メソッドのON/OFFをしたりするので、名前空間の分割と統合は、型の数で制限すべきではないんじゃないかなぁ。ってことでOFFに。

StyleCop


StyleCopのほうは好みが出そう。ただ、あんまり無効にしちゃうと統一性が失われて意味がなくなっちゃうので、最低限に抑えておきたい。

デフォルト状態から、以下の項目をOFFにしています。
DocumentationRulesは、コードコメントの書き方のルール。さらにその中でも、ここで挙げたものは英語でコメントするときの問題で、日本語では関係ない(というか、無理)のでOFFにします。

OrderingRulesは記述の順番に関係するもの。ただ、SA1200は「名前空間の中でUsingを使え。」と言っています。つまり、こう書けと。
namespace Xxx.Yyy.Zzz
{
  using System;
  using System.Collections.Generic;
  using System.Linq;

  public class TheClass
  {
  }
}

はい?はじめて聞きました。こんなの。VisualStudioが吐き出すコードもこうなってないので、これは却下。

LayoutRulesのSA1503は、「中かっこは省略可能でも省略するな。」と言っています。つまり、こう書いちゃだめってこと。
if (condition())
    return false;

うーん。よく言われます。でもねぇ、コードが無意味に縦に伸びるの嫌なんですよ。インデントはされるわけですし、これは勘弁していただきたい。駄目かな?

SA1402は「一つのファイルには一つのクラスのみ含めよ。」と言っています。これもよく言われます。が、たとえばpublicなクラスでも、あるクラスにのみ依存するクラスってありますよね?大概は非常にちっさなクラス。この手のクラスにひとつひとつファイルを作っていると、ファイル数が多くなりすぎて却って参照性が悪くなると思うんです。…やっぱ駄目っすか?


それぞれ無効化しておきたいのはこれっくらいですねぇ。そのほかはMicrosoftさんのルールに従えるなー、というのが個人的な感想です。まぁ、これまでずっと、勉強するときのサンプルコードとかで散々Microsoftの書き方を見て、なんとなくでもわかりやすいと思って真似をしていた部分はあるんだと思います。多分、そのせい。

あー、もちろん、これ以外にその時々で「そんなの従えない!」ってことはあります。その場合は「SuppressMessage」属性をセットします。ちゃんと理由(Justification)を付けて。プロジェクトならできればコードレビューもしましょう。

2013年6月19日水曜日

FxCopとStyleCop まとめ

ADO.netのDataSetをLINQableに書くために (前段)」で書きましたが、「コンパイル(等)のタイミングで、指定したルールに適合していない場合はエラーや警告を出す」、コードの静的解析ツールとして、「FxCop」と「StyleCop」があります。

それぞれよく似た機能を持っているので混同されがちですが、何をしたいかによってどちらを選択するか、あるいは両方使うかが変わってくると思います。

なので、まずはそれぞれどのような特徴を持つのかをまとめてみました。

FxCopStyleCop
目的アセンブリが「クラスライブラリのデザインガイドライン」に適合し、利用者から見てAPIに一貫性と使いやすさが備わっているかを分析する。ソースコードが一定のルールに基づいて、統一的な記述がなされているかを分析する。(空白や改行の入れ方、括弧の位置、記述の順番など)
分析対象アセンブリ(IL:中間言語)C# ソースコード
VisualStudioバンドルPremium/Ultimateなし
VisualStudio統合Premium/Ultimateはビルド統合済み。
Standardは別途ツールをインストールすることで呼び出し可能になる。
Expressでは基本的にFxCopを単体で稼働させる。
Standard以上で可能。ただし、すべてのエディションでcsprojファイルの編集により、ビルドプロセス統合は可能。
カスタムルールの作成可能可能

つまるところ、FxCopは「ほかのアセンブリから呼び出されるアセンブリが、使いやすいものになっているか」どうかを分析し、使いにくいと思われる部分を警告を出して修正を促します。FxCopに怒られたところを直すと、確かに使いやすくなっているような気がします。

それに対してStyleCopは、「C#ソースコードが個々人によって差異が発生しないようにする」ために分析し、統一ルールからはみ出た表記には警告を出して修正を促します。

また、FxCopはアセンブリを対象に分析するため、言語はC#でもVB.netでもその他でもなんでもイケますが、StyleCopはソースコードが対象であり、C#に限定される。という違いもあります。

使ってみた印象としては、C#を使うプロジェクトで、それなりの人数が参加する場合はStyleCopを最初から適用する。さらに、いろんなところから参照されるアセンブリ(共通ロジックなど)はFxCopも適用する。というのがよろしいんではないかなー。と思っております。

ちなみに、ある程度開発が進んだ途中から、これらの静的解析を適用するのは、手間が大きすぎるので、可能な限り開発の着手前に決めておくべきでしょう。

なお、表には記載していませんが、StyleCopはオープンソース(Microsoft Public License:Ms-PL)に対して、FxCopはWindows SDKの一部であり、ソースは公開されていないという違いもあります。

それぞれ、インストール方法や使い方などについてはググってみてください。結構たくさん参考になるサイトが出てきます。

FxCopとStyleCop。もう少し続けます。

2013年6月14日金曜日

今週のモーニング

すべての流れをぶった切って、今週のモーニングについて。

相変わらず絶好調のモーニングですが、今は「カレチ」がとても悲しく、面白い。

「カレチ」は、JRの前身である「国鉄」において、諸々発生する問題を、現場の人たちの丁寧な、誠実な仕事で解決する。というお話で、鉄への愛と国鉄職員への愛で溢れている良作です。今週は最終章、5週連続掲載の2週目。

「国鉄」の「最後」の話。ということで、分割民営化が今描かれています。そして主人公の荻野は民営化前の、リストラの嵐に巻き込まれていきそうです。

今週の話を読んで思ったことをネタバレにならない程度にちょっとだけ言うと、現場で個人個人がどんだけ丁寧な仕事をしたって、組織というのは腐る。大きければ大きいほど腐りやすい。ということ。

それと、何話前だったかに舞台が現在に飛び、偏屈そうなジーさんになった荻野が登場していましたが、今の話があれに繋がるんだろうなぁ。と思うと…。やるせないなー。

楽しみにしていたお話が、あと3週で終わりを迎え、どうにもハッピーエンドは迎えられそうにない雰囲気。作者さんの力量もあり、とても面白いんですが切ないですね。

2013年6月13日木曜日

ADO.netのDataSetをLINQableに書くために (前段)

RDBMS利用時のアーキテクチャの選択方針」では、データセットを使うに当たっては以下をおススメしていました。
型無しデータセットを使う場合は、コードのメンテナンシビリティを保つために、以下の2つを守ることがおススメです。

ただし、 事はそう簡単ではありません。プロジェクトで何人かで開発を進めるなら、.net経験者を集めます。そしてその開発者は大抵過去にデータセットを使ったことがあるはずです。

そしてプロジェクトの方針としてこれらのことを掲げても、故意ではなくてもつい使いなれたメソッドやプロパティを使っちゃいます。そして、それをあとから直すのはとても大変です。つまり、ある程度強制力のある手法が必要になってきます。

このように、「開発にあたってのルールを強制する」方法を考えた時、2つのツールが有用です。それは、「FxCop」と「StyleCop」です。それぞれ「コンパイル(等)のタイミングで、指定したルールに適合していない場合はエラーや警告を出す」という機能を持ちます。この2つは似た機能を持っていますが、目的もアプローチも異なります。

この次のエントリでは、FxCopとStyleCopについてちょっと書いてみたいと思っています。

2013年6月9日日曜日

DbCommandからのレコード取得をLINQableにする拡張メソッド

RDBMS利用時のアーキテクチャの選択方針」では、バッチ処理では生ADO.netが相性が良いと書きました。ただこのクラス群はさすがに古くて、あまり積極的に使いたくないなー。と思ってしまうところがあります。

特に、SQLを実行して結果を取得しない「DbCommand.ExecuteNonQuery」や単一の値を求める「DbCommand.ExecuteScalar」であればともかく、SELECTして複数行にわたって処理する場合、「DbCommand.ExecuteReader」を使う必要がありますが、どう頑張ったってこんなコードになっちゃう。
using (var conn = new SqlConnection(connectionString))
using (var cmd = conn.CreateCommand())
{
  conn.Open();
  cmd.CommandText = selectCmd;

  using (var reader = cmd.ExecuteReader())
  {
    while (reader.Read())
    {
      xxx = reader[columnName];
      // いろいろ処理
    }
  }
}
んむー。せめてwhileじゃなくてforeachを使いたい。できればLINQに繋げたい。そのためには、SELECTの結果のレコードを、IEnumerable<T>で取得できるようなメソッドを用意しておきたい。そこで、DbCommandクラスにこんな拡張メソッドを用意してみます。なお、わかりやすくするためにパラメータのNULLチェックはなしで。
public static IEnumerable<IDataRecord> ExecuteQuery(this DbCommand command)
{
  return ExecuteQuery(command, dr => dr);
}

public static IEnumerable<T> ExecuteQuery<T>(
    this DbCommand command, Func<IDataRecord, T> mapper)
{
  using (var reader = command.ExecuteReader())
  {
    while (reader.Read())
    {
       yield return mapper(reader);
    }
  }
}
「ExecuteQuery」という名前の2つの拡張メソッドを作ります。一つはIEnumerable<IDataRecord>を返し、もう一方は任意の変換関数を用意して、IEnumerbale<T>を返します。ちなみに「IDataRecord」はDbCommand.ExecuteReaderメソッドが返す、DbDataReader抽象クラスの派生クラスで実装されます。

これを使うと多少ましになります。例としてなんか適当なバッチ処理を考えてみましょうか。

SQLServerのあるテーブルをCSVファイルにエクスポートします。その際に「exported」カラムが「0」のレコードのみを対象とします。また、エクスポート実行後、対象レコードの「exported」カラムを「1」に、「exportAt」カラムに現在日時をセットします。

こんなありがちな処理を先のDbCommand拡張メソッドを使って書いてみると、こんなコードになります。
const string connectionString =
  @"Data Source=.\SQLEXPRESS;Initial Catalog=Test;Integrated Security=True";
const string selectCmd =
  "select id, uid, insertAt, qty from TestTable_1 where exported=0";
const string updateCmd =
  "update TestTable_1 set exported=1, exportAt=getdate() where id in ({0})";

static void Main(string[] args)
{
  using (var conn = new SqlConnection(connectionString))
  {
    var ids = new List<int>();

    using (var cmd = conn.CreateCommand())
    using (var csv = new StreamWriter("output.csv"))
    {
      conn.Open();
      cmd.CommandText = selectCmd;

      foreach (var r in cmd.ExecuteQuery())
      {
        ids.Add(r.GetInt32(0));
        csv.WriteLine(string.Format("{0},{1},{2}", r[1], r[2], r[3]));
      }
    }

    if (ids.Count > 0)
    {
      using (var cmd = conn.CreateCommand())
      {
         cmd.CommandText = string.Format(updateCmd, string.Join(",", ids));
         cmd.ExecuteNonQuery();
      }
    }
  }
}
意外と見やすいコードが書けるんじゃないかと思ってるんですが、どうでしょ?

2013年6月8日土曜日

あっというまに

前回のエントリ立てから2週間以上が経過。しかもその間、一回もおうちのPC立ち上げて無いや。ちょっと反省。

どんな内容でもいいので、週に一回はエントリ立てたいという希望を持っていこうと思います。

2013年5月23日木曜日

RDBMS利用時のアーキテクチャの選択方針

全エントリのように、各アーキテクチャには一長一短あるわけですが、それでもどれかを選ばないと始まらないので、「僕だったら」こんな方針でアーキテクチャを選ぶ。というところを書きたいと思います。

バッチ系の処理


バッチ系の処理の場合、SELECTで条件に合致したレコードすべてに対して何らかの処理を行う。という形になると思います。この場合は生ADO.net(DbConnection/DbCommand/DbDataReader)が相性が良いと思います。

こいつは古き良きスタイルなので、レコードを一行一行読んで処理して、という形に自然になること。また、DBへの接続と切断をコントロールしやすいのもメリットです。対象のRDBMSに応じて、DbXxx抽象クラスの派生クラスを使うことで、それぞれのRDBMSの特性を生かした実装ができるのもいいですね。

(型あり|型無し)データセットだと、一旦まとめて読みこむ形になるので、バッチ系処理ではかえって制御が難しいように思います。

EF(Entity Framework)もバッチ処理で使いにくいわけではなさそうですが、たとえばSQLServerなんかだとロックエスカレーションによるデッドロックを回避するために、SELECT時にロックヒントを付けたりしますが、こんな感じのRDBMSごとの配慮がいちいち面倒なイメージがあります。実際にバッチ系処理で使ったことはないですが。

Webアプリケーション


Webアプリの場合はプレゼンテーション側のアーキテクチャを何にするかによると思います。僕の場合は好みの問題で、全力でASP.net MVCを推します。この場合はEFを選択するのがたぶん楽です。

どうしてもASP.net (Webフォーム)から逃げられなかった場合は、型無しデータセットかな。

その他 (2層C/S、3層C/S、Webサービス、etc...)


そのほかはケースバイケースになりますが、メリットデメリットを加味しながら、基本的には好みでEFか、型無しデータセットか、生ADO.netのいずれかを選ぶかな。この3つ以外は選択肢から除外。

ただし…


EFを使う場合は、モデルファーストかコードファーストで開発を始めるのがよいと思います。コードファーストはいろいろ批判的な意見も見かけますが、僕は結構好きです。
それと、インデクッスやトリガ、ストアドプロシージャなんかはどう使うか、何処で使うか、どのタイミングで実際に適用するか。ここら辺は十分な配慮が必要でしょう。あと、ビューは使いづらいです。多分。

型無しデータセットを使う場合は、コードのメンテナンシビリティを保つために、以下の2つを守ることがおススメです。
これを守ることで、前のエントリで列挙した型無しデータセットのデメリットがだいぶ減り、
  • DataTableの各レコードに対して、Enumerable拡張メソッドが使いやすくなり、コードが比較的きれいになる。
  • フィールドからのデータ取得時のキャストが不要になる。また、Nullableを使ってDBNullをNULLにマップできる。
と、形無しデータセットでも、少しはイマドキなコードが書けるようになります。例をあげてみましょう。たとえば、取得済みのDataTable「t」から、「Kana」順に「Name」と「Birthday」を取得してイロイロ。しかも「Birthday」はNullable。ならこんなコードになります。

DataTable t = GetTable();

foreach (var row in t.AsEnumerable().OrderBy(r => r.Field<string>("Kana")))
{
  var name = row.Field<string>("Name");
  var birthday = row.Field<DateTime?>("Birthday");
  // name,birthdayを使っていろいろ処理
}
このコードは、当然Selectメソッドで匿名クラスを作ったりしても問題なく行けます。個人的は「これなら使ってもいっか。」という気になります。

そして、生ADO.netを使う場合は、多段usingネストを減らす工夫があるとよいでしょう。

とりあえずそこは、ちょっと長くなったので別エントリで書くことにします。

2013年5月21日火曜日

悩ましいRDBMSの利用時のアーキテクチャの選択

唐突ですが、C#(というか.netのアプリケーション)で、RDBMSを利用するのは、.netの登場から10年以上たった今でもどうするのが正解か?いまだに難しい問題だなぁと思っています。

探してみるとRDBMSと.netの間に入る、よさそうなミドルウェアもなくはないんだけど、チームでの開発だとライセンスのコストや習熟にかかるコストが読みづらく、正直選択がためらわれる。

で、.netの標準の仕組みでデータベースアクセスをしようとすると、今なら以下のいずれかを選択することになりますね(新しい順)。
  • Entity Framework
  • LINQ to SQL
  • 型付きデータセット/形無しデータセット
  • 生ADO.net (DbConnection+DbCommand+DbDataReader)

個人的な感想を言うと、どれも一長一短なのです。
  • Entity Framework
    • 物理層のRDBMSを抽象化した、論理層に対する操作を行う。論理層への操作(LINQを使った処理)が物理層(SELECT等のSQLコマンド)にマッピングされる動作はすばらしいと思う。
    • が、特定のフィールドのみ更新したい場合ないど、UPDATEとの相性がいまいち。
    • それと、物理層への操作は基本的に排する方針のようで、インデックスやらトリガやらビューやらはどう組み込んでいいものやら。要は、RDBMSでお気楽にできる処理が、簡単じゃなかったりすることがしばしば起こる。
    • インデックスとかを取り込みにくいので、Webアプリのように徐々にレコード数が増えていくものであれば、DBのスキーマが定まった後に、必要に応じてインデックスを追加していくのはイケルかと思う。でも、最初からデータ移行で数十万レコードを考慮しないとならんようなケースでは、インデックスやトリガやビュー等を最初から考慮した設計を行いたいんだけど、…なんかすごくやりづらい。
    • そもそも、習熟した人がほとんどいない。
  • LINQ to SQL
    • SQLServer以外が選択肢にならない。
    • 今後のメンテナンス/機能追加が期待できない。
  • 型付きデータセット
    • きらい。
    • …(それ以外の理由が必要?)
    • LINQを意識した作りになっていない。
    • 自動で付加されるクラス名に、すごい名前付けてくれちゃったり。おかげでよほどベテランさんが意識してコードを書かないと読みにくいのなんの。
    • Visual Studioのデザイナ(UI)の出来がイマイチ。よくわからない挙動にイラッとすることも度々。イヤになるほど遅いし。ちょっと複雑なSELECT文だとどうにもエラーになっちゃったり。
    • 特定のフィールドのみを出力するSELECTや、複数テーブルをJOINしたSELECTとかは別のクラスにするの?一つのクラスでSELECTごとに埋まる項目が変わるの?クラスを呼び出す側はメソッドによってデータが入ってたり無かったりするのかな?どうもこう、どう注意して作っても統一性が取れなくて。
  • 形無しデータセット
    • 当然、LINQを意識した作りになっていない。
    • フィールド名によるインデクサを多用せざるを得ないため、文字列リテラルが大発生。文字列リテラルに間違いがあると実行時のエラーとなってしまうのでバグの元が山盛りある状態に。
    • さらにインデクサで取得してもすべて形無し(object)になってしまうので、キャストやらコンバータやら、本来処理したいもの以外のコードでソースが埋め尽くされる。
    • そこに持ってきて、DBNullがオブジェクトのNULLにマップしてくれないので、NULLチェックによる処理もソースを汚し倒してくれる。
    • これはもう誰が書いてもそうなる。
  • 生ADO.net (DbConnection+DbCommand+DbDataReader)
    • イイ言い方をすれば「由緒正しい」。それってつまり「古臭い」。
    • それぞれ構築と廃棄を意識して作る必要がある。usingによるネストが大発生。using使わなかったりするとそれはそれで悲しいコードになりがち。

と、まぁ、悩むことこの上ない。そんなこんなで僕ならどうするか。次のエントリで書いてみたいと思います。(解決にはなりません。あんまり期待しないでほしいっす。)

いくつかのエントリのタイトルを変えました。

ブログの開設から2ヶ月半といったところ。イイ感じに更新ペースが落ちてってます(ぉぃ)。

ちょっと前に書いたエントリで、タイトルから内容が分かりづらいものがいくつかあったので、わかりやすいタイトルがつけれるものは直しました。

2013年5月9日木曜日

『荒川弘×田中芳樹「アルスラーン戦記」別マガで連載決定』

「荒川弘×田中芳樹「アルスラーン戦記」別マガで連載決定」だって!

別マガですか。講談社ですか。ほへー。「銀の匙」と「百姓貴族」と3つ掛け持ちってこと?牛先生仕事しすぎ。…あれ?「銀の匙」はまだ終わんないよね…?大丈夫だよね??

まぁ、ハガレンの時も「獣神演武」をパラで書いてましたからね。そーいうのに慣れてるのかな。それにしてもすごいなぁ。

しかし、別マガを連載で追いかけるのは無理だな。単行本化を気長に待つことにします。


2013年5月8日水曜日

ファイルが既に存在する場合に、ナンバリングして存在しないファイル名を取得する処理をLINQで。

タイトルにある、ちょっと無理めなお題を考えてみた。

すなわち、Windowsでよくある処理で、たとえば「U:\Test\Test.txt」が存在しない場合はそのままで、存在する場合は「U:\Test\Test (2).txt」を、それも存在する場合は(3)、(4)、…という風にナンバリングし、存在しないファイル名を返す、そんなメソッドを考えてみる。

LINQは意識せず、深く考えずに書いてみたらこうなった。

static string GetNumberedUniqueFilename(string filename)
{
  if (!File.Exists(filename))
    return filename;

  string d = Path.GetDirectoryName(filename),
    fn = Path.GetFileNameWithoutExtension(filename),
    ext = Path.GetExtension(filename);

  for (int i = 2; ; ++i)
  {
    var f = Path.Combine(d, string.Format("{0} ({1}){2}", fn, i, ext));
    if (!File.Exists(f))
      return f;
  }
}


これをLINQ使って書きなおしてみると…。難しいな…。こうか?

static string GetNumberedUniqueFilename(string filename)
{
  if (!File.Exists(filename))
    return filename;

  string d = Path.GetDirectoryName(filename),
    fn = Path.GetFileNameWithoutExtension(filename),
    ext = Path.GetExtension(filename);

  return Enumerable.Range(2, int.MaxValue - 2)
    .Select(i => Path.Combine(d, string.Format("{0} ({1}){2}", fn, i, ext)))
    .SkipWhile(f => File.Exists(f))
    .First();
}


…これはびみょーだなぁ…。

勘違い

昨日は第2火曜日じゃなかった。イブニング発売日だと思っていくつかKIOSK見て回って。売り切れかと落胆していた。7日は一週目だね。

2013年5月3日金曜日

Enumerable.Zipメソッド

2つのコレクションを、同時に列挙したいケースというのはたまにありますよね。たとえば、2つの異なるデータソース上のデータが、何らかのキーでJoinできるわけではなく、単純に格納順序で順番に取り出してなんかしらの処理をして出力したい。とか。

まぁ、こんなケースはたいていデザイン的に問題があったりするんですけど、とはいえリファクタリングするにはなんだか大掛かりになりすぎちゃって、「そこまでしたくないなぁ。」なとき。

こうなると、foreachで2つのコレクションから順次取り出すことはできないので、for文+インデクサを使ったりするわけですが、こんな感じのコードになりますね。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

for (int i = 0; i < numbers.Length; ++i)
{
  Console.WriteLine("{0}:{1}", numbers[i], names[i]);
}

ところが、今となっては単純な列挙にforとかiとか[]とか、こういうのを見るとなんだか可読性が悪い(…様な)気がして、foreachで置き変えたくなっちゃう。こんなときに使えるのが「Enumerable.Zip」メソッドです。2つのコレクションを、まとめて一つにしちゃってくれる、「.net4」で追加されたメソッドです。たとえば、Tupleと組み合わせることで、こんなコードに置き換えできる。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

foreach (var x in numbers.Zip(names, Tuple.Create))
{
  Console.WriteLine("{0}:{1}", x.Item1, x.Item2);
}

やっぱりこっちのほうがすっきり見えるわけです。完全にLINQに毒されてますな。自覚あります。

Item1とかItem2とか、このコードくらいスコープが短ければ気にならないけど、もう少し複雑な処理で、名前にも気を使いたいような場合なら、Tupleの代わりに匿名クラスを使うことで、こうも書けますね。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

foreach (var x in numbers.Zip(names, (number, name) => new { number, name }))
{
  Console.WriteLine("{0}:{1}", x.number, x.name);
}

どちらでも、ケースバイケースで。あるいはお好みで。

2013年4月25日木曜日

Comparison デリゲートを利用した IComparer の実装クラス

訳のわからないタイトル。


事の起こりは、ジェネリックの「SortedDictionary」クラスを使おうと思ったこと。

デフォルトの比較ルールだと、希望する順番にソートされないので、比較関数を置き変えたかった。そのためにはどうするのか?MSDNを引いてみると、



SortedDictionaryのコンストラクタのオーバーロードを確認しても、IComparer<TKey>を指定するものはあっても、Comparison<TKey>を指定できるものはない。えー?このためにクラス一個作んないとダメってことかい?めんどくせ。

List<T>とかのSortメソッドには、Comparison<T>もIComparer<T>もどっちも引き渡せるようになっているのに…。.net 2.0の時代ならいざ知らず…、ちょっと片手落ちじゃないすか?というか、手を入れ損なったのかなぁ。

ま、無いものはしょうがない。とはいえ、クラスを作るにしても似たようなものを何度も作りたくはない。なので、『Comparison<T> デリゲートを利用した IComparer<T> の実装クラス』を作り、汎用的に使いまわせるようにしようと考えた次第。

しかし、ここで悩んだのはそのクラス名。汎用的なIComparer実装クラスなので、「Comparer」クラスにしたかったのだけど、「System.Coolection.Generic.Comparer」クラスが既に存在しているので却下。

悩んだ挙句、「ComparisonComparer<T>」にしました。頭痛が痛いような名前。でもまぁ、名は体を表していることには違いないし、なによりIntellisenceで「Compar...」でリストアップされるので、まぁ、使い勝手は悪くないんじゃないかと思う。

およそ実装はこんな感じ。

class ComparisonComparer<T> : IComparer<T>
{
  private Comparison<T> comparison;

  public ComparisonComparer(Comparison<T> comparison)
  {
    this.comparison = comparison;
  }

  public int Compare(T p1, T p2)
  {
    return comparison(p1, p2);
  }
}

念のため、ジェネリックじゃないIComparerも実装しておいたほうがいいかも。そして、こう使うと。

var dic = new SortedDictionary<string, int>(
    new ComparisonComparer<string>((p1, p2) => string.Compare(p1, p2)));

正直、それほど使い道があるわけじゃない(ざっと見た限り、SortedListやSortedSetではIComparer<T>が必要なので、使い道はありそうだけど、それくらい…)。だけど、いちいちクラス一個作るよりはいいでしょ。

2013年4月22日月曜日

Lambdaの小ネタ / ハッシュ文字列(フィンガープリント)の生成

Lambda の小ネタ / 文字列から特定種類の文字以外を除去したコピーを作る」でも使いましたが、string.Concat(IEnumerable<T>)。これを知ったのはつい最近。が、これが大変使い勝手がよく、昔書いたコードのいろいろを書き直したくなる衝動に駆られます。

たとえば、バイナリデータのMD5ハッシュ文字列(いわゆるフィンガープリント)を生成するのに、こんなコードを書いてました。
static string MD5OldStyle(byte[] tgt)
{
  var sb = new StringBuilder();
  using (var md5 = MD5.Create())
  {
    foreach (var b in md5.ComputeHash(tgt))
    {
      sb.Append(b.ToString("x2"));
    }
    return sb.ToString();
  }
}

MSDNのサンプルもこんなコードになっています。しかし、ここにstring.Concatのメソッドを使うと、
static string MD5NewStyle(byte[] tgt)
{
  using(var md5 = MD5.Create())
  {
    return string.Concat(md5.ComputeHash(tgt).Select(b => b.ToString("x2")));
  }
}

おんなじことがこれでできます。なんて素敵な!

2013年4月19日金曜日

今週のモーニング

モーニングも楽しみにしている作品が多い。

そこに加えて山崎紗也夏と東村アキコが新連載待機中らしい。そりゃ、楽しみ。

「主に泣いてます」が終わったばっかりのような気がしなくもないけど。

Lambda の小ネタ / 文字列から特定種類の文字以外を除去したコピーを作る

ラムダ式を使ったちょっとした小ネタ。

『文字列から、数字以外を除去したコピーの文字列を作る』処理を書きたい。






.net Framework 4.0以降であれば、入力文字列を「input」、出力文字列を「output」とすると、こんな風に書けます。なにしろstring は 「IEnumerable<char>」ですから!

var output = string.Concat(input.Where(c => Char.IsDigit(c)));

シンプルでいいでしょ。

2013年4月18日木曜日

ありがちな。

誰が作ったものか知りませんが、お気に入り。いやいや、あくまでパロディですよ。
でも、戒めとしても。ね。


ネタがないなぁ



そんなときの必殺技。ねこ写真!!

2013年4月11日木曜日

Sandcastleの日本語パッチを見つけました!

いままでいろいろ探してみても、結構古めのバージョンのものしかなく、仕方なく自分で最低限必要なところだけを翻訳して「オレオレパッチ」を作って使ってました。

SHFBの「1.9.6.0」対応バージョンがこちらです。

https://code.google.com/p/sandcastle-help-file-builder-japanese-help-file-pack/

作者さん(tuedaさん)のブログはこちら。

tuedaの日記 - 「SandCastleすごくいい!!」

中身を見てみると、全部のプレゼンテーションタイプの、翻訳可能な個所すべてが日本語化されてます!すばらしい!これはいい!

さっそく使わせてもらおうと、まずはSHFBをバージョンアップさせようとサイトに行ったら、「1.9.7.0」が出てました…。…とりあえず問題なく使えました。

ちなみに、僕は「VS2005」のプレゼンテーションタイプが一番好みです。シンプルで見やすい。

『なんでこれコンパイル通るんだっけ?』

あらかじめ言っておくと、この話にオチはありません。なんかすいません。

深く考えもせず、こんな感じのコードを書いていました。

public void SetValidChars(CharType type, string option)
{
  var validChars = new List<char>();

  if (type == CharType.AllAscii)
    validChars.AddRange(Enumerable.Range(0x20, 0x7f - 0x20)
                                  .Select(n => Convert.ToChar(n)));
  else if (type == CharType.NumericOnly)
    validChars.AddRange("0123456789");
  else if (type == CharType.Optional)
    validChars.AddRange(option);

  // この後、validCharsを使ってごにょごにょ
}

コンパイルかけて、UTも通して、さてと、とコードを眺めていたら、文字を眺めていてゲシュタルト崩壊起こしたときのように、なんだか急に不安になってきて、『…なんでこのコードコンパイル通るんだっけ?』。なんでList<char>.AddRange()にstringを引き渡せるんだっけ?

念のため「List<T>.AddRange」のオーバーロードを確認してみようとググってみたら、パラメータに「IEnumerable<T>」を受け付けるモノのみ。ふーん。やっぱそうだよね。

じゃあ、stringクラスのほうで「IEnumerable<char>」への暗黙の変換とかができるのかな?と思い、まずはstringクラスの型の構文をググって調べてみた。



ふーん。「IEnumerable」と「IEnumerable<string>」なのか。それだと「IEnumerable<char>」には勝手に変換できないよな。じゃあ、stringクラスに「IEnumerable<char>」に暗黙的に変換できるImplicitなオペレータがあるんだろう。と思って探してみたけど、…ない。…うん。そんなの見た覚えない。

もうわからん。なんか僕が知らない型変換の仕組みでもあるのか?と軽くパニックになりかけたところで、

『ん?stringクラスがIEnumerable<string>を実装?』。いったい何を列挙すんだよ、それ?

よくよくググった結果を見てみると、「.net Framework 2.0」と書いてある。まさかなー。と思いながら「.net Framework 4.5」に切り替えてみると、

public sealed class String : IComparable, 
 ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, 
 IEnumerable, IEquatable<string>

わぁ。やっぱり「IEnumerable<char>」じゃねーか。じゃぁ、「2.0」バージョンでの説明が間違ってたんだよね。と、ちょっとほっとした。

…一応フィードバックを書いといたよ。

2013年4月10日水曜日

今週のイブニング

引き続きのアタリっぷり。イブニングたのしー。

『CAPTAINアリス』はもうすぐ終わりそう。さすがです。面白いです。

『ジャポニカの歩き方』は今号が最終回?

「ラオ編」は終わるだろうなー、と思ってたけど、大使が別の国に赴任になって、カラドもそれについていって、それでもって赴任先で新たなかわいいおねーさんがでてきていろいろ。とかそんな展開じゃないの?ないか。

まぁ、きっちりと終わらせてくれたので、満足です。

『とろける鉄工所』も今号がラスト。毎号楽しみに読んでおりました。

それから『もやしもん』は新刊が出ておりますな。未購入ですがまだ限定版手に入るかな?


2013年4月4日木曜日

ラムダ式を利用したリファクタリングの例 その4 / 後編

前編の続き。Exifファイルのタグ一覧をコンソール上に表示するプログラム。前編最後のコードはこうでした。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    foreach (PropertyItem pr in bmp.PropertyItems)
    {
      Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
        pr.Id,
        PropertyItemReader.GetExifTagId(pr.Id),
        pr.Len,
        pr.Type,
        PropertyItemReader.GetExifType(pr.Type));

      if (pr.Len > 0 && pr.Type == 2)
      {
        Console.WriteLine(", Value={0}", 
                      PropertyItemReader.ReadStringValue(pr));
      }
      else if (pr.Len > 0 && new short[] {3,4,5,9,10}.Contains(pr.Type))
      {
        Console.WriteLine(", Value={0}", string.Join(",",
                      PropertyItemReader.ReadValues(pr).Cast<object>()));
      }
      else
      {
        Console.WriteLine("");
      }
    }
  }
}

ここで、if ~ else if ~ else の構文が条件も各々の処理も「PropertyItem」をもとにしていることに気がつき、せめてもう少し見栄えが良くなるように、条件と処理を管理するこんなシンプルなクラスを作ってみました。

パラメータに対する条件と処理のペアを複数個登録しておき、実行時には最初に条件に合致した処理を実行する。というクラスです。switch ~ case や if ~ else if ~ else をオブジェクトにしたような存在。
public class SwitchAction<T>
{
  private List<Tuple<Func<T, bool>, Action<T>>> switches;
  private Action<T> actionDefault = _ => { };

  public SwitchAction()
  {
    switches = new List<Tuple<Func<T, bool>, Action<T>>>();
  }

  public SwitchAction(Action<T> actionDefault) : this()
  {
    this.actionDefault = actionDefault;
  }

  public void AddCase(Func<T, bool> condition, Action<T> action)
  {
    switches.Add(Tuple.Create(condition, action));
  }

  public void Action(T target)
  {
    var sw = switches.FirstOrDefault(s => s.Item1(target));
    if (sw != null)
      sw.Item2(target);
    else
      actionDefault(target);
  }
}

このクラスを使うと、こう書きかえることができます。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    var sw = new SwitchAction<PropertyItem>(_ => Console.WriteLine());

    sw.AddCase(p => p.Len > 0 && p.Type == 2,
      p => Console.WriteLine(", Value={0}", 
        PropertyItemReader.ReadStringValue(p)));
    sw.AddCase(p => p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type),
      p => Console.WriteLine(", Value={0}",
        string.Join(",", PropertyItemReader.ReadValues(p).Cast<object>())));

    foreach (PropertyItem property in bmp.PropertyItems)
    {
      Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
        property.Id,
        PropertyItemReader.GetExifTagId(property.Id),
        property.Len,
        property.Type,
        PropertyItemReader.GetExifType(property.Type));

      sw.Action(property);
    }
  }
}

んん~?な~んか違うなぁ。どの条件の時も処理はすべて「Console.WriteLine」だ。だから、コンソールに出力する文字列だけを取り出す処理にするように変えよう。なので、条件と処理を管理するクラスを、さらに戻り値を返す物を別に作ろう。
public class SwitchFunc<T, TResult>
{
  private List<Tuple<Func<T, bool>, Func<T, TResult>>> switches;
  private Func<T, TResult> funcDefault = _ => default(TResult);

  public SwitchFunc()
  {
    switches = new List<Tuple<Func<T, bool>, Func<T, TResult>>>();
  }

  public SwitchFunc(TResult resultDefault) : this()
  {
    this.funcDefault = _ => resultDefault;
  }

  public SwitchFunc(Func<T, TResult> funcDefault) : this()
  {
    this.funcDefault = funcDefault;
  }

  public void AddCase(Func<T, bool> condition, Func<T, TResult> func)
  {
    switches.Add(Tuple.Create(condition, func));
  }

  public TResult Func(T target)
  {
    var sw = switches.FirstOrDefault(s => s.Item1(target));
    if (sw != null)
      return sw.Item2(target);
    else
      return funcDefault(target);
  }
}

これを使うと、foreach ループの中身が一文であらわされます。なので、Array.ForEachを使う形に変えてみました。こうなりました。ブロックがusing の一つだけになりました。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    var sw = new SwitchFunc<PropertyItem, string>(string.Empty);

    sw.AddCase(p => p.Len > 0 && p.Type == 2,
      p => ", Value=" +
        PropertyItemReader.ReadStringValue(p));
    sw.AddCase(p => p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type),
      p => ", Value=" +
        string.Join(",", PropertyItemReader.ReadValues(p).Cast<object>()));

    Array.ForEach(bmp.PropertyItems, p =>
      Console.WriteLine("ID=0x{0:X}({1}), Len={2}, Type={3}({4}){5}",
        p.Id,
        PropertyItemReader.GetExifTagId(p.Id),
        p.Len,
        p.Type,
        PropertyItemReader.GetExifType(p.Type),
        sw.Func(p)));
  }
}

…悪くはないけど、わざわざ別にクラスを作ったメリットが感じられないコードになった…。気がする。これなら三項演算子を重ねてみても変わらないんじゃないかなぁ?

ってことで書き変えてみたのがこれ。
private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    Array.ForEach(bmp.PropertyItems, p =>
      Console.WriteLine("ID=0x{0:X}({1}), Len={2}, Type={3}({4}){5}",
        p.Id,
        PropertyItemReader.GetExifTagId(p.Id),
        p.Len,
        p.Type,
        PropertyItemReader.GetExifType(p.Type),
        p.Len > 0 && p.Type == 2 ? 
            ", Value=" + PropertyItemReader.ReadStringValue(p) :
          p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type) ?
            ", Value=" + string.Join(",", 
              PropertyItemReader.ReadValues(p).Cast<object>()) : ""));
  }
}

あー。動くけど、もはや何がしたいのか誰にもわからんコードになったなぁ。三項演算子を使うにしても、せめて「WriteLine」の{5}に当たる処理は、インラインにするには複雑すぎるので、外に追い出そう。ということで、引数チェックや例外処理を含む、最終的なコードはこうなりました。
private static void Main(string[] args)
{
  if (args.Length < 1)
  {
    Console.WriteLine("Less Arguments.");
    return;
  }
  try
  {
    using (Bitmap bmp = new Bitmap(args[0]))
    {
      Func<PropertyItem, string> getvalues = p =>
        p.Len > 0 && p.Type == 2 ?
          ", Value=" + PropertyItemReader.ReadStringValue(p) :
        p.Len > 0 && new short[] {3,4,5,9,10}.Contains(p.Type) ?
          ", Value=" + string.Join(",", 
            PropertyItemReader.ReadValues(p).Cast<object>()) : "";

      Array.ForEach(bmp.PropertyItems, p =>
        Console.WriteLine("ID=0x{0:X}({1}), Len={2}, Type={3}({4}){5}",
          p.Id,
          PropertyItemReader.GetExifTagId(p.Id),
          p.Len,
          p.Type,
          PropertyItemReader.GetExifType(p.Type),
          getvalues(p)));
    }
  }
  catch (Exception e)
  {
    Console.WriteLine(e.Message);
  }
}

結局のところ、2つクラス(SwitchActionクラスとSwitchFuncクラス)を作ったけど、使わなかった。まぁ、どっかで使うこともあるかもしれないし、無いかもしれないし。

そして最終的なコードはちょっと微妙なにおいが残ったなぁ。それでもいろいろ書いてみた中では一番ましのような気がするけれど。うーん…。

という、ちょっと歯切れの悪い終わり方でした。あ~あ。

2013年4月3日水曜日

ラムダ式を利用したリファクタリングの例 その4 / 前編

デジカメとかで撮影した画像ファイルの、Exifタグの中身を参照する、こんなコードがありました。コンソールアプリで第一引数にファイルパスをとる形です、Mainメソッドのみ書き出します。

private static void Main(string[] args)
{
  if (args.Length < 1)
  {
    Console.WriteLine("Less Arguments.");
    return;
  }
  try
  {
    using (Bitmap bmp = new Bitmap(args[0]))
    {
      foreach (PropertyItem pr in bmp.PropertyItems)
      {
        Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
          pr.Id,
          PropertyItemReader.GetExifTagId(pr.Id),
          pr.Len,
          pr.Type,
          PropertyItemReader.GetExifType(pr.Type));

        if (pr.Len > 0)
        {
          if (pr.Type == 2)
          {
            Console.WriteLine(", Value={0}", 
                              PropertyItemReader.ReadStringValue(pr));
          }
          else if (pr.Type == 3 || pr.Type == 4 || pr.Type == 5 ||
                   pr.Type == 9 || pr.Type == 10)
          {
            StringBuilder sb = new StringBuilder();
            foreach (object o in PropertyItemReader.ReadValues(pr))
            {
              sb.Append(o.ToString());
              sb.Append(",");
            }
            sb.Remove(sb.Length - 1, 1);
            Console.WriteLine(", Value={0}", sb.ToString());
          }
          else
          {
            Console.WriteLine("");
          }
        }
        else
        {
          Console.WriteLine("");
        }
      }
    }
  }
  catch (Exception e)
  {
    Console.WriteLine(e.Message);
  }
}
ちなみに、実行するとこんな結果を吐き出します。



この中の「PropertyItemReader」というのは、別途作ったstaticなクラスで、PropertyItemからExif-Tagが文字列ならその値を文字列で取り出したり、数値型の配列ならArrayで取り出したり。または、Tag-IDやTag-Typeを表示名に変換したりといった機能を持ちます。(とりあえずここでは、そいつのコードは割愛します。)

で、コードをぼーっと眺めてみて、なんだかかっこ悪い。if文があまり意味もなくネストしてるし、配列をカンマ区切りの文字列にするために、StringBuilderを使っているけど、string.Joinを使えばも少しシンプルになりそう。それと、数値型の判断をしている条件文がなんだか冗長。

と、気がついたところを直してみました。こうなりました。なお、これ以降説明用に、引数チェックと例外処理は省略します。

private static void Main(string[] args)
{
  using (Bitmap bmp = new Bitmap(args[0]))
  {
    foreach (PropertyItem pr in bmp.PropertyItems)
    {
      Console.Write("ID=0x{0:X}({1}), Len={2}, Type={3}({4})",
        pr.Id,
        PropertyItemReader.GetExifTagId(pr.Id),
        pr.Len,
        pr.Type,
        PropertyItemReader.GetExifType(pr.Type));

      if (pr.Len > 0 && pr.Type == 2)
      {
        Console.WriteLine(", Value={0}", 
                      PropertyItemReader.ReadStringValue(pr));
      }
      else if (pr.Len > 0 && new short[] {3,4,5,9,10}.Contains(pr.Type))
      {
        Console.WriteLine(", Value={0}", string.Join(",",
                      PropertyItemReader.ReadValues(pr).Cast<object>()));
      }
      else
      {
        Console.WriteLine("");
      }
    }
  }
}

だいぶいい感じになったかな。でも、なんだかまだちょっと不満。何でだろう。

どうやら、if ~ else if ~ elseのブロックが、それぞれがシンプルな処理なだけに、どうも気に入らない。ような気がしてきた。こいつをどうにかすることを考えよう。

が、今日はここまで。寝ないとまずいってば。続きは次のエントリで。

2013年4月2日火曜日

放置のままあっという間に一週間…

開設から一カ月も経たないうちに…。反省…。

いや、ネタを考えたりいろいろ試したりしてるんですが、公開できる形にまとまらなくて…。

そーいうこともありますよね?ね…?

2013年3月26日火曜日

今週のヤンマガ

『センゴク一統記』。いよいよ本能寺も佳境。面白いんだけど、光秀が謀反に至った経緯が今一つピンとこなかった。まぁ、いろいろ小説とかも読んだけど、ピンと来たことなんてないから、イインダケドネ。

それよりも、ここからのイベント目白押しが楽しみ。

高松城攻めを講和→清水宗治切腹→中国大返し→山崎の戦い→賤ヶ岳の戦い→小牧・長久手の戦い→四国攻め→九州攻め。

ここまでいってようやっとゴンベイが失脚するわけだよ。あと何年連載するつもりなのかしらん。


…それと、そういえば『ハチイチ』は無かったか。残念。

2013年3月23日土曜日

ラムダ式を利用したリファクタリングの例 その3

.net Framework 1.0のころに書いた、こんなコードがありました。名前空間付きのXML文書をXPathに処理させるため、XmlNamespaceManagerを構築する前処理で、XML文書の指定ノードから、XML名前空間のプレフィックスと名前空間名を辞書の形で取り出すメソッドです。
public static StringDictionary 
    GetNodeNamespaces(XmlNode node, string defaultNSSubstitute)
{
  // 名前空間用辞書オブジェクトの生成
  StringDictionary namespaces = new StringDictionary();

  // 対象XMLノードからXML属性コレクションを取得
  XmlAttributeCollection attribs = node.Attributes;

  // XML属性すべてについて
  foreach (XmlAttribute attrib in attribs)
  {
    // "xmlns"で始まる場合
    if (attrib.Name.StartsWith("xmlns"))
    {
      string prefix = null;

      // デフォルトの名前空間の場合
      if (attrib.Name == "xmlns")
      {
        // 代替が指定されていれば代替文字列をプリフィクスにして登録
        if (!string.IsNullOrEmpty(defaultNSSubstitute))
          prefix = defaultNSSubstitute;
        else
          prefix = string.Empty;
      }

      // デフォルト以外の場合
      else if (attrib.Name[5] == ':' && attrib.Name.Length > 6)
        prefix = attrib.Name.Substring(6);

      // 名前空間用辞書にプリフィクスと名前空間のペアを追加
      if (prefix != null)
      {
        namespaces.Add(prefix, attrib.Value);
      }
    }
  }

  return namespaces;
}

昔書いたこのコードを使いまわそうとして、多少書き直しをしておこうと思い、この辺を直してみようと思いました。
  • StringDictionaryをDictinaory<string, string>に。
  • varを使う。
  • foreach+ifのブロックを、Whereを使ってネストを一つ減らす。
DOMのコレクション類は、IEnumerable<T>の実装ではないですが、IEnumerableは実装しているので、拡張メソッドCast<T>()を使えばWhere拡張メソッドも使えます。
public static Dictionary<string, string>
    GetNodeNamespaces(XmlNode node, string defaultNSSubstitute)
{
  var namespaces = new Dictionary<string string>();

  foreach (var attrib in node.Attributes.Cast<XmlAttribute>()
                             .Where(a => a.Name.StartsWith("xmlns")))
  {
    string prefix = null;

    if (attrib.Name == "xmlns")
    {
      if (!string.IsNullOrEmpty(defaultNSSubstitute))
        prefix = defaultNSSubstitute;
      else
        prefix = string.Empty;
    }

    else if (attrib.Name[5] == ':' && attrib.Name.Length > 6)
      prefix = attrib.Name.Substring(6);

    if (prefix != null)
    {
      namespaces.Add(prefix, attrib.Value);
    }
  }

  return namespaces;
}

foreachループの中で、デフォルト名前空間のプレフィックスを決定していますが、よく考えたらforeachの外でも処理できます。さらに、せっかくなのでNULL合体演算子を使いましょう。
public static Dictionary<string, string>
    GetNodeNamespaces(XmlNode node, string defaultNSSubstitute)
{
  var defprefix = defaultNSSubstitute ?? string.Empty;
  var namespaces = new Dictionary<string, string>();

  foreach (var attrib in node.Attributes.Cast<XmlAttribute>()
                             .Where(a => a.Name.StartsWith("xmlns")))
  {
    string prefix = null;

    if (attrib.Name == "xmlns")
      prefix = defprefix;
    else if (attrib.Name[5] == ':' && attrib.Name.Length > 6)
      prefix = attrib.Name.Substring(6);

    if (prefix != null)
    {
      namespaces.Add(prefix, attrib.Value);
    }
  }

  return namespaces;
}

んー。なんかあと一歩な感じがする。もうちょっと考えてみると、foreachループの中で、辞書に突っ込むケースと突っ込まないケースがある。これをそもそもWhereで辞書に突っ込まないケースはフィルタしてしまえばよいかもしれない。

辞書に突っ込むケースというのは、XML属性名が「xmlns」か、「xmlns:」で始まる場合のいずれかのみ。
public static Dictionary<string, string>
    GetNodeNamespaces(XmlNode node, string defaultNSSubstitute)
{
  var defprefix = defaultNSSubstitute ?? string.Empty;
  var namespaces = new Dictionary<string, string>();

  foreach (var attrib in node.Attributes.Cast<XmlAttribute>()
           .Where(a => a.Name == "xmlns" || a.Name.StartsWith("xmlns:")))
  {
    string prefix = null;

    if (attrib.Name == "xmlns")
      prefix = defprefix;
    else
      prefix = attrib.Name.Substring(6);

    namespaces.Add(prefix, attrib.Value);
  }

  return namespaces;
}

これなら三項演算子を使って、変数をインライン化してしまえば、
public static Dictionary<string, string>
    GetNodeNamespaces(XmlNode node, string defaultNSSubstitute)
{
  var defprefix = defaultNSSubstitute ?? string.Empty;
  var namespaces = new Dictionary<string, string>();

  foreach (var attrib in node.Attributes.Cast<XmlAttribute>()
           .Where(a => a.Name == "xmlns" || a.Name.StartsWith("xmlns:")))
  {
    namespaces.Add(attrib.Name == "xmlns" ? 
                   defprefix : attrib.Name.Substring(6), attrib.Value);
  }

  return namespaces;
}
こうなると、IEnumerable<T>をDictionaryに変換しているだけなので、
public static Dictionary<string string>
    GetNodeNamespaces(XmlNode node, string defaultNSSubstitute)
{
  var defprefix = defaultNSSubstitute ?? string.Empty;

  return node.Attributes.Cast<xmlattribute>()
      .Where(a => a.Name == "xmlns" || a.Name.StartsWith("xmlns:"))
      .ToDictionary(a => a.Name == "xmlns" ?
                    defprefix : a.Name.Substring(6), a => a.Value);
}

これで済んじゃうなぁ…。最初のコードとの違いに我ながら笑えました。

2013年3月22日金曜日

忘れたころに『ドリフターズ』3巻

『ドリフターズ』(平野耕太)の3巻が発売されてました。もう一回1~2巻読まないとついてけないぞ。

確か2巻の終わりでは、世界中のいろんな時代で夭折した有名人がわらわらと異世界にやってきたあたりで終わっていたような記憶がある。あってる?

マンガ大賞2013

マンガ大賞2013が決まったそうです。今年は吉田秋生さんの『海街dialy』。おめでとーございまーす!!

とはいえ、今年のノミネート作品。ほとんど読んだことがない。読んでるのは『山賊ダイアリー』くらい。大賞作品位は機会を見つけて読んでみたいな。

2013年3月18日月曜日

ラムダ式を利用したリファクタリングの例 その2

こんな要求があったとします。

  • Shift-JISのCSVファイルを入力し、UTF-8のCSVファイルを出力する。
  • 入力したCSVの各カラムは、固定長で前後に空白が入る可能性があり、その空白は除去して出力する。
  • 各行の先頭のカラムはIDになっていて、特定のIDのみ出力対象とする。
この処理を実現するために、C#でこんなコードを書きました。(『特定のIDのみ出力』の処理は、単純化のため奇数のみ出力するようにしています。)

var sb = new StringBuilder();
string line;

using (var sr = new StreamReader(inFile, Encoding.GetEncoding("shift-jis")))
using (var sw = new StreamWriter(outFile))
{
  while ((line = sr.ReadLine()) != null)
  {
    var items = line.Split(',');
    sb.Length = 0;

    if (int.Parse(items[0]) % 2 != 0)
    {
      foreach (var item in items)
      {
        sb.Append(item.Trim()).Append(',');
      }
      sw.WriteLine(sb.ToString(0, sb.Length - 1));
    }
  }
}

まぁ、これで全く問題はないんですが、もうちょっと書きようはないか考えてみました。試行錯誤の結果、3つのstaticメソッドを作ることで、以下のような、割と好みのコードができました。

var sb = new StringBuilder();

using (var sr = new StreamReader(inFile, Encoding.GetEncoding("shift-jis")))
using (var sw = new StreamWriter(outFile))
{
  sw.WriteLines(sr.GetLineEnumerator()
    .Select(line => line.Split(','))
    .Where(items => int.Parse(items[0]) % 2 != 0)
    .Select(items => sb.Clear().AppendAll(
       items.Select(i => i.Trim() + ",")).ToString(0, sb.Length - 1)));
}

新たに作った3つのstaticメソッドは、以下のものすごくシンプルなものです。

1つめは、TextReaderクラスから各行ごとの読み込みを列挙可能なものにする拡張メソッド。
public static IEnumerable<string> GetLineEnumerator(this TextReader tr)
{
  string s;
  while ((s = tr.ReadLine()) != null)
  {
    yield return s;
  }
}

2つめは、TextWriterクラスに列挙可能な文字列配列等を渡して、各行ごとに出力する拡張メソッド。
public static void WriteLines(
          this TextWriter tw, IEnumerable<string> values)
{
  foreach (var line in values)
  {
    tw.WriteLine(line);
  }
}

3つめは、StringBuilderクラスに列挙可能な文字列配列等を渡して文字列を追記していく拡張メソッド。
public static StringBuilder AppendAll(
          this StringBuilder sb, IEnumerable<string> values)
{
  foreach (var item in values)
  {
    sb.Append(item);
  }
  return sb;
}

個人的にはこれで満足。

『でも結局ライン数もタイプ数も増えちゃってるじゃん。』…まったくもってその通りでございます。

でもでも、この3つのstaticメソッドを使いまわせるようにしておけば、かなり使い出のあるライブラリになるんすよ。

たとえば、テキストファイルを読み取って、文字列配列を作りたければこう書けるし、
using (var reader = new StreamReader(inFile))
{
  return reader.GetLineEnumerator().ToArray();
}

逆に文字列配列をテキストファイルにザーッと出力させたいときはこう書ける。
using (var writer = new StreamWriter(outFile))
{
  writer.WriteLines(lines);
}

あるいはたとえば、ネットワークストリームからの入力をファイルに落とすときはこう。
using (var reader = new StreamReader(netstream))
using (var writer = new StreamWriter(outFile))
{
  writer.WriteLines(reader.GetLineEnumerator());
}


こうやって、手持ちのライブラリを増やしていくと、よりコードを書くのが楽しく、ラクになると思うんす。

ちなみに、ここで一つ悩ましいのは、「TexReader.GetLineEnumerator」のメソッド名。「TextWriter.WriteLines」との対称性があったほうが分かりやすいんですが、「TextReader.ReadLines」としちゃうと、 その場でReadしてstring[]の戻りの型を予想しちゃうので、遅延実行のイメージがまるでなくなっちゃうため、あえて対称性を崩す形にしてます。が、これが正解かどうかはわからんですね。

もひとつちなみに、このコードはVS2008/.net 3.5だと失敗します。なんと、「StringBuilder.Clear」メソッドが.net 4.0以降にしかないのです。が、それならClearメソッドをStringBuilderクラスの拡張メソッドで作っちゃえば問題なしです。なんとかなります!


(2013/06/23 追記)
String.Joinを使えば、StringBuilderの拡張メソッドは不要でした。「SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。」で別記事で書きました。

(2015/10/22 追記)
FileクラスにstaticメソッドのReadLines/ReadAllLines/WriteAllLinesというテキスト行操作メソッド群があったんですね。今日の今日まで知りませんでした。はずかしいいいい。
そんなわけで最新の記事は「SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。(改)」に書き直しました。

2013年3月17日日曜日

今ハズセナイ連載中漫画 (2013年春版)


今楽しみにしている、連載中漫画リスト。さすがに全部掲載誌で読んでるわけじゃないけど。
  • 週刊少年マガジン
    • あひるの空/日向武史
    • ベイビーステップ/勝木光
    • アゲイン!!/久保ミツロウ
    • 七つの大罪/鈴木央
    • 波打際のむろみさん/名島啓二
    • 我妻さんは俺のヨメ/蔵石ユウ・西木田景志
  • 週刊少年サンデー
    • 神のみぞ知るセカイ/若木民喜
    • マギ/大高忍
    • 銀の匙 Silver Spoon/荒川弘
    • 絶対可憐チルドレン/椎名高志
    • ハヤテのごとく!/畑健二郎
    • 最後は?ストレート!!/寒川一之
  • ヤングマガジン
    • センゴク一統記/宮下英樹
    • 雪にツバサ/高橋しん
    • エイト/楠みちはる
    • 8♀1♂/咲香里
  • ビックコミックスピリッツ
    • アイアムアヒーロー/花沢健吾
    • とめはねっ!/河合克敏
    • くーねるまるた/高尾じんぐ
    • あさひなぐ/こざき亜衣
  • ビックコミックスペリオール
    • 人生画力対決/西原理恵子
  • モーニング
    • GIANT KILLING/ツジトモ・網本将也
    • 宇宙兄弟/小山宙哉
    • グラゼニ/森高夕次・アダチ ケイジ
    • ひらけ駒!/南Q太
    • 鬼灯の冷徹/江口夏美
    • ピアノの森/一色まこと
    • ライスショルダー/なかいま強
  • イブニング
    • 少女ファイト/日本橋ヨヲコ
    • いとしのムーコ/みずしな孝之
    • ADAMAS/皆川亮二
    • ジャポニカの歩き方/西山優里子
    • プロチチ/逢坂えみ子
    • もやしもん/石川雅之
    • Eから弾きな。/佐々木拓丸
  • アフタヌーン
    • ヒストリエ/岩明均
    • ヴィンランド・サガ/幸村誠
  • ゲッサン
    • MIX/あだち充
    • アオイホノオ/島本和彦
  • グランドジャンプ
    • 瞬きのソーニャ/弓月光
    • ぼくの体はツーアウト/よしたに
    • アントルメティエ/早川光・きたがわ翔
  • その他
    • こどものじかん/私屋カヲル
    • 3月のライオン/羽海野チカ
    • ましろのおと/羅川真里茂
    • 純潔のマリア/石川雅之
    • 白衣のカノジョ/日坂水柯
    • kiss×sis/ぢたま某
    • よつばと!/あずまきよひこ

2013年3月14日木曜日

URI中の指定キーに対応するクエリ文字列の値を取り出す処理をLINQで。UTもついでに。

以前書いた、こんなコードがありました。

string valueKey1 = null;

foreach (string query in uri.Query.Split('&')) // uriはUriオブジェクト
{
  string[] kv = query.Split('=');

  if (kv.Length < 2)
    continue;
  if (kv[0].StartsWith("?"))
    kv[0] = kv[0].Substring(1);

  if (kv[0] == "key1")
  {
    valueKey1 = kv[1];
    break;
  }
}

要は、URIのクエリ文字列から、指定のパラメータの値を取り出す処理です。これをもう少し今っぽいコードにして、ついでに汎用化(ライブラリ化)して、さらについでにユニットテストまでやってしまおうと目論みました。

VS Express 2012 を起動し、新規プロジェクトをソリューション付きで、クラスライブラリをターゲットに作成します。


ライブラリ化にあたっての方針はこんな感じ。

  • Uriクラスの拡張メソッドにする。
  • パラメータ名を指定して値を取り出すメソッドを実装。
  • このメソッドにはオプショナルで、Uriエスケープの有無を指定できるようにする。ただし、VS2008とかでもソースをそのまま使えるように、メソッドのオーバーロードで対応する。
  • Uriエスケープありの場合、Uriエスケープをほどいた値を返す。

こうしてみました。

public static class UriExtensions
{
  public static string FindQuery(this Uri uri, string key)
  {
    return FindQuery(uri, key, true);
  }

  public static string FindQuery(this Uri uri, string key, bool uriEscape)
  {
    throw new NotImplementedException();
  }
}

テストコードを書きます。「テスト」メニューを開いてみると、「新しいテスト」メニューがないのね…。んじゃ、「新しいプロジェクト」で単体テストプロジェクトを、ソリューションに追加するように作成。


デフォルトで作成されたテストクラスの名前を変更して、参照設定にテスト対象プロジェクトを追加。さらにUsingを指定してテスト準備OK。

作成されたテストコードには、初期状態でClassInitializeもTestInitializeも無いんだ…。テストコードがすっきりしてるのはいいけど、いざテストの初期化をしようとしたときに面倒だなぁ。VS2010までで慣れてるといろいろ違和感がある。まぁ、そのうちなれるかな。

気を取り直して、テストコードを書き始めます。この辺が網羅できればいいでしょ。

  • Uriクエリ文字列のパラメータ数が、0、1、2以上。
  • 指定したキーがヒットする、しない。
  • Uriエスケープされている、されていない。
  • Uriエスケープのオプション指定をする、しない。

書いてみたテストコードがこれです。
[TestClass]
public class UriExtensionsTest
{
  [TestMethod]
  public void FindParamValueTest()
  {
    Uri u1 = new Uri("http://unkkown/test.html");
    Assert.IsNull(u1.FindQuery("unknownkey"));

    Uri u2 = new Uri("http://unknown/test.html?key1=xxx&key2=yyy&key3=zzz");
    Assert.IsNull(u2.FindQuery("key4"));
    Assert.AreEqual("yyy", u2.FindQuery("key2"));

    string key = "%E3%82%AD%E3%83%BC"; // "キー"のURIエンコード(UTF-8)
    string value = "%E5%80%A4"; // "値"のURIエンコード(UTF-8)
    Uri u3 = new Uri("http://unknown/test.html?" + key + "=" + value);
    Assert.AreEqual("値", u3.FindQuery("キー"));
    Assert.AreEqual(value, u3.FindQuery(key, false));
  }
}

テストを実行すると失敗して、「NotImplementedException」がスローされたとのこと。予定通り。ここからUriExtensionsクラスを実装します。試行錯誤の結果、テストをパスして出来上がったコードがこれ。

public static class UriExtensions
{
  public static string FindQuery(this Uri uri, string key)
  {
    return FindQuery(uri, key, true);
  }

  public static string FindQuery(this Uri uri, string key, bool uriEscape)
  {
    string query = uri.Query;
    string keyName = uriEscape ? Uri.EscapeDataString(key) : key;
    Func<string, string> vconv = s => s;

    if (string.IsNullOrEmpty(query))
      return null;
    if (query.StartsWith("?"))
      query = query.Substring(1);
    if (uriEscape)
      vconv = s => Uri.UnescapeDataString(s);

    return query.Split('&').Where(q => q.StartsWith(keyName + "="))
       .Select(q => vconv(q.Substring(keyName.Length + 1)))
        .FirstOrDefault();
  }
}

こんな感じでしょうかね。うん。 あとはコードコメントを書いてリリースビルドすれば作業完了です。そこは省略。

2013年3月13日水曜日

今週のイブニング

ここ最近のイブニングの面白さったら!! アタリが多くて読むのに時間がかかる。

問題は、表紙に『CAPTAIN アリス』と書いているのに、普通に休載してること。単行本発売だからだろうけど、わかりづらいよ。

あるフォルダ以下の全ファイルサイズの合計を求める処理をLINQで。

サンプルとして、あるフォルダ以下のすべてのファイルの、合計サイズを算出するアプリケーションを作ります。

VS2012 Express for Desktopを使って、コンソールアプリケーションを作り、第一引数に総サイズを取得したいフォルダを指定するものとします。

フォルダ以下のフォルダの列挙、ファイルの列挙はSystem.IO.Directoryクラスを、ファイルのサイズを取得するにはSystem.IO.FileInfoクラスを使います。そして、フォルダ以下のすべてのファイルを取得するには再帰を使う必要があるでしょう。

出来上がったコードはこちら。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace GetTotal
{
  class Program
  {
    static void Main(string[] args)
    {
      if(args.Length < 1)
      {
        Console.Error.WriteLine("Less argument.");
        return;
      }

      try
      {
        Func<string, long> gettotal = null;
        gettotal = p =>
            Directory.GetDirectories(p).Select(f => gettotal(f)).Sum() +
            Directory.GetFiles(p).Select(f => new FileInfo(f).Length).Sum();
        Console.WriteLine("Size: {0:#,0} Bytes", gettotal(args[0]));
      }
      catch(Exception e)
      {
          Console.Error.WriteLine(e.Message);
      }
    }
  }
}

VS2012のプログラムフォルダで試してみました。


うん。うまくいった。


でも、21~24行目は本当はこう書きたかった。

Func<string, long> gettotal = p => 
    Directory.GetDirectories(p).Select(f => gettotal(f)).Sum() +
    Directory.GetFiles(p).Select(f => new FileInfo(f).Length).Sum();

『未割当のローカル変数 'gettotal' が使用されました。』と怒られるのでした。仕方ない。ぐぅ。

2013年3月12日火曜日

Googleサイト

Googleサイトってのもあるのね。ここで書こうとしている内容からすると、こっちのほうが向いていたかもしれないな。

ひとまずBloggerで書き溜めておいて、溜まってきて、さらに気が向いたら移すことを考えてみようかな。

2013年3月10日日曜日

ラムダ式を利用したリファクタリングの例


ラムダ式とは?


ラムダ式とは、.net Framework 3.5(Visual Studio 2008)以降で採用された、「デリゲートを簡潔に書くための構文」だと思ってもらえればよいと思います。

デリゲートとは…。C/C++をかじったことのある人なら、「コールバック関数」や「関数ポインタ」にあたります。VBならイベントを通知する仕組みが近いです。かじってない人は…。関数(処理)をオブジェクトにして、オブジェクト化された関数を別のところで呼び出す仕組み、とでも言えばいいでしょうか…。

馴染みがないと非常にわかりにくいと思いますので、サンプルを2つほど挙げてみます。

ラムダ式を使ったリファクタリングのサンプル (1)


設計


まずは設計から。

  • クラスXxxAccessorは、ストレージに対してInsert/Delete/Updateが可能。
  • Insert/Delete/Updateの各処理は、すべて以下の順序で行う。
    • プロキシオブジェクトの生成
    • ログ出力(親クラスのメソッドを使用)
    • プロキシオブジェクト経由でのInsert/Delete/Update
    • プロキシオブジェクトの破棄
    • 例外の発生有無にかかわらず、クリーンナップ(親クラスのメソッドを使用)
  • Insert/Delete/Updateの各処理は、例外発生時はその例外をそのまま外にスローする。

実装


上記の設計に対して、以下のように実装しました。

public class NoLambdaAccessor : Accessor
{
  public void Insert(string line)
  {
    try
    {
      using (AbstractProxy proxy = AbstractProxy.Create())
      {
        Log("start");
        proxy.Insert(line);
      }
    }
    finally
    {
      Cleanup();
    }
  }

  public void Delete(int id)
  {
    try
    {
      using (AbstractProxy proxy = AbstractProxy.Create())
      {
        Log("start");
        proxy.Delete(id);
      }
    }
    finally
    {
      Cleanup();
    }
  }

  public void Update(int id, string line)
  {
    try
    {
      using (AbstractProxy proxy = AbstractProxy.Create())
      {
        Log("start");
        proxy.Update(id, line);
      }
    }
    finally
    {
      Cleanup();
    }
  }
}

見ての通り、3つのメソッドはほぼ同じ処理で、プロキシオブジェクト経由の呼び出し処理の行のみが異なっています。

追加の設計


上記コードに対して、今度はInsert(string line, int number)と、Update(int id, string line, int number)の2つのオーバーロードを追加する必要が生じました。

現状のまま2つのメソッドを追加してもよいのですが、リファクタリングを実施して冗長な部分を除いてから追加したいと思いました。

さて、どうリファクタリングしましょうか?

リファクタリング


リファクタリングの仕方はいろいろあると思いますが、ラムダ式を使って以下の様にリファクタリングしてみました。

public class LambdaAccessor : Accessor
{
  private void Access(Action<AbstractProxy> action)
  {
    try
    {
      using (AbstractProxy proxy = AbstractProxy.Create())
      {
        Log("start");
        action(proxy);
      }
    }
    finally
    {
      Cleanup();
    }
  }

  public void Insert(string line)
  {
    Access(proxy => proxy.Insert(line));
  }

  public void Delete(int id)
  {
    Access(proxy => proxy.Delete(id));
  }

  public void Update(int id, string line)
  {
    Access(proxy => proxy.Update(id, line));
  }
}

戦略としては、
  • 3つのメソッドに共通するコードのうち、異なる部分をデリゲートにしたプライベートメソッド「Access」を用意する。
  • 3つのメソッドは、個別の処理をラムダ式で表現したデリゲートを用意し、そのデリゲートを引数として上記Accessメソッドを呼び出す。

これならぱっとソースコードを眺めてみたとき、それほど違和感はないんじゃないかと思いますが、どうでしょう?

また、このようにリファクタリングしておくことで、2つのオーバーロードメソッドを追加するにしても、中身が1行のメソッドを2つ追加すれば対応完了となります。(もちろん、対応するユニットテストコードは書く必要がありますが。)

なお、Accessメソッドの引数「Action<Xxx>」は、.net Frameworkで定義済みのデリゲートです。以下の様に定義されています。

namespace System { public delegate void Action<T>(T obj); }

ラムダ式を使ったリファクタリングのサンプル (2)


設計


こちらも設計から。

  • クラスInputCheckerは、複数のコントロールの入力チェックを行う。
  • コントロールを示すクラスControlは、Tagプロパティ、Textプロパティ、Validプロパティを持ち、それぞれのプロパティは以下の通りの意味を持つ。
    • Textプロパティはユーザの入力文字列を保持する。
    • Tagプロパティは「require」、「number」、それ以外のいずれかの文字列で、「require」の場合入力は必須。「number」の場合数字のみ入力可。それ以外の場合は入力制限は特になし。
    • Validプロパティは入力チェックの結果NGの場合falseとなる。(この時コントロールの背景はピンク色で表示される。)
  • InputCheckメソッドはコントロールの配列を受け取り、以下の処理を行う。
    • コントロール配列のすべてについて、以下の処理を行う。
      • コントロールのTagがrequireで、Textが空の場合、Validをfalseにする。
      • すべてのコントロールのうち、一つでもValidがfalseのものがある場合、適切なエラーメッセージを表示して処理を返す。
    • 再びコントロール配列のすべてについて、以下の処理を行う。
      • コントロールのTagがnumberで、Textが数字以外が含まれている場合、Validをfalseにする。
      • すべてのコントロールのうち、一つでもValidがfalseのものがある場合、適切なエラーメッセージを表示して処理を返す。

実装


ちょっと面倒な設計ですが、以下のように実装しました。

public class NoLambdaInputChecker : InputChecker
{
  public void InputCheck(List<Control> controls)
  {
    bool allvalid = true;
    int result;

    foreach (Control c in controls)
    {
      if (c.Tag == "require")
      {
        if (string.IsNullOrEmpty(c.Text))
        {
          allvalid = false;
          c.Valid = false;
        }
      }
    }

    if (!allvalid)
    {
      Msg("require field is not set");
      return;
    }

    foreach (Control c in controls)
    {
      if (c.Tag == "number")
      {
        if (!int.TryParse(c.Text, out result))
        {
          allvalid = false;
          c.Valid = false;
        }
      }
    }

    if (!allvalid)
    {
      Msg("not a number");
    }
  }
}

見ての通り、似たような処理が縦に2つ並んでますね。

追加の設計


上記コードに対して、今度は「Tagプロパティがdateだった時に、Textプロパティは数字のみ8ケタでない、または、yyyyMMdd形式で日付として正しくない場合はValidプロパティをfalseにする」という仕様追加を行う必要が生じました。

現状のままInputCheckメソッドを修正してもよいのですが、リファクタリングを実施して冗長な部分を除いてから追加したいと思いました。

さて、どうリファクタリングしましょうか?

リファクタリング


これもリファクタリングの仕方はいろいろあると思いますが、ラムダ式を使って以下の様にリファクタリングしてみました。

public class LambdaInputChecker : InputChecker
{
  private bool InputCheckByCondition
        (List<Control> controls, Func<Control, bool> condition)
  {
    bool allvalid = true;
    foreach (Control c in controls.Where(c => condition(c)))
    {
      allvalid = false;
      c.Valid = false;
    }
    return allvalid;
  }

  public void InputCheck(List<Control> controls)
  {
    int result;

    if (InputCheckByCondition(controls, c =>
        c.Tag == "require" && string.IsNullOrEmpty(c.Text)))
    {
      Msg("require field is not set");
      return;
    }

    if (InputCheckByCondition(controls, c =>
        c.Tag == "number" && !int.TryParse(c.Text, out result)))
    {
      Msg("not a number");
      return;
    }
  }
}

戦略としては、
  • 縦に並んでいる似たようなコードのうち、条件判断の部分のみをデリゲートにしたプライベートメソッド「InputCheckByCondition」を用意する。
  • 縦に並んだ2つの似たような処理は、個別の条件判断をラムダ式で表現したデリゲートを用意し、そのデリゲートを引数として上記InputCheckByConditionメソッドを呼び出す。

前のサンプルよりは複雑ですが、元のコードと比べてこっちのほうがぱっと見わかりやすいんじゃないかと思いますが、どうでしょう?

そして、このようにリファクタリングしておくことで、追加の仕様変更に対しては、条件判断と表示メッセージのみを変更した一つのifブロックを追加すればOKとなります。

なお、InputCheckByConditionメソッドの引数「Func<Xxx, Yyy>」は、.net Frameworkで定義済みのデリゲートです。以下の様に定義されています。

namespace System { public delegate TResult Func<T, TResult>(T arg); }

まとめ


上記で上げた2つのサンプルのようなケースでは、最初からラムダ式を検討する必要はないと思いますが、リファクタリングの際に威力を発揮します。

また、使ってみればわかりますが、Visual StudioのIntelliSenseが大変賢く、ラムダ式の中でもちゃんと効いてくれるため、楽に使うことができます。

機能追加の際などに、コードを見て冗長に見えて、リファクタリングを考えてみてもうまくいきそうにないとき、「ラムダ式で、一部の処理や条件判断を置き換えられるようにしたらどうか?」と考えてみると、うまくリファクタリングできるケースが多いです。