Active Directory は DirectoryEntry を使って検索する

.NET で、ドメインサーバーにある情報を検索するには、3 つのクラスを駆使します。

  • DirectoryEntry クラス: エントリそのもの
  • DirectorySearcher クラス: LDAP クエリで検索
  • SearchResult クラス: DirectorySearcher で検索した結果

あとは、DirectoryEntry オブジェクトの Properties コレクションを使えば、なんとかなるのですが…結構、これが慣れるまでが大変なので、メモがてら公開しておきます。

# 事情があって、コードは VB で。

■ドメイン構成と問題

ドメイン構成は、下記のようになっています。

訳あって、ドメインサーバーが2つあります。通常、ログインするほうは、plan.local ドメインなのですが、グループの設定やらなにやらがあるのは、moonmile.local ドメインのほうなのです。まぁ、通常業務のセキュリティ(文書閲覧とか)は plan.local ドメインで行っていて、アプリケーション絡みのややこしいセキュリティ関係は moonmile.local に閉じ込めた、と考えてください。

ここで、tomoaki@plan.local のユーザーがログインしているときに、GRP001 などのグループに属しているか?をチェックする、ことになります。

普通ならば、plan.local のほうにグループを作ればいいのですが…そこは業務的な制限です。

■ユーザーとグループの設定

実験的に、windows server 2008 R2 を使って、設定しています。

tomoaki@plan.local ユーザーを、どのようにして moonmile.local のほうに潜り込ませるかというと、tomoaki@plan.local の SID を使ったユーザーを moonmile.local に作成します。

これを moonmile.local ドメイン内で検索して、グループに入っているかどうかをチェックしようという仕組みです。
ForeignSecurityPrincipals のほうに入れているのは、SID を公開しているか、一応、ってことですね。本来ならば、moonmile.local と plan.local の SID を同じものにすれば話は簡単なのですが、作り方が分からない(苦笑)ので、別々の SID になります。


SID 自体をユーザー名にしていまいます。windows server 2008 R2 だと、SID の長さのままだと後ろのほうが切れてしまうので、実際に検索するのは表示名(displayName)になります。

■実験開始

少しずつ作っている/作ったので、ボタンが4つあります。

  • AD 検索(全検索): ひとまず、全検索してみる。
  • ForeignSecurityPrincipals: CN を指定して、絞ってみる。
  • ad-sv 問合せ: tomoaki ユーザーがログインするほうで、SID を取得します(実際は、ドメインのユーザー で、WindowsIdentity.GetCurrent().User のように SID が取得できます。
  • win2008-sv 問合せ: グループ名まで検索します。

■AD 全検索

単純に、AD の情報を取得します。

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click

	Dim root As New DirectoryEntry("LDAP://win2008-sv/DC=moonmile,DC=local", "masuda", "password")
	Dim se As New DirectorySearcher(root)

	ListBox1.Items.Clear()
	For Each res As SearchResult In se.FindAll
		Dim de As DirectoryEntry = res.GetDirectoryEntry
		Debug.Print(de.Path)
		ListBox1.Items.Add(de.Path)
	Next
End Sub

LDAP クエリを指定して、DirectoryEntry オブジェクトを作成します。ここでは、ドメインに入っていない状態なので、AD を検索可能なユーザー名とパスワードを指定していますが、既にドメインに入っている場合は、

Dim root As New DirectoryEntry("LDAP://win2008-sv/DC=moonmile,DC=local")

のように指定しても OK です。また ドメインサーバーのフォワードがきちんと設定されていれば、

Dim root As New DirectoryEntry("LDAP://DC=moonmile,DC=local")

のように、サーバー名が無くても動作します。

全検索して、プログラム内で for/if しても良いのですが、ドメインサーバーに負担を掛けそうなので、もうちょっと工夫が必要です。

■CN などで検索を絞る

外部に公開している場合「CN=ForeignSecurityPrincipals」を付ければ少しは負担が軽くなります。

Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
	Dim root As New DirectoryEntry("LDAP://win2008-sv/CN=ForeignSecurityPrincipals,DC=moonmile,DC=local", "masuda", "password")
	Dim obj = root.NativeObject
	Dim se As New DirectorySearcher(root)
	Dim de2 As DirectoryEntry = Nothing
	ListBox1.Items.Clear()
	For Each res As SearchResult In se.FindAll
		Dim de As DirectoryEntry = res.GetDirectoryEntry
		Debug.Print(de.Path)
		ListBox1.Items.Add(de.Path + " " + de.Properties("displayName").Value)
		If de.Path.IndexOf("増田 トニー") >= 0 Then
			de2 = de
		End If
	Next
	For Each nm In de2.Properties.PropertyNames
		Debug.Print(nm)
	Next
End Sub

表示名を調べるときは、de.Properties(“displayName”).Value のように、Properties プロパティを使えば OK です。ただし、目的のユーザーが指定できる場合は、DirectorySearcher で new するときにフィルターを指定したほうが良さそうです。

ちなみに、ここでデバッグ出力されるプロパティは、以下のものです。

objectClass
cn
sn
givenName
distinguishedName
instanceType
whenCreated
whenChanged
displayName
uSNCreated
memberOf
uSNChanged20111221
name
objectGUID
userAccountControl
badPwdCount
codePage
countryCode
badPasswordTime
lastLogoff
lastLogon
pwdLastSet
primaryGroupID
objectSid
accountExpires
logonCount
sAMAccountName
sAMAccountType
userPrincipalName
objectCategory
dSCorePropagationData
msDS-SupportedEncryptionTypes
nTSecurityDescriptor

ここでは、表示名(displayName)とSID(objectSid)を使います。あと、ユーザーが属しているグループを memberOf を使うと取得できます。

■フィルターを使ってみる

DirectorySearcher クラスで指定するフィルター(LDAPクエリ)を使って、カテゴリ(objectCategory)と名前で検索データを絞れます。

Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) Handles Button3.Click
	Dim root As New DirectoryEntry("LDAP://ad-sv/DC=plan,DC=local", "tomoaki", "password")
	Dim obj = root.NativeObject
	Dim filter As String = "(&(objectCategory=User)(name=tomoaki))"
	Dim se As New DirectorySearcher(root, filter)
	Dim de As DirectoryEntry = se.FindOne.GetDirectoryEntry

	ListBox1.Items.Clear()
	For Each nm In de.Properties.PropertyNames
		Debug.Print(nm)
		Dim s As String = String.Format("{0}={1}", nm, de.Properties(nm).Value)
		ListBox1.Items.Add(s)
	Next

	Dim sid As String = SidToStringSid(
	 CType(de.Properties("objectSid").Value, Byte()))
	Debug.Print(sid)

