February 2009
Volume 24 Number 02
実践的なパターン - 設定より規約
Jeremy Miller | February 2009
目次
言語の技術革新の影響
重複の排除
実用的な既定値
設定より規約
次のステップ
プロジェクトの中心課題への取り組みと純粋に技術的な問題への対処にかかる時間の比重について考えてみたことがありますか。ソフトウェアの新しいテクノロジや手法の最大の目標は、開発者がソフトウェアで実現しようとする目的と、その目的のコードでの実現内容との隔たりを少なくすることにあるという意見もあるでしょう。業界では、開発者が機能の実現により多くの時間を割き、低レベルのインフラストラクチャにかかる時間を削減できるようにするために継続的に抽象化の強化を進めてきましたが、まだまだその試みは道半ばです。
少し考えてみてください。あなたが作成したコードを業務担当者に見せるとしたら、その業務担当者にコードを解読する意欲があったとしても、どの程度までコードに関心を示すでしょうか。おそらく、関心が得られるのは、システムのビジネス機能を表しているコード部分のみでしょう。このコード部はシステムの "内容" です。一方、型宣言、構成設定、try/catch ブロック、ジェネリック制約などのコードにはほとんど関心が持たれないでしょう。これらのコードはインフラストラクチャ、つまり "形式" であり、開発者がコードを作成するためだけに必要な処理です。
前回のコラムでは、主に、長い間設計基準の一部とされてきた基本的な設計概念と原則について説明しました。今回のコラムでは、コードの形式部分の量を削減するための新しい手法を説明します。耳慣れない概念もあるかもしれませんが、これらの手法は数年のうちに .NET の主流になるでしょう。
言語の技術革新の影響
まず、プログラミング言語の選択とその言語の使い方を説明します。プログラミング言語がコードの形式部分の量に与える影響を示すため、少し回り道をして古い歴史の話をします。
私は、この 10 年間の前半には Visual Basic 6.0 で大規模システムを構築していました。この場合、各メソッドは図 1 のようになります。このコードの情報はすべて形式です。
図 1 形式に依存したコード
Sub FileOperations()
On Error Goto ErrorHandler
Dim A as AType
Dim B as AnotherType
' Put some code here
Set A = Nothing
Set B = Nothing
ErrorHandler:
Err.Raise Err.Number, _
"FileOperations" & vbNewLine & Err.Source, _
Err.Description, _
Err.HelpFile, _
Err.HelpContext
Exit Sub
デバッグしやすくするため、各メソッドに対して相当量の定型パターンを使用し、スタック トレースと同等の機能を作成しました。メソッド内で変数を逆参照し (Set A = Nothing)、これらのオブジェクトを解放する必要もありました。また、各メソッドのエラー処理とオブジェクト クリーンアップのコードを検証するためだけに、コード レビューに関する厳密な基準を設けました。システムの内容部分となる実際のコードは、これらの形式の間に散在しています。
話を現在に戻して、C# や Visual Basic .NET などの最新プログラミング言語の場合を考えてみましょう。現在では、ガベージ コレクションの機能があるため、Visual Basic 6.0 プログラミングの場合のような明示的なメモリ クリーンアップを行う必要がほとんどなくなりました。また、例外のスタック トレースは Microsoft .NET Framework に組み込まれたため、プログラミングする必要がなくなりました。.NET Framework への移行によって不要になる定型コードのことを考えれば、形式部のコードが削減されるため、.NET 準拠の言語の方が Visual Basic 6.0 より生産性および可読性が高いと言えます。
.NET Framework によって大きな飛躍が遂げられましたが、言語の進化はまだ完了したわけではありません。C# を使用して従来の方法で単純なプロパティを実装する場合を考えてみましょう。
public class ClassWithProperty {
// Higher Ceremony
private string _name;
public string Name {
get { return _name; }
set { _name = value; }
}
}
このコードの内容は、ClassWithProperty クラスに文字列プロパティ Name が存在するというだけのことです。同じ処理を C# 3.0 の自動プロパティで記述すると次のようになります。
public class ClassWithProperty {
// Lower Ceremony
public string Name { get; set; }
}
このコードは従来の形式のプロパティと目的はまったく同じですが、ご覧のとおり "コンパイラのノイズになる" コードが大幅に減ります。
とはいえ、一般に、ソフトウェア開発者がプログラミング言語を完全に制御することはできません。新しいプログラミング言語の技術革新や代替言語を利用することはもちろん重要ですが、ここで、現在主流となっている C# および Visual Basic で使用可能な設計概念に話を移します。
ドメイン指向の検証
.NET Framework では、ASP.NET Validator コントロールなどのツールを使用して、宣言型のフィールド レベルの検証を簡単にユーザー インターフェイスに追加できます。それでも、以下の理由から、検証ロジックを実際のドメイン モデル クラス内、または少なくともドメイン サービス内の近い場所に配置することにメリットがあります。
- 検証ロジックはビジネス ロジックに関連するものなので、すべてのビジネス ロジックをビジネス ロジック クラスに含めることをお勧めします。
- 検証ロジックをユーザー インターフェイスから切り離されたドメイン モデルまたはドメイン サービスに配置することにより、画面間の重複が削減される可能性があり、同じ検証ロジックをアプリケーションによって公開された非ユーザー インターフェイス サービス (Web サービスなど) で実行することができるようになります (繰り返しますが、"重複の排除" が重要です)。
- モデル内の検証ロジックに対する単体テストや受け入れテストを作成する方が、同じロジックをユーザー インターフェイスの一部として実装してテストするよりはるかに容易です。
重複の排除
プロジェクトの途中で 1 つのデータ フィールドの定義に変更を必要とする点が見つかった場合はどうなるでしょう。ほとんどの場合、データベースのわずかな変更がアプリケーション全体に波紋のように広がります。これは、1 か所のロジックの変更に対応するために中間層、データ アクセス コード、さらにはユーザー インターフェイス層のさまざまな部分を変更しなければならなくなるのと同様です。
以前の勤務先では、このようにわずかなデータ フィールドの変更によってもたらされる波及効果をワームホールのアンチパターンと呼んでいました。1 か所の小さい変更の必要性がすべての層に波及することは避けたいものです。このワームホール効果を減速させるには、まず不要な層を削減します。より困難で効果の大きい手順は、重複を排除する方法を探すことです。
この重複排除の原則では、各ファクトまたはポリシーの権限のあるソースがシステム全体で 1 つに限られることを規定します。たとえば、Create (作成)、Read (読み取り)、Update (更新)、および Delete (削除) の (CRUD) の機能を持つ Web アプリケーションを作成する場合を考えてみましょう。この種の多くのシステムには、データ フィールドの編集および保存操作が伴います。これらのフィールドは、画面上で編集し、サーバー上で検証し、データベースに保存し、ユーザー エクスペリエンスを高めるためにできればクライアント側でも検証する必要があります。このとき、"フィールドを必須フィールドにする"、"フィールドの文字数を最大 50 文字にする" などの設定はコードに 1 回だけ指定すれば済むようにしたいものです。
この場合、使用できるアプローチはいくつかあります。たとえば、データベース スキーマをマスタ定義にし、中間層とユーザー プレゼンテーション層のコードをスキーマから生成できます。また、データ フィールドを XML ファイルなどの外部メタデータ保存領域に定義し、コード生成機能を使用してデータベース スキーマ、中間層オブジェクト、ユーザー インターフェイス画面を作成する方法もあります。個人的には大規模なコード生成は好まないので、私のチームでは別の方法を選択しました。
通常、私たちは、まずドメイン モデル クラスを設計し、検証ロジックをドメイン モデルのエンティティ クラスの役割にするかどうかを検討します。必須フィールドや最大文字数などの単純な検証ルールの場合は、図 2 の Address クラスのようにプロパティを検証属性で修飾します。
図 2 検証属性を使用する
public class Address : DomainEntity {
[Required, MaximumStringLength(250)]
public string Address1 { get; set; }
[Required, MaximumStringLength(250)]
public string City { get; set; }
[Required]
public string StateOrProvince { get; set; }
[Required, MaximumStringLength(100)]
public string Country { get; set; }
[Required, MaximumStringLength(50)]
public string PostalCode { get; set; }
public string TimeZone { get; set; }
}
属性の使用は、検証ルールを指定する単純で一般的な方法です。検証ルールを命令型コードで実装するのではなく、宣言型で記述するため、内容部と形式部のテストに合格したと言うことができます。
ただし、必須フィールドおよび最大文字数のルールをデータベースにレプリケートする必要があります。私のチームでは、永続化メカニズムに NHibernate を使用しています。NHibernate の優れた機能の 1 つに、NHibernate マッピングからデータ定義言語 (DDL) コードを生成する機能があります。この DDL コードを使用してデータベース スキーマを作成し、ドメイン モデルとの同期を維持することができます (この方法が新しいプロジェクトに最適であることは明らかです)。データベースをドメイン モデルから生成するこの方法を有効なものにするには、非 NULL フィールドのマークを付け、文字数を指定する情報を NHibernate マッピングに追加する必要がありました。
そこで、新しい Fluent NHibernate メカニズムを使用してオブジェクト マッピングを定義しました。また、図 3 のコードに示すように、Required 属性および MaximumStringLength 属性がモデル クラスに存在する場合の処理方法を Fluent NHibernate に指示することによって、Fluent NHibernate のセットアップ コードにマッピングの自動規則を設定しました。
図 3 NHibernate で属性を処理する
public class MyPersistenceModel : PersistenceModel {
public MyPersistenceModel() {
// If a property is marked with the [Required]
// attribute, make the corresponding column in
// the database "NOT NULL"
Conventions.ForAttribute<RequiredAttribute>((att, prop) => {
if (prop.ParentIsRequired) {
prop.SetAttribute("not-null", "true");
}
});
// Uses the value from the [MaximumStringLength]
// attribute on a property to set the length of
// a string column in the database
Conventions.ForAttribute<MaximumStringLengthAttribute>((att, prop) => {
prop.SetAttribute("length", att.Length.ToString());
});
}
}
これで、これらの規則がプロジェクトのすべてのマッピングに適用されます。Address クラスの処理は、永続化するプロパティを Fluent NHibernate に通知しているだけです。
public class AddressMap : DomainMap<Address> {
public AddressMap() {
Map(a => a.Address1);
Map(a => a.City);
Map(a => a.TimeZone);
Map(a => a.StateOrProvince);
Map(a => a.Country);
Map(a => a.PostalCode);
}
}
Fluent NHibernate に検証属性を通知してあるため、Address テーブルの DDL を生成できます (図 4 を参照)。図 4 の SQL では、文字数が Address クラスの MaximumStringLength 属性の定義と一致しています。同様に、NULL または NOT NULL の値も Address クラスの Required 属性に従っています。
図 4 DDL コードを生成する
CREATE TABLE [dbo].[Address](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[StateOrProvince] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Country] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[PostalCode] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TimeZone] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Address1] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Address2] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[City] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
データベース構造がドメイン モデル オブジェクトの検証属性から派生しているインフラストラクチャを導入しましたが、まだクライアント側の検証と外観の問題が残っています。私たちは、ブラウザ内で単純な入力検証を実行し、必須フィールド要素に何らかの方法でマークを付けてユーザー エクスペリエンスを向上させたいと考えました。
そこで、到達した方法は、検証属性を認識する入力要素の HTML レンダリングを行うことでした (コンテキストには、ASP.NET Model View Controller (MVC) および Framework Beta1 を使用し、Web フォームをビュー エンジンとして使用しています)。このシステムの Address ビューのマークアップは図 5 のようになります。
図 5 Address ビューのマークアップ
<div class="formlayout_body">
<p><%= this.TextBoxFor(m => m.Address1).Width(300)%></p>
<p><%= this.TextBoxFor(m => m.Address2).Width(300) %></p>
<p>
<%= this.TextBoxFor(m => m.City).Width(140) %>
<%= this.DropDownListFor(m =>
m.StateOrProvince).FillWith(m => m.StateOrProvinceList) %>
</p>
<p><%= this.TextBoxFor(m => m.PostalCode).Width(80) %></p>
<p><%= this.DropDownListFor(m =>
m.Country).FillWith(m => m.CountryList) %></p>
<p><%= this.DropDownListFor(m =>
m.TimeZone).FillWith(m => m.DovetailTimeZoneList) %></p>
</div>
TextBoxFor および DropDownListFor は、MVC アーキテクチャのすべてのビューに使用される共通基本ビュー内の短い HTML ヘルパです。TextBoxFor のシグネチャを次に示します。
public static TextBoxExpression<TModel> TextBoxFor< TModel >(
this IViewWithModel< TModel > viewPage,
Expression<Func< TModel, object>> expression)
where TModel : class {
return new TextBoxExpression< TModel >(
viewPage.Model, expression);
}
このコードの重要な点は、入力引数が Expression (正確には Expression<Func<TModel, object>>) であるということです。TextBoxExpression クラスはテキスト ボックスの実際の HTML を作成し、以下の操作を行います。
- Expression を解析し、バインドされたプロパティの正確な PropertyInfo オブジェクトを検出します。
- 検証属性が存在するかどうかをその PropertyInfo に問い合わせます。
- 問い合わせ結果に従ってテキスト ボックスの HTML をレンダリングします。
ここでは、Required 属性にマークが付いたプロパティにバインドされたすべての HTML 要素に required という名前のクラスを追加しただけです。同様に、バインドされたプロパティに MaximumStringAttribute が見つかった場合、HTML テキスト ボックスの maxlength 属性をその属性と一致するように設定し、ユーザー入力を許可された文字数に制限します。結果の HTML は次のようになります。
<p><label>Address:</label>
<input type="text" name="Address1" value=""
maxlength="250" style="width: 300px;"
class="required textinput" /></p>
必須フィールドの外観は、CSS クラスの required の外観を編集するだけで簡単に制御できます (私たちは画面の必須フィールドの色を薄い青に設定しました)。実際のクライアント側の検証には jQuery Validation プラグインを使用しました。入力要素に required クラスが存在するかどうかを検索するだけの処理には、このプラグインは便利な機能です。テキスト要素に最大文字数を適用するには、入力要素の maxlength 属性を設定するだけで済みます。
これは完全な実装ではありません。時間をかけて実際の実装を行う場合もそれほど難しいことはありません。難しい部分は、複数の層のメタデータやコーディングの反復を排除する方法を考えることでした。私のチームが行ったような方法でオブジェクト モデルからデータベースを生成することに多くのチームはあまり熱心にならないと思いますが、それでもかまいません。このコラムの真の目的は、重複排除の原則に従って開発作業を効率化する方法のヒントを提示することにあります。
実用的な既定値
ここまで、Fluent NHibernate を使用してオブジェクト リレーション マッピング (ORM) を表現する場合の簡単な例を AddressMap クラスで示してきました。次に、Site クラスから Address オブジェクトへの参照を表すもう少し複雑な例を示します。
public SiteMap() {
// Map the simple properties
// The Site object has an Address property called PrimaryAddress
// The code below sets up the mapping for the reference between
// Site and the Address class
References(s => s.PrimaryAddress).Cascade.All();
References(s => s.BillToAddress).Cascade.All();
References(s => s.ShipToAddress).Cascade.All();
}
ORM ツールを構成する場合には、一般に以下の作業が必要です。
- Entity クラスのマップ先テーブル名を指定します。
- Entity の主キー フィールドを指定し、通常は主キー値の割り当て方法 (自動付番またはデータベース シーケンスにするか、システムで主キー値を割り当てるか、主キーに GUID を使用するかなど) も指定します。
- オブジェクトのプロパティまたはフィールドをテーブル列にマップします。
- Site から Address へのマッピングのようにオブジェクト間に対一関係を設定する場合、ORM ツールで使用できる外部キー列を指定して親レコードと子レコードを結合する必要があります。
通常、これらの作業は手間がかかります。これらは ORM でオブジェクトを永続化するために必要な形式です。コードに実用的な既定値を埋め込むことによって、これらの手間のかかる処理の一部を排除することができます。
ご覧のように、マッピングの例ではテーブル名、主キーの方式、外部キー フィールド名を指定していません。この場合、Fluent NHibernate マッピングのスーパークラスでマッピングの既定値を設定しています。
public abstract class DomainMap<T> : ClassMap<T>,
IDomainMap where T : DomainEntity {
protected DomainMap() {
// For every DomainEntity class, use the Id property
// as the Primary Key / Object Identifier
// and use an Identity column in SQL Server,
// or an Oracle Sequence
UseIdentityForKey(x => x.Id, "id");
WithTable(typeof(T).Name);
}
}
こだわりのあるソフトウェア
規約の採用を "制限" として説明していることにお気付きかもしれません。"設定より規約" の原則の背後には、設計に人為的な制約を設けて "こだわりのあるソフトウェア" を作成するという哲学があります。
こだわりのあるフレームワークでは、開発者は柔軟性を排除する寸前までの制約を期待されます。こだわりのあるソフトウェアの支持者は、このような制約によって開発者による決定を排除し、一貫性を高めることで開発が効率化されると感じています。
私のチームが採用したこだわりは、すべてのドメイン モデル クラスが Id という 1 つの long 型のプロパティによって完全に識別されるということでした。
public virtual long Id { get; set; }
この単純なルールが設計にいくつかの大きな影響をもたらしました。すべてのエンティティ クラスが同一の方法で識別されるため、最上位エンティティごとに専用のリポジトリ クラスを作成するのではなく、1 つのリポジトリ クラスを使用することができます。同様に、Web アプリケーションの URL 処理もエンティティ クラス間で統一されているため、エンティティごとに専用のルーティング ルールを登録する必要がありません。
このこだわりに従うことによって、新しいエンティティ型を追加するためのインフラストラクチャのコストが低減します。この方法の短所は、オブジェクト識別子に自然キーや複合キー、または GUID を使用することがきわめて困難だということです。私のチームでは特に問題はありませんでしたが、この短所はほとんどの場合に他のチームが私たちと同じこだわりを選択することを難しくする要因になります。
では、これらのこだわりをどのように適用したらよいでしょうか。それには、まず、チーム内でこれらのこだわりについての共通の理解と合意を取り付けます。事情がわかっていれば、開発者が設計上のこだわりの選択を効果的に利用する可能性が高くなります。"設定より規約" の原則も、開発者が既存の規約をよく知らない場合や規約が紛らわしい場合には混乱を招く可能性があります。
静的コード分析ツールを継続的な統合ビルドの一部として使用し、プロジェクトの規約を自動的に適用することも考えられます。
このコードでは、DomainEntity のすべてのサブクラスを ID 方式で割り当てられた Id プロパティによって識別するポリシーを設定しています。テーブル名はクラス名と同じであることを前提にしています。これで、これらの選択をいつでもクラスごとにオーバーライドできますが、その必要はめったにありません (SQL Server の予約語との競合を避けるためだけに User クラスを Users テーブルにマップする必要はありました)。同様に、Fluent NHibernate は他のクラスを参照するプロパティ名に基づいた外部キー名を前提にしています。
個々のマッピング クラスのコード行はあまり削減されませんが、マッピング全体のノイズ コードが削減されることによってマッピングの可変部分が解読しやすくなります。
設定より規約
ソフトウェア開発者は、動作を命令型コードから宣言型の XML 構成に移行することにより、生産性を高め、システムをより動的にすることを目指してきました。多くの開発者は XML 構成が氾濫しすぎて有害だと感じています。明示的な構成より既定値を重視する方式は "設定より規約" とも呼ばれます。
"設定より規約" は、明示的なコードを必要とする代わりにコードの構造から必然的に導き出される既定値を適用することを目指す設計理論および手法です。その目的は、開発者の課題をアプリケーションおよびアーキテクチャの非定型的な部分だけに絞って、開発を簡素化することにあります。
最近では、ASP.NET MVC Framework の熱心な利用者が増え、さまざまな使い方が試されるようになってきました。Web 開発の MVC モデルには、"設定より規約" の適用が最適な反復コードのソースがいくつかあります。
以下の 5 つの手順は、MVC モデルの 1 つの要求の基本的な流れを示しています。
- クライアントから URL を受け取ります。ルーティング サブシステムによって URL が解析され、この URL を処理するコントローラの名前が決まります。
- ルーティング サブシステムによって決定されたコントローラ名から適切なコントローラ オブジェクトを作成または検索します。
- 適切なコントローラ メソッドを呼び出します。
- 適切なビューを選択し、コントローラ メソッドから生成されたモデル データをこのビューにマーシャリングします。
- ビューをレンダリングします。
標準では、ASP.NET MVC Framework で Web ページを作成する場合に反復される形式がありますが、これらの形式は制限的な規約を採用することによって削減できます。
それには、まず、適切なコントローラ クラスを使用して受信 URL を Web サイトに接続します。MVC Framework のルーティング ライブラリを使用して、URL を問い合わせ、コントローラ名を決めることができます。MVC Framework は、受信 URL から決定したコントローラ名に一致するコントローラに該当するコントローラ オブジェクトを、登録済みの IControllerFactory オブジェクトに要求します。
多くのチームは、単純にコントローラの構築を制御の反転 (IOC) ツールに委任しています。私のチームでは、オープン ソースの StructureMap ツールを使用してコントローラのインスタンスを名前によって解決しています。
public class StructureMapControllerFactory
: IControllerFactory {
public IController CreateController(
RequestContext requestContext, string controllerName) {
// Requests the named Controller from the
// StructureMap container
return ObjectFactory.GetNamedInstance<IController>(
controllerName.ToLowerInvariant());
}
}
コントローラを要求する処理はきわめて単純ですが、先に IOC コンテナを使用して、すべてのコントローラ クラスの名前を登録しておく必要があります。これによって、アーキテクチャに形式が追加されるのではないかという懸念が生じます。数年前だったら、コントローラ クラスの明示的な IOC 構成を思い切って次のようにしたことでしょう。
public static class ExplicitRegistration {
public static void BootstrapContainer() {
ObjectFactory.Initialize(x => {
x.ForRequestedType<IController>().AddInstances(y => {
y.OfConcreteType<AddressController>().WithName("address");
y.OfConcreteType<ContactController>().WithName("contact");
// and so on for every possible type of Controller
});
});
}
}
このコードは、IOC ツールを提供すること以外に何の存在理由もないまったく冗長な形式です。登録コードをよく見ると、一貫したパターンに従っていることがわかります。AddressController はアドレスとして登録され、ContactController は連絡先として登録されています。各コントローラを明示的に構成しなくても、単に規約を作成するだけで、各コントローラ クラスのルーティング名を自動的に決定することができます。
さいわいなことに、StructureMap では規約ベースの登録が直接サポートされているため、IController の具象型を自動登録する新しい ControllerConvention を作成できます。
public class ControllerConvention : TypeRules, ITypeScanner {
public void Process(Type type, PluginGraph graph) {
if (CanBeCast(typeof (IController), type)) {
string name = type.Name.Replace("Controller", "").ToLower();
graph.AddType(typeof(IController), type, name);
}
}
}
次に、図 6 のように、新しい規約を使用して StructureMap コンテナをブートストラップするコードが必要です。新しい ControllerConvention が準備され、IOC コンテナの一部がブートストラップすると、アプリケーションに追加する新しいコントローラ クラスが自動的に IOC の登録に追加されるので、開発者が明示的に構成する必要はありません。そのため、開発者が新しい画面の新しい構成要素を追加し忘れてエラーやバグが発生する心配がなくなります。
図 6 StructureMap の新しい規約
/// <summary>
/// This code would be in the same assembly as
/// the controller classes and would be executed
/// in the Application_Start() method of your
/// Web application
/// </summary>
public static class SampleBootstrapper {
public static void BootstrapContainer() {
ObjectFactory.Initialize(x => {
// Directs StructureMap to perform auto registration
// on all the Types in this assembly
// with the ControllerConvention
x.Scan(scanner => {
scanner.TheCallingAssembly();
scanner.With<ControllerConvention>();
scanner.WithDefaultConventions();
});
});
}
}
参考までに付記しておきますが、この自動登録方式は、IOC コンテナがプログラムによる登録 API を公開している限り、.NET Framework に関して私が認識しているすべての IOC コンテナで使用可能です。
次のステップ
今回のテーマは、目的とその目的を実現するコードとの隔たりや不調和を軽減することでした。このコラムでは、明示的なコードではなく、名前付け規則を使用することによってコードが "自ら答えを導く" 方法や、システム内の情報の重複を避ける方法を示してきました。また、属性に埋め込まれた情報を再利用して機械的な作業を削減する再帰的な方法も示しました。
ここで紹介した各設計概念は開発上の形式部分の反復を削減するために役立ちますが、それなりのコストも伴います。"設定より規約" に批判的な人は、この方法の本質的な "魔力" に不満を持っています。コードやフレームワークにこだわりを埋め込むと、これらのこだわりが適さない場合に新しいシナリオでの再利用の可能性が損なわれます。
今回説明できなかったトピックがたくさん残っているので、今後のコラムで紹介していきたいと思います。もちろん、言語指向のプログラミング、F#、IronRuby、IronPython などの代替言語、および内部のドメイン固有言語の使用がソフトウェアの設計プロセスに与える影響についても説明したいと思います。
ご意見やご質問は、mmpatt@microsoft.com まで英語でお送りください。
Jeremy Miller は、C# の Microsoft MVP であり、.NET での依存関係の注入用に公開されているオープン ソースの StructureMap ツール、および .NET での FIT テスト用に近日公開予定の意欲的な StoryTeller ツールの作成者でもあります。彼のブログ「The Shade Tree Developer (日陰の開発者)」は、CodeBetter サイト上で公開されています。