今朝の妄想から - AI知財・情報金融の未来に関する考察
❓ RAGデータやプロンプトのような「AI知財」には、著作物として出版・流通され、証券のように取引される時代が来るだろうか? それは「情報金融」という新たな産業と政策の領域を生み出すだろうか?
1. AI知財の出版・流通と市場の構想
❓ RAGデータや汎用プロンプトを「出版」して社会に流通させる仕組みは、AI産業の発展にどう寄与できるか?
RAGデータや汎用プロンプトを「出版」して流通させるのはどうか。それは、AI産業全体の発展に寄与する可能性がある。標準化の整備は必要だろう。とはいえ、証券取引のような「市場」の形成も有用と考えられる。これらは「情報金融」という概念を生む。そのもとで、政策的・国際的な統制を必要とするものになるだろう。なお、この方向性はすでに部分的には動き始めているようだ。Hugging FaceのDatasets/Modelsハブやプロンプトマーケットプレイスなどがその萌芽といえる。
2. 情報の価値の根拠——生成コストと希少性
❓ 複製コストがほぼゼロのデジタル情報に、なぜ経済的価値が生まれるのか?
デジタル情報は複製コストがほぼゼロである。しかし、生成にはコストがかかる。この「生成コスト」こそが希少性と価値の根拠となるだろう。これは古典的な労働価値説に近い構造だ。著作権保護の根拠と同じ論理でもある。ただし、AIそのものがRAGデータやプロンプトの生成コストを急速に低下させていることに注意しよう。長期的に価値が残るのはAIで代替できない一次情報や、検証・信頼性の保証、人間の判断が入ったキュレーションの部分になっていくと考えられる。
3. 情報の価値は有用性によって測定される
❓ 情報の「真の価値」はどのように測定するのが最も合理的か?
情報の価値は、その情報があったがゆえに生まれた有用性によって測定するのが合理的だ。いわば「情報の限界効用」による評価である。これは、生成コスト基準よりも本質的な指標といえるだろう。ただ、実装上の課題はある。成果への帰属問題だ。あるRAGデータを使って成果が生まれた場合、その成果をどこまでそのデータに帰属させるか。それは、金融のデリバティブ評価に似た複雑な問題だ。この考え方を突き詰めると「情報の価値は使われるまで確定しない」という性質が導かれるだろう。情報は本質的に「潜在的な価値」しか持たない。文脈と組み合わせによって初めて価値が実現する。これは株式の期待値的な性格に似ている。「情報金融」という概念で把握することができよう。
4. 死蔵された情報——採掘されていない金鉱のアナロジー
❓ 誰にも使われないまま眠り続ける情報は、社会にとって何を意味するのか?
利用されない情報は、発見されず採掘されていない金鉱と同じだ。このアナロジーを延長してみよう。情報にも探鉱(有用な情報の発見)、採掘(アクセス・収集)、精錬(クリーニング・構造化)、流通という工程があると言える。それぞれに異なるプレイヤーと価値が生まれる。金鉱は発見されれば価値が確定する。しかし、情報はちがう。「誰が・どう使うか」によって価値が変わる。その点が金鉱とは異なる。「情報市場」というのは、結局のところ、情報と適切な使い手をマッチングする機能だ。これは証券市場が資金と投資機会をマッチングするのと同じ構造だ。死蔵された情報を掘り起こすインセンティブ設計が産業政策として重要であろう。図書館や文書館のデジタル化助成金などは、その萌芽といえるのではないか。
5. 利用されない特許と知財制度の課題
❓ 社会で実際に使われることのない特許は、知財として意味を持つのか?
特許などの知財も、社会に実際に役立てられずに公知化を目的とするだけのものにどれほどの効用があるだろうか。実質的な価値はほとんどないといってよい。防衛特許や牽制目的の特許は、有用性が実現されないまま法的コストと管理コストだけが発生する。情報価値論から見ると社会的損失ではないか。防衛特許は「情報の公開」という形をとりながら、法的障壁によって他者が活用できない状態に置かれるものだ。そのため、実質的には情報の死蔵と同じ効果をもたらす——「採掘禁止区域に指定された金鉱」のようなものである。
6. 特許年金の割引による知財活用促進——政策提言
❓ 特許の「死蔵」を防ぎ、社会への還元を促すために、制度はどう設計できるか?
特許にはその権利の保証金として年金がかかる。利用されない特許にもである。しかし、利用許諾をすることによってこの年金を割引または免除する制度を設ければ、どうなるだろう。個々の特許の利用される可能性は増えるだろう。また、特許の数自体も増えるだろう。総じて、産業発展に寄与する可能性が増えるだろう。このアプローチは罰則ではなくインセンティブでの誘導が主体になる。段階的な設計として「ライセンス許諾のみで割引」「実際に相手方が実施すればさらに割引」「実施によって生まれた有用性に応じてさらに減免」という形が考えられる。これは有用性による価値測定を制度に組み込むことになる。有用性の測定・認定機関の整備は課題となるだろう。しかし、それ自体が新たな産業(情報格付け機関)を生む可能性もある。特許庁が単なる登録・管理機関から脱皮して「知財の活用促進機関」へと役割を拡張していく方向への一歩となるのではないだろうか。
おわりに
こうした考察も、社会に公開・還元することで、何かのきっかけで誰かの目に留まり、そのアイデアを刺激できるなら幸いだ。なあんてね。
Programming By Example のためのちいさなライブラリ
表形式で事前条件と事後条件の列を並べた動作例を1件以上用意して1件ずつ検証し、その結果をまとめてアサートするのにちょっと便利なライブラリ Tester を作ってみた。
データの(空行でない)先頭行に「事前条件」セルと「事後条件」セルを書き、次の行には事前条件の列名のセルを並べた後に事後条件のセルを並べる。その際、事前条件先頭列のすぐ上に前の行の「事前条件」セル、事後条件先頭列のすぐ上に前の行の「事後条件」セルを配置する。3行目以降は各列名に沿った値例を記述する。ここまでが準備。
Tester.Evaluateメソッドでテストコードを評価する。このメソッドは、上で用意した複数行にわたる表形式の文字列を第一引数にとり、第二引数には、Tester.TestCaseクラスを受け取るAction(ラムダ式)の形でテスト評価処理をとる。その際、テストケースの値をValueOfメソッドで参照できるので、下記のコード例のように書くことができる。各行のテストケースデータについてのテスト評価結果がすべて合格(成功)ならなにも起きない。1行以上のテストケースが不合格なら全テストケースデータの結果の文字列を含む TestCaseFailedException が発生するのでテストメソッドは不合格になる。
制約事項:
表のセルデータ内には表のセル区切り文字列と行区切り文字列を含めることはできない。
ただし、表のセル区切り文字列(既定では"|")、表の行区切り文字列(既定では"\r\n")には任意の文字列を指定できる。
そのため、区切り文字列の指定を調整すれば、たとえばExcelで作った表をテストコードにコピー&ペーストしてもいい(はず)。
使い方と挙動は、テストコードを見るとわかるかと思う。
using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; namespace Tester.Tests { [TestClass()] public class TesterTests { [TestMethod()] public void EvaluateTest() { try { Tester.Evaluate(@" |事前条件||事後条件 備考|x1|x2|y=x1+x2 負|-2|-1|-3 零|0|0|0 正|5|1|6 ", (testCase) => { string note = testCase.ValueOf("", "備考"); int x1 = int.Parse(testCase.ValueOf("事前条件", "x1")); int x2 = int.Parse(testCase.ValueOf("事前条件", "x2")); int expected = int.Parse(testCase.ValueOf("事後条件", "y=x1+x2")); Assert.AreEqual(expected, x1 + x2, note); }, "|", "\r\n"); } catch (Exception ex) { Assert.Fail(string.Format("例外は発生しないはずです。{0}", ex.ToString())); } } [TestMethod()] public void EvaluateTest_Fail() { try { Tester.Evaluate(@" |事前条件||事後条件 備考|x1|x2|y=x1+x2 負|-2|-1|-3 零(故意の誤り)|0|0|1 正(故意の誤り)|5|1|0 ", (testCase) => { string note = testCase.ValueOf("", "備考"); int x1 = int.Parse(testCase.ValueOf("事前条件", "x1")); int x2 = int.Parse(testCase.ValueOf("事前条件", "x2")); int expected = int.Parse(testCase.ValueOf("事後条件", "y=x1+x2")); Assert.AreEqual(expected, x1 + x2, note); }, "|", "\r\n"); } catch (TestFailedException ex) { Assert.AreEqual(@"0:成功 1:Assert.AreEqual に失敗しました。<1> が必要ですが、<0> が指定されました。零(故意の誤り) 2:Assert.AreEqual に失敗しました。<0> が必要ですが、<6> が指定されました。正(故意の誤り) ", ex.Message); } } [TestMethod()] public void EvaluateTest_テストケースカテゴリ参照誤り() { try { Tester.Evaluate(@" |事前条件||事後条件 備考|x1|x2|y=x1+x2 負|-2|-1|-3 零|0|0|0 正|5|1|6 ", (testCase) => { string note = testCase.ValueOf("", "備考"); int x1 = int.Parse(testCase.ValueOf("事前条件", "x1")); int x2 = int.Parse(testCase.ValueOf("事前条件", "x2")); int expected = int.Parse(testCase.ValueOf("事前条件", "y=x1+x2"));//正しくは"事後条件" Assert.AreEqual(expected, x1 + x2, note); }, "|", "\r\n"); Assert.Fail(string.Format("ここまでは到達しないはずです。")); } catch (TestFailedException ex) { Assert.Fail(string.Format("この種類の例外は検出されないはずです。{0}", ex.ToString())); } catch (TestErrorException ex) { Assert.AreEqual(@"ValueOf(事前条件,y=x1+x2)が呼び出されましたが、カテゴリ 事前条件 には列名 y=x1+x2 が見つかりません。", ex.Message); Assert.IsInstanceOfType(ex, typeof(TestCaseColumnNameNotFoundException)); } } [TestMethod()] public void EvaluateTest_テストケース列参照誤り() { try { Tester.Evaluate(@" |事前条件||事後条件 備考|x1|x2|y=x1+x2 負|-2|-1|-3 零|0|0|0 正|5|1|6 ", (testCase) => { string note = testCase.ValueOf("", "備考"); int x1 = int.Parse(testCase.ValueOf("事前条件", "x"));//正しくは x1 int x2 = int.Parse(testCase.ValueOf("事前条件", "x2")); int expected = int.Parse(testCase.ValueOf("事後条件", "y=x1+x2")); Assert.AreEqual(expected, x1 + x2, note); }, "|", "\r\n"); Assert.Fail(string.Format("ここまでは到達しないはずです。")); } catch (TestFailedException ex) { Assert.Fail(string.Format("この種類の例外は検出されないはずです。{0}", ex.ToString())); } catch (TestErrorException ex) { Assert.AreEqual(@"ValueOf(事前条件,x)が呼び出されましたが、カテゴリ 事前条件 には列名 x が見つかりません。", ex.Message); Assert.IsInstanceOfType(ex, typeof(TestCaseColumnNameNotFoundException)); } } [TestMethod()] public void EvaluateTest_未定義のテストケースカテゴリ名参照() { try { Tester.Evaluate(@" |事前条件||事後条件 備考|x1|x2|y=x1+x2 負|-2|-1|-3 零|0|0|0 正|5|1|6 ", (testCase) => { string note = testCase.ValueOf("", "備考"); int x1 = int.Parse(testCase.ValueOf("事前条件", "x1")); int x2 = int.Parse(testCase.ValueOf("事前条件", "x2")); int expected = int.Parse(testCase.ValueOf("誤記", "y=x1+x2"));//正しくは 事後条件 Assert.AreEqual(expected, x1 + x2, note); }, "|", "\r\n"); Assert.Fail(string.Format("ここまでは到達しないはずです。")); } catch (TestFailedException ex) { Assert.Fail(string.Format("この種類の例外は検出されないはずです。{0}", ex.ToString())); } catch (TestErrorException ex) { Assert.AreEqual(@"ValueOf(誤記,y=x1+x2)が呼び出されましたが、カテゴリ 誤記 が見つかりません。", ex.Message); } } [TestMethod()] public void ParseTestCaseListTest_0ケースの例_テストケースなし() { try { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" 事前条件|事後条件 x|!x "); Assert.Fail("例外が発生するはずです。"); } catch (TestErrorException ex) { Assert.AreEqual("テストケースがありません。列名定義 x|!x に沿って、1行以上のテストケースのデータ行が必要です。", ex.Message); Assert.IsInstanceOfType(ex, typeof(TestCaseEmptyException)); } } [TestMethod()] public void ParseTestCaseListTest_0_列名なし() { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" 事前条件|事後条件 "); Assert.AreEqual(0, testCases.Count); } [TestMethod()] public void ParseTestCaseListTest_2ケースの例() { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" 事前条件|事後条件 x|y 0|1 2|3 "); Assert.AreEqual(2, testCases.Count); Assert.AreEqual("0", testCases[0].ValueOf("事前条件", "x")); Assert.AreEqual("1", testCases[0].ValueOf("事後条件", "y")); Assert.AreEqual("2", testCases[1].ValueOf("事前条件", "x")); Assert.AreEqual("3", testCases[1].ValueOf("事後条件", "y")); } [TestMethod()] public void ParseTestCaseListTest_3ケースの例_事前条件の前の列あり() { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" |事前条件||事後条件 備考|x1|x2|y=x1+x2 負|-2|-1|-3 零|0|0|0 正|5|1|6 "); Assert.AreEqual(3, testCases.Count); Assert.AreEqual("負", testCases[0].ValueOf("", "備考")); Assert.AreEqual("-2", testCases[0].ValueOf("事前条件", "x1")); Assert.AreEqual("-1", testCases[0].ValueOf("事前条件", "x2")); Assert.AreEqual("-3", testCases[0].ValueOf("事後条件", "y=x1+x2")); Assert.AreEqual("零", testCases[1].ValueOf("", "備考")); Assert.AreEqual("0", testCases[1].ValueOf("事前条件", "x1")); Assert.AreEqual("0", testCases[1].ValueOf("事前条件", "x2")); Assert.AreEqual("0", testCases[1].ValueOf("事後条件", "y=x1+x2")); Assert.AreEqual("正", testCases[2].ValueOf("", "備考")); Assert.AreEqual("5", testCases[2].ValueOf("事前条件", "x1")); Assert.AreEqual("1", testCases[2].ValueOf("事前条件", "x2")); Assert.AreEqual("6", testCases[2].ValueOf("事後条件", "y=x1+x2")); } [TestMethod()] public void ParseTestCaseListTest_セル区切り文字変更() { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" 事前条件,事後条件 x,y 0,1 2,3 ", ","); Assert.AreEqual(2, testCases.Count); Assert.AreEqual("0", testCases[0].ValueOf("事前条件", "x")); Assert.AreEqual("1", testCases[0].ValueOf("事後条件", "y")); Assert.AreEqual("2", testCases[1].ValueOf("事前条件", "x")); Assert.AreEqual("3", testCases[1].ValueOf("事後条件", "y")); } [TestMethod()] public void ParseTestCaseListTest_行区切り文字変更() { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@"@事前条件|事後条件@x|y@0|1@2|3@", "|", "@"); Assert.AreEqual(2, testCases.Count); Assert.AreEqual("0", testCases[0].ValueOf("事前条件", "x")); Assert.AreEqual("1", testCases[0].ValueOf("事後条件", "y")); Assert.AreEqual("2", testCases[1].ValueOf("事前条件", "x")); Assert.AreEqual("3", testCases[1].ValueOf("事後条件", "y")); } [TestMethod()] public void ParseTestCaseListTest_事前条件事後条件のテキスト変更() { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" pre-condition,post-condition x,y 0,1 2,3 ", ",", null, new string[] { "pre-condition", "post-condition" }); Assert.AreEqual(2, testCases.Count); Assert.AreEqual("0", testCases[0].ValueOf("pre-condition", "x")); Assert.AreEqual("1", testCases[0].ValueOf("post-condition", "y")); Assert.AreEqual("2", testCases[1].ValueOf("pre-condition", "x")); Assert.AreEqual("3", testCases[1].ValueOf("post-condition", "y")); } [TestMethod()] public void ParseTestCaseListTest_事前条件事後条件の欠損() { try { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" x|y 0|1 2|3 "); Assert.Fail("例外が発生するはずです。"); } catch (TestErrorException ex) { Assert.AreEqual("テストケース定義の書式に誤りがあります。テストケース行とそれらの前の列名の行に先行して「事前条件」と「事後条件」を含む行が必要です。", ex.Message); Assert.IsInstanceOfType(ex, typeof(TestCaseCategoriesMissingException)); } } [TestMethod()] public void ParseTestCaseListTest_列名行の欠損() { try { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" 事前条件|事後条件 0|1 2|3 "); Assert.AreEqual("0", testCases[0].ValueOf("事前条件", "x")); Assert.AreEqual("1", testCases[0].ValueOf("事後条件", "y")); Assert.AreEqual("2", testCases[1].ValueOf("事前条件", "x")); Assert.AreEqual("3", testCases[1].ValueOf("事後条件", "y")); Assert.Fail("例外が発生するはずです。"); } catch (TestErrorException ex) { Assert.AreEqual(@"ValueOf(事前条件,x)が呼び出されましたが、カテゴリ 事前条件 には列名 x が見つかりません。", ex.Message); Assert.IsInstanceOfType(ex, typeof(TestCaseColumnNameNotFoundException)); } } [TestMethod()] public void ParseTestCaseListTest_事前条件事後条件のカテゴリ名変更誤り() { try { List<TestCase> testCases; testCases = Tester.ParseTestCaseList(@" 事前条件|事後条件 x|y 0|1 2|3 ", null, null, new string[] { }); Assert.Fail("例外が発生するはずです。"); } catch (TestErrorException ex) { Assert.AreEqual(@"事前条件のカテゴリ名と事後条件のカテゴリ名を持つ2要素の文字列配列が必要ですが、0要素の配列 {} が指定されました。", ex.Message); Assert.IsInstanceOfType(ex, typeof(TestCaseCategoriesReplacementException)); } } } }
上のテストコードに合格するコードはこんな感じ。
using System; using System.Collections.Generic; using System.Runtime.Serialization; namespace Tester { public class Tester { public static readonly string DefaultRowSeparator = "\r\n"; public static readonly string DefaultCellSeparator = "|"; public static readonly string DefaultPreConditionLabelText = "事前条件"; public static readonly string DefaultPostConditionLabelText = "事後条件"; public string RowSeparator = DefaultRowSeparator; public string CellSeparator = DefaultCellSeparator; public string PreConditionLabelText = DefaultPreConditionLabelText; public string PostConditionLabelText = DefaultPostConditionLabelText; public Tester(string cellSeparator = null, string rowSeparator = null, string[] prePostStrings = null) { CellSeparator = (null == cellSeparator) ? DefaultCellSeparator : cellSeparator; RowSeparator = (null == rowSeparator) ? DefaultRowSeparator : rowSeparator; if (null != prePostStrings && 2 != prePostStrings.Length) { throw new TestCaseCategoriesReplacementException(prePostStrings); } PreConditionLabelText = (null == prePostStrings) ? DefaultPreConditionLabelText : prePostStrings[0]; PostConditionLabelText = (null == prePostStrings) ? DefaultPostConditionLabelText : prePostStrings[1]; } public static void Evaluate(string testCases, Action<TestCase> testCode, string cellSeparator = null, string rowSeparator = null, string[] prePostStrings = null) { Tester tester = new Tester(cellSeparator, rowSeparator, prePostStrings); tester.Evaluate(testCases, testCode); } public static List<TestCase> ParseTestCaseList(string testCases, string cellSeparator = null, string rowSeparator = null, string[] prePostStrings = null) { Tester tester = new Tester(cellSeparator, rowSeparator, prePostStrings); return tester.ParseTestCaseList(testCases); } private void Evaluate(string testCases, Action<TestCase> testCode) { var cases = this.ParseTestCaseList(testCases); var actuals = new List<TestResult>(); bool hasFailures = false; foreach (var testCase in cases) { var actual = new TestResult(); try { testCode(testCase); actuals.Add(actual.Success()); } catch (TestErrorException) { throw; } catch (Exception ex) { actuals.Add(actual.Fail(ex.Message)); hasFailures = true; } } if (hasFailures) { throw new TestFailedException(actuals); } } private List<TestCase> ParseTestCaseList(string testCases) { var cases = new List<TestCase>(); int indexPreCond = -1; int indexPostCond = -1; var columnNames = new Dictionary<int, string>(); string[] lines = testCases.Split(new string[] { this.RowSeparator }, StringSplitOptions.None); for (int r = 0; r < lines.Length; ++r) { string line = lines[r]; if (string.IsNullOrWhiteSpace(line)) { continue; } if (0 == columnNames.Count) { if (!(0 <= indexPreCond && 0 <= indexPostCond)) { FindPrePostConditionIndexes(ref indexPreCond, ref indexPostCond, lines, r); } else { MemoryColumnNames(columnNames, lines, r); } } else { SetCellValues(cases, indexPreCond, indexPostCond, columnNames, lines, r); } } if (0 == cases.Count) { if (!(0 <= indexPreCond && 0 <= indexPostCond)) { throw new TestCaseCategoriesMissingException(this.PreConditionLabelText, this.PostConditionLabelText); } if (0 < columnNames.Count) { throw new TestCaseEmptyException(this.GetColumnNamesDefinition(columnNames)); } } return cases; } private string GetColumnNamesDefinition(Dictionary<int, string> columnNames) { var names = new List<string>(); for (int i = 0; i < columnNames.Count; ++i) { names.Add(columnNames[i]); } return string.Join(this.CellSeparator, names.ToArray()); } private void MemoryColumnNames(Dictionary<int, string> columnNames, string[] lines, int r) { string[] cells = lines[r].Split(new string[] { this.CellSeparator }, StringSplitOptions.None); for (int c = 0; c < cells.Length; ++c) { var cell = cells[c]; columnNames.Add(c, cell); } } private void FindPrePostConditionIndexes(ref int indexPreCond, ref int indexPostCond, string[] lines, int r) { string[] cells = lines[r].Split(new string[] { this.CellSeparator }, StringSplitOptions.None); for (int c = 0; c < cells.Length; ++c) { var cell = cells[c]; if (this.PreConditionLabelText.Equals(cell)) { indexPreCond = c; } if (this.PostConditionLabelText.Equals(cell)) { indexPostCond = c; } } } private void SetCellValues(List<TestCase> cases, int indexPreCond, int indexPostCond, Dictionary<int, string> columnNames, string[] lines, int r) { var dic = new Dictionary<string, Dictionary<string, string>>(); dic[""] = new Dictionary<string, string>(); dic[this.PreConditionLabelText] = new Dictionary<string, string>(); dic[this.PostConditionLabelText] = new Dictionary<string, string>(); string[] cells = lines[r].Split(new string[] { this.CellSeparator }, StringSplitOptions.None); for (int c = 0; c < cells.Length; ++c) { string categoryName = GetCategoryName(c, indexPreCond, indexPostCond); string columnName = columnNames[c]; string value = cells[c]; dic[categoryName].Add(columnName, value); } cases.Add(new TestCase(dic)); } private string GetCategoryName(int c, int indexPreCond, int indexPostCond) { if (c < indexPreCond) { return ""; } if (c < indexPostCond) { return this.PreConditionLabelText; } return this.PostConditionLabelText; } } public class TestResult { public static string SUCCESS = "成功"; public string Result { get; set; } internal TestResult Success() { this.Result = SUCCESS; return this; } internal TestResult Fail(string message) { this.Result = message; return this; } public override bool Equals(object obj) { if (obj == null) { return false; } if (!obj.GetType().Equals(this.GetType())) { return false; } return this.ToString().Equals(obj.ToString()); } public override string ToString() { if (null == this.Result) { return string.Empty; } return this.Result.ToString(); } public override int GetHashCode() { return this.Result.GetHashCode(); } } public class TestCase { private Dictionary<string, Dictionary<string, string>> Value; public TestCase(Dictionary<string, Dictionary<string, string>> dicTestCases) { this.Value = dicTestCases; } public string ValueOf(string categoryName, string columnName) { if (!this.Value.ContainsKey(categoryName)) { throw new TestCaseCategoryNameNotFoundException(categoryName, columnName); } if (!this.Value[categoryName].ContainsKey(columnName)) { throw new TestCaseColumnNameNotFoundException(categoryName, columnName); } return this.Value[categoryName][columnName]; } [Serializable] public class TestCaseKeyException : TestErrorException { public List<TestResult> ActualTestResults { get; private set; } public TestCaseKeyException(string message) : base(message) { } } } /// <summary> /// テストコード自体の異常の場合の例外。 /// すなわち、テストケースの失敗ではなく、テストケースライブラリの用法または実装の異常を検出した場合の例外。 /// </summary> [Serializable] public class TestErrorException : Exception { public TestErrorException(string message) : base(message) { } } /// <summary> /// テストコードが失敗した(被験体のコードがテストに不合格となった)場合の例外。 /// </summary> [Serializable] public class TestFailedException : Exception { public List<TestResult> ActualTestResults { get; private set; } public TestFailedException(List<TestResult> actuals) : base(TestFailedException.Description(actuals)) { this.ActualTestResults = actuals; } public static string Description(List<TestResult> actuals) { string description = ""; for (int i = 0; i < actuals.Count; ++i) { TestResult actual = actuals[i]; string value = (null == actual) ? "" : actual.ToString(); description += string.Format("{0}:{1}\r\n", i, value); } return description; } } [Serializable] public class TestCaseCategoriesReplacementException : TestErrorException { private string[] prePostStrings; public TestCaseCategoriesReplacementException(string[] prePostStrings) : base(Description(prePostStrings)) { this.prePostStrings = prePostStrings; } private static string Description(string[] prePostStrings) { return string.Format("事前条件のカテゴリ名と事後条件のカテゴリ名を持つ2要素の文字列配列が必要ですが、{0}要素の配列 {1} が指定されました。", prePostStrings.Length, string.Format("{{{0}}}", string.Join(",", prePostStrings))); } } [Serializable] public class TestCaseCategoryNameNotFoundException : TestErrorException { public TestCaseCategoryNameNotFoundException(string categoryName, string columnName) : base(Description(categoryName, columnName)) { } public static string Description(string categoryName, string columnName) { return string.Format("ValueOf({0},{1})が呼び出されましたが、カテゴリ {0} が見つかりません。", categoryName, columnName); } } [Serializable] public class TestCaseColumnNameNotFoundException : TestErrorException { public TestCaseColumnNameNotFoundException(string categoryName, string columnName) : base(Description(categoryName, columnName)) { } public static string Description(string categoryName, string columnName) { return string.Format("ValueOf({0},{1})が呼び出されましたが、カテゴリ {0} には列名 {1} が見つかりません。", categoryName, columnName); } } [Serializable] public class TestCaseEmptyException : TestErrorException { public TestCaseEmptyException(string columnNamesDefinition) : base(Description(columnNamesDefinition)) { } public static string Description(string columnNamesDefinition) { return string.Format("テストケースがありません。列名定義 {0} に沿って、1行以上のテストケースのデータ行が必要です。", columnNamesDefinition); } } [Serializable] public class TestCaseCategoriesMissingException : TestErrorException { public TestCaseCategoriesMissingException(string preConditionLabelText, string postConditionLabelText) : base(Description(preConditionLabelText, postConditionLabelText)) { } public static string Description(string preConditionLabelText, string postConditionLabelText) { return string.Format("テストケース定義の書式に誤りがあります。テストケース行とそれらの前の列名の行に先行して「{0}」と「{1}」を含む行が必要です。", preConditionLabelText, postConditionLabelText); } } }
バイナリ形式ファイルを扱うためのクラスライブラリ BinaryFormat.dll を作ってみた。
単体テストコードも書いてみた(BinaryFormatTest.dll)。
サンドキャッスル(SHFB)(別途ダウンロードしてインストールした)で生成したヘルプファイル(documentation.chm)もいちおう付けてみた。
Microsoft VisualStudio 2013 で作ったソリューションのフォルダをZIP圧縮したものを貼っておく(.Net 4向け)。
BinaryFormat.zip ![]()
MITライセンスにしたので、誰がどう使ってもOK(ただし無保証)。
XAMPPで htdocs 下に置いたファイルが他のコンピュータから見えなかった件
XAMPPは、便利です。これをひとつインストールするだけで、Apache、MySQL、PHP、FTPをインストーラーでかんたんにインストールできます。
このあいだから、MacでもXAMPPを使ってWEBアプリをいじっていましたが、とくにさしつかえなく使えていました。
ところが今回、 XAMPP インストールフォルダ直下の htdocs (これは実際にはシンボリックリンクですが)に置いたWEBアプリを iPhone6 からアクセスしてみよーと思って試すと、失敗してしまいました。
HTTPエラー、403 (forbidden)。Apacheの承認が拒否されるのでした。
(IPアドレス)/xampp/
これでアクセスしてみたら403のエラーになります。
でも、ローカルPCで(つまりXAMPPを起動しているPCにあるブラウザで)XAMPPのアプリにアクセスする分には問題なくWEBページが表示できます。
localhost/xampp/
その表示されたページに記載された セキュリティ というリンクを開くと、
これらのXAMPPページは一般的にネットワーク経由でアクセス可能です 現在ご覧になっているすべてのXAMPPデモページは、ネットワーク上で一般的にアクセス可能です。あなたのIPアドレスを知っている人は誰でもこれらのページを見ることができます。
なんて書いてあるくらいです(いまはそれを見ることができなくて困っているのに)。
つまり、XAMPPを配布してくれている人たちは、XAMPPのインストールが正常に終わったときには、IPアドレスを知ってさえいればこのPCのウェブページを見ることができると考えているということですね。とくに手で設定ファイルを修正したりすることも必要なく。
気を取り直してXAMPP付属のWEBアプリのメニューから マニュアル へのリンクを開くと、 Apache 2 ドキュメント というのがあります。
そのリンクを開くと Apache HTTP サーバ バージョン 2.4 ドキュメント というページが表示されます。
その リリースノート のセクションには、 2.2 から 2.4 へのアップグレード というリンクがあります。
それを開くと(英語ですが)いろいろと「ここが変わったんだよ〜」的な説明があります。
「あれぇ? エラーだよ。なにが起きたんだか」ということで、Apache のエラーログを見ます。
XAMPPの Mac OS X 版は、Apache等の起動や停止に manager-osx というコントロールツールを使うようになっています。
そのツールの Manage Servers タブで Apache Web Server を選んでから Configure ボタンをクリックすると、 Configure Apache Web Server ダイアログが開くのですが、そこで Open Error Log をクリックすると Apache のエラーログが開きます。
エラーログには、
[authz_core:error]
とかで
AH01630: client denied by server onfiguration: /Applications/XAMPP/xamppfiles/htdocs/xampp/
というエラーメッセージが出ていました。
XAMPPのバージョンは XAMPP for Mac OS X 5.6.3-0 です。
エラーメッセージで検索すると、たいていの問題はだいたい片付くので、とりあえず Google 検索してみましたが、同じような問題がみつかったものの、その解決方法がどうもしっくりきません。おなじように対処しようとしてもそこに書かれているファイルがなかったり、試してみても同じ成功の結果がえられなかったり。
「そもそも前にもXAMPPは使っていたけど、 403 になんてならなかったのにな」という疑問がわきました。
その理由は、 Apache が 2.2 系から 2.4 系に変わったためらしいです。で、そのために変更になった Apache の設定があるというので真似してみましたが、設定ファイルを修正して Apache を再起動してからブラウザでアクセスしなおしてみても、同じ403エラーのままでした。
「おかしいなぁ。おかしいなぁ。」といったんは諦めて別のことをはじめてみたりもしました。
で、もういちど向きなおって、 Apache の設定ファイルがどこにあるのか、そのパスを探してみました。
Apache の設定ファイルは httpd.conf という名前のテキストファイルですが、 XAMPP for Mac OS X の場合はこのファイルは /Applications/XAMPP/etc/httpd.conf です。ところが、 etc が xamppfiles/etc へのシンボリックリンクになっているので、その実体は /Applications/XAMPP/xamppfiles/etc/httpd.conf というパスにあります。このファイルの末尾に、次のような記述がありました。
# XAMPP Include etc/extra/httpd-xampp.conf Include "/Appliactions/XAMPP/xamppfiles/apache2/conf/httpd.conf"
ここに記された /Appliactions/XAMPP/xamppfiles/apache2/conf/httpd.conf というファイルの内容は、数行のテキストファイルでした。
Alias /bitnami/ "/Applications/XAMPP/xamppfiles/apache2/htdocs/" Alias /bitnami "/Applications/XAMPP/xamppfiles/apache2/htdocs" <Directory "/Applications/XAMPP/xamppfiles/apache2/htdocs"> Options Indexes FollowSymLinks AllowOverride All Order allow,deny Allow from all </Directory>
これを見ると、 /bitnami というURL( http://localhost/bitnami )という別名(ブラウザで見るときの名前)が定義してあります。で、そのフォルダについての承認は誰に対しても開かれているようです。
でも、それは "/Applications/XAMPP/xamppfiles/apache2/htdocs" についてであって、
"/Applications/XAMPP/xamppfiles/htdocs" についてではないんですね。
ということなので、
(IPアドレス)/bitnami/
というURLを試してみました。
うまく表示されました。
/Applications/XAMPP/htdocs/xxx に置いたWEBページを http://(IPアドレス)/xxx で参照することはできませんでした。
でも
/Applications/XAMPP/xamppfiles/apache2/htdocs/xxx に置いたWEBページを http://(IPアドレス)/bitnami/xxx で参照することはできるということになりそうです。
設定ファイルは変えなくて、WEBアプリのディレクトリの起き場所を変えればよいわけですね。
(「もうちょっと説明がされていてもよかったんじゃないかなぁ」と思わされました。)
phonegapのインストールを試す
PhoneGap というのを使うと、iPhone のアプリを JavaScript と HTML とCSS で書けるというのでインストールしてみた。(同じソースで Android のアプリも作れてしまうらしい。それぞれにアプリの作法の違いもあるから同じに作れてもそれでは好ましくないということもあるだろうけど、できるということは便利なことだ。)
インストールにあたって npm を使うので、そのためにまず node.js をインストールする。
node.js はインストーラーがあるので、起動して、質問に答えていけば無事に終わった。
それから、phonegap自体を
sudo npm install -g phonegap
(Windows の場合は sudo なし。 Mac の場合は root 権限で実行するために sudo ではじめることが必要。)
とコマンドラインで実行すればいいらしかったけど、
ECONNRESET というエラーが起きて失敗した。
KIMURA-no-MacBook-Pro:~ kimurashinichi$ sudo npm install -g phonegap WARNING: Improper use of the sudo command could lead to data loss or the deletion of important system files. Please double-check your typing when using sudo. Type "man sudo" for more information. To proceed, enter your password, or type Ctrl-C to abort. Password: npm WARN deprecated deflate-crc32-stream@0.1.2: module has been merged into crc32-stream npm ERR! Darwin 14.1.1 npm ERR! argv "node" "/usr/local/bin/npm" "install" "-g" "phonegap" npm ERR! node v0.12.2 npm ERR! npm v2.7.4 npm ERR! code ECONNRESET npm ERR! errno ECONNRESET npm ERR! syscall read npm ERR! network read ECONNRESET npm ERR! network This is most likely not a problem with npm itself npm ERR! network and is related to network connectivity. npm ERR! network In most cases you are behind a proxy or have bad network settings. npm ERR! network npm ERR! network If you are behind a proxy, please make sure that the npm ERR! network 'proxy' config is set properly. See: 'npm help config' npm ERR! Darwin 14.1.1 npm ERR! argv "node" "/usr/local/bin/npm" "install" "-g" "phonegap" npm ERR! node v0.12.2 npm ERR! npm v2.7.4 npm ERR! code ECONNRESET npm ERR! errno ECONNRESET npm ERR! syscall read npm ERR! network read ECONNRESET npm ERR! network This is most likely not a problem with npm itself npm ERR! network and is related to network connectivity. npm ERR! network In most cases you are behind a proxy or have bad network settings. npm ERR! network npm ERR! network If you are behind a proxy, please make sure that the npm ERR! network 'proxy' config is set properly. See: 'npm help config' npm ERR! Darwin 14.1.1 npm ERR! argv "node" "/usr/local/bin/npm" "install" "-g" "phonegap" npm ERR! node v0.12.2 npm ERR! npm v2.7.4 npm ERR! code ECONNRESET npm ERR! errno ECONNRESET npm ERR! syscall read
検索すると、プロキシを使ってるのにプロキシの設定がしてないと出る、なんて書いてあったりしたものの、結局は、 npm の registry の設定をするとうまくいった。
sudo npm -g config set registry http://registry.nodejs.org/
npm の設定に registry 項目が追加されてる。
KIMURA-no-MacBook-Pro:~ kimurashinichi$ sudo npm -g config list ; cli configs global = true user-agent = "npm/2.7.4 node/v0.12.2 darwin x64" ; globalconfig /usr/local/etc/npmrc registry = "http://registry.npmjs.org/" ; node bin location = /usr/local/bin/node ; cwd = /Users/kimurashinichi ; HOME = /Users/kimurashinichi ; 'npm config ls -l' to show all defaults.
これでうまくいった。
KIMURA-no-MacBook-Pro:~ kimurashinichi$ sudo npm install -g phonegap
npm WARN engine npm@1.4.28: wanted: {"node":">=0.8","npm":"1"} (current: {"node":"0.12.2","npm":"2.7.4"})
npm WARN engine cordova-js@3.7.3: wanted: {"node":"~0.10.x"} (current: {"node":"0.12.2","npm":"2.7.4"})
npm WARN engine xmlbuilder@2.2.1: wanted: {"node":"0.8.x || 0.10.x"} (current: {"node":"0.12.2","npm":"2.7.4"})
npm WARN deprecated deflate-crc32-stream@0.1.2: module has been merged into crc32-stream
> ws@0.4.31 install /usr/local/lib/node_modules/phonegap/node_modules/connect-phonegap/node_modules/socket.io/node_modules/engine.io/node_modules/ws
> (node-gyp rebuild 2> builderror.log) || (exit 0)
CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
> ws@0.4.31 install /usr/local/lib/node_modules/phonegap/node_modules/connect-phonegap/node_modules/socket.io/node_modules/socket.io-client/node_modules/engine.io-client/node_modules/ws
> (node-gyp rebuild 2> builderror.log) || (exit 0)
CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
/usr/local/bin/phonegap -> /usr/local/lib/node_modules/phonegap/bin/phonegap.js
phonegap@4.2.0-0.25.0 /usr/local/lib/node_modules/phonegap
├── pluralize@0.0.4
├── colors@0.6.0-1
├── semver@1.1.0
├── minimist@0.1.0
├── qrcode-terminal@0.9.4
├── shelljs@0.1.4
├── phonegap-build@0.9.1 (colors@0.6.2, qrcode-terminal@0.8.0, optimist@0.3.7, shelljs@0.0.9, phonegap-build-api@0.3.3)
├── prompt@0.2.11 (revalidator@0.1.8, pkginfo@0.3.0, read@1.0.5, winston@0.6.2, utile@0.2.1)
├── cordova@4.2.0 (underscore@1.7.0, q@1.0.1, nopt@3.0.1, cordova-lib@4.2.0)
└── connect-phonegap@0.15.0 (home-dir@0.1.2, connect-inject@0.3.2, ip@0.3.1, ncp@0.6.0, findit@2.0.0, shelljs@0.2.6, request-progress@0.3.1, tar@0.1.19, http-proxy@1.8.1, node-static@0.7.0, gaze@0.4.3, archiver@0.10.1, localtunnel@1.3.0, useragent@2.0.8, request@2.33.0, connect@2.12.0, socket.io@1.0.4)
KIMURA-no-MacBook-Pro:~ kimurashinichi$ PhoneGap は Apache Cordova という名前のプロジェクトにもなっているそうだけど、なるほど、無事インストールが終わると、cordovaもインストールされるんだ。cordova フォルダができてるね。
noteのもくじ
誰でも参加できるので、私もきむらしんいちのnoteを作りました。あなたもnoteに参加してみませんか?(フォローしてもらえるとうれしいです。)
パズル
随想または空想
つぶやきとそれへのコメント
たとえばこんなnoteのしくみがあったら
note じゅうの発言やコメントをキーワード検索できるといいのにな。 twitterみたいに @なまえ で発言者やコメント宛先も検索できると、もっといいんだけど。
どんなクリエーターさんがいるんだろ?
イメージボタンで画像ファイルをアップロードできるだけでなく、直接お絵かきできるといいな。
noteを「買う」ってどういうことなんだろ。 別に買ったからって「ひとりじめ」することにはならないし。 その「noteを見る」権利の獲得っていうのもピンとこない。 切手やコインを集めたりするのと同じようなもの? 「思い出を集める」っていうことなのかな。 まだまだ、もやもや。