End Sub

Declare Auto Function ConvertSidToStringSid Lib "advapi32.dll" (ByVal pSID() As Byte, _
	ByRef ptrSid As IntPtr) As Boolean
Private Function SidToStringSid(ByRef bytes As Byte()) As String

	Dim psid As IntPtr = Nothing
	Dim sid As String = ""
	ConvertSidToStringSid(bytes, psid)
	sid = System.Runtime.InteropServices.Marshal.PtrToStringAuto(psid)
	Return sid

End Function

あと、おまけですが、objectSid で取得するデータは byte 型の配列なのでちょっと扱いづらいのです。「S-…」のような文字列で使っていきたいので、変換関数を作ります。

ちなみに、CType(de.Properties(“objectSid”).Value, Byte()) のところが非常に遅いのですよね…CType を使って Byte 配列にするところが遅いらしい。DirectCast を使ってみたのですが、スピードはさほど変わらないので、妙なことになっているのかもしれません。このあたりは、後で調べる…ハズ。

■属しているグループの検索

ドメインにログオンしているユーザーの SID は WindowsIdentity.GetCurrent.User で取得できるので、最初の「ad-sv で “tomoaki” を検索」部分は不要になります。

Private Sub Button4_Click(sender As System.Object, e As System.EventArgs) Handles Button4.Click

	' ad-sv で "tomoaki" を検索
	Dim root As New DirectoryEntry("LDAP://ad-sv/DC=plan,DC=local", "tomoaki", "password")
	Dim filter As String = "(&(objectCategory=User)(name=tomoaki))"
	Dim se As New DirectorySearcher(root, filter)
	Dim de As DirectoryEntry = se.FindOne.GetDirectoryEntry
	Dim bytes As Byte() = CType(de.Properties("objectSID").Value, Byte())
	Dim sid As String = SidToStringSid(bytes)

	' win2008-sv で sid で検索
	root = New DirectoryEntry("LDAP://win2008-sv/CN=ForeignSecurityPrincipals,DC=moonmile,DC=local", "masuda", "password")
	filter = String.Format("(&(objectCategory=User)(displayName={0}))", sid)
	se = New DirectorySearcher(root, filter)
	de = se.FindOne.GetDirectoryEntry
	Dim sid2 As String = SidToStringSid(CType(de.Properties("objectSID").Value, Byte()))

	ListBox1.Items.Add("SID1:" + sid)
	ListBox1.Items.Add("SID2:" + sid2)

	' 属しているグループを取得
	root = New DirectoryEntry("LDAP://win2008-sv/DC=moonmile,DC=local", "masuda", "password")
	Dim groups As List(Of DirectoryEntry) = GetGroups(root, de)
	For Each ent As DirectoryEntry In groups
		ListBox1.Items.Add(ent.Properties("name").Value)
	Next
End Sub

Private Function GetGroups(root As DirectoryEntry, de As DirectoryEntry) As List(Of DirectoryEntry)
	Dim lst As New List(Of DirectoryEntry)

	If de.Properties("memberOf").Value IsNot Nothing Then
		If de.Properties("memberOf").Value.GetType Is GetType(String) Then
			Dim grp As String = de.Properties("memberOf").Value
			Dim se As New DirectorySearcher(root, String.Format("(&(objectCategory=Group)(distinguishedName={0}))", grp))
			Dim ent As DirectoryEntry = se.FindOne.GetDirectoryEntry
			lst.Add(ent)
			lst.AddRange(GetGroups(root, ent))
		Else
			Dim groups As Object() = CType(de.Properties("memberOf").Value, Object())
			For Each grp As String In groups
				Dim se As New DirectorySearcher(root, String.Format("(&(objectCategory=Group)(distinguishedName={0}))", grp))
				Dim ent As DirectoryEntry = se.FindOne.GetDirectoryEntry
				lst.Add(ent)
				lst.AddRange(GetGroups(root, ent))
			Next
		End If
	End If

	Return lst
