業務でファイルを一行ずつ読み込んで処理するプログラムをコーディングしているのですが、提供された設計書のフローチャートに従って実装を進めていたら、脳みそがぐちゃぐちゃにかき回されるようないらだちを覚えました。
"ファイルを一行ずつ読み込んで処理する"という要件は、あちこちで需要がありそうなので、モデルを考えてみました。
Process、FileFinder、FileHandler、LineHandlerの4つのクラスで構成します。
2014年9月29日月曜日
2014年9月22日月曜日
VB.NETを使い始めて戸惑ったこと3つ
最近VB.NETを使っているのですが、いくつか戸惑ったことがあったので紹介します。
Forループの終了条件
処理を10回繰り返そうとして次のようにFor文を記述します。
charリテラル
VB.NETでは、charリテラルを次のように記述します。
抽象クラスの宣言
VB.NETでは、抽象クラスを次のように記述します。
Forループの終了条件
処理を10回繰り返そうとして次のようにFor文を記述します。
For i = 0 To 10 Console.WriteLine(i) Next実行すると、11回実行されます。Toもループ変数の範囲に含むんですね。
charリテラル
VB.NETでは、charリテラルを次のように記述します。
Dim c As Char = "A"c""c…。
抽象クラスの宣言
VB.NETでは、抽象クラスを次のように記述します。
Public MustInherit Class AbstractClass End ClassMustInheritは「継承しないと使えないよ」という意味でしょうか。
2014年9月15日月曜日
JScriptからInternetExplorerを操作してYahoo!JAPANを検索する
JScriptからIEを操作し、Yahoo!JAPANで"ほどよくしっかり"を検索させてみました。
ちゃんと、このブログが先頭に表示されました。
JScriptからIEを操作できるということは、標準入力と標準出力を使ってIEを操作できるということで、諸々の自動化が捗るかもしれないということです。興味深い。
以下、コードです。
ちゃんと、このブログが先頭に表示されました。
JScriptからIEを操作できるということは、標準入力と標準出力を使ってIEを操作できるということで、諸々の自動化が捗るかもしれないということです。興味深い。
以下、コードです。
2014年9月8日月曜日
.NETで改行を含む文字列を組み立てるならStringWriterがおすすめ
プログラムの実行時に文字列を組み立てるなら、StringBuilderクラスを使うのが定石です。しかし、改行で区切った文字列を組み立てようとすると、どうにも見苦しくなってしまいます。
そんなときはStringWriterクラスを使うのがお勧めです。
コードがスッキリ見やすくなりました。
参照
StringWriterクラス
StringBuilderクラス
var sb = new StringBuilder() .Append("select *").Append(Environment.NewLine) .Append(" from Table1").Append(Environment.NewLine) .Append(" where Column1 = @Value1").Append(Environment.NewLine);
そんなときはStringWriterクラスを使うのがお勧めです。
var sw = new StringWriter(); sw.WriteLine("select *"); sw.WriteLine(" from Table1"); sw.WriteLine(" where Column1 = @Value1");
コードがスッキリ見やすくなりました。
参照
StringWriterクラス
StringBuilderクラス
2014年9月1日月曜日
ADO.NETのData ProviderでRDBへの依存をカプセル化するにはDbProviderFactoryを使う
これまでに経験したシステム開発では、DBを決めずに開発が始まることはなかったので、必要になる場面はなかなかないと思いますが、DbProviderFactoryを使えばRDBに依存しないコードが書けます。
次に例を示します。
class Class1 { private readonly DbProviderFactory factory; private readonly string connectionString; public Class1(DbProviderFactory factory, string connectionString) { this.factory = factory; this.connectionString = connectionString; } public List<object> Method1() { using (var conn = factory.CreateConnection()) using (var command = factory.CreateCommand()) { conn.ConnectionString = connectionString; conn.Open(); command.Connection = conn; command.CommandText = "select * from Table1 where ID = @ID"; var param = factory.CreateParameter(); param.ParameterName = "ID"; param.Value = "ID1"; command.Parameters.Add(param); var reader = command.ExecuteReader(); var result = new List<object>(); while (reader.Read()) { var arr = new object[reader.FieldCount]; reader.GetValues(arr); result.Add(arr); } return result; } }
実際にはSQLもRDBに合わせてコーディングしないといけないので、DbProviderFactoryだけでは不十分と思います。
2014年8月25日月曜日
ASP.NET開発を始めるにあたって参考にした記事3つ
ASP.NETアプリケーション開発に取り組んで1ヶ月になりました。
開発も終盤にさしかかってきたので、忘れないうちに開発スタート時に参考にした記事を3つ紹介します。
@IT プログラミングASP.NET
ASP.NETについて、基本から解説してあります。(連載なので、正確には1記事ではないかも)
全体を把握するのにおすすめです。特に重要だと感じたキーワードを挙げておきます。
ASP.NET Page Life Cycle Overview
ASP.NETプログラミングの特徴のひとつに、イベントドリブン方式で実装することがあります。この特徴を活用するには、イベントの順番を把握しなければなりません。この記事ではそれが紹介されています。
今回のプロジェクトで主に使用したイベントと、用途を紹介します。
ASP.NET Application Life Cycle Overview for IIS 7.0
上で紹介したライフサイクルよりも、大きなサイクルについての記事です。アプリケーションやセッションの初期処理を組み込もうとするときに参考になります。
これまで食わず嫌いでASP.NETには手をつけてこなかったのですが、実際に開発をしてみるとすんなり進められました。今後は、ASP.NET案件にも積極的に取り組んでいこうと思います。
開発も終盤にさしかかってきたので、忘れないうちに開発スタート時に参考にした記事を3つ紹介します。
@IT プログラミングASP.NET
ASP.NETについて、基本から解説してあります。(連載なので、正確には1記事ではないかも)
全体を把握するのにおすすめです。特に重要だと感じたキーワードを挙げておきます。
- ポストバック
- ビューステート
- データバインド
ASP.NET Page Life Cycle Overview
ASP.NETプログラミングの特徴のひとつに、イベントドリブン方式で実装することがあります。この特徴を活用するには、イベントの順番を把握しなければなりません。この記事ではそれが紹介されています。
今回のプロジェクトで主に使用したイベントと、用途を紹介します。
- Page_Init…ページやコントロールのフィールドを初期化する。オブジェクトをセッションから取得するなど。
- Page_Load…コントロールの初期化をする。ドロップダウンリストの項目をセットするなど。
- Button_Click…ユーザーの入力値を受け取る。入力値をもとに業務ロジックを呼び出して結果を受け取る。
- Page_PreRender…業務ロジックの結果をコントロールへ反映する。
ASP.NET Application Life Cycle Overview for IIS 7.0
上で紹介したライフサイクルよりも、大きなサイクルについての記事です。アプリケーションやセッションの初期処理を組み込もうとするときに参考になります。
これまで食わず嫌いでASP.NETには手をつけてこなかったのですが、実際に開発をしてみるとすんなり進められました。今後は、ASP.NET案件にも積極的に取り組んでいこうと思います。
2014年8月18日月曜日
Javaでクラス図を書いてみた
Javaでクラス図(1クラスだけ!)を書いてみたの続きになります。
クラス同士の関連を出力できるようにしました。
クラス同士の関連を出力できるようにしました。
上の図が出力に使用したプログラムを出力したものです。
ざっくりとしたイメージを把握する程度の要求は満たせるかな、という感じです。
2014年8月11日月曜日
ASP.NETでJavaScriptからポストバックを発生させる方法
ASP.NETアプリケーションをコーディングしていて、JavaScriptからポストバックしたいというケースがあります。例えば、検索結果の一覧をテーブルで表示しており、任意のセルをクリックした時に詳細を表示する画面へ遷移するケースなどです。
こういう状況に対応できるのが、次のクラスとインターフェイスです。
IPostBackEventHandler
こういう状況に対応できるのが、次のクラスとインターフェイスです。
まず、JavaScriptのイベントハンドラに対して、ClientScriptManager.GetPostBackEventReferenceで取得した文字列を呼び出すように実装します。このとき、引数として渡した文字列が、次で実装するポストバックイベントハンドラの引数になります。ClientScriptManagerオブジェクトは、Page.ClientScriptから取得できます。
class MyPage : Page { protected void Page_PreRender(object sender, EventArgs e) { var args = "xxx"; // 例えば、詳細情報を表示するためのプライマリキー var js = ClientScript.GetPostBackEventReference(this, args); Table1.Rows[0].Cells[0].Attributes.Add("onclick", js); } }
次に、ポストバックを受けるクラスが、IPostBackEventHandler.RaisePostBackEventを実装します。
// 上のクラスへIPostBackEventHandlerを実装 class MyPage : Page, IPostBackEventHandler { ... public void RaisePostBackEvent(string eventArgs) { // 例えばセッションへeventArgsをセットして詳細ページへ遷移する Session.Add("key", eventArgs); var detail = ResolveUrl("~/MyDetailPage.aspx"); Server.Transfer(detail); } }
以上でJavaScriptからのポストバックが機能します。私が最初に思いついたのは、CSSで非表示にしたボタンをJavaScriptからクリックするという方法でした。しかし、こちらのほうが何倍もスマートだと思います。
ClientScriptManagerには、他にも様々なメソッドがあります。まだまだ便利な機能がありそうです。
参考:
ClientScriptManagerIPostBackEventHandler
2014年8月4日月曜日
ASP.NETアプリケーションの起動時にWeb.configファイルを読み込むには
ASP.NETアプリケーションの起動時に設定ファイルを読み込み、システムの初期設定をしたいというケースがあると重います。
設定ファイルを読み込むための、WebConfigurationManager.OpenWebConfigurationメソッドには、引数として設定ファイルの仮想パスが必要です。仮想パスの取得方法として、ASP.NET Web プロジェクトの仮想パスに手順が記載されていますが、アプリケーションの起動時には適用できません。なぜなら、紹介されている方法ではHttpRequestクラスを使用するのですが、アプリケーション起動時には、HttpRequestオブジェクトが使える状態にないからです。
そこで、代わりに使用するのが、HttpRuntimeクラスです。このクラスの、AppDomainVirtualPathプロパティから、アプリケーションの仮想パスを取得できるので、あとはファイル名を結合するだけです。結合には、VirtualPathUtilityクラスを使うとベターでしょう。
仮想パスをいい加減に扱うと、サーバーへインストールしたら動かなくなることがあります。HttpRuntimeクラスを使い、環境の変化に強いコードを書きましょう。
設定ファイルを読み込むための、WebConfigurationManager.OpenWebConfigurationメソッドには、引数として設定ファイルの仮想パスが必要です。仮想パスの取得方法として、ASP.NET Web プロジェクトの仮想パスに手順が記載されていますが、アプリケーションの起動時には適用できません。なぜなら、紹介されている方法ではHttpRequestクラスを使用するのですが、アプリケーション起動時には、HttpRequestオブジェクトが使える状態にないからです。
そこで、代わりに使用するのが、HttpRuntimeクラスです。このクラスの、AppDomainVirtualPathプロパティから、アプリケーションの仮想パスを取得できるので、あとはファイル名を結合するだけです。結合には、VirtualPathUtilityクラスを使うとベターでしょう。
var path = VirtualPathUtility.Combine(HttpRuntime.AppDomainVirtualPath, "Web.config"); var conf = WebConfigurationManager.OpenWebConfiguration(path);
仮想パスをいい加減に扱うと、サーバーへインストールしたら動かなくなることがあります。HttpRuntimeクラスを使い、環境の変化に強いコードを書きましょう。
2014年7月28日月曜日
Javaでクラス図(1クラスだけ!)を書いてみた
グラフを書くためのライブラリの一つに、Graphvizというものがあります。
調べてみたところ、クラス図を書くこともできるようなので、Javaのリフレクションを使ってクラス図を書いてみました。
まだ単体のクラスを出力できるだけですが、関連を出力できるようになるとなかなかおもしろそうです。
参照
Graphviz
UML Diagrams Using Graphviz DOT
調べてみたところ、クラス図を書くこともできるようなので、Javaのリフレクションを使ってクラス図を書いてみました。
まだ単体のクラスを出力できるだけですが、関連を出力できるようになるとなかなかおもしろそうです。
参照
Graphviz
UML Diagrams Using Graphviz DOT
2014年7月21日月曜日
C#でアプリケーション設定ファイルを拡張する方法
.NETには標準で設定ファイルを読み込むためのAPIが公開されています。
そのままでも単純なキー=値形式の定義できます。
カスタマイズするには、主に次の3つのクラスを使用します。
カスタマイズした設定項目のルートになるクラスです。
ConfigurationElementクラス
設定値をもつXMLエレメントに対応するクラスです。
ConfigurationElementCollectionクラス
複数のConfigurationElementをまとめるクラスです。
そのままでも単純なキー=値形式の定義できます。
カスタマイズするには、主に次の3つのクラスを使用します。
- ConfigurationSectionクラス
- ConfigurationElementクラス
- ConfigurationElementCollectionクラス
カスタマイズした設定項目のルートになるクラスです。
class NetworkConfigurationSection : ConfigurationSection { [ConfigurationProperty("LocalHost")] public NetworkConfigElement LocalHost { get { return base["LocalHost"] as NetworkConfigElement; } set { base["LocalHost"] = value; } } [ConfigurationProperty("Networks")] public NetworkConfigElementCollection Networks { get { return base["Networks"] as NetworkConfigElementCollection; } } }
ConfigurationElementクラス
設定値をもつXMLエレメントに対応するクラスです。
class NetworkConfigElement : ConfigurationElement { [ConfigurationProperty("IPAddress")] public string IPAddress { get { return base["IPAddress"] as string; } set { base["IPAddress"] = value; } } }
ConfigurationElementCollectionクラス
複数のConfigurationElementをまとめるクラスです。
class NetworkConfigElementCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement() { return new NetworkConfigElement(); } protected override object GetElementKey(ConfigurationElement element) { NetworkConfigElement elm = element as NetworkConfigElement; if (null == elm) return null; return elm.IPAddress; } }
2014年7月14日月曜日
ちょっとしたツールを作るのに便利なPythonのcmdモジュールを使うための3ステップ
シンプルなコマンドラインツールが必要なとき、Pythonのcmdモジュールが有力な選択肢になります。ツールを実装するために必要な3ステップを紹介します。
以下Tips。必須ではありませんが、知っておくと便利です。
- cmd.Cmdクラスを継承したクラスを作る
- do_XXXメソッドを定義する
- cmdloopメソッドを呼ぶ
cmd.Cmdクラスを継承したクラスを作る
特に言うべきことはありません。普通に継承するだけです。
class HelloCmd(cmd.Cmd): ...
do_XXXメソッドを定義する
メソッド名の"XXX"がコマンドになります。
パラメータを受け取るためのargパラメータがポイントです。
def do_hello(self, arg): print 'hello, ' + str(arg)
cmdloopメソッドを呼ぶ
クラスをインスタンス化し、cmdloopを呼ぶとREPLがスタートします。
終了するにはCtrl-Cで落とします。
>>> HelloCmd().cmdloop() (Cmd) hello Taro Hello, Taro (Cmd) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python2.7/cmd.py", line 130, in cmdloop line = raw_input(self.prompt) KeyboardInterrupt
以下Tips。必須ではありませんが、知っておくと便利です。
2014年7月7日月曜日
Google GuiceのBest Practicesを訳してみた - Guiceがインスタンス化するクラスのコンストラクタはできるだけ隠蔽する
Guiceがインスタンス化するクラスのコンストラクタはできるだけ隠蔽する
次のシンプルなインターフェイスについて考える。
一般的には、次のようなpublicクラスで実装することだろう。
ちょっと見ただけでは、このコードには何も問題がないようにみえる。不幸なことに、
よく知られたことだが、コンストラクタをpublicにするといいことは何もない。publicコンストラクタは、ソースとともに誤った使用方法を広めてしまう。誤った使用方法には次のようなデメリットがある。
正すためには、単純に実装クラスとコンストラクタの両方の可視性を制限すればよい。通常はパッケージプライベートを選択すればよく、次のような効果が得られる。
参照
Keep constructors on Guice-instantiated classes as hidden as possible.
次のシンプルなインターフェイスについて考える。
public interface DataReader { Data readData(DataSource dataSource); }
一般的には、次のようなpublicクラスで実装することだろう。
public class DatabaseDataReader implements DataReader { private final ConnectionManager connectionManager; @Inject public DatabaseDataReader( ConnectionManager connectionManager) { this.connectionManager = connectionManager; } @Override public Data readData(DataSource dataSource) { // ... read data from the database return Data.of(readInData, someMetaData); } }
ちょっと見ただけでは、このコードには何も問題がないようにみえる。不幸なことに、
よく知られたことだが、コンストラクタをpublicにするといいことは何もない。publicコンストラクタは、ソースとともに誤った使用方法を広めてしまう。誤った使用方法には次のようなデメリットがある。
- リファクタリングが困難になる
- インターフェースによる抽象化を破壊する
- ソースとの結びつきを強める
正すためには、単純に実装クラスとコンストラクタの両方の可視性を制限すればよい。通常はパッケージプライベートを選択すればよく、次のような効果が得られる。
- クラスを同じパッケージ内のModuleでバインドする
- クラスを対象とする単体テストは、直接インスタンス化する
参照
Keep constructors on Guice-instantiated classes as hidden as possible.
2014年6月30日月曜日
Google GuiceのBest Practicesを訳してみた - Moduleでは条件分岐を避けよ
Moduleでは条件分岐を避けよ
Moduleに変化する部品をもたせることと、異なった環境で異なった動作をするように定義するのは推奨されない。
このことからわかるのは、アプリケーションの設定項目は最小限にすべきだということだ。開発環境と本番環境を別々のModuleへ分割すると、本番コードの全てがテストされているのを証明するのはより容易になる。この例では、FooModuleをRemoteFooModuleとInMemoryFooModuleへ分割する。これはまた、製品版のクラスがテストコードに依存しなくなる効果もある。
参照
Avoid conditional logic in modules
Moduleに変化する部品をもたせることと、異なった環境で異なった動作をするように定義するのは推奨されない。
public class FooModule { private final String fooServer; public FooModule() { this(null); } public FooModule(@Nullable String fooServer) { this.fooServer = fooServer; } @Override protected void configure() { if (fooServer != null) { bind(String.class).annotatedWith(named("fooServer")).toInstance(fooServer); bind(FooService.class).to(RemoteFooService.class); } else { bind(FooService.class).to(InMemoryFooService.class); } } }条件分岐自身にはそれほど問題はない。しかし、設定がテストされていない時に問題は現れる。この例では、InMemoryFooServiceは開発環境で使用され、RemoteFooServiceは本番環境で使用される。しかし、この特定の状況をテストしていないと、RemoteFooServiceが統合されたアプリケーションと動作するかは保証できない。
このことからわかるのは、アプリケーションの設定項目は最小限にすべきだということだ。開発環境と本番環境を別々のModuleへ分割すると、本番コードの全てがテストされているのを証明するのはより容易になる。この例では、FooModuleをRemoteFooModuleとInMemoryFooModuleへ分割する。これはまた、製品版のクラスがテストコードに依存しなくなる効果もある。
参照
Avoid conditional logic in modules
2014年6月23日月曜日
Google GuiceのBest Practicesを訳してみた - Providerでの入出力に注意せよ
Providerの入出力に注意せよ
Providerは便利だが、以下の機能を欠いている。
参照
Be careful about I/O in Providers
Providerは便利だが、以下の機能を欠いている。
- Providerはチェック例外を宣言しない。特定のエラーからの復帰しなければいけないコードを書いているのなら、TransactionRolledbackExceptionはcatchできない。ProvisionExceptionにより、一般的な生成エラーから復旧でき、その原因を列挙することもできる。しかし、それらの原因を指定することはできない。
- Providerはタイムアウトをサポートしない。
- Providerはリトライ戦略を定義しない。値が有効でないとき、何度もget()を呼ぶと、何度も失敗することになるだろう。
参照
Be careful about I/O in Providers
2014年6月16日月曜日
Google GuiceのBest Practicesを訳してみた - Moduleは高速で副作用がないほうがよい
Moduleは高速で副作用がないほうがよい
Guiceのモジュールは、設定にXMLファイルではなく、Javaコードを使用する。Javaは親しみやすく、IDEの機能を活用でき、リファクタリングにも対応できる。
しかし、Javaの力はコストももたらす。つまり、Moduleでやりすぎてしまうのだ。GuiceのModuleでは、データベースへ接続し、HTTPサーバーを起動することもできる。ダメだ!Moduleで困難な仕事をすると、問題が発生する。
参照
Modules should be fast and side-effect free
Guiceのモジュールは、設定にXMLファイルではなく、Javaコードを使用する。Javaは親しみやすく、IDEの機能を活用でき、リファクタリングにも対応できる。
しかし、Javaの力はコストももたらす。つまり、Moduleでやりすぎてしまうのだ。GuiceのModuleでは、データベースへ接続し、HTTPサーバーを起動することもできる。ダメだ!Moduleで困難な仕事をすると、問題が発生する。
- Moduleは起動するが、終了しない ― データベース接続を開いたとき、その接続を閉じるフックはない
- Moduleはテストされるべき ― 実行時にデータベースへ接続すると、単体テストが難しくなる
- Moduleはオーバーライドできる ― GuiceのModuleはオーバーライドをサポートしており、製品版とテスト版や軽量版と置換えができる。製品版の機能がモジュール実行の一部として実装されたとき、そのようなオーバーライドは効果がない。
Module自身で実行するよりも、適切な抽象レベルのインターフェイスを定義する。アプリケーションでは、このようなインターフェイスを使用できる。
public interface Service { /** * Starts the service. This method blocks until the service has completely started. */ void start() throws Exception; /** * Stops the service. This method blocks until the service has completely shut down. */ void stop(); }
Injectorを生成した後でServiceを開始し、アプリケーションの起動を完了する。また、アプリケーションが停止したときに、リソースを解放するシャットダウンフックを追加する。
public static void main(String[] args) throws Exception { Injector injector = Guice.createInjector( new DatabaseModule(), new WebserverModule(), ... ); Service databaseConnectionPool = injector.getInstance( Key.get(Service.class, DatabaseService.class)); databaseConnectionPool.start(); addShutdownHook(databaseConnectionPool); Service webserver = injector.getInstance( Key.get(Service.class, WebserverService.class)); webserver.start(); addShutdownHook(webserver); }
参照
Modules should be fast and side-effect free
2014年6月9日月曜日
Google GuiceのBest Practicesを訳してみた - @Nullableを使用せよ
@Nullableを使用せよ
NullPointerExceptionをコードから追い出すために、null参照についてよく知らなければならない。次のシンプルなルールに従うことで、成功を収めている。
すべてのパラメータは、特に示されない限り、nullではない。
Guava: Google Core Libraries for JavaとJSR-305にはnullをコントロールするためのシンプルなAPIがある。Preconditions.checkNotNullはnull参照を発見したら直ちに終了するために使用でき、@Nullableはパラメータがnullを許可することを示すために使用できる。
Guiceはデフォルトでnullを拒絶する。nullの注入を拒否し、代わりにProvisionExceptionをスローして失敗する。もしnullを許可したいのであれば、フィールドやパラメータへ@Nullableを付けられる。Guiceはedu.umd.cs.findbugs.annotations.Nullableのようなあらゆる@Nullableに対応している。
参照
Use @Nullable
NullPointerExceptionをコードから追い出すために、null参照についてよく知らなければならない。次のシンプルなルールに従うことで、成功を収めている。
すべてのパラメータは、特に示されない限り、nullではない。
Guava: Google Core Libraries for JavaとJSR-305にはnullをコントロールするためのシンプルなAPIがある。Preconditions.checkNotNullはnull参照を発見したら直ちに終了するために使用でき、@Nullableはパラメータがnullを許可することを示すために使用できる。
import static com.google.common.base.Preconditions.checkNotNull; import static javax.annotation.Nullable; public class Person { ... public Person(String firstName, String lastName, @Nullable Phone phone) { this.firstName = checkNotNull(firstName, "firstName"); this.lastName = checkNotNull(lastName, "lastName"); this.phone = phone; }
Guiceはデフォルトでnullを拒絶する。nullの注入を拒否し、代わりにProvisionExceptionをスローして失敗する。もしnullを許可したいのであれば、フィールドやパラメータへ@Nullableを付けられる。Guiceはedu.umd.cs.findbugs.annotations.Nullableのようなあらゆる@Nullableに対応している。
参照
Use @Nullable
2014年6月6日金曜日
Google GuiceのBest Practicesを訳してみた - 静的状態を避けよ
静的状態を避けよ
静的状態とテスタビリティは敵対している。テストは高速で、副作用がないほうがよい。しかし、定数以外が静的フィールドに保持されると、管理に苦痛をもたらす。テストによってモックが作られた、静的なシングルトンを安全に処分するのは難しい。さらに、他のテストにも悪影響をおよぼす。
参照
Avoid static state
関連
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ
Google GuiceのBest Practicesを訳してみた - 循環する依存関係を解決する
静的状態とテスタビリティは敵対している。テストは高速で、副作用がないほうがよい。しかし、定数以外が静的フィールドに保持されると、管理に苦痛をもたらす。テストによってモックが作られた、静的なシングルトンを安全に処分するのは難しい。さらに、他のテストにも悪影響をおよぼす。
requestStaticInjection()は松葉杖である。Guiceは、静的に構成されたアプリケーションをDIに対応したスタイルに簡単に移行できるように、このAPIを追加した。新しく開発されるアプリケーションは、このメソッドを使用するべきではない。
静的状態は悪いが、静的であること自体には問題はない。静的クラスはOKであり(むしろ好まれる)、純粋な関数(ソートや数学)にとっては、静的であることが相応しい。
静的状態は悪いが、静的であること自体には問題はない。静的クラスはOKであり(むしろ好まれる)、純粋な関数(ソートや数学)にとっては、静的であることが相応しい。
Avoid static state
関連
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ
Google GuiceのBest Practicesを訳してみた - 循環する依存関係を解決する
2014年6月2日月曜日
Google GuiceのBest Practicesを訳してみた - 循環する依存関係を解決する
循環する依存関係を解決する
Store、Boss、Clerkを含むいくつかのクラスがあるアプリケーションについて考える。
今のところ、依存関係は全く問題ない。Bossを使用してStoreをインスタンス化し、Clerkを使用してBossをインスタンス化する。ところが、販売のためにClerkにCustomerを取得させようとすると、Storeへの参照が必要になる。
これはClerk -> Store -> Boss -> Clerkという循環をもたらす。Clerkをインスタンス化しようとするとStoreがインスタンス化され、StoreはBossのインスタンスが必要であり、BossはClerkのインスタンスが必要なのだ。
この循環を解決するための方法がいくつかある。
循環を断ち切る(推奨)
しばしば循環は不十分な責務の分割を反映している。そのような循環を断ち切るには、分割したクラスへ依存を抽出する。
この例では、来店する顧客を管理する機能をCustomerLineというクラスに抽出でき、ClerkとStoreへ注入できる。
StoreとClerkはどちらもCustomerLineへ依存しているが、循環は存在しない(StoreとClerkの両方が同じインスタンスを参照するときも)。このことはまた、テントで特売セールをするとき、店員が自動車を販売できることも意味する。ただ単に他のCustomerLineを注入すれば良い。
Providerを使用して循環を解決する
Providerを注入することで、依存関係へ継ぎ目を追加できる。ClerkはまだStoreに依存しているが、必要になるまでShopを参照することはない。
StoreがSingletonや他の再利用するスコープを指定しない時、shopProvider.get()は新たなShopをインスタンス化し、Bossをインスタンス化し、Clerkをインスタンス化することに注意が必要だ。
ファクトリメソッドを使用して2つのオブジェクトを結びつける
依存するクラス同士がより密接に結びついているときは、上記のメソッドでは解決できない。View/Presenterパターンを使用していると、このような状況に出くわす。
どちらのオブジェクトも相手のオブジェクトを必要としている。このような状況とうまくやるために、AssistedInjectが使える。
そのような状況は、Guiceを使用してビジネスモデルを表現しようとするときに出くわす。そのモデルには、異なる関係を反映した循環があるかもしれない。そのような状況にも、AssistedInjectは効果的だ。
参照
Resolving Cyclic Dependencies
関連
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ
Store、Boss、Clerkを含むいくつかのクラスがあるアプリケーションについて考える。
public class Store { private final Boss boss; //... @Inject public Store(Boss boss) { this.boss = boss; //... } public void incomingCustomer(Customer customer) {...} public Customer getNextCustomer() {...} } public class Boss { private final Clerk Clerk; @Inject public Boss(Clerk Clerk) { this.Clerk = Clerk; } } public class Clerk { // Nothing interesting here }
今のところ、依存関係は全く問題ない。Bossを使用してStoreをインスタンス化し、Clerkを使用してBossをインスタンス化する。ところが、販売のためにClerkにCustomerを取得させようとすると、Storeへの参照が必要になる。
public class Store { private final Boss boss; //... @Inject public Store(Boss boss) { this.boss = boss; //... } public void incomingCustomer(Customer customer) {...} public Customer getNextCustomer() {...} } public class Boss { private final Clerk clerk; @Inject public Boss(Clerk clerk) { this.clerk = clerk; } } public class Clerk { private final Store shop; @Inject Clerk(Store shop) { this.shop = shop; } void doSale() { Customer sucker = shop.getNextCustomer(); //... } }
これはClerk -> Store -> Boss -> Clerkという循環をもたらす。Clerkをインスタンス化しようとするとStoreがインスタンス化され、StoreはBossのインスタンスが必要であり、BossはClerkのインスタンスが必要なのだ。
この循環を解決するための方法がいくつかある。
循環を断ち切る(推奨)
しばしば循環は不十分な責務の分割を反映している。そのような循環を断ち切るには、分割したクラスへ依存を抽出する。
この例では、来店する顧客を管理する機能をCustomerLineというクラスに抽出でき、ClerkとStoreへ注入できる。
public class Store { private final Boss boss; private final CustomerLine line; //... @Inject public Store(Boss boss, CustomerLine line) { this.boss = boss; this.line = line; //... } public void incomingCustomer(Customer customer) { line.add(customer); } } public class Clerk { private final CustomerLine line; @Inject Clerk(CustomerLine line) { this.line = line; } void doSale() { Customer sucker = line.getNextCustomer(); //... } }
StoreとClerkはどちらもCustomerLineへ依存しているが、循環は存在しない(StoreとClerkの両方が同じインスタンスを参照するときも)。このことはまた、テントで特売セールをするとき、店員が自動車を販売できることも意味する。ただ単に他のCustomerLineを注入すれば良い。
Providerを使用して循環を解決する
Providerを注入することで、依存関係へ継ぎ目を追加できる。ClerkはまだStoreに依存しているが、必要になるまでShopを参照することはない。
public class Clerk { private final ProvidershopProvider; @Inject Clerk(Provider shopProvider) { this.shopProvider = shopProvider; } void doSale() { Customer sucker = shopProvider.get().getNextCustomer(); //... } }
StoreがSingletonや他の再利用するスコープを指定しない時、shopProvider.get()は新たなShopをインスタンス化し、Bossをインスタンス化し、Clerkをインスタンス化することに注意が必要だ。
ファクトリメソッドを使用して2つのオブジェクトを結びつける
依存するクラス同士がより密接に結びついているときは、上記のメソッドでは解決できない。View/Presenterパターンを使用していると、このような状況に出くわす。
public class FooPresenter { @Inject public FooPresenter(FooView view) { //... } public void doSomething() { view.doSomethingCool(); } } public class FooView { @Inject public FooView(FooPresenter presenter) { //... } public void userDidSomething() { presenter.theyDidSomething(); } //... }
どちらのオブジェクトも相手のオブジェクトを必要としている。このような状況とうまくやるために、AssistedInjectが使える。
public class FooPresenter { privat final FooView view; @Inject public FooPresenter(FooView.Factory viewMaker) { view = viewMaker.create(this); } public void doSomething() { //... view.doSomethingCool(); } } public class FooView { @Inject public FooView(@Assisted FooPresenter presenter) {...} public void userDidSomething() { presenter.theyDidSomething(); } public static interface Factory { FooView create(FooPresenter presenter) } }
そのような状況は、Guiceを使用してビジネスモデルを表現しようとするときに出くわす。そのモデルには、異なる関係を反映した循環があるかもしれない。そのような状況にも、AssistedInjectは効果的だ。
参照
Resolving Cyclic Dependencies
関連
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ
2014年5月29日木曜日
Google GuiceのBest Practicesを訳してみた - 直接の依存性のみ注入せよ
直接の依存性のみ注入せよ
他のオブジェクトを取得するために注入することを避けよ。例えば、Accountを取得するためにCustomerを注入してはいけない。
代わりに、依存するクラスを直接注入せよ。これはテストを簡単にする。テストはテスト自身とCustomerにだけ注意すればよい。@Providesを付けたメソッドを使い、CustomerからAccountをバインドする。
依存性を直接注入することで、コードはシンプルになる。
public class ShowBudgets { private final Account account; @Inject ShowBudgets(Customer customer) { account = customer.getPurchasingAccount(); }
代わりに、依存するクラスを直接注入せよ。これはテストを簡単にする。テストはテスト自身とCustomerにだけ注意すればよい。@Providesを付けたメソッドを使い、CustomerからAccountをバインドする。
public class CustomerModule extends AbstractModule { @Override public void configure() { ... } @Provides Account providePurchasingAccount(Customer customer) { return customer.getPurchasingAccount(); }
依存性を直接注入することで、コードはシンプルになる。
public class ShowBudgets { private final Account account; @Inject ShowBudgets(Account account) { this.account = account; }
2014年5月26日月曜日
Google GuiceのBest Practicesを訳してみた - 可変性を最小化せよ
可変性を最小化せよ
可能であればいつでも、不変なオブジェクトを生成するようにコンストラクターインジェクションを使用すること。不変なオブジェクトはシンプルで、共有でき、再利用できる。このパターンにしたがって注入可能なクラスを定義する。
このクラスの全てのフィールドはfinalであり、@Injectが付けられたコンストラクタで初期化されている。Effective Javaでは不変性の他の利点について述べられている。
メソッドインジェクションとフィールドインジェクション
コンストラクターインジェクションにはいくつかの制限がある。
public class RealPaymentService implements PaymentService { private final PaymentQueue paymentQueue; private final Notifier notifier; @Inject RealPaymentService( PaymentQueye paymentQueue, Notifier notifier) { this.paymentQueue = paymentQueue; this.notifier = notifier; } ...
このクラスの全てのフィールドはfinalであり、@Injectが付けられたコンストラクタで初期化されている。Effective Javaでは不変性の他の利点について述べられている。
メソッドインジェクションとフィールドインジェクション
コンストラクターインジェクションにはいくつかの制限がある。
- 注入されるコンストラクタはオプションにならない。
- オブジェクトがGuiceによって生成されない限り、使用されない。これは一部のフレームワークとの関係を断つ。
- サブクラスはsuper()を全ての依存するクラスとともに呼ばなければならない。これはコンストラクターインジェクションを扱いにくくする。親クラスに変更があったときは特に。
Guiceによって生成されないオブジェクトを初期化するなら、メソッドインジェクションがもっとも便利だ。AssistedInjectやMultibinderのような拡張は、バインドされたオブジェクトを初期化するために、メソッドインジェクションを使用する。
フィールドインジェクションは、コンパクトに記述できる。故に、例を示すときやスライドで頻繁に目にする。それはカプセル化もできていないし、テストもしにくい。finalなフィールドへ注入してはいけない。注入された値が全てのスレッドで参照できることを、JVMは保証していない。
2014年5月19日月曜日
Lispでバケツソート
日経ソフトウェアの2014年6月号で、バケツソートが紹介されていました。
興味深かったので、Lispで実装してみました。
ソース
なんとなく汚い感じがするのは、きっと自分の腕が未熟だからでしょう。
興味深かったので、Lispで実装してみました。
ソース
(defun backet-sort (v backet-count) "バケツソート" (let ((backet (array-to-backet v backet-count))) (backet-to-array backet (length v)))) (defun array-to-backet (arr backet-count) "配列からバケツを構成する" (let ((backet (make-array backet-count :initial-element 0))) (dotimes (i (length arr) backet) (let ((n (aref arr i))) (setf (aref backet n) (1+ (aref backet n))))))) (defun backet-to-array (backet array-length) "バケツからソート済みの配列を構成する" (let ((arr (make-array array-length))) (loop with arr-index = 0 for i below (length backet) when (< 0 (aref backet i)) do (loop repeat (aref backet i) do (setf (aref arr arr-index) i) (setq arr-index (1+ arr-index)))) arr)) (defun main () "動作確認のための関数" (let ((l (loop with state = (make-random-state t) repeat 10 collect (random 10 state)))) (print (sort (make-array (length l) :initial-contents l) #'<)) (print (backet-sort (make-array (length l) :initial-contents l) 10)))) (main)
なんとなく汚い感じがするのは、きっと自分の腕が未熟だからでしょう。
2014年5月12日月曜日
JavaScriptでは"false"はtrueとして扱われる
タイトルだけでは「そんなバカな」と思われるかもしれませんが、要はtruthy/falsyの話です。
JavaScriptでのTruthy/Falsy
仕様は、ECMAScript Language Specification - 9.2 ToBooleanにあります。これによれば、falseとして扱われるのは次の6つです。
JavaScriptでのTruthy/Falsy
仕様は、ECMAScript Language Specification - 9.2 ToBooleanにあります。これによれば、falseとして扱われるのは次の6つです。
- undefined
- null
- false
- 0
- NaN
- "" (空文字列)
Boolean関数を使って確認してみました。
"false"がtrueとして扱われることが確認できました。
問題になるケース
問題になるケースとしては、サーバーサイドの言語からJavaScriptへ、type="hidden"のinputのvalueへfalseをセットする場合が考えられます。サーバーでセットされた値を使用して、次のようなif文を書くと問題が発生します。
回避するには、明示的に"true"と比較するのがいいでしょう。
// <input id="someElement" type="hidden" value="false" />のとき if (document.getElementById('someElement').value) { // このブロックが実行される ... }
回避するには、明示的に"true"と比較するのがいいでしょう。
// <input id="someElement" type="hidden" value="false" />のとき if ("true" === document.getElementById('someElement').value) { // このブロックは実行されない ... }
JavaScriptの型は独特のゆるさがあるので、ついついチェックがあまくなりがちです。しかし、今自分がどんな型を扱おうとしているのか把握していないと、思わぬところでハマってしまいます。という記事でした。
おまけ
「Boolean型に変換するAPIはないのか」と思って調べてみましたが、次のような結果に。
参考
ECMAScript Language Specification
おまけ
「Boolean型に変換するAPIはないのか」と思って調べてみましたが、次のような結果に。
参考
ECMAScript Language Specification
2014年5月5日月曜日
unittest.mockの簡単な紹介
単体テストに欠かせないツールのひとつに、モックがあります。
Pythonにも3.3からモックモジュールが追加されました。
unittest.mockといいます。今回はこのモジュールの簡単な使い方を紹介します。
Pythonにも3.3からモックモジュールが追加されました。
unittest.mockといいます。今回はこのモジュールの簡単な使い方を紹介します。
モックが必要になる場面として、システム日付の取得があります。
例えば、当日が閏日かどうか判定する関数を考えてみましょう。
なにも考えずに関数を実装すると、次のようになると思います。
def is_leap(): d = datetime.date.today() if 2 == d.month and 29 == d.day: print(str(d) + ' is LEAP') return True else: print(str(d) + ' is NOT LEAP') return False
このままではOSの時計を設定しないと試験ができないので、
日付をパラメータとして渡すようにします。
日付をパラメータとして渡すようにします。
def is_leap(d): if 2 == d.month and 29 == d.day: print(str(d) + ' is LEAP') return True else: print(str(d) + ' is NOT LEAP') return False
日付を注入できるようにしたことで、テストしやすくなりました。
例えば、このようにします。
例えば、このようにします。
assert True == is_leap(datetime.date(2012, 2, 29)) assert False == is_leap(datetime.date(2012, 3, 1))
unittest.mockを使うと、次のような感じになります。
d = unittest.mock.MagicMock() d.month = 2 d.day = 29 assert True == is_leap(d)
モックを使わないほうが単純です。こういう場合はdateオブジェクトを使ったほうがいいでしょう。
さて、現実には全てのコードを自由に変更できるとは限りません。
たとえそんな場合でも、unittest.mock.patchを呼ぶことで、対応できる場合があります。
is_leap関数を修正せずに次のようにします。
さて、現実には全てのコードを自由に変更できるとは限りません。
たとえそんな場合でも、unittest.mock.patchを呼ぶことで、対応できる場合があります。
is_leap関数を修正せずに次のようにします。
with unittest.mock.patch('datetime.date') as dt: dt.today().month = 2 dt.today().day = 29 assert True == is_leap()
datetime.date.today()メソッドが返すオブジェクトをモックオブジェクトと置き換え、
属性を設定することでテストをパスしています。
確かにこの方法でも試験はできますが、日付をパラメータ化するほうがベターだと思います。
他にも、unittest.mockにはメソッドの呼び出しを記録する機能もあります。
詳しいことはオフィシャルのドキュメントを参照してください。
属性を設定することでテストをパスしています。
確かにこの方法でも試験はできますが、日付をパラメータ化するほうがベターだと思います。
他にも、unittest.mockにはメソッドの呼び出しを記録する機能もあります。
詳しいことはオフィシャルのドキュメントを参照してください。
参考:
2014年4月28日月曜日
xyzzyでブックマーク機能
Emacsのブックマーク機能で紹介したEmacsのブックマーク機能が便利だったので、xyzzyでも使いたくなりました。
Lispの勉強も兼ねて、自分で実装してみました。
プログラムは記事の最後に載せます。
機能
実装済みの機能は以下のとおりです。
Lispの勉強も兼ねて、自分で実装してみました。
プログラムは記事の最後に載せます。
機能
実装済みの機能は以下のとおりです。
- 現在表示しているバッファをブックマークとして登録する
- ブックマークの一覧を表示する
- ブックマークの一覧からファイルを開く
- ブックマークを削除する
使用方法
まず、プログラムを読み込みます。専用のパッケージを定義したので、合わせてuse-packageします。
(require "bookmark") (use-package "bookmark")
次に、キーバインドの設定をします。今のところ公開している関数は次の2つです。
- bookmark-add-current-buffer: 表示しているバッファをブックマークする
- bookmark-list-bookmarks: ブックマークの一覧を表示する
(global-set-key '(#\C-c #\a #\b) 'bookmark-add-current-buffer) (global-set-key '(#\C-c #\l #\b) 'bookmark-list-bookmarks)
bookmark-add-current-bufferを呼ぶと、現在のバッファをブックマークします。その際、ブックマーク名を入力できます。
bookmark-list-bookmarksを呼ぶと、ブックマーク一覧を表示します。一覧からファイルの表示と、ブックマークの削除ができます。ブックマークしたファイルの表示は[f]キーを押下し、ブックマークの削除は[d]キーです。
ブックマーク一覧を実装するときは、xyzzy付属のbuf-menu.lが参考になりました。
よろしければ使ってみてください。
2014年4月21日月曜日
Emacs: 検索/置換チュートリアル
Google+で流れてきたEmacsのチュートリアルを日本語に訳してみました。
Emacs: 検索/置換チュートリアル
このページでは、Emacsの検索/置換機能を紹介します。
大文字と小文字を区別するかどうかについて紹介します。
正規表現にマッチした文字列を大文字や小文字に変換する方法について紹介します。
検索/置換コマンド
最も便利な検索/置換コマンドについて紹介します。
これらは、メニューのEdit > Replaceにあります。
コマンド名 | キーバインド | 対象 | 目的 |
---|---|---|---|
query-replace | M-% | 有効なリージョン カーソルから後方 |
対話的な検索/置換 |
query-replace-regexp | C-M-% | 有効なリージョン カーソルから後方 |
正規表現による対話的な検索/置換 |
dired-do-query-replace-regexp | diredでQ | マークしたファイル | 複数ファイルに対する対話的な検索/置換 |
例:query-replaceを呼び、検索文字列と置換文字列を入力します。
コマンドが確認を求めた時の一般的なコマンドは以下のとおりです。
- y - 置換を実行する
- n - スキップする
- ! - これ以降確認なしで置換する
- C-g - キャンセル(置換を元に戻すにはundoを呼ぶ)
dired-do-query-replace-regexpについては、Interactively Find/Replace String Patterns on Multiple Filesを参照してください。
一括置換
Emacsにはreplace-stringとreplace-regexpコマンドもあります。
それらはquery-replaceとquery-replace-regexpの対話的でないバージョンです。
一度の実行で確認せずにすべて置換します。
練習段階では、これらはさほど便利ではありません。
対話的なバージョンを使用し、!を入力すれば一括で置換できます。
デフォルトの大小文字区別: 自動
デフォルトでは、検索文字に大文字を含むとき、自動的に大小文字を区別して検索します。そうでなければ、大証文字を区別せずに検索します。
デフォルトでは、置換後の文字列の大小文字検索にマッチした文字列に依存します。
例えば、検索文字列が「here」で置換文字列が「dragon」のときについて考えます。
Emacsは「here」「Here」「HERE」のいずれかを検索します。
そして、「here」を「dragon」で置換し、「Here」を「Dragon」で置換し、「HERE」を「DRAGON」で置換します。
入力したとおりの文字列で置換したいときは、case-replace変数へnilをセットします。set-variableを使用します。
自動大小文字区別のON/OFF
検索と置換の両方で入力した通りの大小文字を使用したいときはtoggle-case-fold-searchを呼ぶか、メニューのOptions > Case-Insensitive Searchを使用します。
キーやエイリアスを割り当てることもできます。(Emacs: How to Define Keys) (Emacs: Defining Alias to Increase Productivity)
正規表現にマッチした文字列の大小文字を強制的に置換する
検索に正規表現を使用しており、大文字や小文字に置換したいとき、「\,(upcase \1)」や「\,(downcase \1)」を使用できます。
例えば、次のようなテキストについて考えます。
キャプチャした文字を大文字に置換するために、置換文字列に「<p>\,(upcase \1)」を使用します。「\,」は続く文字列がlisp式であることを示します。「(upcase \1)」はlisp式です。「upcase」はlisp関数で、「\1」は1番目のキャプチャ文字列です。
「\,」を使用したより複雑な置換については、Regex Replace with a Function in Emacs Lispを参照してください。
参考
Emacs: Find/Replace Tutorial
一括置換
Emacsにはreplace-stringとreplace-regexpコマンドもあります。
それらはquery-replaceとquery-replace-regexpの対話的でないバージョンです。
一度の実行で確認せずにすべて置換します。
練習段階では、これらはさほど便利ではありません。
対話的なバージョンを使用し、!を入力すれば一括で置換できます。
デフォルトの大小文字区別: 自動
デフォルトでは、検索文字に大文字を含むとき、自動的に大小文字を区別して検索します。そうでなければ、大証文字を区別せずに検索します。
デフォルトでは、置換後の文字列の大小文字検索にマッチした文字列に依存します。
例えば、検索文字列が「here」で置換文字列が「dragon」のときについて考えます。
Emacsは「here」「Here」「HERE」のいずれかを検索します。
そして、「here」を「dragon」で置換し、「Here」を「Dragon」で置換し、「HERE」を「DRAGON」で置換します。
入力したとおりの文字列で置換したいときは、case-replace変数へnilをセットします。set-variableを使用します。
自動大小文字区別のON/OFF
検索と置換の両方で入力した通りの大小文字を使用したいときはtoggle-case-fold-searchを呼ぶか、メニューのOptions > Case-Insensitive Searchを使用します。
キーやエイリアスを割り当てることもできます。(Emacs: How to Define Keys) (Emacs: Defining Alias to Increase Productivity)
正規表現にマッチした文字列の大小文字を強制的に置換する
検索に正規表現を使用しており、大文字や小文字に置換したいとき、「\,(upcase \1)」や「\,(downcase \1)」を使用できます。
例えば、次のようなテキストについて考えます。
<p>once upon a time ...</p>すべてのパラグラフを大文字で開始したいとき、<p>に続く一文字をキャプチャする「<p>\{[a-z]\}」のようなパターンを使います。
<p>there is a dragon who lived in ...</p>
<p>princess tana is still witing ...</p>
キャプチャした文字を大文字に置換するために、置換文字列に「<p>\,(upcase \1)」を使用します。「\,」は続く文字列がlisp式であることを示します。「(upcase \1)」はlisp式です。「upcase」はlisp関数で、「\1」は1番目のキャプチャ文字列です。
「\,」を使用したより複雑な置換については、Regex Replace with a Function in Emacs Lispを参照してください。
参考
Emacs: Find/Replace Tutorial
2014年4月14日月曜日
シェルスクリプト初心者の私がまず覚えたこと9つ
今年の4月にシェルスクリプトを書く機会がありました。今までシェルを使った経験は多少あったものの、シェルスクリプトを書くのは初めてでした。シェルスクリプトを書くにあたり、まず抑えたことを紹介します。
- 1行目のおまじない
- 変数の使い方
- 関数の使い方
- 制御文
- バッククォート
- 終了ステータス
- 設定ファイル
- 文字列操作
- 文字列のエスケープ
それぞれについて以下に記載します。
2014年4月7日月曜日
GZIP圧縮に対応したHTTPクライアントを作る
前回の記事で予告したとおり、今回はC#でGZIP圧縮に対応したHTTPクライアントを作ります。
ポイントは3つあります。
GZipStreamを使って入力データを圧縮する
ポイントは3つあります。
- GZipStreamを使って入力データを圧縮する
- HttpClientHandlerのAutomaticDecompressionプロパティを設定する
- HTTPリクエストヘッダのContent-Encodingをgzipに設定する
GZipStreamを使って入力データを圧縮する
圧縮用にMemoryStreamをインスタンス化し、GZipStreamでラップします。
usingをネストしているのはGZipStreamをCloseするタイミングで圧縮が実行されるからです。
byte[] data = null; using (var memory = new MemoryStream()) { using (var gzip = new GZipStream(memory, CompressionMode.Compress)) { int input = 0; byte[] buffer = new byte[1]; while (-1 != (input = Console.Read())) { buffer[0] = Convert.ToByte(input); gzip.Write(buffer, 0, buffer.Length); } } data = memory.ToArray(); }
HttpClientHandlerのAutomaticDecompressionプロパティを設定する
リクエストボディの圧縮は実装する必要がありますが、レスポンスの解凍は.NET Frameworkが対応しています。
var clientHandler = new HttpClientHandler(); clientHandler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
HTTPリクエストヘッダのContent-Encodingをgzipに設定する
コンテンツがgzip圧縮されていることを示すため、Content-Encodingをgzipに設定します。
using(var client = new HttpClient(clientHandler)) using(var content = new ByteArrayContent(data)) { content.Headers.ContentEncoding.Add("gzip"); using(var message = client.PostAsync(url, content).Result) { Console.WriteLine(message.Content.ReadAsStringAsync().Result); } }
一般的に、HTTPリクエストが圧縮を必要とするほど大きくなることはありません。
全ての画面データを毎回やりとりするような設計であれば、効果があるかもしれません。
プログラム全体を以下に記載します。
2014年3月31日月曜日
FilterでHTTP通信のGZIP圧縮に対応する
HTTP/1.1では仕様に電文の圧縮が盛り込まれています。
今回はサーブレットフィルタでGZIP圧縮に対応してみたいと思います。
必要なクラス
圧縮されたHTTPリクエストの解凍と、HTTPレスポンスの圧縮を実現するには、次の5つのクラスが必要です。
今回はサーブレットフィルタでGZIP圧縮に対応してみたいと思います。
必要なクラス
圧縮されたHTTPリクエストの解凍と、HTTPレスポンスの圧縮を実現するには、次の5つのクラスが必要です。
- GZIPFilter - Filterを実装します。HTTPヘッダを確認し、圧縮/解凍をするかどうか判断します。
- GZIPRequest - HttpServletRequestWrapperを継承します。GZIPServletInputStreamを返します。
- GZIPServletInputStream - HTTPリクエストを解凍するInputStreamです。
- GZIPResponse - HttpServletResponseWrapperを継承します。GZIPServletOutputStreamを返します。
- GZIPServletOutputStream - HTTPレスポンスを圧縮するOutputStreamです。
ほか、リクエストをそのままレスポンスするEchoServletを準備します。
2014年3月24日月曜日
PythonでEvernoteへメールを送る - 添付ファイル追加
「PythonでEvernoteへメールを送る」で載せたプログラムへ、ファイル添付機能を追加しました。
合わせてクラス設計を見直しました。
複数ファイルを添付するとうまくいかないところが問題です。
また時間のあるときに対応したいと思います。
合わせてクラス設計を見直しました。
# -*- coding: utf-8 -*- # ---- CONFIGURATIONS ---- EVERNOTE_MAIL_ADDRESS = 'evernote_mail_address' FROM_ADDRESS = 'from_address' SMTP_HOST = 'smtp_host' SMTP_PORT = 25 # ---- CONFIGURATIONS ---- import datetime import mimetypes import optparse import os.path import smtplib import sys from email import encoders from email.header import Header from email.mime.audio import MIMEAudio from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText class EverMailClient(object): def __init__(self, smtp): assert isinstance(smtp, smtplib.SMTP) self._smtp = smtp def send(self, mail): assert isinstance(mail, EverMail) msg = mail.as_MIME() mail_from = msg['From'] mail_to = msg['To'] self._smtp.sendmail(mail_from, mail_to, msg.as_string()) class EverMail(object): def __init__(self, title, from_address, to_address, body, attached_files): assert isinstance(title, EverMailTitle) assert isinstance(body, EverMailBody) self._title = title self._from_address = from_address self._to_address = to_address self._body = body self._attached_files = attached_files def as_MIME(self): msg = MIMEMultipart() msg['Subject'] = Header(str(self._title), 'utf-8') msg['From'] = self._from_address msg['To'] = self._to_address msg.attach(self._body.as_MIME()) for attached_file in self._attached_files: msg.attach(attached_file.as_MIME()) return msg class EverMailTitle(object): def __init__(self, title, notebook, tags): self._title = title if title else str(datetime.datetime.now()) self._notebook = notebook if notebook else '' self._tags = tags if tags else [] def __str__(self): result = self._title if 0 < len(self._notebook): result += ' @' + self._notebook for tag in self._tags: result += ' #' + tag return result class EverMailBody(object): def __init__(self, stdin): assert hasattr(stdin, 'read') self._stdin = stdin def as_MIME(self): body = sys.stdin.read() msg = MIMEText(bytes(body)) msg.set_charset('utf-8') return msg class AttachedFile(object): def __init__(self, path): self._path = path def as_MIME(self): ctype, encoding = mimetypes.guess_type(path) if ctype is None or encoding is not None: ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) msg = None with open(self._path, 'rb') as f: if maintype == 'text': msg = MIMEText(f.read(), _subtype=subtype) elif maintype == 'image': msg = MIMEImage(f.read(), _subtype=subtype) elif maintype == 'audio': msg = MIMEAudio(f.read(), _subtype=subtype) else: msg = MIMEBase(maintype, subtype) msg.set_payload(f.read()) encoders.encode_base64(msg) file_name = os.path.basename(self._path) msg.add_header('Content-Disposition', 'attachment', filename=file_name) return msg class EverMailOptionParser(optparse.OptionParser): def __init__(self): optparse.OptionParser.__init__(self) self.add_option('--notebook', '-n', action='store') self.add_option('--tag', '-t', action='append') def create_smtp_client(): smtp = smtplib.SMTP(SMTP_HOST, SMTP_PORT) smtp.set_debuglevel(1) return smtp if __name__ == '__main__': option_parser = EverMailOptionParser() (options, args) = option_parser.parse_args() note_title = args[0] if args and 0 < len(args) else '' title = EverMailTitle(note_title, options.notebook, options.tag) body = EverMailBody(sys.stdin) attached_files = [] if 2 <= len(args): attached_files = [AttachedFile(path) for path in args[1]] mail = EverMail(title, FROM_ADDRESS, EVERNOTE_MAIL_ADDRESS, body, attached_files) smtp = None try: smtp = create_smtp_client() client = EverMailClient(smtp) client.send(mail) finally: if smtp: smtp.close()
複数ファイルを添付するとうまくいかないところが問題です。
また時間のあるときに対応したいと思います。
2014年3月21日金曜日
Emacsのブックマーク機能
Google+で流れてきたEmacsのチュートリアルを日本語に訳してみました。
Emacsのブックマーク機能を使う
このページはEmacsに搭載されたブックマーク機能のチュートリアルです。
Emacsのブックマーク機能は、ブラウザのそれによく似ています。
Emacsのブックマークは、しばしば必要になるファイルを簡単に開けるようにします。
ブックマークを使う
ファイルをブックマークへ追加する
追加したいファイルを開いてください。
続いて、bookmark-setを呼ぶ[C-x r m]と、名前を入力するプロンプトが表示されます。
ブックマークファイルを開く
ブックマークを開くには、bookmark-bmenu-listを呼びます[C-x r l]。
ブックマーク一覧が表示されます。
この一覧でEnterを入力するかクリックすれば、ファイルを開きます。
また、oを入力すれば分割されたウィンドウでファイルを開きます。
ブックマークを保存する
ブックマークを保存するには、bookmark-saveを呼びます(ブックマーク一覧でsを入力する)。
もし保存しなければ、新たに追加されたブックマークは現在のセッションでのみ有効です。
つまり、Emacsを再起動すると表示されなくなってしまいます。
ブックマークを削除または名前を変更する
ブックマーク一覧を開いた状態で、Dでマークすればブックマークを削除します。
また、rを入力すればブックマークの名前を変更します。
ブックマークのコマンドとショートカットを忘れないようにするには
ブックマークコマンドは、メニューのEdit->Bookmarksにあります。
コマンドやショートカットを忘れてしまったときは、メニューを見てください。
メニューはEmacsのコマンドを覚えるための偉大なる道です。
また、ブックマークコマンドはすべてbookmark-で始まります。
本当に覚えなければいけないのは、bookmark-bmenu-listだけです。
いったんブックマーク一覧を開いてしまえば、describe-mode[F1 m]を呼ぶことでコマンドとショートカットの一覧を見ることができます。
起動時にブックマークを表示する
起動時にブックマークを表示するには、次のelispを初期化ファイルへ追加してください。
ブックマークファイルの場所
emacs 24.xでは、ブックマークファイルは~/.emacs.d/bookmarksにあります。emacs 23.xでは、~/.emacs.bmkにあります。
デフォルトの場所は、変数bookmark-default-fileによって制御されます。describe-variable[F1 v]を呼び、見てみてください。
デフォルトの場所は、次のように設定できます。
ファイルを早く開くほかの方法
最近開いたファイルを開くのも便利な機能です。参考:Emacs: Open File Fast: recentf-mode
ブックマークの1つの問題は、ファイルを直接開くキーがないことです。しかし、Emacs Lisp: Hotkeys to Open File Fastで実現できます。
参考
Emacs: Using Bookmark Feature
Emacsのブックマーク機能
Emacsのブックマーク機能を使う
このページはEmacsに搭載されたブックマーク機能のチュートリアルです。
Emacsのブックマーク機能は、ブラウザのそれによく似ています。
Emacsのブックマークは、しばしば必要になるファイルを簡単に開けるようにします。
ブックマークを使う
ファイルをブックマークへ追加する
追加したいファイルを開いてください。
続いて、bookmark-setを呼ぶ[C-x r m]と、名前を入力するプロンプトが表示されます。
ブックマークファイルを開く
ブックマークを開くには、bookmark-bmenu-listを呼びます[C-x r l]。
ブックマーク一覧が表示されます。
この一覧でEnterを入力するかクリックすれば、ファイルを開きます。
また、oを入力すれば分割されたウィンドウでファイルを開きます。
ブックマークを保存する
ブックマークを保存するには、bookmark-saveを呼びます(ブックマーク一覧でsを入力する)。
もし保存しなければ、新たに追加されたブックマークは現在のセッションでのみ有効です。
つまり、Emacsを再起動すると表示されなくなってしまいます。
ブックマークを削除または名前を変更する
ブックマーク一覧を開いた状態で、Dでマークすればブックマークを削除します。
また、rを入力すればブックマークの名前を変更します。
ブックマークのコマンドとショートカットを忘れないようにするには
ブックマークコマンドは、メニューのEdit->Bookmarksにあります。
コマンドやショートカットを忘れてしまったときは、メニューを見てください。
メニューはEmacsのコマンドを覚えるための偉大なる道です。
また、ブックマークコマンドはすべてbookmark-で始まります。
本当に覚えなければいけないのは、bookmark-bmenu-listだけです。
いったんブックマーク一覧を開いてしまえば、describe-mode[F1 m]を呼ぶことでコマンドとショートカットの一覧を見ることができます。
起動時にブックマークを表示する
起動時にブックマークを表示するには、次のelispを初期化ファイルへ追加してください。
(setq inhibit-splash-screen t) (require 'bookmark) (bookmark-bmenu-list) (switch-to-buffer "*Bookmark List*")
ブックマークファイルの場所
emacs 24.xでは、ブックマークファイルは~/.emacs.d/bookmarksにあります。emacs 23.xでは、~/.emacs.bmkにあります。
デフォルトの場所は、変数bookmark-default-fileによって制御されます。describe-variable[F1 v]を呼び、見てみてください。
デフォルトの場所は、次のように設定できます。
(setq bookmark-default-file (concat user-emacs-directory "bookmarks"))ブックマークファイルの読み込みは、次のようにします。
(bookmark-load bookmark-default-file t)
ファイルを早く開くほかの方法
最近開いたファイルを開くのも便利な機能です。参考:Emacs: Open File Fast: recentf-mode
ブックマークの1つの問題は、ファイルを直接開くキーがないことです。しかし、Emacs Lisp: Hotkeys to Open File Fastで実現できます。
参考
Emacs: Using Bookmark Feature
Emacsのブックマーク機能
2014年3月17日月曜日
PythonでEvernoteへメールを送る
Evernoteへメールを送ることでノートを追加できるのはご存知かと思います。
最近、古いネットブックをUSBブートのLinuxで使用しています。ネットブック上で作成したファイルをEvernoteへアップロードしたいと思うことがあるのですが、ブラウザから操作するのは厳しいと感じています。
Pythonからメールでファイルを送るようにすればいいのではないかと思い、標準入力から読み込んだ内容をEvernoteへ送るプログラムを書きました。
プログラム
使用例
工夫したところは、optparseモジュールを使ってオプションの解析を実装したところです。
暇があれば、画像の添付なども対応したいと思います。
参考
15.5. optparse — コマンドラインオプション解析器
Eメールを送信するだけでEvernoteに簡単送信!
最近、古いネットブックをUSBブートのLinuxで使用しています。ネットブック上で作成したファイルをEvernoteへアップロードしたいと思うことがあるのですが、ブラウザから操作するのは厳しいと感じています。
Pythonからメールでファイルを送るようにすればいいのではないかと思い、標準入力から読み込んだ内容をEvernoteへ送るプログラムを書きました。
プログラム
# -*- coding: utf-8 -*- # ever_mail.py # ---- CONFIGURATIONS ---- EVERNOTE_MAIL_ADDRESS = 'evernote_mail_address' FROM_ADDRESS = 'from_address' SMTP_HOST = 'smtp._host' SMTP_PORT = 25 DEBUG = True # ---- CONFIGURATIONS ---- import datetime import email.header import optparse import smtplib import sys from email.mime.text import MIMEText class EverMailClient(object): def __init__(self, smtp): assert isinstance(smtp, smtplib.SMTP) self._smtp = smtp def send(self, msg): assert isinstance(msg, MIMEText) mail_from = msg['From'] mail_to = msg['To'] self._smtp.sendmail(mail_from, mail_to, msg.as_string()) class EverMailTitle(object): def __init__(self, title, notebook, tags): self._title = title if title else str(datetime.datetime.now) self._notebook = notebook if notebook else '' self._tags = tags if tags else [] def __str__(self): result = self._title if 0 < len(self._notebook): result += ' @' + self._notebook for tag in self._tags: result += ' #' + tag return result class EverMailOptionParser(optparse.OptionParser): def __init__(self): optparse.OptionParser.__init__(self) self.add_option('--notebook', '-n', action='store') self.add_option('--tag', '-t', action='append') def create_smtp_client(): smtp = smtplib.SMTP(SMTP_HOST, SMTP_PORT) if DEBUG: smtp.set_debuglevel(1) return smtp def create_message_from_stdin(title): assert isinstance(title, EverMailTitle) body = sys.stdin.read() msg = MIMEText(bytes(body)) msg['Subject'] = email.header.Header(str(title), 'utf-8') msg['From'] = FROM_ADDRESS msg['To'] = EVERNOTE_MAIL_ADDRESS msg.set_charset('utf-8') return msg if __name__ == '__main__': option_parser = EverMailOptionParser() (options, args) = option_parser.parse_args() note_title = args[0] if args and 0 < len(args) else '' title = EverMailTitle(note_title, options.notebook, options.tag) msg = create_message_from_stdin(title) smtp = None try: smtp = create_smtp_client() client = EverMailClient(smtp) client.send(msg) finally: if smtp: smtp.close()
使用例
$ cat your_important_file | python ever_mail.py TITLE -nNOTEBOOK -tTAG -tTAG
工夫したところは、optparseモジュールを使ってオプションの解析を実装したところです。
暇があれば、画像の添付なども対応したいと思います。
参考
15.5. optparse — コマンドラインオプション解析器
Eメールを送信するだけでEvernoteに簡単送信!
2014年3月10日月曜日
DIに1年間はまってみて気づいたこと
2013年は、個人的にDIを推進しました。
1年間活動してみた結果、気づいたことをまとめておきます。
DIコンテナは使いませんでした。自作のファクトリクラスでインスタンス化するようにしました。理由は2つあり、1つはDIコンテナを組み込む権限がプロジェクト内で与えられていなかったから、もう1つは自分の担当範囲で使用するだけなので、シンプルな仕組みで十分だったからです。
気づいたこと
クラスを設計するときの考え方が変わりました。新しいクラスを追加するとき、「そのクラスが機能を実現するにはどんなクラスが必要か」ということを考えるようになりました。こうすることで、クラスが果たすべき責務が明確になり、クラス設計が改善されました。
反面、注意すべきことも見えてきました。「依存する側」と「依存される側」という観点でクラスをどんどん分割していきますが、やり過ぎると何もしないクラスができてしまいます。望ましい設計について、「2回測って1回で切る」とどこかで読んだことがあります。自分なりに解釈すると、1回目は機能をどんどん分割していく設計、2回目は無駄なクラスを統合していく設計とするとうまくいくような気がしました。
自作のファクトリにも問題点が見えてきました。小さいアプリケーション(2画面/5人日)だったのでファクトリクラスを1つしか用意しなかったのですが、巨大なクラスになりかけていました。ファクトリを分割し、ファクトリ同士のDIも必要になると思いました。ただ、そこまで複雑になるようであれば、DIコンテナの採用を検討するべきだと思います。
1年間活動してみた結果、気づいたことをまとめておきます。
DIコンテナは使いませんでした。自作のファクトリクラスでインスタンス化するようにしました。理由は2つあり、1つはDIコンテナを組み込む権限がプロジェクト内で与えられていなかったから、もう1つは自分の担当範囲で使用するだけなので、シンプルな仕組みで十分だったからです。
気づいたこと
クラスを設計するときの考え方が変わりました。新しいクラスを追加するとき、「そのクラスが機能を実現するにはどんなクラスが必要か」ということを考えるようになりました。こうすることで、クラスが果たすべき責務が明確になり、クラス設計が改善されました。
反面、注意すべきことも見えてきました。「依存する側」と「依存される側」という観点でクラスをどんどん分割していきますが、やり過ぎると何もしないクラスができてしまいます。望ましい設計について、「2回測って1回で切る」とどこかで読んだことがあります。自分なりに解釈すると、1回目は機能をどんどん分割していく設計、2回目は無駄なクラスを統合していく設計とするとうまくいくような気がしました。
自作のファクトリにも問題点が見えてきました。小さいアプリケーション(2画面/5人日)だったのでファクトリクラスを1つしか用意しなかったのですが、巨大なクラスになりかけていました。ファクトリを分割し、ファクトリ同士のDIも必要になると思いました。ただ、そこまで複雑になるようであれば、DIコンテナの採用を検討するべきだと思います。
2014年3月3日月曜日
Pythonで単体テストをするには
「pythonでunittestをするには」にて、
テストプログラム
テストプログラムのディレクトリ構成とプログラムは以下のとおりです。
test/test1.py
test/test2.py
test/testsub/__init__.py
空のファイルです。testsubをモジュールとして認識させるために必要です。
test/testsub/testsub1.py
フォルダ配下のテストをすべて実行する
pythonの-mオプションのパラメータとしてunittestを渡します。詳細な出力を得るために、-vオプションも追加します。
モジュールを指定してテストする
上のオプションに加え、モジュール名を追加するとモジュールのテストになります。
クラスを指定してテストする
さらにクラス名を追加します。
テストメソッドを指定してテストする
さらにメソッド名を追加します。
テストスイートを指定してテストする
テストスイートを指定することもできます。要領はクラスやメソッドを指定する時と同様です。
一括して実行できるだけでなく、任意のテストメソッドを選択することもできるので、テンポよく開発が進められると思いました。
しかし、複数のテストファイルを作成した後でまとめてテストを実行する手段がないようなので、以下のようなコードを書いてみました。という記載がありますが、Python 2.6にてまとめてテストを実行する手段が追加されましたので、勝手にフォローさせていただきます。動作確認にはPython 3.3を使用しました。
テストプログラム
テストプログラムのディレクトリ構成とプログラムは以下のとおりです。
test ├test1.py ├test2.py └testsub ├__init__.py └testsub1.py
test/test1.py
# -*- coding: utf-8 -*- import unittest class TestCase1(unittest.TestCase): def test_success(self): pass def test_fail(self): self.fail() class TestCase2(unittest.TestCase): def test_success(self): pass def test_fail(self): self.fail() suite = unittest.TestSuite() suite.addTest(TestCase1('test_success')) suite.addTest(TestCase2('test_success'))
test/test2.py
# -*- coding: utf-8 -*- import unittest class TestCase1(unittest.TestCase): def test_success(self): pass def test_fail(self): self.fail()
test/testsub/__init__.py
空のファイルです。testsubをモジュールとして認識させるために必要です。
test/testsub/testsub1.py
# -*- coding: utf-8 -*- import unittest class TestCase1(unittest.TestCase): def test_success(self): pass def test_fail(self): self.fail()
フォルダ配下のテストをすべて実行する
pythonの-mオプションのパラメータとしてunittestを渡します。詳細な出力を得るために、-vオプションも追加します。
~/py/test $ python3 -m unittest -v test_fail (test1.TestCase1) ... FAIL test_success (test1.TestCase1) ... ok test_fail (test1.TestCase2) ... FAIL test_success (test1.TestCase2) ... ok test_fail (test2.TestCase1) ... FAIL test_success (test2.TestCase1) ... ok test_fail (testsub.testsub1.SubTestCase1) ... FAIL test_success (testsub.testsub1.SubTestCase1) ... ok ====================================================================== FAIL: test_fail (test1.TestCase1) ---------------------------------------------------------------------- Traceback (most recent call last): File "/cygdrive/c/home/myamamo/py/test/test1.py", line 10, in test_fail self.fail() AssertionError: None ====================================================================== FAIL: test_fail (test1.TestCase2) ---------------------------------------------------------------------- Traceback (most recent call last): File "/cygdrive/c/home/myamamo/py/test/test1.py", line 17, in test_fail self.fail() AssertionError: None ====================================================================== FAIL: test_fail (test2.TestCase1) ---------------------------------------------------------------------- Traceback (most recent call last): File "/cygdrive/c/home/myamamo/py/test/test2.py", line 10, in test_fail self.fail() AssertionError: None ====================================================================== FAIL: test_fail (testsub.testsub1.SubTestCase1) ---------------------------------------------------------------------- Traceback (most recent call last): File "/cygdrive/c/home/myamamo/py/test/testsub/testsub1.py", line 10, in test_fail self.fail() AssertionError: None ---------------------------------------------------------------------- Ran 8 tests in 0.004s FAILED (failures=4)
モジュールを指定してテストする
上のオプションに加え、モジュール名を追加するとモジュールのテストになります。
~/py/test $ python3 -m unittest -v test1 test_fail (test1.TestCase1) ... FAIL test_success (test1.TestCase1) ... ok test_fail (test1.TestCase2) ... FAIL test_success (test1.TestCase2) ... ok ====================================================================== FAIL: test_fail (test1.TestCase1) ---------------------------------------------------------------------- Traceback (most recent call last): File "test1.py", line 10, in test_fail self.fail() AssertionError: None ====================================================================== FAIL: test_fail (test1.TestCase2) ---------------------------------------------------------------------- Traceback (most recent call last): File "test1.py", line 17, in test_fail self.fail() AssertionError: None ---------------------------------------------------------------------- Ran 4 tests in 0.002s FAILED (failures=2)
クラスを指定してテストする
さらにクラス名を追加します。
~/py/test $ python3 -m unittest -v test1.TestCase1 test_fail (test1.TestCase1) ... FAIL test_success (test1.TestCase1) ... ok ====================================================================== FAIL: test_fail (test1.TestCase1) ---------------------------------------------------------------------- Traceback (most recent call last): File "test1.py", line 10, in test_fail self.fail() AssertionError: None ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
テストメソッドを指定してテストする
さらにメソッド名を追加します。
~/py/test $ python3 -m unittest -v test1.TestCase1.test_fail test_fail (test1.TestCase1) ... FAIL ====================================================================== FAIL: test_fail (test1.TestCase1) ---------------------------------------------------------------------- Traceback (most recent call last): File "test1.py", line 10, in test_fail self.fail() AssertionError: None ---------------------------------------------------------------------- Ran 1 test in 0.002s FAILED (failures=1)
テストスイートを指定してテストする
テストスイートを指定することもできます。要領はクラスやメソッドを指定する時と同様です。
~/py/test $ python3 -m unittest -v test1.suite test_success (test1.TestCase1) ... ok test_success (test1.TestCase2) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
一括して実行できるだけでなく、任意のテストメソッドを選択することもできるので、テンポよく開発が進められると思いました。
2014年2月24日月曜日
lispでiniファイルからalistをconsする
lispでiniファイルからalistをconsするプログラムを書きました。
サンプルのiniファイルはこのようになっています。
プログラム本体は以下のとおりです。
実行すると、以下のように出力します。
工夫したところは、関数"ini-assoc"でstring-equalを使用し、大文字と小文字を区別し内容にしたところです。
alistで返すことが目標だったので、パフォーマンスはよくありません。
今までの書き方であれば、DictionaryやMapを更新しまくるプログラムを書いてました。reduceに渡す関数を実装するにあたり、「新しいリストを返す」という発想に辿り着くまで時間がかかりました。
サンプルのiniファイルはこのようになっています。
[Section1] Key1=Value1 Key2=Value2 [Section2] Key1=Value1 [Section3]
プログラム本体は以下のとおりです。
;; inifile.l (defun read-lines (input) (loop as line = (read-line input nil nil) while line collect line)) (defun sectionp (line) (and (stringp line) (<= 2 (length line)) (eq #\[ (char line 0)) (eq #\] (char line (1- (length line)))))) (defun keyvaluep (line) (and (stringp line) (<= 3 (length line)) (find #\= line) (< 0 (position #\= line)))) (defun strip-section (line) (if (not (sectionp line)) line (subseq line 1 (1- (length line))))) (defun split-keyvalue (line) (if (not (keyvaluep line)) line (list (subseq line 0 (position #\= line)) (subseq line (1+ (position #\= line)))))) (defun convert-to-alist (alist line) (cond ((sectionp line) (cons (list (strip-section line)) alist)) ((keyvaluep line) (cons (append (car alist) (list (split-keyvalue line))) (cdr alist))) (t alist))) (defun ini-assoc (section key alist) (car (last (assoc key (cdr (assoc section alist :test 'string-equal)) :test 'string-equal)))) ;; 以下実行部 (setf ini-alist (reduce 'convert-to-alist (read-lines *standard-input*) :initial-value '())) (format t "~A~%" ini-alist) (format t "~A ~A = ~A~%" "Section2" "Key1" (ini-assoc "Section2" "Key1" ini-alist)) (format t "~A ~A = ~A~%" "SECTION1" "KEY2" (ini-assoc "SECTION1" "KEY2" ini-alist))
実行すると、以下のように出力します。
$ cat sample.ini | clisp inifile.l ((Section3) (Section2 (Key1 Value1)) (Section1 (Key1 Value1) (Key2 Value2))) Section2 Key1 = Value1 SECTION1 KEY2 = Value2
工夫したところは、関数"ini-assoc"でstring-equalを使用し、大文字と小文字を区別し内容にしたところです。
alistで返すことが目標だったので、パフォーマンスはよくありません。
今までの書き方であれば、DictionaryやMapを更新しまくるプログラムを書いてました。reduceに渡す関数を実装するにあたり、「新しいリストを返す」という発想に辿り着くまで時間がかかりました。
2014年2月17日月曜日
IronPythonで.NETモジュールをテストする
MSDN Magazineの記事を読んでいたら、タイトルのような記事を見つけました。
どれどれ、と読んでみるとせっかくIronPythonを使用しているのに、unittestモジュールを使用していないではありませんか。
もしかしたら当時のIronPythonには、まだunittestモジュールが含まれていなかったのかもしれません。
unittestモジュールを使用すれば、もっと簡単にテストが書けると思われるので、試してみます。
unittestモジュールの詳しい説明は割愛します。公式のドキュメントなどを参照してください。
テストコード
unittestモジュールを使用して書きなおしたテストが以下になります。
元の記事と同様、3番目のテストケースはスキップするようにしました。
テスト結果
テストの実行結果は以下のようになります。
元記事との比較
元記事と比較して、テストランナーを実装していない分、工数を減らす効果はあったと思います。
反面、テストケースの追加については、元記事ではテキストファイルに1行追加するだけなのに対して、unittestを使用した場合はメソッドの定義が必要になっています。サードパーティ製のユニットテストフレームワークであるpytestはパラメータ化されたテストにも対応しているようなので、検討する価値があると思いました。
参考
どれどれ、と読んでみるとせっかくIronPythonを使用しているのに、unittestモジュールを使用していないではありませんか。
もしかしたら当時のIronPythonには、まだunittestモジュールが含まれていなかったのかもしれません。
unittestモジュールを使用すれば、もっと簡単にテストが書けると思われるので、試してみます。
unittestモジュールの詳しい説明は割愛します。公式のドキュメントなどを参照してください。
テストコード
unittestモジュールを使用して書きなおしたテストが以下になります。
元の記事と同様、3番目のテストケースはスキップするようにしました。
# -*- coding: shift-jis -*- import clr import unittest clr.AddReferenceToFileAndPath('Class1.dll') from TwoCardPokerLib import Hand class HandTest(unittest.TestCase): def test_royal_flush_wins_pair_aces(self): h1 = Hand('Ac', 'Kc') h2 = Hand('Ad', 'As') self.assertEquals(1, h1.Compare(h2)) def test_straight_flush_diamonds_eq_straight_flush_hearts(self): h1 = Hand('Td', '9d') h2 = Hand('Th', '9h') self.assertEquals(0, h1.Compare(h2)) @unittest.skip('') def test_straight_loses_ace_high(self): h1 = Hand('Ah', '2c') h2 = Hand('As', '9c') self.assertEquals(1, h1.Compare(h2)) def test_flush_9_6_high_loses_flush_9_7(self): h1 = Hand('9h', '6h') h2 = Hand('9d', '7d') self.assertEquals(-1, h1.Compare(h2)) def test_king_high_loses_ace_high(self): h1 = Hand('Kc', 'Jh') h2 = Hand('As', '5d') self.assertEquals(-1, h1.Compare(h2))
テスト結果
テストの実行結果は以下のようになります。
C:\>ipy.exe -m unittest -v testhand test_flush_9_6_high_loses_flush_9_7 (testhand.HandTest) ... FAIL test_king_high_loses_ace_high (testhand.HandTest) ... ok test_royal_flush_wins_pair_aces (testhand.HandTest) ... ok test_straight_flush_diamonds_eq_straight_flush_hearts (testhand.HandTest) ... ok test_straight_loses_ace_high (testhand.HandTest) ... skipped '' ====================================================================== FAIL: test_flush_9_6_high_loses_flush_9_7 (testhand.HandTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "c:\testhand.py", line 29, in test_flush_9_6_high_loses_flush_9_7 self.assertEquals(-1, h1.Compare(h2)) AssertionError: -1 != 0 ---------------------------------------------------------------------- Ran 5 tests in 0.164s FAILED (failures=1, skipped=1)
元記事との比較
元記事と比較して、テストランナーを実装していない分、工数を減らす効果はあったと思います。
反面、テストケースの追加については、元記事ではテキストファイルに1行追加するだけなのに対して、unittestを使用した場合はメソッドの定義が必要になっています。サードパーティ製のユニットテストフレームワークであるpytestはパラメータ化されたテストにも対応しているようなので、検討する価値があると思いました。
参考
2014年2月10日月曜日
Traceを使用してログを出力するときの設定例(Filter)
ログに要求される機能の1つとして、レベルによるフィルタリングがあります。
Traceを使用したログ出力にも、同様の機能があります。
具体的には、<filter>タグを使用します。
設定例
出力例
Informationレベルのログが出力されなくなりました。
参考
<filter> Element for <add> for <listeners> for <trace>
関連記事
Traceを使用してログを出力するときの設定例(TraceListener)
Traceを使用してログを出力するときの設定例(TraceOutputOptions)
Traceを使用したログ出力にも、同様の機能があります。
具体的には、<filter>タグを使用します。
設定例
<configuration> <system.diagnostics> <trace autoflush="true"> <listeners> <clear /> <add name="file" type="System.Diagnostics.TextWriterTraceListener" initializeData="TEXT-WRITER.log"> <!-- Warning以上を出力 --> <filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning" /> </add> </listeners> </trace> </system.diagnostics> </configuration>
出力例
TraceTest Warning: 0 : TraceWarning TraceTest Error: 0 : TraceError
Informationレベルのログが出力されなくなりました。
参考
<filter> Element for <add> for <listeners> for <trace>
関連記事
Traceを使用してログを出力するときの設定例(TraceListener)
Traceを使用してログを出力するときの設定例(TraceOutputOptions)
2014年2月3日月曜日
Traceを使用してログを出力するときの設定例(TraceOutputOptions)
Traceを使用してログを出力するときの設定例(TraceListener)でTraceListenerの設定方法を書きました。
しかし、あれだけでは最低限のメッセージしか表示されません。
この記事では、日時などを表示する方法について書きます。
メッセージ以外を表示するには、<listeners>の<add>のtraceOutputOptions属性を使用します。
例
この属性を設定した時の出力例は、以下のようになります。
属性の詳しい説明については、TraceOutputOptions Valuesを参照してください。
しかし、あれだけでは最低限のメッセージしか表示されません。
この記事では、日時などを表示する方法について書きます。
メッセージ以外を表示するには、<listeners>の<add>のtraceOutputOptions属性を使用します。
例
<configuration> <system.diagnostics> <trace autoflush="true"> <listeners> <clear /> <!-- テキストファイル --> <add name="file" type="System.Diagnostics.TextWriterTraceListener" initializeData="TEXT-WRITER.log" traceOutputOptions="Callstack, DateTime, LogicalOperationStack, ProcessId, ThreadId, Timestamp" /> </listeners> </trace> </system.diagnostics> </configuration>
この属性を設定した時の出力例は、以下のようになります。
TraceTest.exe Information: 0 : TraceInformation ProcessId=5700 LogicalOperationStack= ThreadId=1 DateTime=2014-02-02T12:47:28.3398027Z Timestamp=6032477769 Callstack= 場所 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) 場所 System.Environment.get_StackTrace() 場所 System.Diagnostics.TraceEventCache.get_Callstack() 場所 System.Diagnostics.TraceListener.WriteFooter(TraceEventCache eventCache) 場所 System.Diagnostics.TraceListener.TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, Int32 id, String message) 場所 System.Diagnostics.TraceInternal.TraceEvent(TraceEventType eventType, Int32 id, String format, Object[] args) 場所 TraceTest.TraceTest.Main(String[] args)
属性の詳しい説明については、TraceOutputOptions Valuesを参照してください。
2014年1月27日月曜日
Lispでxmlの実体参照変換
当ブログでは、プログラムをハイライト表示するときにSyntaxHighlighterを使用しています。
XMLを表示するときは、実体参照へ変換しなければいけません。
「普段使用しているエディタから変換できれば楽ちんだよね」と思い、xyzzy Lispで変換プログラムを書いてみました。
プログラム
実行すると現在のバッファを読み込み、一時バッファへ変換後のxmlを表示します。
工夫したところは、変換の定義をalistにしたところです。
参考
XMLを表示するときは、実体参照へ変換しなければいけません。
「普段使用しているエディタから変換できれば楽ちんだよね」と思い、xyzzy Lispで変換プログラムを書いてみました。
プログラム
; convert-xml (defparameter *conversion-alist* '((#\< . "<") (#\> . ">") (#\& . "&") (#\" . """) (#\' . "'"))) (defun convert-xml () (interactive "*") (let ((input-buffer (window-buffer (selected-window)))) (with-output-to-temp-buffer ("*Converted*") (with-open-stream (s (make-buffer-stream input-buffer)) (loop (let ((c (read-char s nil))) (if c (format t "~A" (convert-char c *conversion-alist*)) (return nil)))))))) (defun convert-char (c alist) (let ((conversion (assoc c alist))) (if conversion (cdr conversion) c)))
実行すると現在のバッファを読み込み、一時バッファへ変換後のxmlを表示します。
工夫したところは、変換の定義をalistにしたところです。
参考
2014年1月20日月曜日
Traceを使用してログを出力するときの設定例(TraceListener)
.Netでログを出力するときの選択肢の1つとして、System.Diagnostics.Traceクラスを使用する方法があります。
この時のアプリケーション構成ファイルの記述例を記載します。
テストプログラムとして、次のプログラムを使用します。
次にTraceListenerの設定例を記載します。
<trace> の <listeners> の <add> 要素
この時のアプリケーション構成ファイルの記述例を記載します。
テストプログラムとして、次のプログラムを使用します。
namespace TraceTest { using System.Diagnostics; public class TraceTest { public static void Main(string[] args) { Trace.TraceInformation("TraceInformation"); Trace.TraceWarning("TraceWarning"); Trace.TraceError("TraceError"); } } }
次にTraceListenerの設定例を記載します。
<configuration> <system.diagnostics> <trace autoflush="true"> <listeners> <clear /> <!-- 標準出力 --> <add name="console" type="System.Diagnostics.ConsoleTraceListener" /> <!-- 区切りリスト --> <add name="delimited-list" type="System.Diagnostics.DelimitedListTraceListener" initializeData="DELIMITED-LIST.log" /> <!-- イベントログ --> <add name="eventlog" type="System.Diagnostics.EventLogTraceListener" initializeData="Application" /> <!-- テキストファイル --> <add name="file" type="System.Diagnostics.TextWriterTraceListener" initializeData="TEXT-WRITER.log" /> <!-- XML形式 --> <add name="xml-writer" type="System.Diagnostics.XmlWriterTraceListener" initializeData="XML-WRITER.log" /> </listeners> </trace> </system.diagnostics> </configuration>参考
<trace> の <listeners> の <add> 要素
2014年1月13日月曜日
FormでShowing/Hidingイベントを実装
System.Windows.Forms.Formにありそうでないイベントの一つに、表示前/非表示前イベントがあります。
なければ作っちゃえということで、SetVisibleCoreをオーバーライドして実装してみました。
処理の流れは以下のようになります。
なお、注意点として、SetVisibleCoreでShow/Hideメソッドを呼ばないこと、Visibleプロパティを更新しないことがあります。これらに違反すると、StackOverflowExceptionが発生してしまいます。
なければ作っちゃえということで、SetVisibleCoreをオーバーライドして実装してみました。
処理の流れは以下のようになります。
- 引数と現在のVisibleを比較し、等しければイベントを発生させずにSetVisibleCoreを呼んで終了する。
- 引数がtrueのとき、Showingイベントを発生させる
- 引数がfalseのとき、Hidingイベントを発生させる
- 2,3の結果、CancelEventArgs.Cancelがtrueのとき、引数を逆にしてSetVisibleCoreを呼び、終了する。
- SetVisibleCoreを呼び、終了する。
なお、注意点として、SetVisibleCoreでShow/Hideメソッドを呼ばないこと、Visibleプロパティを更新しないことがあります。これらに違反すると、StackOverflowExceptionが発生してしまいます。
サンプル
参考
Form.SetVisibleCore メソッド
登録:
投稿 (Atom)