End Function

LDAP クエリを使って、表示名(displayName)の SID の一致を検索するわけです。その時の DirectoryEntry オブジェクトが、それぞれのグループに属しているので、memberOf を使って調べていきます。取得した DirectoryEntry の SID を表示させていますが、実はこれも不要です。

属しているグループは、再帰的に検索させています。これは、GRP001 が GRP001PA に属している場合、ユーザーが属しているグループとしては「GRP001,GRP001PA」のように、両方とも取得させたいためです。memberOf プロパティで取得するデータは、ややこしいことに、String 単体と object 配列の 2 種類が存在します。属しているグループが1つの場合は String 単体で、2 つ以上属している場合は、String 配列が返されるという….変な仕様のため、GetType でクラスを比較させています。

ここまで来ると、属しているグループの一覧が取得できるので、グループのエントリから name プロパティなどを使えば、どのグループに属しているかどうかは簡単に調べられます。

■パフォーマンスの問題

これを試しに実行すると結構待たされます。多分、LDAP クエリの作り方がまずいような気がするのですが、object 配列から byte 配列へのキャスト(ctype)も結構重いのです。

最後の例だと、

	se = New DirectorySearcher(root, filter) ★1
	de = se.FindOne.GetDirectoryEntry
	Dim sid2 As String = SidToStringSid(CType(de.Properties("objectSID").Value, Byte())) ★2

のように、★1 の検索と、★2 の byte 配列への変換で遅くなります。
ここでは、SID を表示させているだけなので、ここは削ってしまうと早くなります。

LDAP クエリの検索部分は、キャッシュを使うようにすれば早くなるんですかね…問合せなので多少は掛かってもよいのでしょうが、もうちょっとレスポンスが良いほうがいいなぁと。

— 補足 2011/12/22

byte 配列のところ、以下のように分解すると、

Dim obj As Object = de.Properties("objectSid").Value ★ここで遅くなっている
Dim bytes As Byte() = CType(obj, Byte())
Dim sid As String = SidToStringSid(bytes)

どうやら、Properties にアクセスして値を拾ってくるところが重たいようです。byte 配列は関係ないですね。
キャッシュを有効にするとかで、スピードがあがる?

カテゴリー: 開発, VB | 3件のコメント

速効で Active Directory の環境を試してみる

ざっと作ってみたので、メモとして。
最終的な目的は、ドメインサーバーが2台(何故にw)とクライアントが1台という構成にする予定。

  • ドメインのほうは、「moonmile.local」と「plan.local」という2つのドメインが別々にある。
  • クライアントのほうは、どちらのドメインのアカウントも持っている。

という不思議な構成。まぁ、現実にあった構成。

■ドメインサーバーの構築

  1. windows server 2008 R2 をインストールする
  2. ライセンス認証をして、リモートデスクトップで動かせるようにする。
  3. リモートデスクトップ用にファイアウォールを切る(本当は、きちんと穴をあけるべき)
  4. 「Administrator」以外のユーザーを作成しておく。ここで作成したユーザーが、そのままドメイン内のアカウントになるので便利。
  5. DNS サーバーも兼ねるため、固定 IP を振っておく。
  6. 役割の追加で、「Active Directory ドメインサービス」をインストールする。
  7. 同時に DNS サーバーもインストールする。
  8. サーバーマネージャーの「Active Directory ドメインサービス」→「Acitve Directory ユーザーとコンピューター」でドメインを作成する。
  9. 「Computers」にあらかじめ、クライアントのコンピュータをいれておく。ここでは「vs2010」になっている。
  10. 「Users」を確認しておく

これで、ドメインサーバー側はおしまい。

■クライアントの構築

  1. VMWare で Windows 7 をインストール。お試しのドメインサーバーしか参照しないので、新たにクライアントは作成したほうが便利。
  2. 優先 DNS を設定する。
  3. コントロールパネルから、「ネットワークの状態とタスクの表示」を開いて「ローカルエリア接続」を選択して、優先 DNS サーバーで、ドメインサーバー兼 DNS サーバーの固定 IP を指定する。



  4. マイコンピュータのプロパティから「設定の変更」をクリックして、ドメインに参加させる。

これでクライアントの設定は完了

■クライアントからドメインにログイン

ドメインに参加すると、windows 7 のログイン画面が下記のようになって、

「ユーザーの切り替え」をすると、ドメインを指定してログインができます。

これを、moonmile.local ドメインと、plan.local ドメインのどちらでもログインができるようにすれば OK。

もうひとつのドメインサーバーは、VMware 上に立てるつもりなので、後で。

カテゴリー: 開発 | 速効で Active Directory の環境を試してみる はコメントを受け付けていません

DevFC.exe が暴走している場合は、プロセスを切っても良し。

昨日、ふとタスクマネージャを見ていると、DevFC.exe というプロセスが 200 MB ほどメモリを喰っている…ので何故?と思って調べてみると、

Azure の Compute Emulator のようですね。ver1.5 だとたまに暴走することがあるみたいです。現在、私の環境は、ver1.6 なので直っていないのか、このコンピューターはあまり再起動していないので、ver1.5 のものが動作したままなのか。

# 説明のところが空白なので、何かのウィルスかと思ってしまった。

なので、プロセスをいきなり切っても OK だし、インジケーターから「Windows Azure Simulator Monitor」を右クリックして「Showdwon Compute Emulator」を選択しても OK です。Visual Studio を使って Azure 開発をしていないときはプロセスを切っておいてもいいですね。デバッグ実行したときは自動的に起動されるので、落としておいても問題ありません。

ちなみに、Compute Emulator ってのは、こんなの。Visual Studio でデバッグ実行した時に、ローカルコンピューターでエミュレーションする機能です。起動時のログなんかが分かります。

カテゴリー: 開発, Azure | DevFC.exe が暴走している場合は、プロセスを切っても良し。 はコメントを受け付けていません

データ指向で画面設計をする手順

ここでいう「データ指向」というのは、古くはホストコンピューターの時代から連綿と続いているIBM 内の秘伝のタレ(かな?)のことで、昨今の O/R マッピングの話が出る以前の設計と実装手順のことです。20 年以上前ってことになるかなぁと。

  1. テーブル構造を作成する
  2. テーブルへアクセスするCRUDを記述する
  3. テーブルを連結して一覧する方法を記述する
  4. 画面を設計する

いきなり、テーブル構造(データ構造)から始めているのが「データ指向」の所以です。オブジェクト指向の場合は、どのデータが「存在するのか」を調べた後に、どのまとまり(クラス)にするのか、という整理の仕方(オブジェクト図からクラス図の設計の流れがそれ)をしますが、データ指向の場合は、まず、まとまったデータがいきなりあって、そこからスタートします。

そして、データ指向で実装をする場合には、後戻りをしませんッ!!! ここは重要です。データ指向をする場合には、最初のデータ構造ありきのところがあるので、逐一 CRUD を作るところから、完全なウォーターフォール方式(あるいは流れ作業方式)で作れ、完全に分業が可能な開発工程ができます。

■テーブル構造を作成する

アプリオリな話になりますが、データ構造をどうやって分析していたのか(いるのか?)は私には不明です。後には DFD を使ってデータ構造を作ったりするのですが(本来は、DFD はデータの前後の関係、データの加工手順を示すものなのですが)、それ以前はどうしていたのでしょうね?

なんとなく、まとまりがあるところから、なんとなく正規化をして、という流れで設計をしているのでしょうね。おそらく。

また、ホストコンピュータの時代からのデータ構造を流用し続けていくので、データ構造を新しく作ることはないと言っても過言ではありません。あらかじめ、データベースにテーブル構造があるところから、移行設計、追加設計をしていくのが普通です。

■テーブルへアクセスする CRUD を記述する

CRUD(Create, Read, Update, Delete)の表を、テーブル毎にすべて作ります。そして、CRUD する関数を全て最初に作ってしまいまいます。今でこそ、自動化なり、リフレクションを使って、なぞと小細工…いえ効率化をすることができるのですが、当時はちまちまとコーディングです。

基本はひとつのテーブルに対して、ひと揃いの CRUD を用意していきます。このあたり、いわゆる登録系の画面(いわゆる年金を登録する画面とかね)と、検索系の画面(いわゆる株価の動向を見る画面とかね)の二種類があって、ここで CRUD 表を作るのは、登録系の画面で使うものです。

検索する場合であっても、主キーのみで検索したり、外部キーで他のテーブルを参照したりするぐらいのリレーションを持っているものです。

これをひたすら設計/実装していきます。

■テーブルを連結して一覧する方法を記述する

いまでこそデータベースと BI はビジネス的に切っても切れない関係にありますが、かつてはデータを分析するのは別の機能でしたし、非常にお金のかかるものでした。なので、分析して帳票に出すというのは、結構手間が掛かりお金もかかったので、あまり機能的には豊富ではなかったのですね。
いまのように GUI でちょいちょいと検索項目を入れて、数分後には帳票ができるというものではありませんでした。プリンターも高かったし。

なので、帳票を出力する場合は、「事前に決めた検索項目」を使って出力するってのが普通だったし、それがデータ指向をするときの欠点にもなっています。

日次、月次のような決まりきった帳票を出すためのインターフェース(関数のロジック)を事前に決めることができるのですが、インタラクティブに GUI で検索項目を変える、というところにまでデータ指向の利点はでてきません。

なので、この部分は、現在データ指向でプログラミングする場合は、

  • 事前に分かっている検索結果、帳票しか出さない要件とする。
  • それ相応に、要件定義を練り込む

という制限が必要になります。

■画面を設計する

ここに来て、やっとユーザーが利用する画面を設計&実装していきます。
データベースへのアクセスについては、2 と 3 で作成した関数を必ず通すので、おのずから画面のスタイルは決まっています。

  • 更新や閲覧のためのテーブル構造そのままの画面
  • リレーション先は、別画面(あるいは子画面)
  • リレーションが必要な一覧は、帳票として出力

というパターンになります。

当然、ユーザーがあれこれと画面を渡り歩くことはできず、画面遷移もデータ構造に一致します。

このデータ指向での設計&実装での最大の利点は、

  • 機能数や画面数が事前に見積もりやすい(FP を利用しやすい)
  • 機能数が事前に分かるので、開発期間が見積しやすい
  • CRUD 表に従うので、標準的なプログラマだけでよい(スーパープログラマはいらない)

というところになります。

欠点としては、

  • 開発プロセスとして面白みがない(改善する余地がほとんどないので、効率化もできない)
  • 画面にユーザーの要望が入らない。
  • 分析系が複雑化すると破綻しやすい(事前の関数の数を見極めにくい)

というところです。

でも、非常に開発プロセスとしては安定しているので、大規模開発には向いています。
ただし、効率化できるところが少ないので、アジャイルプロセスのように「作業の省略」ができません。全てを綿密に作る(無駄も含めて)ことになるので、あとから見ると無駄な関数を大量に作る可能性があります。

時間があれば、後で図解でも up しますか。

カテゴリー: 設計 | データ指向で画面設計をする手順 はコメントを受け付けていません

newQX でプログラミングしている時に落ちる場合の対処

エディタは newQX α0.31 を使っているのですが、C++ のプログラミングをしている時にアプリケーションごと落ちます…というか、ブログの下書きを書いているときにコードを貼りつけて何かやっている時に落ちます。

「何かやっているとき」ってのがプログラマらしからぬ意見なので、現象の元ネタが分かったらメーリングリストに報告に行こうかなぁと思ってしばし…3ヶ月ほど?

どうやら、変数表示のツールチップを出す時に落ちていることが最近わかりました。フォントの関係かなぁと思って(Osakaフォントを使っていたりするので)、しばし、MS ゴシックに戻したりしていたのですが、落ちる頻度としては C 言語のプログラミングをしている時が多いし、なんだろうと思っていました。

のように、変数を定義している位置を知らせてくれるのですが、どうもこのタイミングで落ちることが多い…ツールチップの表示自体ではなくて、何らかの組み合わせのようなのですが、ちょっと不明です。

で、対処なのですが「共通設定」の「開発」タブで、「c言語のコンテキスト補完」「c言語のコンテキストツールチップ」のあたりを全て OFF にします。

すると、ぴたりと落ちる現象が消えるので…このあたりの組み合わせっぽいですね。

落ちるタイミングがツールチップなので「c言語のコンテキストツールチップ」だけ OFF にすればいいような気もしましたが、それだとまだ落ちていたんですよね。なので、コンテキスト補完の不備なのかなぁ、と想像したり。

カテゴリー: QXエディタ | newQX でプログラミングしている時に落ちる場合の対処 はコメントを受け付けていません

使ってはいけない定数定義の一例

実際は、Visual Basic 2005 なんだけど、C# でも同じ、まぁ、実は C 言語でも同じ。

「マジックナンバーを使ってはいけません」というのを小さい頃(プログラミングを始めたばかりの頃ってこと)に教えられた人は、コードの中に即値を入れないようにします。だから、次のように書く…って、書くなぁッ!!!

const int ZERO = 0;
const int TEN  = 10;

for ( int i = ZERO; i < TEN; i++ ) {
	// 何かの処理
}

その…なんというか、顎が外れた。言葉が出ない。

これの応用編(?)として、こんな風なコードもあった…あるなぁッ!!!


public class CONSTS {
	public const int ZERO = 0;
	public const int TEN  = 10;
}

...
for ( int i = CONSTS.ZERO; i < CONSTS.TEN; i++ ) {
	// 何かの処理
}

ええっ、思いっきり応用していますね。応用です。鷹揚に構えないと(ぜはぜは)。

で、この手のものをどう書くのですか?と聞かれると、C言語だとこう書きます(実はC#だと悩ましかったり)。


#define MAX 10;

...

for ( int i=0; i<MAX; i++ ) {
   ...
}

「MAX」のところは最大値を示す意味のある言葉を入れるわけで、望ましくないのは「LOOPMAX」とか「COUNTMAX」とかですね。そうなる位であれば「MAX」のほうが潔い。
どうせならば「MAX_TABLES」とか「TABLE_COUNTS」な感じです。

さて、同じようなものをC#で書くと、

const int MAX = 10;
...
for ( int i=0; i<MAX; i++ ) {
   ...
}

という感じになりますが、ちょっと違う。この MAX という定義は、C 言語の場合は、関数(メソッド)の外に書くことができるけど、C# や VB の場合は、メソッドの中になってしまう。なので、オブジェクト指向のメリットを活かせば、

class A {
	const int MAX = 10;
	public method() {
		...
		for ( int i=0; i<MAX; i++ ) {
		   ...
		}
	}
}

な風に、クラス内の private フィールドにするのが適しているかと。
実は、更に悩ましいことに、この MAX ってのは、共通で使うことが多いので、B クラスでも C クラスでも使うってことになると、

public class CONSTS {
	public const MAX = 10;
}

なんていう CONSTS クラスを作っておいて、


class A {
	public method() {
		...
		for ( int i=0; i<CONSTS.MAX; i++ ) {
		   ...
		}
	}
}

class B {
	public method() {
		...
		for ( int i=0; i<CONSTS.MAX; i++ ) {
		   ...
		}
	}
}

のようにすれば良いのだが、実はこれは隠蔽化を崩している。
新しく D クラスを作った時に、CONSTS.MAX というのが別の意味で使いたい場合に名前が重複してしまう。実務的には、アセンブリを変えるとか、定義名を別にするとかいう方法もあるのですが、実はインターフェースという感覚で

public class CONSTS {
	public const int MAX = 10;
}
public class A : CONSTS {
	public void method() {
		for ( int i=0; i<MAX; i++ ) {
			...
		}
	}
}

とするのが妥当かと思うのですが、.NET のクラスでは多重継承ができないので、CONSTS を複数作って継承させるってことができない。

なので、実務的なところでは、

public class TABLE_CONSTS {
	public const int MAX = 10;
}
public class PEOPLE_CONSTS {
	public const int MAX = 100;
}
public class A {
	public void method() {
		for ( int i=0; i<TABLE_CONSTS.MAX; i++ ) {
			...
		}
	}
}

な風に、CONSTS クラスをあらかじめ分類しておいて、名前空間風に使ってみる、っていうのが普通です…というか、今回はそうしました。
まぁ、CONSTS クラスが多くなるとちょっと…何が何やら分からなくなりますがね。

と・も・か・くッ!!! ONE, TWO THREE のような定義はやめておくなまし。

カテゴリー: 雑談, C# | 3件のコメント

Azure の Strage Emulator が作成するテーブル構造

Azure をローカルコンピュータで動かすと、ローカルにストレージアカウントが作成されます。
これは、.\SQLEXPRESS というインスタンスに、「DevelopmentStrageDb<作成日付?>」な風なデータベースが作成されていて、

テーブル構造はこんな感じ。

キー情報としては、

  • AccountName アカウント名
  • ContainerName コンテナ名
  • BlobName ブロブ名
  • VersionTimestamp タイムスタンプ
  • TableName テーブル名
  • PartitionKey テーブルストレージのパーミッションキー
  • RowKey 列名

な簡単な構造をしています。

# 実際は Blob の Block 構造のために、Blob ストレージのほうはややこしいみたいですが。

DDL は、以下のようになっているので、

CREATE TABLE [dbo].[Blob](
	[AccountName] [varchar](24) NOT NULL,
	[ContainerName] [varchar](63) NOT NULL,
	[BlobName] [nvarchar](256) NOT NULL,
	[VersionTimestamp] [datetime] NOT NULL,
	[BlobType] [int] NULL,
	[CreationTime] [datetime] NOT NULL,
	[LastModificationTime] [datetime] NULL,
	[ContentLength] [bigint] NOT NULL,
	[ContentType] [varchar](128) NULL,
	[ContentMD5] [binary](16) NULL,
	[ServiceMetadata] [varbinary](max) NULL,
	[Metadata] [varbinary](max) NULL,
	[LeaseId] [uniqueidentifier] NULL,
	[LeaseTypeInt] [int] NULL,
	[LeaseDuration] [bigint] NULL,
	[LeaseEndTime] [datetime] NULL,
	[SequenceNumber] [bigint] NULL,
	[IsCommitted] [bit] NULL,
	[HasBlock] [bit] NULL,
	[UncommittedBlockIdLength] [int] NULL,
	[MaxSize] [bigint] NULL,
	[FileName] [nvarchar](260) NULL,
 CONSTRAINT [PK_dbo.Blob] PRIMARY KEY CLUSTERED 
(
	[AccountName] ASC,
	[ContainerName] ASC,
	[BlobName] ASC,
	[VersionTimestamp] ASC
)
CREATE TABLE [dbo].[TableRow](
	[AccountName] [varchar](24) NOT NULL,
	[TableName] [varchar](63) NOT NULL,
	[PartitionKey] [nvarchar](256) NOT NULL,
	[RowKey] [nvarchar](256) NOT NULL,
	[Timestamp] [datetime] NOT NULL,
	[Data] [xml] NULL,
 CONSTRAINT [PK_dbo.TableRow] PRIMARY KEY CLUSTERED 
(
	[AccountName] ASC,
	[TableName] ASC,
	[PartitionKey] ASC,
	[RowKey] ASC
)
  • AccountName: 24 文字以内
  • ContainerName: 63 文字以内
  • BlobName: 256 文字以内
  • TableName: 63 文字以内

と想像がつくわけですが、果たして、実際の Azure システムでは如何に?

Windows Azureストレージ・サービスの命名規則を調べてみた – waりとnaはてな日記
http://d.hatena.ne.jp/waritohutsu/20100404/1270414253

ちなみに、SQL Server の場合、(デフォルトでは)比較時に大文字小文字を判別しないので、アカウント名やコンテナ名などには大文字小文字で区別させるのは厳禁。エミュレータ環境で動かなくなるという罠が。

カテゴリー: 開発, Azure | Azure の Strage Emulator が作成するテーブル構造 はコメントを受け付けていません

アプリを作ろう!iPhone入門―ゼロから学ぶアプリの作成から公開まで…を買う

ええと、業務的な観点から、アプリの公開部分が欲しくって買い…と言いますか、日経BPさんから貰ってもいいのだけど、今回は他社でのアレだからなぁ、という訳で資料的に。

入門書としては非常に薄いので、ちょっと iPhone のプログラミングをやっている人であれば、だから何ができるの?ってな感じになりかねませんが、AppStore の公開まで丁寧に書かれているのがいいです。私としてはこの1点買いです。
美女 Linux の iPhone アプリのお試しを作ろうとして、最初に困っていたのが、

  • iOS SDK ってどうダウンロードするのか?
  • 会員にならないと、実機で動かせないらしいのだが、どうすればよいのか?
  • 最終的には無料公開ぐらいまではしたいのだが、どうすればよいのか?

ってなところです。google を使ってサーチしてみたものの、断片的なものしかなくて(実際、課金アプリなどを公開している方は、その断片的な…というか、それ以前の英語版のヘルプを見て公開している模様)、なかなか一気に作ると、「どの程度手間がかかりそうなのか?」というのが分かりません。

いわゆる、「どの程度手間が掛かりそうなのか」を見積もるには、どの程度時間がかかりそうなのか?(by ワインバーグ)の問題でして、なんやかやと調べていくと、見積もりの見積りに3日位かかった覚えがあります。
そのあたりを短縮してくれます、ってことで買いました。

で、公開までにどのくらい掛かるかというと、落ち着いてやれば1週間弱なのかなぁと。

カテゴリー: 雑談 | アプリを作ろう!iPhone入門―ゼロから学ぶアプリの作成から公開まで…を買う はコメントを受け付けていません

経済的な視点を持ってソフトウェア開発を見直す

先に書いたエントリーで、「経済的な」という単語が出てきたので、ちょっと補足。

「経済的な」というのは、費用のことを考えてプログラミングをする(ソフトウェア開発をする)ということで、マクロ的なところではオフショアな話から、ミクロ的なところでは2時間悩むぐらいであれば3000円の書籍を買ったほうがよい、という話だったりします。

対費用効果を考えるとき、学生時代には時間がたくさんあった(たくさんあった、というよりもお金がなかった)ので、費用(経費)のほうが時間よりも大切で、自分で調べるなり先輩に聞くなりネットで調べるなり(当時はネットなぞなかったので、図書館でってことになりますが)する方法を取るわけですが、いざ社会人になって「プロ」になる、時間の費用のほうが高くつくので、なにかと費用を掛けたほうが手早くできあがったりします(無料にこだわらなくてもよくて、ある程度の費用ならば捻出可能であるという意味で)。

これをソフトウェア開発のプロジェクトという枠で捉えると、プロジェクト自体の予算があり、人件費があり、ということで、割り算すれば単位時間あたりの費用が簡単にでてきます。この単位時間あたりの費用を如何に有効に使うかとうことが優先される…はずなのですが、まぁ、そうでないこともたびたびなのですが。

なので、作業時間というものを見積もった上で、その作業時間が自分の単価により非常に高くつくのであれば、別の手段に置き換えたほうが安く済むイコール「効率が良い」ということになります。ここでいう「効率」というのは、対費用効果という視点からですね。
なので、「2時間ほど悩むのであれば、3000円の書籍を買うほうがよい」というパターンは、あれこれとネットで探して悩むよりは、さくっと初心者本なりを買って、その数ページだけを活用してしまえばよいということです。それで、その本の価値(この場合は私から見た価値)は十分足りているということになります。
なんか、非常に効率が悪いように見えますが、本屋での立ち読みよりも、さっくりと amazon で買うなり、本屋でざっくりと選別して買うとい方式を取ります。
業務に使う場合は、専らこのパターンです。資料的に切り取りという感じで。

これを、もう少しマクロな視点からプロジェクトでの開発工程全体に広げてみると、

  • ツール購入の対費用効果
  • 自動化ツール作成の対費用効果
  • 重箱の隅的な議論の対費用効果
  • 複雑すぎる仕様に対する対費用効果

などを考えていきます。IT 屋さんの営業的には、ツール購入の対費用効果が持ち出されるわけで、過剰なところでは「5秒の作業時間が削減できます」とかなんとか。昔、実際にあった宣伝文句です。
が、そのツールを購入して習熟して実行するところまで考えたときには、ツール自体に費用に値するかどうか、は結構別な話で、むしろ、「習熟して実行する」ところまでの対価(イコール人件費)のほうが大きくて、対費用効果がマイナスということもあります。つまり、ツール自体が0円であっても、習熟までの人件費を考えれば、マイナスうん十万円という損が出る可能性があるということです。

どこかの経済書か経営書にありましたが、結果的には人には「時間」のみが平等に割り振られているわけで(時間の少ない方もいるんので、必ずしもそうという訳ではありませんが)、どの位の時間で何をするのか、それがオーバーしそうなのか、オーバーするのであれば作業を止めるべきか、ということまで考えて行動すると、「経済的な」ソフトウェア開発に近づきます。

と言うほど、私自身はできていないわけですが、業務的なところは業務的なところで追及するとして、人生の楽しみとしては楽しみとして追及させていただくという話です。談志のように?

カテゴリー: プロジェクト管理, 雑談 | 1件のコメント

オブジェクト指向と手続きプログラミングの組み合わせ

オブジェクト指向型のプログラム言語を使うと、オブジェクト指向のプログラミングが自然にできて、開発効率がアップする、というのは幻想で、オブジェクト指向的な考え方というか、ノウハウを利用することによってでしか、開発効率はアップしないとか、極論を書いてみるテスト。

研究的なプログラミングとか、ライトウェイトなプログラミングをしている場合は別として、いわゆる業務で使うアプリケーションのようなもの全般、いわゆる SIer が請け負うアプリケーションに関して云えば、オブジェクト指向一辺倒で行くのはプロジェクトの成功率的に云えば、危険である。プロジェクトの成功率というのは、以前書いた(と思うけど)、プロジェクトが赤字にならないことであって、その他の個々人のスキルとか将来性とか、更に云えば会社の将来性とかいうのとは別ので、目の前のプロジェクトを如何に完遂するのか?というのが焦点になる。
良いとか、悪いとかは別として、それが「プロジェクトの成功」と定義するワケ。

で、このプロジェクトの成功に関してどのようなプログラミング的な知識が適用/寄与できるかというと、一般的に言えば、オブジェクト指向であったり、プロトタイピングであったり、アジャイルであったり、諸々の手法であったりするわけだが、失敗する可能性を低くしたいのであれば(ローリスク、ローリターンという意味で)、既存の成功パターンに少しだけ他の成功パターンを加えるほうがいい。
全面的に変える必要がある場合は、

  • この予算では、アプリケーションが完成できない、という見込み。
  • このメンバでは、アプリケーションを完成させられない、という見込み。
  • この期間では、アプリケーションを完成させらんれない、という見込み。

といった、局所的な最適化あるいは改善策では、到底達成さできない場合に入れ替えが必要になる。

さて、改善の積み重ね、あるいは地道な努力、という程度(という言い方も悪いかもしれないが)で済む話であるならば、全面的なオブジェクト指向の手法の取り入れは止めておいたほうがベター…というか、SIer にとっては、リスキーなパターンを取るよりも、安全策を取るほうが良いのだ。
が、何をすれば安全であるのかというと、2つのパターンがあって、

  • 従来の設計書、プログラミング手法、試験手法を続ける。
  • オブジェクト指向の手法を少しだけ取り入れて、設計、プログラミング、試験の手法を変えていく。

というものがある。「従来の」と書くと、旧態依然という感じがして最新技術を追う業界(これは疑問なのだけど)にとっては、ちょっと…と二の足を踏むかもしれないのだが、建築業界のようにゼネコンを目指すのであれば、より実績のある技術に負うところが多い、と言い換えてもよい。従来の技術であっても、10 年以上続けているのであれば(場合によっては、20 年以上前でもよい)それは、それで実行時に効率化されているであろうし、変えなくてもよい、と言い切れる。

が、従来通りの手法で作っていると、やはり間に合わなくなってくる部分があり…というか、最近の開発の傾向として、

  • 昔よりも、不具合に関して、厳しくなくなっている。
  • 昔よりも、運用時のバージョンアップが可能になっている。

という点を汲み入れると、

  • ある程度、強固でなくても良い部分がある。
  • ある程度、後に切り替えられる部分がある。

という結論になる。
建前上は、「完成品」が求められるものの、一般的な受託開発のソフトウェアは自動車や航空機ほど完成度を求められていない。となれば、求められてないところは、求められてないなりの「経済性」で開発をする、ということが暗に求めらているのである、と言い切る。

そこで、アプリケーションの中では、強固であることが必須な部分と、さほど強固でなくてよい部分があることがわかる。これをどのように分けるかは、いくつかの手法があるのだが、

  • 強固な部分 → ライブラリ化(オブジェクト指向)
  • 緩い部分 → 一般的な手続き的手法で、プログラミング

という分け方をする。よく「共通化」とか「共通基盤」とか言われるのが、ライブラリ化にあたり、このあたりは昔からベテランが対応することになっている。また、ベテランがプログラミングする故に品質がよく、落ちにくいという頑健さが求められる。
で、逆に言えば、この強固ではない部分

  • GUI 部品を使った画面のプログラミング
  • 共通ライブラリを使ったプログラミング
  • コピー&ペーストを繰り返すプログラミング

のようなところは、一般的なスキル(設計から試験も、ほどよくという「経済性」を保つ)で開発を行うという方法がある。

このあたり、MVC パターンと絡むところがあるのだが、設計から試験まで、2 つの視点を使いプロジェクトの「経済性」を保ちつつ、成功率を高めるという手法になる。

カテゴリー: 開発, プロジェクト管理 | オブジェクト指向と手続きプログラミングの組み合わせ はコメントを受け付けていません