こうなったらまた出戻る以外の選択肢はない

とんでもないことになってしまった.大きな過ちを冒していた.こうなったらまた出戻る以外の選択肢はない.リスタートポジションは確保したのでもう一度やり直すことにしよう.大部分の修正はヘッダファイルの書き換えなので,修正がそのまま再利用できるから必要な作業は最小限のものに留まる見込みだ.おそらく今日中にもクリアできるだろう.

この作業は二つのポイントから見て重要だ.条件コンパイルは基本的にプログラム開発の分岐点に相当するから,分岐点がp個あれば,観念的には2^pの異なるバージョンが存在することになる.仮に条件コンパイル文が100個存在するとすれば,

2^100=1,267,650,600,228,229,401,496,703,205,376

もの異種バージョンが存在し得ることになる.修正をフィックスするという作業の目的はこの分岐点を最終的にはゼロ個まで削減し,2^0=1,つまり,単一バージョンまで統合することにある.この状態になって始めてプログラムは「正式版」と呼ばれることになるだろう.

条件コンパイルのもう一つの解釈は,それがプログラム開発の履歴を表現するものとなっているというポイントだ.つまり,「分岐点」というのは一つの新しい「進化」の方向性であり,プログラムの成長を促す「新芽」であると考えられる.修正をフィックスした時点でその分岐に付けられたコメントは「修正履歴」として記録されるべきだ.この「履歴」はプログラムの開発途上で「経験」したさまざまな事象の蓄積であるとも考えられる.この「経験された事象」は具体的には「障害」ないし「障壁」であると考えられるから,それに対処するために実装された「対策」を体系化したものが「免疫系」と呼ばれるシステムであり,「自己の恒真性を維持するための仕組み」であるとも言える.

さて,ぼちぼち始めることにしよう.現状では#defineで定義された値を持たないキーワード(コンパイルオプション)は226箇所ある.昨日確認された186件に比べると大分多いが,これはヘッダファイルが最新でC++ソースファイルが古いバージョンに戻っているため重複が発生していることによる.まず,この重複を解消するところから始めよう.昨日Meryを再インストールして「並び替え」マクロを使うことができるようになったので,この作業は簡単だ.検索結果をMeryで並び替えして重複行をチェックするだけでよい.⇒対処した.

#define\s+\S*\s*(/|$) (C)

で検索すると,まだ200個残っている.14件のダブリが残っているが,これはリネームしているものがあるためと思われる.DLLだけバックアップを取って進めることにしよう.現状では#ifdef が372個,#ifndef が59個,#if defined が429個,#if !defined が44個ある.#if の方はすでに原則としてすでに始末されていると考えられるので,#ifdef, #ifndef の431個を個別にチェックしてゆくことにする.#ifdef, #ifndef を #if の形に書き換えるのは正規表現を使って一括処理できるはずだから,まず,以下のような修正を実施することにする.

  1. #ifdef, #ifndef の条件式(キーワード)が#define によって定義済みのときは何もしない
  2. キーワードが未定義のときは,現状のままフィックスする つまり,グレー表示のアクティブでないブロックを削除し,平文に戻す アクティブでないブロックが存在しないときも同じ
  3. キーワードが未定義でアクティブなブロックが存在しないときは,x行削除のコメントを残してアクティブでないブロックを抹消する
  4. ただし,内容に疑義があるとき,ないしそのオプションの存続が必要と認められるときには,新たなキーワードを定義し,負論理を正論理に改めた上で条件分岐文を温存する
  5. 特定ヘッダファイル(nodule.h, comdebug.h, coupling.h, Bobject.h)以外のファイルにある#define文はLOCAL: に属するものを除いてすべて抹消する
  6. 「衝突検定で計算誤差を許容する」に関わる論理はすべて温存して後日再検証することにする 未定義のものに関しては新たにキーワードを定義し,「衝突検定で計算誤差を許容する」という表記とMAXKEISANGOSAの適用が「同期」するように真偽値を定める 
  7. ただし,「計算誤差を見る」の類は現状(AboutEqualを使う論理)でフィックスする 「計算誤差を無視する@20170509」も確定する 「MAXKEISANGOSAを適用する@20171229」は現状でフィックスした
  8. 「この関数は最高速で実行されるべきだ」の類は「速度最優先オフ」に統合する
  9. キーワードが定義済か否かの検査を「定義をここに表示」メニューで判定してはならない 定義はあってもコメントアウトされている場合があるので,つねに「実検索」の結果を見て判断する必要がある
  10. 何かの理由で温存したい論理がある場合には,if 0/1 のような表記を認める
  11. 「配偶者の性別不問とした@20180201」は未定義だが,新設してPENDINGとする 「メタファイルに出力する@20180312」も未定義だが,PENDINGとした

コンパイルエラーが発生した ⇒最優先オフをオンにしたためだ.checkブロックの内容が古くなっていた.⇒対処した.

非アクティブブロックがグレー表示されなくなってしまった.これでは修正を誤る可能性が高い.⇒ビルドし直したら正常に戻った.⇒ダメだ.また出てきた.今度はビルドして戻らない.一旦VSを落として再起動でようやく正常に戻った.

古いコードでDEBUGPRINTというマクロが見当たらない.⇒空のマクロを作っておく.DEBUGDUMPというキーワードはマクロとオプションの両方に使われている.整変数に使っているところもある.整変数はiDEBUGDUMPとしてみよう.マクロはmDEBUGDUMPとする.

NAMEBOX:: PaintNameFrameは2つの関数から呼び出されている.デバッグ時にはNAMEBOX::Drawから,リリース版ではNAMEBOX::DrawNameからという変則的な呼び出しになっている.おかしいので,つねにNAMEBOX::Drawで実行するように書き換えた.

#ifdef と#ifndef の点検は完了した.あとは整理するだけとなった.その前に#ifdef を#if defined の形式に変換してしまおう.いや,その前に#defineされて使われていない定義というのがあるはずなので,それを先に片付けてしまおう.⇒#defineの個数は200個になった.増減があるので,多分これで正しいのだと思う.バックアップを取っておこう.#ifdef から #if defined への一括変換は昨日やっている.

検索:#ifdef (.+)\b
置換:#if defined($1)

ただし,この方式ではコメントが入っている行ではコンパイルエラーが発生する.コメント付きの場合にも適用できるようにしたい…

検索:#ifdef\s+(\S*)(/|\b)*?   (S)
置換:#if defined($1)       (R)

これでなんとか行けそうだ.#ifdef は140個ある.140個置換した.⇒うまくいった!ビルドも通った.#ifndef も同じ要領でやっておこう.#ifndef は61個ある.⇒問題なく変換できた.さて,この200個の#define文が1個になるまで削減するというのだが,可能だろうか?とりあえず,やり易いところからやってゆくことにしよう.

まず,ローカルに定義されているLOCAL: は整変数に変えてしまってもよいだろう.実際この方LO法は現在すでに使われている.#define DISPALWAYS を int DISPALWAYS に変えるというだけだ.⇒これで定義文は7個減って193になった.残りはすべて特定ヘッダファイルだけということになる.

これはダンプルーチンなどをシステムの装置に組み込むということを意味する.計測装置をシステムに組み込むというのは,たとえば車にスピードメーターを付けるようなものであり,もしロジックが混み合うようなことがあれば,その部分だけ切り出してルーチン化してやればよい.Bobject.hにはDEBUG:という区分でデバッグ用の定義がどっさり入っているが,装置化できるものは装置化してしまうのが一番よいと思う.少なくとも1つのファイル内で完結している定義に関してはそれをやるべきだろう.いままでやってきたことと真逆のことをやることになるが,それも仕方ない.⇒この修正は一旦撤回することにする.

▲comdebug.hの古いプリント文などの定義は廃棄してもよいのではないか?

▲_DEBUG_,XDEBUGなどの動作をチェックする必要がある 古いバージョンは廃棄した方がよい

すべての定義文をデフォルト(=現状)でオンとデフォルトでオフに区分けして管理する その上ですべての定義文をオンにして動作を確認する また,すべての定義文をオフにして動作を確認する ただし,comdebug.hとcoupling.hの管理する分は除く(ということは対象はnodule.hとBobject.h内の定義文に限定される

まず,すべてのスイッチオンを試してみる.コンパイルエラーが出ている.MERGETESTCARDIMAGEでbool NOCARDIMAGEを初期化しているが,参照されていない.⇒#if 0 で仮止めした.COLCOLORSEGMENTやCOLVERTICALSEGMENTで使っているHというパラメータが未定義.⇒対処した.COLVERTICALSEGMENTで使っているvsegmentという変数が未定義.⇒垂直セグメント検定という機構は完全に廃止されている.SIZEOVERAUTOZOOMOUTでLimitSizeOverの引数が足りない.⇒補充した.⇒とりあえず動いた.

▲TESTタグのPRESETPARAMETERを実行するとNAMEBOX:EraseGhostNode→setCriticalでエラーが発生する.チャンネル不足が発生しているものと思われる.チャンネル数自動ならエラーは発生しない.このエラーを仮修正でパスすると,MakePairListClean中にnoduleのデストラクタで非参照カウントの残留が発生する.サンプルはゼルコバの木モジュール構成図 TEST.ZEL.エラーを無視して描画は可能.PAIRLIST→PAIRBOXの参照が残っている.TESTPERFECTでも同じエラーになる.⇒いや,これはプリセット値が入っていたためと思われる.しかし,自動テストの動作にはなっていない…

▲PENDINGをすべてオン,COMDEBUGは無指定,それ以外のすべてのオプションをオフにしてエラーが発生する.スプリットが発生しているが,それ以上に画面が壊れている.

過去の修正をFIXするという取り組み

過去の修正をFIXするという取り組みは進んではいるが,進捗は捗々しいものではない.あまり手が掛かるので一括変換で#ifdef を #if defined に変換したのが却って裏目に出ている.キーワード(コンパイルオプション)の定義・未定義を判別するのが著しく困難になってしまった.こうなったら振り出しに戻ってすべての#if definedを1個づつ点検してゆくしかないのだが,その前に正規表現の構文を研究して,#define文で(値を設定しない)定義だけのキーワードを選り分けることができるようにしておきたい.後ろにコメントが付いていない文は検索できるが,コメント付き定義文だけを抽出することができない.

昔はSEDやVIを使ってバッチファイルで多数ファイルの一括変換などを自在にやっていたのだが,一度あるところで社内LANの大元のHD(システム全体をカバーするタワー型のストレージ,当時のことでわずか70MB,ユーザの端末にはHDは搭載されていなかった)のユーザファイルを全部消してしまうという大失策をやったことがあり,それからは足が遠のいてしまった.(このときはHPのサポートチームが飛んできて鮮やかに復活せてくれたのだが…大いに懲りた)

できた!これでコメントありの定義文をすべて抽出できる

#define\s+\S*\s*/ (A)

157個あった.#define文は修正FIXの問題があるので集中管理した方がよいと思う.ただし,そのファイル内に限定したローカルなデバッグ用オプションは従来通り認めることにする.ローカルなオプションは(デバッグ時にしか使われないのだから)#if defined(_DEBUG)のブロックに置かなくてはならない.#define文は原則として以下の4つのファイルのいずれかに置くことにする.

  1. nodule.h 修正履歴,関数の論理・仕様に関わる定義(INCOMPLETE,PENDING)
  2. comdebug.h バージョン管理に関わる定義,ライセンスに関わる定義,デバッグ用ツールに関わる定義 (VERSION)
  3. Bobjecct.h デバッグ用オプション定義(TEMPORARY),検証に関わる定義(VERIFICATION)
  4. coupling.h テスト環境に関わる定義(ENVIRONMENT)

まず,この観点から定義文を整理してみよう.たとえば次の例文を見てみよう.

#define SOLVETRIBESPLIT // 系列間スプリット検定の動作を従来仕様に戻す

もし,SOLVETRIBESPLITがどこからも参照されていなければ,この定義は無条件で「廃止」される.いまの場合,このキーワードは別の場所で別の意味「系列間スプリットの発生により移動が実施された」として使われていた.つまり,本来の用途してはフィックスしていることになるが,そうでない場合にはnodule.hのINCOMPLETEかないしPENDINGカテゴリに移されなければならない.もし,この修正が正当であることが検証されれば,定義を廃止して修正履歴に移動する.ただし,単純に「廃止」されただけの場合は必ずしも「履歴」として保存されなくてもよい.いまの場合は「単純な廃止」に相当する.

検証用にこの論理を(場合によっては加工して)残す必要がある場合には,nodule.hのVERIFICATIONに移動する.また,事後の再発に備えてデバッグ用に論理を残す場合にはBobject.hのTEMPORARYに預けることになる.昨日コメントなし定義の抽出用に使っていた #define (?!.*( |\t)).*$ で検索するとヒット件数が7しかない.ほとんどの定義の後ろにコメントを付けたこともあるが,付けていないものもある.

#define\s+\S*\s*$ (B)

これで30件ヒットした.多分これは動作していると思う.(A)式と(B)式を合体して1式にできるだろうか?

#define\s+\S*\s*/
#define\s+\S*\s*$

これは特に難しくなさそうだ.末尾が/か$で終わればよいのだから…つまり,

#define\s+\S*\s*(/|$) (C)

でよいはずだ.これで186件検出した.もう一度個別にテストしてみよう.前者が158件,後者が30件で単純合計は188件となる.ということは重複が2件存在しなくてはならないのだが…エディタで並び替えができると簡単に重複行を調べることができるのだが…常用しているエディタのMeryではマクロでその機能が提供されているはずだが,見つからない.もう一度インストールし直してみよう.⇒今度はProgram FilesのMeryの中にMacrosというフォルダができた.早速使ってみよう.

確かにダブっているエントリが2つある.

#define    _POLLFILE_H_
#define    __HOSTCMD_

テキストに「/」が含まれる行を探す(A)式を実行しても上の2つが検索結果に入ってしまう.このタイプの記述はすべてのヘッダファイルが持っているので,この2つだけ特異な動作になるというのはおかしい.(A)式を実行して「/」を含まない行が検出されるということはあり得ないのでVSの「検索」のバグと考えるしかない.(C)式を使えばもちろんこのようなことは起こらない.⇒すべてのオプション定義にコメントを付けて以下のコードで識別できるようにした.

  1. INCOMPLETE: nodule.h 0
  2. PENDING: nodule.h 28
  3. COMDEBUG: comdebug.h 25
  4. TEST: coupling.h 17
  5. VERIFY: Bobject.h 47
  6. DEBUG: Bobject.h 35
  7. LOCAL: *.cpp 7
  8. 合計 6種 *.h 5本, *.cpp 6本 159

オプション定義のある*.cppファイルは,SimpleGraph.cpp, Potential.cpp, PairBox.cpp, MargBox.cpp,Jikusenzu.cpp,DoublyBlessed.cppがある.たとえば,「LOCAL:」を含む定義を検索するときには以下のような式を使った.

(#define\s+\S*\s*(/|$))(.* LOCAL:)

INCOMPLETEはいまのところゼロだが,検証結果によってはPENDINGから移行してくることもあり得る. Bobject.hに含まれるVERIFYとDEBUGは完全にニュートラルでなくてはならず,実行フローやロジックに影響を与えないことが保証されなくてはならないが,現状ではその確証はない.PENDINGに含まれる項目のほとんどは直ちにフィックスしてもほぼ問題ないと考えられるが,ここでは一旦置いて,先に「無定義オプション」つまり,「負論理で定義された仮修正」の始末を急ぐことにしよう.これを行う方法としては,とりあえず,#if defined/!defined文を虱潰しにチェックするしかないような気がする.ともかくそれをやってみよう.

VERIFICATIONとDEBUGの相違点は前者は一般に常時走っているのに対し,DEBUGはバグをトレースする目的で一時的に出動するというところだ.この意味ではASSERTIONなどはVERIFICATIONのカテゴリに属するとも言える.というか,VERIFICATIONとして扱われているロジックは将来的にはASSERTIONに昇格するものと見るべきだろう.VERIFICATIONにはデバッグ支援という目的で設置されているので,(DEBUG_NEVERなどと同様)必ずしもリリース版で動作することを意図していない.つまり,ASSERTIONとVERIFICATIONの違いはリリース版に常設されるものであるか否かという点にある.VERIFICATIONでは事象が発生した時点で停止するだけでよいが,ASSERTIONにはアプリを続行するための例外処理が整備されなくてはならない.

▲comdebug.hで定義するASSERTIONマクロに例外処理を整備する

「MAXKEISANGOSAを適用する@20171229」は未定義だが,論理が逆(負論理)になっている.この論理は「再開発スタート版」でもそのようになっている.つまり,本来は#ifndefでなくてはならないところが,#ifdefになっていたのではないかと思われる.「MAXKEISANGOSAを適用」に関係する論理は無数にあるが,オプションになっているところでは.「MAXKEISANGOSAを適用しない」という流れになっている.しかし,必ずしも普遍的にそうなっている訳ではないので,どうも統一的な方針を欠いたまま場当たりに修正しているのではないか?という懸念がある.ここでは,「MAXKEISANGOSAを適用する@20171229」に#define文を与えた上で,現行論理のまま据え置くことにしておくが,全般的な見直しが必要と考える.

いや,違う.もっと重大な失敗を冒している.やはり,機械的に#ifdef から#if definedに置換した失敗は大間違いだった.定義されている場合はそれでよいが,未定義の場合は例外なく負論理で書いているので真偽を逆にしなくてはならない.手動で修正していたときにはそのような論理の書き換えを行っていたのだが…機械的に置換したところより,前まで戻るしかない.とんでもないことになってしまった.

一括変換をやったのは昨日のことだ.昨日は2回バックアップを取っている他にDLLフォルダだけのバックアップを一度やっている.

  1. 始業時バックアップ 10:07
  2. 一回目バックアップ 13:53
  3. DLLをバックアップ  15:32
  4. 二回目バックアップ 19:06 

おそらく一回目バックアップは一括変換を実施する前に実行されていると思う.(でなかったらアホだ)⇒一回目バックアップではまだ,prohibitlistとshiftlistの始末も付けていない.DLLをバックアップ でも同じだ.二回目バックアップでも手を付けていない…#ifdef は379点まで減少している.多分「#ifdef _DEBUG, _DEBUG_, XDEBUGの3種をすべて#if defined()に一括変換」という辺りだろう.

prohibitlistとshiftlistのパージはログの冒頭近くで書いているが,実際には修正のFIXと並行して,その片手間にやっていたのだろう.いずれにしても二回目バックアップは安全なところまでしか一括変換をやっていないので,ここからスタートすれば十分安全と思われる.すべてをここまで巻き戻すのではなく,*.hに関わる修正は現在のものが使えるのではないかと思う.重要な書き換えはすべてヘッダファイルに入っているので,これを再利用できれば相当な利得がある.まず,そういう形で設(しつら)えてみよう.かなり手戻りだが,失敗するよりはましだ.

系列枠のシフト管理用ノード対リストを廃止

#ifdef プラグマを潰す作業を続けているところだが,まだ840個も残っている.今日中に終わるだろうか?動作には影響のない益の少ない作業のようにも思われるが,これが完了すればソースコードがかなり読み易くなることは確実なのでがんばってみることにしよう.幸いVSは検索結果を表示するウィンドウを3つ持っているので,残存する#ifdefの検索結果を「検索結果テーブル」に表示しながら,別のウィンドウでキーワード検索するなどのことが並行して実行できるのは大変都合がよい.

ノード対は通常基本世代枠リスト→世代枠→ノード対リストに接続されている.系列枠は世代シフト管理用ノード対リストというのを2種類持っている.一般にリストはリスト要素オブジェクトをスロットゼロを使ってチェーン状にリンクしている.逆に言えば,リスト要素は一つのリストにしか接続できない.系列枠が持っているノード対リストはどうやってノード対にアクセスしているのだろう?

系列枠のprohibitlistとshiftlistは現在使われていないように思われる.⇒終了時にcancelするところはあるが,リストを構成している論理は見当たらない.すでに廃れているのではないだろうか?⇒TRIBEBOX::CleanSansyoでは対象オブジェクトがこの2種のノード対リストである場合は停止するような論理になっている.つまり,明らかにこの2つはとっくの昔に廃止されている.⇒パージは簡単に終わったが,実行時エラーが出た.⇒クラス構成の始末がついていなかった.TRIBEsPROHIBITとTRIBEsSHIFTが残っていた.

この修正に伴ってNAMEBOX::shiftnum,NAMEBOX::targetnumも不用になるのではないか?TRIBEBOX::RemoveFromShiftListも不用になる.UpdateShiftList,ShiftMultiCards,RemoveFromShiftList,DumpShiftList,DumpProhibitListも同様.もしかすると不用ロジックはもっと広範囲に拡がる可能性もある.そもそも,カードシフトという概念自体がすでに崩れているのではないだろうか?もしかするとこれは事実かも知れないが,影響が大き過ぎるので後日ということにして先に進む.OBSOLETEで仮止めしてある部分が廃棄されることは確実なので#ifdefを#if definedに書き換える必要はないと思われるが,OBOSOLETEではあまりに一般的過ぎるので「系列枠のシフト管理用ノード対リストを廃止@20201109」に変えておこう.

▲InitializePrinterはすでに使われていないのではないか

#ifdef _DEBUG, _DEBUG_, XDEBUGの3種をすべて#if defined()に一括変換して#ifdef は362個まで減少した.あとは個別にこつこつと修正してゆくしかない.⇒VSの検索・置換は正規表現が使えるので試してみよう.以下で一括変換できた.

検索:#ifdef (.+)\b
置換:#if defined($1)

キーワードの中に全角@が入っていても処理できる.ただし,この置換は行末に閉じ括弧が入るので行中にコメントが入っているとコンパイルエラーになる.コメントが入っているのは1ファイル1個くらいなのですぐにビルドできるようになった.あとは,存続するものと廃止するものを切り分けるだけだ.#define文は1210個もある.定義だけで値を持たないリテラルをピックアップできればよいのだが…以下の検索式で定義だけのリテラルを検索できる.

検索:#define (?!.*( |\t)).*$

ただし,後ろにコメントが付いているものはこの式では弾かれてしまう.mmm…少しおかしい.49個しかヒットしない.尻尾に何か付いているものがあるのだろうか?⇒コメントが付いているものと思われる.これは別途扱うことにする.

ともかくまず,この49個を始末してしまおう.いや,これらは基本的には問題ない.というか検証する必要はあるにしても原則としては存続というのがルールだ.始末しなくてはならないのはむしろ「無定義」のキーワードだ.それをどうやって見つけることができるだろう?#ifdef から#if definedに書き換える手順の中でそれを一つづつやってきたのだが,一括変換してしまったので境界線が消えてしまった…あとは,#if definedを一つづつチェックするしかないのではないだろうか

残っている分岐はすべて現状でフィックス

過去の修正をすべてフィックスして完全にさらの状態から再出発するための工程を進めているところだ.肝要な部分に関してはすでにほぼ完了しているが,#ifdef, #ifndefによる条件コンパイルの分岐はまだ1000箇所以上ある.できるところまでは整理しておきたいが,基本的に現在残っている分岐はすべて「現状でフィックス」という方針なので作業的には難しいものではない.分岐には#defineでキーワードを定義したものと,それを欠いているものがある.#defineがないものはその場でフィックスしてよいが,#defineがあるものについては検証が必要だ.

分岐を残す場合にはその目的・用途によって以下の4種に区分することにする.①TEMPORARY,②INCOMPLETE,③PENDING,④VERIFICATION.TEMPORARYはデバッグ時のダンプなど必要に応じて一時的に用いるもの,INCOMPLETEは修正が確定していないもの,PENDINGは再検討を要するもの,VERIFICATIONは動作確認のために(常時/一時的に)設置するものとする.

キーワードに関わる分岐が複数ファイルに関係する場合にはヘッダファイルを使った広域に適用される定義が必要だが,1ファイル内に留まる場合にはソースファイルの冒頭に直接記述する略式を認める.キーワードには原則日本語でその趣旨を明示するようにする.フィックスしたキーワードは原則としてコメントとしてファイルに残すこと,修正箇所は少なくとも@日付でマーキングして修正履歴をトレースための手がかりを残しておくこととする.つまり,@日付はタイムスタンプであると同時に識別子でもある.キーワードにはコメントで解説を付することが望ましい.日付だけのキーワードは望ましくない.また,日付が変わっても一連の修正には同一キーワードを適用する方がよい.

分岐を残す場合にはすべて#if definedないし#if !definedの形式に改める.ただし,一時的な仮修正では#ifdef 形式で負論理の条件式を用いることを認める.(逆に言えば,#ifdef形式の修正はすべて「仮修正」と認定される)暫定的な修正では#define文を切った上で,PENDINGとすべきである.PAIRLISTでdatacountの残留が発生するという事象があるので,先に見ておくことにしよう.

PAIRLIST::~PAIRLISTでdatacountの残留が発生している.アプリを起動→終了で再現できる.⇒これはノーマルな動作である.delete PAIRLIST→~PAIRLIST→dispose→cancelで始めてdatacountはゼロになる.check文の中で検査すればよいのだが,呼び出し頻度の高い関数は余分なものをできるだけ持たないようにしているので,速度最優先オフのときはcheckブロックが使えるようにしておこう.

同上サンプルで終了時,世代に_INT_MAX(FARFUTURE)が入っているPAIRLISTがある.~PAIRLISTのscount=76と77,SNUMは307と308だ.PAIRLISTのダンプで表示される世代番号は親の世代枠から取っている.世代枠が空であるためだ.⇒系列枠は世代シフト管理用ノード対リストというのを2種類持っている.シフトノード対リストと禁止ノード対リストだ.これらは管理用のノード対リストであり,直接系列枠に接続して,世代枠には繋がっていない.⇒系列枠に接続しているPAIRLISTの場合はそれを明示的にダンプするようにした.PAIRBOX::dumpのASSERTIONには誤りがあった.

また失敗した.

1>d:\zelkova\zelkovadll\src\mergecard.cpp(878): fatal error C1020: 予期しない #endif です。

多分これは孤立した#endifを削除するだけで通るだろう.⇒うまくいったようだ.RECYCLENEWNUMBER(リサイクル時には新しい通番を付与する)は現状でフィックスすることにする.すべての系列枠を親参照パスから切断する@20180503も確定しておこう.この他,確定した主なオプションはあとでリストにして示すことにする.これらの修正履歴は原則nodule.hに記載することにする.

#ifndefは完全に片付いた.以下は除外した.①マイクロソフトのコード,②ヘッダファイル冒頭のヘッダ名の宣言,③_DEBUGなどデバッグモードを示すキーワード.#ifndefにはこれらによって73件が残っている.#ifdefの現在数は846だ.ともかく,片付くまでやるしかない.一度バックアップを取っておこう.

コンパイルオプションの真偽を負論理から正論理に変更する

過去のすべての修正をフィックスし,まっさらなところから「再開発スタート版」を開始するという方針を立てて作業を続けている.fixという英語は「修理する」という意味に使われるが,ここでは「修正が完了し,バージョンの分岐が存在しなくなった状態」と定義する.つまり,#if, #else, #endif がソースコード上から消えることを意味する.nodule.h, Bobject.h で定義されていた条件コンパイル式に関してはすべてFIX完了し,「仮修正」,「暫定」の文字列はシステム内から完全に一掃された状態になったが.,分岐を持つ修正がまだかなりの個数残っている.これらはTEMPORARY, INCOMPLETE,および保留※で識別できるようにしてある.※はPENDINGで識別することにした.

TEMPORARYは一時的な修正で元のソースに戻すという前提で挿入されているもの,INCOMPLETEはまだ修正が確定していないとみなされるものという位置付けだ.これらが,その定義通りの状態になっているかどうかを試すには,#defineで定義された識別子の真・偽を切り替えてみればよい.TEMPORARYは一時的なコードなのだから,オンでもオフでも正常に動作しなくてはならない.従って,TEMPORARYプラグマがこの条件を満たしていれば,リリース版では復旧させるという条件下で存続が認められる.INCOMPLETEの場合も,同様に識別子をオン・オフして動作を確認することで正当性をチェックできる.もし,オンで不具合が出ればオフの論理が正しく,オフで停止すればオンの論理で確定できる.いずれでも不具合が発現しないとすれば(実際はほとんどそのようになっていると思う),その修正の意味を読んでメリットのある方を選択することになる.オンとオフの動作が同じなら,場合によってはTEMPORARYに移管するということも考えられる.

一つ問題なのは現状ではこれらのプラグマのほとんどが負論理になっているという点だ.これは#define文を省略して,識別子の意味と反対の論理で記述されているということを意味する.つまり,たとえば「この文は誤り@20201107」が未定義のまま使われている #ifdef この文は誤り@20201107 #else #endif のブロックではアクティブなブロックが誤りで,アクティブでないブロックが正しいという逆の意味になる.

#ifdef この文は誤り@20201107 (#defineしていないので偽)
  アクティブでないブロック(この文は正しい)
#else
  アクテイブなブロック(この文は誤り)
#endif

オン・オフの切り替えテストを行うためにはかなり不都合なので,とりあえず,すべてのプラグマで#define文を宣言して正論理に切り替えておく必要がある.まず,この修正をやっておこう.修正が必要なところは60箇所くらいある.ことのついでにこのような場所では#ifdef ではなく #if defined() を使うようにすることにしよう.こうしておけばより詳細な条件式を論理演算子を使って記述することもできるし,通常の#ifdefとも区別することができるだろう.

現在システム上には#ifdef文が893個存在する.#if文は69個あるが,うちVSが生成する*.rcの4個を除く65個はすべてVBの#If文(大文字のI)だ.いや,まだある.#ifndef が178箇所で使われている.#ifdefと合わせると1071個の条件コンパイル式があるということになる.これを総点検するというのは現実的ではない.ここでは#defineを置いていない条件式は「一掃」の対象になると宣言するに留めておこう.

▲「保留」されている論理を点検しフィックスする必要がある

ともかく,TEMPORARYとINCOMPLETEに含まれる項目を正論理に変えるところから始めよう.

▲終了時PAIRLISTのデストラクタでdetacount 非ゼロが起こり,ダンプが表示される.サンプルはゼルコバの木モジュール構成図 TEST.ZEL,基準ノードは#33 baselist,軸線図法.datacount=2.

▲#ifdef _DEBUG_で実行を抑制しているブロック/関数が135箇所ある.この中にはすでに廃れてしまっているロジックなども含まれると思われるが,一度点検する必要がある.

#ifdef OBSOLETE(廃れた)はNOTHINGと同様,無条件に削除してよい.いや,待てよ.何かおかしなことが起きている.OBSOLETEの中に含まれているGetOSDisplayStringという関数はそれ以外のどこにも含まれていないのに未定義にならない.⇒いや,出てきた.未解決の外部シンボルとなっている.つまり,この関数はOBSOLETEにはできない.あるいは完全に廃止するかのどちらかだ.このコードは確か外部から拾ってきたものでOSのバージョンなどを取得するためのルーチンだ.残しておいてもよいのではないか?⇒if !defined(GetOSDisplayStringを停止@20190109)で使えるようにしておいた.

この関数を廃止した理由はこのコードがWindows 7 あたりまでしかサポートしていないためだ.修正すれば,Windows 10でも使えるようになるかもしれないが,’GetVersionExA’: が非推奨 というエラー(警告)が出てビルドできないので差し替えが必要だ.警告をエラーとして扱うというオプションを緩和すれば使えないことはないが…このようなこともあるのでいきなりOBSOLETEを削除もリスクがある.⇒OBSOLETE 3件を削除した.NOTHINGは6件ある.⇒始末した.

OCXで使われているint TEMPORARYの意味・用途が不明.「プレビュー→ノーマル一時切り替え中」となっているが,値はゼロのまま変化していない.⇒廃止でよいと思う.TEMPORARYはすべて整理した.INCOMPLETEに移ろう.INCOMPLETE@20190128はdecidePrimaryNodeを隠蔽しているが#elseは空で,改訂版のdecidePrimaryNodeは無条件で動いている.これはすでに確定しているものと判断する.INCOMPLETE@20190129には複数の用途がある.

  1. decidePrimaryNodeで(primarynode->getmarglink() != getOyalink())のとき停止しない
  2. SetMinorTribeで「基準ノードを参照する系列の優先ノード切り替えは禁止」を無視する

多分どちらも反例があると思われるので検証が必要だ.INCOMPLETE@20190202はもっといろいろな場合を含んでいる.

  1. EstablishMajorTribeChainで「系列順位が逆転している場合」を無視
  2. MakeUpTreeで先祖ノードが単身先祖配偶者のとき,EstablishMajorTribeChain(先行系列との接続関係を確立する)を実行しない
  3. TRIBEBOX::MakeUpTreeのループでSetPrimeNode(系列優先仮ノードと優先実ノードを設定)を実行する
  4. MakeUpTreeで「系列優先ノードが隠蔽されている」とき停止しない
  5. SetupPrimaryNodeで始系列の場合も「系列優先実ノードが家内婚配偶者で隠蔽されている場合」の探索を実施する
  6. SetupPrimaryNodeで始系列でない場合には優先実ノードが隠蔽されている場合を認める

これはおそらく完全に確定した修正のように思われる.INCOMPLETE@20190204を見てみよう.

  1. TRIBELIST::MakeUpTreeでEstablishMajorTribeChain(先行系列との接続関係を確立する)を実行する代わりにSetupPrimaryNode(系列優先仮ノードと優先実ノードを設定)している
  2. setMajortribeでsetrealnode(系列優先実ノード参照をリセット)しない
  3. GetMajorTribeChainでIsSolidNameBox検査とsetrealnodeを実行している
  4. TRIBEBOX::GetRealnodeで系列優先実ノードが見つからないとき,優先仮ノードのNameBoxから取り出している処理を廃止

これも確定しているように思われるが,項目4が正当であるためには,前段で系列優先仮ノードから必ず優先実ノードを決定できることが示されなくてはならない.INCOMPLETE@20190130は2箇所だけだ.

  1. ReduceMultiCardで人名枠をIsVisibleCardではなくIsValidNameBoxで判定している
  2. ReduceMultiCardで仮ノード消去の対象カードから配偶者を除外している

項目2は配偶者が消去されるのはBTWの場合だけということを主張している.これは正しいのではないか?最後のINCOMPLETE@20190131は6箇所だ.

  1. TRIBELIST::MakeUpTreeで系列優先仮ノードが隠蔽されているとき,優先仮ノードを探索してTRIBELIST::MakeUpTree(系列優先仮ノードと優先実ノードを設定)するブロックを廃止している.ただし,#elseブロックは空なのでこれは確定と考えるしかない.SetupPrimaryNodeには「隠蔽された系列優先ノードの補正」という処理が入っているので,おそらくここでは不用になったものと思われる.
  2. CARDLINK::getProxyで系列優先仮ノードを無条件で可視化している.いや,違う.これは一般の人名ノードの場合だ.結婚リンクを与えて,その結婚リンクに繋がる仮ノードを返すという関数だ.ここで可視化する必要があるのかどうか?という点に関しては疑問がある.
  3. TRIBEBOX::SetPrimeNodeでSetupPrimaryBoxの代わりにsetPrimenodeを呼び出している.SetupPrimaryBoxという関数はすでに廃止されているので,これは確定だ.
  4. TRIBEBOX::GetRealnodeで系列優先実ノードが有効なカードでなかった場合の処理が挿入されている.#elseにはこれを代替するようなコードは含まれていないので,これで確定とするしかない.
  5. Bobject::getvisibleで(hidden() && visible && PHASE >= INITIALIZED)のとき停止するのを抑制している.hiddenかつvisibleという状態はあり得ないとしているのか?無視してよいと見ているのか,意図が不明なので暫定的に検査を復活させておく.

項目2で隠蔽ノードまで無差別に可視化しているところを見ると,この修正ではカードが隠蔽状態にあることと可視であることは無関係と考えているように思われる.実際,隠蔽状態にあるノードが表示されないというのは事実だがそれは可視状態であるか否かということとは無関係だ.従って,項目2も確定と見てよいのではないだろうか?そうすれば,INCOMPLETE@20190131のプラグマは完全に消える.まあ,一応残して様子を見ることにしよう.まず,TEMPORARYのカテゴリに属するオプションを全部止めて動作を確認しておこう.これらは止めた状態がノーマルな状態のはずだから,当然動かなくてはならない.

GetOSDisplayStringで「’GetVersionExA’: が非推奨として宣言されました。」が出る.「GetOSDisplayStringの使用停止@20190109」がTEMPORARYに入っている.⇒これはPENDING というカテゴリを作って移しておこう.

上の1件を除く4件のTEMPORARYオプションを止めて走らせたところ,Bobject::getvisibleで停止した.これは上記INCOMPLETE@20190131の項目5で「意図が不明なので暫定的に検査を復活させておく」とした論理に該当する.これは現行論理で確定でよいと思われるが,気になるのでどんなノードが引っかかっているのかを見ておこう.printnamaeでダンプしようとしたが,この関数からgetvisibleを呼び出しているのでスタックオーバーフローが発生してしまう.printshortなら大丈夫だろう…⇒#279 kakeizu(0)だ.PHASEは12でMAKEUPTREE.系図木を構築しているところだ.Yリストへの繋ぎ込みはこのフェーズで実行されるので,このフェーズでは無視としてみよう.⇒これで一応描画できた.

完全木テストを通してみよう.全体図が通れば親族図は大体通るので全体図テストでもよかったのかもしれないが,サンプルが小さいのでそんなには掛からないだろう…この版は多分完全被参照リスト管理が動いているのでかなり遅い…それでも1分以内に終わった.添付サンプルも問題ないようだ.次にINCOMPLETEのカテゴリのオプションをすべて止めて動作を見ることにしよう.いや,この辺りで一度バックアップを取っておいた方がよいかもしれない…次はINCOMPLETEオプションをすべて止めて動作を見ることにする.

TRIBEBOX::GetAlternativePrimeNodeで停止した.サンプルは渋沢一族8.ZEL.基準ノードは#211 大川平兵衛の息子.このエラーはMakeUpTree→EstablishMajorTribeChainの中で起きているものだ.forループの中で (T2 == this) のとき,(oyalink)で停止する.これにおそらく一番近いのはINCOMPLETE@20190202なのでこのオプションを復活させて走らせてみよう.⇒+INCOMPLETE@20190131で動作

TRIBEBOX::GetRealnodeにかなりひどいバグがあった.これはオプションとはまったく無関係の独立のバグだ.realodeが空のときにrealnode->getrelationを呼び出そうとしている.考えられないミスだ.ここには「実ノードが単親婚保持ノードの配偶者の場合:配偶者の配偶者を実ノードとする」という説明が付いている.こんな初歩的なバグが残っているとは…⇒このバグが発現しなかったということはこのフローを踏まなかったということなので廃棄しておこう.⇒解決

INCOMPLETE@20190131を追加してこの図面は出力できた.渋沢の全体図テストをやってみよう.いや,それどころではない.下のパネルを出した状態でハングしてしまった.

image

再現しなくなった…⇒表示が出るまでに時間が掛かっているのではないか?通常はOKボタンで直ちにパネルを閉じるが,何かのタイミングで閉じるのが遅くなったための現象ではないか?渋沢で描画までに8.5秒掛かっている.PAIRLISTのダンプを出しているためかもしれない…

INCOMPLETE@20190131単独では同じエラーが発現する.INCOMPLETE@20190131と@20190202がセットとなる必要があるので,この2つは確定ということにしておこう.@20190131の修正は例の隠蔽ノード可視に関わるものなので,この論理が動作に不可欠であることが分かる.@20190202は未決が7個もあるので一発で確定でよいのかどうか不安もあるが,一連の修正として一括確定とする.

INCOMPLETE@20190129の修正は2箇所で,一つはASSERT_NEVER(primarynode->getmarglink() != getOyalink())の停止,もう一つは「優先仮ノードが変化しない場合,系列に多重カードが存在する場合は切り替えを許可する」という緩和策を廃止して一律に基準ノード参照系列をリセットするものだが,いずれもオリジナルを復活させておくことにする.(オリジナルの場合,条件が成立するときは停止するようになっている)

INCOMPLETE@20190130は確定でよいと思われる.また,この確定により,IsVisibleCardは使用されないことが確定する.INCOMPLETE@20190204の修正の主眼はEstablishMajorTribeChainの廃止ではないかと思う.その代わりに導入されたのが,SetupPrimaryNodeだろう.EstablishMajorTribeChainの呼び出しを行わないというのはすでに@20190202で導入されていてそれを徹底したのがINCOMPLETE@20190204ということになるのではないか?どの道,その方向に進んでいるのだから徹底した方がよい.

これでINCOMPLETEはすべてフィックスされた.これらのオプションは2019年1月28日から2月4日までの比較的短い期間に集中している.つまり,一貫した一つのテーマの下に進められたものと思われる.

作業開始時点で893個あった#ifdefは873個になった.わずか20個しか減っていないが,後の残りは原則すべて現状でFIXでよいのではないかと思う.日本語で定義されている分だけでも確定してしまいたいのだが,どのくらい時間が掛かるだろうか?⇒意外にそれほどの時間は掛からないものかもしれないが…プラグマが一つのファイルに限定される場合にはヘッダファイルを使わずにCPPだけで始末ができるだろう.そのやり方ならあまり時間は掛からないと思う.

すべての修正をフィックスし「再開発スタート版」のインタクトな開始点を確保する

サブマシンにログインしようとしたらパスワードが間違っています,になった.何度打ち直しても通らない.電源を落として再起動したが,変わらない.仕方がないのでキーボードを外してタブレットでスクリーンキーボードを使って入力したら通った.キーボードをつなぎエディタを起動してテキストを入力してみると,”ai”と入力したつもりが,”a5”となっている.NUMキーがオンになっていた.いつもと違ってキーボードの右上にあるインジケータ3つのうち2つが点灯していることには気付いていたが,文字というより刻印のアイコンが小さくて読めない.ともかくログインできたらネットでマニュアルを探して調べてみようと思っていたが,ログインできないのだからどうすることもできなかった.

NUMキーをオンにするためにはFnキーを押しながらInsキーを押さなくてはならない.そんな込み入ったことを間違ってもやるはずはないと思うのだが,そうなっていたのだからいつの間にかそんなことをしていたのだろう.昨夜は意識はかなりはっきりしていたのでまだまだ作業できそうな気分だったが,思っているよりずっと意識が希薄になっていたのかもしれない.昨日は仕掛りの修正をいくつかFIXしておこうと思ったのだが,何度も惨めな失敗を繰り返している.

クリティカルな修正を行う場合には,まず共通ヘッダファイルで「SIMPLEEDGE_generationを廃止@20201020」のようなリテラルを定義し,それから#ifdef #else #endifで修正前のコードを保存したまま修正する.Visual Studioは偽になっているプラグマのブロックをグレー表示してくれるので,FIXするときにはグレーのブロックを削除すればよいというだけの話だが,#if #endifがネストしているような場合もあり,そういうときにはすべてグレーで塗りつぶされてしまうので見間違えるリスクはかなり高くなる.実際問題として100箇所修正を入れるより,修正点が100箇所ある修正Aをフィックスする方が難しい.

今日はもちろん全天快晴でクリアできるとは思うが,やってみなければ分からない.対策としては失敗に備えて細かくバックアップを取るしかないが,パッケージ全体をバックアップするのではなくDLLのソースとヘッダに限定すればかなり簡単になるだろう.

昨日の目標は「いくつかの修正をFIXする」ことだったが,今日は「すべての修正をFIXする」ところまで拡張しようと思う.このセッションを「再開発スタート版」というテーマで開始した以上,まず,それに相応しい開始ポジションを確保しなくてはならない.#defineで定義された条件コンパイルによる修正の他に,「仮修正」ないし「暫定修正」のコメント付き修正が無数にある.これらを一掃して完全にクリアな状態までソースコードを戻すというのが今日の目標だ.作業に掛かる前の版を別に保存しておくことにしよう.

とは言え,STOP文の実行を一時的に抑制するために入れた暫定修正などもある.⇒これらは一度通常の状態に戻してから改めて逐次止めるなどの措置を取り直すことにする.このような暫定修正が2本ある.暫定修正@20201026と暫定修正@20201028だ.⇒一応現行のサンプルではどこにも停止しない.あとはグレー表示のブロックを一掃するだけだ.ただし,日付だけはコメントに残して置いた方がよい.日付があれば後日ログを逆引きすることも可能だ.いや,これでも結構失敗する.大きいブロックでは修正するたびにコンパイルして失敗したらUNDOで戻るというのしかない.VSはプラグマブロックをーで閉じると「アクティブな/でないプリプロセッサブロック」を表示してくれる.これに従うのが一番確かだ.小さい+ーのチェックボックスを使うので,マウスが滑らかに動かないと作業にならない.⇒大体要領がつかめた.

ようやく調子に乗ってきたと思ったら,

fatal error C1004: 予期せぬ EOF が検出されました。

が出てしまった.UNDOでどこまで戻ればよいか?⇒一つ戻っただけで収まった!つねにプラグマ(ディレクティブ)を3つ削除しなくてはならないのだが,最後に除去する#elseを取り忘れていたのだろう?これでソースファイル1本が終わった.SimpleGraph.cppには元のコードが見えなくなるくらい修正が入っていた.ビルドしてみよう.ビルドも通る.実行も可能だ.⇒if (0)などのコードがある.暫定修正で処分が決まらない場合は,「保留」のリテラルを使ってコードを残しておく.「NOTHING」は無条件削除.Jikusenzu.cppが終わった.いや,終わっていない.Bobject.hにもかなりのコンパイルオプションがある.これらも一旦すべてFIXしてしまうしかないだろう.もし,後日問題が発生したら,「ZELKOVA 2020-11-06 最終版」まで戻って調べ直すしかない.⇒ともかく先にnodule.hのオプションに集中しよう.

ここでバックアップを取っておこう.ZelkovaDLLフォルダの中にはINCLUDEとSRCがあるが,フォルダごとバックアップすることにする.370MB,コピーは数秒で終わる.⇒どうも大失敗をやらかしたような気がする.「SIMPLEGRAPHのattributeを廃止@20201105」を止めたままで作業を開始してしまった.全部一からやり直さなくてはならない.⇒この修正は一旦放棄することにしよう.DrawingObjectを実装するときにもう一度見直せばよい.それほど大きな修正ではない.残っている部分は「保留」にしておけばよい.

「有効結婚リンク判定の重複処理削減@20201031」をFIXして実行時エラーが出てしまった.修正に失敗している可能性が高いが…UNDOでそこまで戻せるかどうか?やってみよう.関係するファイルは,KinshipDegree.cpp,PartialMap.cpp,TableSort.cppの3本だけだ.ファイルを比較してしまった方が早いだろう.⇒余分なことをしていた.FilteringKinshipを実行後でなければ意味がない検査を前方に移動していた.その場の思い付きが命取り…なんでそんなことをやったかって?見た目にその方がかっこいいと思ったから,そばにあった大文字のASSERT_NEVER3つを寄せて並べてみただけ…

「SIMPLEGRAPHのattributeを廃止@20201105」はやっぱり復活させておかないと不都合だ.すでにこの修正を当てにしているところがある…⇒いや,それも少しおかしいのでは…止めた状態でこれまでビルドできていたのだから,どこかが変わってしまったと見るしかない.未定義エラーが出ているところで修正を誤ったのではないか?⇒確かにそのようだ.バックアップから復元した.⇒フルバックアップしておこう.⇒また修正を誤っているようだ.未定義が出てきた.DataList.cpp.DATALIST::cancelでBottomlistを呼び出している.⇒修正した.

nodule.hの分が完了した.17本の条件コンパイル付き修正をFIXした.他にテスト用暫定修正が3件.次はBobject.hで定義されているものを見てみよう.ここではまとめてバックアップしておこう.⇒Bobjectの分が完了した.14件をFIXした.うち4件は廃止.その他に保留としたものが2件.「ファイルへ出力をサポートしない@20180314」とCOLLAPTIBLEMARRIAGE(結婚枠を折り畳む機能をサポートする)だが,よくわからないので保留としておいた.DLLだけバックアップ.

仮修正が41件,暫定修正が35件,そのほかに暫定を含む文字列が26件ある.暫定と仮修正では仮修正の方が一時的なものという度合いが強いので,まずこれからチェックしてみよう.いや,こちらは後回しにした方がよい.仮修正の中にはテスト用に設置してそのまま放置というのもあり得る.暫定修正は一応論理的には意味のある修正と考えられるから,それが安定動作しているという意味で外せるかもしれない.

暫定修正の場合は「暫定」を取って「修正」とし,日付だけ残すことにしよう.削除されている場合は「x行削除」とする.暫定修正で一時的に設置しているものはTEMPORARY@yyyymmdd のように表記することにしよう.⇒暫定の文字列はソリューション全体から一掃された.一時的な動作に関してはすべてTEMPORARYの語を含むようにした.暫定公開,暫定リリースなどは非公式版公開,非公式リリース,応急対処版などとした.現在「TEMPORARY」の出現は28箇所ある.

▲LoadWndPostの修正にあいまいな点があるので確認を要する.

仮修正@20190202が8箇所もある.これは調べる必要がある.修正が閉じていない可能性があるものはINCOMPLETEを付することにする.失敗した.手順を誤って仮修正@20190202をすべて仮修正に変えてしまった.⇒バックアップから復元した.仮修正@20190131も12箇所ある.これもINCOMPLETEに付け替えておく.⇒仮修正はほとんど同じ日付で複数存在する.@20190204が4個,@20190128が3個,@20190130が3個,@20190129が2個.あとはFIXできそうだ.INCOMPLETEは32箇所になった.

  • TEMPORARY 28
  • INCOMPLETE 32

TEMPORARYはあくまで一時的なものなのでそれほど問題性はないが,INCOMPLETE はFIXする必要がある.

「完全被参照リスト管理」の実装を完了した

「完全被参照リスト管理」という仕組みを実装しているところだ.これが完成すれば,込み入った参照関係の網目が完全にアンダーコントロールに入ったことになる.この処理の追加コストが時間的・空間的にどのくらいのものになるのかが興味あるところだが,まだバグが取り切れていないのでもうしばらくかかりそうだ.ファイルを開くまでは順調に動作しているが,系統並び替えを実行するとPAIRLIST::cancelで被参照カウントの残留が発生する.何が残っているかは新設した完全参照リストをダンプするだけで確定できた.DATALISTクラスで参照を発生させないための改修を行っているのでその修正がまだ収束していない.

再現手順は構成図 TEST.zelをUNDOCHAINで開いてgraph3でソートだけだが,#43490 PAIRBOXで被参照カウントの残留が起きる.暫定的にBottomlist(NULL)の行をリスト要素の削除の前に実行するようにして解消した.この修正が正当なものであることはあとから見ることにしよう.引き続き同種のエラーが今度はTRIBELISTのcancelで発生する.ただし,このエラーの原因は明瞭だ.TRIBELIST::cancelの中からDATALIST::cancelを呼び出しているためだ.これをNLIST::cancelないしLIST::cancelとすれば問題は解決する.⇒解消した.PAIRLIST:cancelのループでリスト要素を削除する前にBottomlist(NULL)で要素への参照を解除することは妥当でありかつ必要である.

このサンプルでざっと包括テストを通してみよう.

上記サンプルを#29 PDBでソートして「逆転循環サイクルの枝を破棄」が発生し,SIMPLEEDGE::dumpでlinkにPAIRBOXが入っているために停止した.GENEBOX::CheckInverseCycleでは検査に枝グラフgraph1を使っている.このグラフの節点はPAIRBOXだ.グラフを作成するときにそのグラフのノードの型を設定できるようにしておくとよいのではないか?graph1は逆転循環検査にしか使っていないので名前も明示的なものにした方がよい.⇒InvertCycleGraphと呼ぶことにした.

SIMPLEGRAPHにはattributeという変数を持っているが,モノ/ヒモノの判別以外の用途には使われていないので,この領域に属性ではなくノードクラスのCIDを格納するとよいのではないか?そのグラフが何を扱っているかを特定することは有用だし,CIDが分かればモノ/ヒモノを判別することもできる.モノはCIDを持っているが,ヒモノの場合はたとえば-1などの値を入れればよい.もし,ヒモノもデータタイプごとに識別したいのであれば,負値の定数を与えればよい.⇒改造してみよう.デフォルトはnoduleタイプとし,原則つねにグラフのnew関数ではコンストラクタに引数を与えて呼び出すことにする.⇒この修正は一段落してから実施することにして,ここでは停止しないようにしておこう.

完全木テストを実施してみる.全体図テストと親族図テストを統合したものだ.⇒どうもさすがに描画が遅くなっているような気がする.これではリリース版には採用できないかもしれない.つまり,デバッグ用途にしか使えないということになる.まぁ,それはそれでそれなりの有用性はあると思われるが…現在のサンプルで計測したパフォーマンスは

TIME=0.193 validcardnum=66 validmargnum=24 nodecount=2967 lastsnum=64006  INTRASH=204 REUSE=60835 WASTE=61039 NEWOBJ=64006

のような数値だ.カード66,結婚24で系統並び替えに要する時間は0.2秒,アクティl部なオブジェクトは2967個,ゴミ箱に204個あり,生成されたオブジェクト総数(再利用を含む)は64006だ.これを完全参照リスト管理導入前と比較してみよう.一旦ここでバックアップを取っておく.⇒旧版に戻して実行してみた.

TIME=0.084 validcardnum=66 validmargnum=24 nodecount=580 lastsnum=805  INTRASH=72 REUSE=153 WASTE=225 NEWOBJ=805

この差は隠しようがない.時間効率で2.3倍,空間効率で5倍,ステップ数で80倍の差がある.これだけ大きな差があるとさすがに公開版に適用するのは無理があるのではないだろうか?完全参照リスト管理の有用性は後処理にあるが,最大の後処理はアプリ終了時の一回しか実施されないし,そのときにはユーザはそこでどれほど時間が掛かってもほとんど気にしないだろう.(この後処理は完全参照リストを使ってもっと効率化することはできるが,実効的にはほとんど有意性を認められない)

ロジックが分岐するのはメンテナンス上好ましくない.ロジック的には前者の方が整理されているし,後者は前者の特定ケースとして処理可能なので,前者を採用することでフィックスし,ReferenceControlだけをNOPにすることで対応するということにしよう.残念だが,仕方ない.ReferenceControlを止めると数字は以下のように変わる.

TIME=0.084 validcardnum=66 validmargnum=24 nodecount=580 lastsnum=805  INTRASH=72 REUSE=153 WASTE=225 NEWOBJ=805

導入前と完全に一致している.まぁ,これでよいのではないだろうか?これを現実として受け入れるしかないだろう.ところで,この改修(完全被参照リスト管理の導入)に取り組むことになったきっかけは何だったのだろう?きっかけは「抽象グラフ検証系の複線化」という修正の中で非参照カウントの残留が発生したという辺りのようだ.シューティングのために従来方式の参照リストを使おうとしたら不具合が起きたりしたためだろう.ことのついでに「NODULE,Bobject,DATALISTなどを「抽象クラス」として再定義する」というのをやってみよう.

クラスを抽象クラス化するには仮想関数の少なくとも1個を純仮想関数にする必要がある.まず,DATALISTクラスを見てみよう.ここには仮想関数は一つしかない.この関数は実装されているが,実際には何もしないでゼロを返しているだけなのでこれを純粋仮想関数とするのは容易だ.「DATALISTクラスの抽象クラス化@20201105」で修正してみよう.⇒実装したが,DATALISTのインスタンスを生成しているところがある.さっきまで掛かっていたReferenceControlだ.DATALISTの抽象化を止めるか,新たに派生クラスを作るしかない.

この参照リストを作るためにNODEREFという構造体を新設しているが,その代わりに参照リストクラスというクラスを新設するというのがもっとも正しい.REFLISTというリテラルはすでに使われているので,NODEREFLISTとしてみよう.クラスを追加登録するためには,まず,nodule::getClassNameにCIDとクラス名文字列を登録しなくてはならない.以下のように登録した.

case ‘x’: s = “NODEREFLIST”; break;    // < ‘h’ LIST

同様の内容をnodule.hのコメント欄にも記録する.あとはCOMMONHEADERPARTマクロがすべてやってくれる.LISTにも仮想関数の実装が必要だ.これで使えるようになった.いや,まだある.CleanSansyo,Dispose,dumpの3つの関数だけは実装しなくてはならない.これらはCOMMONHEADERPARTの中で定義されている.NODULEファミリィの共通ヘッダ定義にはCOMMONHEADERSHORTとCOMMONHEADERPARTがあるが,どう違うのだろう?

COMMONHEADERPARTはCOMMONHEADERSHORTに上記3つの関数を追加したものだ.これらを必要としないクラスはCOMMONHEADERSHORTを選択することができる.COMMONHEADERSHORTを使っているクラスには,BASETABLE,NLIST,nlist,ARRAY,PSCLASSがある.クラス定義内で実装する必要がある場合にそうしているのではないか?テンプレートクラスの場合はその方が対処し易い.NODEREFLISTも手抜きしてCOMMONHEADERSHORTを使うことにした.

実行できるようになった.修正を戻した版も実行可能であることを確認しておこう.⇒問題なさそうだ.こんどはBobjectを仮想化してみよう.Bobjectには仮想関数はたくさんあるが,実装を持たない派生クラスも結構ありそうな気がする.Bobject::Drawという関数はどうか?おそらくこの関数はほとんどすべての派生クラスで実装しているはずだ.⇒中には持っていないものもある.たとえばTREEVIEW,というかこれだけだ.逆にRefreshという関数を持っているのはTREEVIEWだけだ.Refreshを廃止してDrawに統一すればよいのではないか?

Refreshを廃止してもコンパイルエラーは出ない.おそらくRefreshを使っているのはTREEVIEWだけだが,TREEVIEW::Refreshはシグネーチャが違うのでエラーにならない.この関数は引数を3つ持っているが,最後のmapmodeをMAP_DISPLAYで定数化したバージョンをTREEVIEW::Drawとしておこう.⇒これでビルドできた.

しかし,動作上の問題が出てきた.TREEVIEW::Refreshはデバイスコンテキストが空で呼び出されることを認めていないが,DrawがRedrawからcdc空で呼び出される場合がある.TREEVIEW::Refreshはあくまで実描画を目的としているので,TREEVIEW::DrawにはBobject::Drawの内容を移しておくことにする.⇒動作するようになった.少なくとも現行ではTREEVIEW::Drawは実描画には使われていない.修正を戻して動作確認する.⇒OKだ.

noduleは実装クラスだが,NODULEは仮想化できるはずだ.適当な仮想関数があるだろうか?仮想関数は山のように持っているのだが…void setpid(void)はどうか?この関数はCOMMONHEADERSHORTで定義されているのでほとんどすべてのクラスで実装されている.NODULEもこの関数の実装を持っているが,これはコンストラクタの中で一度実行されるだけなのでコンストラクタに内容を転記しておけばよいはずだ.

実装した.動作している.setpidは共通ヘッダ部定義で定義されている他はnoduleにあるだけだ.つまり,共通ヘッダ定義を持たないクラスはNODULEとnoduleだけと考えられる.NODULEは別としてnoduleが共通ヘッダ定義を使わないというのはなぜだろう?noduleは共通ヘッダ定義の関数をほとんど自前で持っているように見えるので,なぜ共通ヘッダ部定義を使わないのか理由が分からない.noduleクラスにヘッダ定義を持ち込むとどういうことになるのか調べてみよう.

共通ヘッダ定義と重複する関数を除くと,未定義変数が3つ現れる.pclassid,vsfunctable,VSLOTFUNCだ.また,nodule::_SLOTが非標準の構文とされる.⇒これらの定義がnoduleのクラス定義より後方に置かれていたためだ.DrawTreeView.cppまでコンパイルするとまたnodule.hでエラーが出始めた.

nodule::operator new’: すべてのコントロールのパス、関数を回帰するとランタイム スタック オーバーフローが発生します。

このエラーはDrawTreeView.cppで始めて発生し,それ以外のファイルでは何も起きない.MakeTribeBox.cppでも出た.RestoreTable.cpp,nodule.cppでも出る.⇒最終的にエラーが出るのはDrawTree.cpp,MakeTribeBox.cpp,RecycleSystem.cpp,ZelkovaDLL.cppだけになった.ただし,これらのファイル名がプリントされているということはコンパイル完了とみなされるので,おそらくこれらのファイルのあとにコンパイルしたファイルでエラーが出ているものと思われる.

このエラーの原因は明瞭だが,なぜこのエラーが出るファイルと出ないファイルに分かれるのかは調べておいた方がよい.C++ファイル80本のうちオブジェクトファイルができていないもの26個あった.共通点はよく分からないがBobject.cppを含め,Bobjectクラスのファイルが多いような気がする.⇒※動作から推定すると,*.hの検査と*.cppのコンパイルは別スレッドで同時進行しているように思われる.

void *nodule::operator newとnodule::operator deleteを外した共通ヘッダ部を作ってCOMMONHEADERBASICとし,COMMONHEADERSHORTはこれにこれらの関数を追加したものという構成に変えて,noduleクラスではCOMMONHEADERBASICを展開するようにした.nodulleのoperator newとdeleteをNODULEに移管できない主な理由は,オブジェクトのリサイクルをnoduleが担当しているためだ.リサイクル操作はnewとdeleteの中で実行されるので,このような仕掛けが必要となっている.

これまではnoduleは他のNODULEクラスとはまったく異なる扱いになっていたが,この整理によってoperator newとdeleteを除けば,他のクラスと全く同じ同じ構成を持つNODULEの標準的クラスとして位置付けられるようになった.修正を戻しても動作することを確認した.

SIMPLEGRAPH::attributeを廃止し,グラフのノードから参照するオブジェクトのクラスのCIDを格納するように仕様変更する.CIDが正のときは通常のnoduleクラス,負値の場合は-CIDサイズのデータが格納されるものとする.⇒実装した.

本日4回目のバックアップを取っておこう.これまでの修正をいくつかフィックスしておくことにする.まず,「SIMPLEEDGE_generationを廃止@20201020」と「NODULE:nodegeneを廃止する@20201021」は確定でよいだろう.⇒ダメだ.修正に失敗してしまった.もう一度やり直そう.大きいブロックを消すときは一旦隠蔽してから削除した方が安全だ.前者が18ヶ所,後者が47ヶ所ある.失敗すると痛いのでまた取っておこう.⇒また失敗だ.未解決の外部参照が2つも出てしまった.CARDLINK::getnodegene(void)とCARDLINK:setnodegene(int)だ.今日はもう寝たほうがいいのかもしれない…

「完全な被参照リスト」を整備する

「完全な被参照リスト」を整備するというのは積年の懸案だった.実装をためらっていたのは主に(時間的・空間的)効率上の理由からだが,CPUがここまで速くなりRAMの実装も4~8GBといういまの時代にあってはためらうべき何の理由もない.完全参照リストが完備するとこれまでのシステムメンテナンス上の苦労の過半が解消することになり,システムは思い切りシンプルなものになることだろう.とは言え,これによって参照管理が完全に自動化されるというものでもない.オブジェクトがランタイムで削除されるときシステムの恒常性を維持するためにはそれなりのことは(いままで通り)実行されなくてはならない.そこまで自動化できればプログラミングの苦労も相当軽減されて,本質的な論理構築に専念できるようになるのだが…

それでも完全な参照リストがあれば,そのオブジェクトを参照しているノードを完全に把握できるので,そのノードのクラスと参照の置かれたスロット番号が分かれば状況はかなり改善できるはずだ.この意味では完全参照リストに記載する情報にスロット番号を追記しておくことは意味があるかもしれない.そうすれば,完全参照リストは真の意味で完全なものになるとも言える.これに要するコストはそれほど大きくはないので実装すべきだろう.すでにnoduleクラスにはReferenceListという単体オブジェクトを対象とする参照リスト管理が備わっている.これはデバッグ支援を目的としているが,対象オブジェクトを全ノードに拡大すればそのまま完全参照リストシステムになり得るものだ.

すでに基盤作りは完了しているのであとはReferenceListを使う論理の拡張を行うだけになっている.修正は「完全被参照リスト管理の整備@20201104」とする.⇒とりあえず,ReferenceListをそのまま使って対象を全ノードに拡張する修正を試してみる.⇒この関数はNODULE::xpandを使うように特化されているので,別建てにした方がよい.⇒nodule::ReferenceControlとした.⇒実装した.

一発でスタックオーバーフローしてしまった.原理的に無理なことをやっているのか?単純なミスなのか?それが問題だ.LISTノードを再帰的に生成しようとしている.参照リストはNODULEのreferistスロットに接続されるだけなので参照が発生する可能性はないと思っていたのだが,LISTは参照末尾を押さえるためにbottomという参照リンクを使っている…LISTはDATALISTの派生クラスだが,bottomはDATALISTですでに定義されている.bottomを持たなくてもリスト管理できない訳ではないが,末尾を押さえていれば要素を追加するときの便宜がある.

DATALISTは抽象クラスとみなされるので,bottomをLISTで管理するようにして,参照リストはDATALISTから直接派生するようにしたらよいのではないか?⇒それしかないような気がする.どうも結構大きな修正になってきた.DATALISTから直接派生しているのはLISTしかなかったと思うが,システム図で確認しておこう.⇒確かにそのようだ.

DATALISTからbottomを除去するのは簡単にできた.あとは,DATALISTで除去した分をLISTに移すだけだ.⇒完了した.スタックオーバーフローは発生しなくなったが,参照カウントの不一致が発生している.これを見る前に参照リストをDATALISTから直接派生しなくてはならない.⇒これはnewステートメントを書き換えるだけで済むが,LISTの関数を使っているので,それを整備しなくてはならない.findlinkとremoveだ.この2つは逆にDATALISTに移入してやることにしよう.この2つの関数はLISTにも残しておく必要がある.findlist,precedingも必要.⇒LISTに残しておくべきいくつかの関数をDATALISTで直接書き換えてしまった.⇒バックアップから復元した.

ようやく動き始めたが,どこか壊してしまったようだ.TOPOLOGY:FindJikusenAncestorで枝グラフの操作中にエラーが出ている.グラフの枝リストはLISTの派生クラスであるNLISTだが,toplistが存在しているのにbottomlistが空になっている.いや,リスト自体は健全だ.datacount=0でtoplist,bottomlistともに空になっている.エラーが発生しているのはputを連結する位置のノードをthisで初期化しているためだ.⇒違うところを見ていた.テンプレートクラスの関数はクラス内で定義されていてもシグネーチャごとに実装されているので,動いているのは別ものだ.確かにtoplistが非空でbottomが空という状態になっている.datacountはゼロなのにtoplistが空になっていない.

toplistはDATALISTが設定しているので,これはDATALISTの論理の誤りと思われる.おかしい.LIST::_removeでもtoplistを更新している場所が見えない.リスト要素はtoplistに接続しているので明示的にリンクしなくてもdeleteなどのアクションで自動的に更新されるようになっている.それをやっているのはオブジェクトのデストラクタではないかと思うのだが…この枝リストが空になるタイミングを掴まえるしかないのではないか?⇒こういうときは迷わずSWOを使うしかない.

不良は#1450のEDGELILSTで起きている.⇒カーソルが動かなくなってしまった.開発機だけでなくサブマシンもハング状態になっている.サブマシンを再起動して復旧した.SWOの設定を誤っていたのが原因と思われる.SWOはCPUパワーをいきなり食うのでヒットしないで爆走するようになるとすべてが止まってしまう可能性がある.

SIMPLEGRAPH::add→NLIST::putでnew SIMPLEEDGEを実行→SIMPLEEDGEのコンストラクタで停止した.ここでtriggerを掛けてもう一度走らせてみよう.LIST::_removeを実行しているところだ.

▲NODULE::_SLOTの論理はかなりおかしい.これではまったく動作しないように思われる.(edan >= 0)のとき,

if (edan <= SLOTsNODULE) return (nodule*&)bnod[edan];
else throw ERR_WRONGSLOTINDEX;

となっているが,SLOTsNODULEは定数で値はゼロだ.この関数はNODULEのメンバーで仮想関数ではないから,決めつけの動作になっている.これを呼び出しているのはNODULE::checkpnodeという検査関数でデバッグ時しか実行されていないが…対象ノードはnodl_float中だが,すべてのスロットにアクセスできなければおかしい.checkpnodeの参照は14箇所.いや,_SLOTという関数はCOMMONHEADERSHORTで定義されているので,すべてのクラスが固有関数を持っている.従って,NODULE::_SLOTがこのような動作になっているというのは必ずしも誤りとは言えない.しかし,明らかにcheckpnodeが意図している動作にはなっていない._DEBUG_で止めてある関数いは少し仕様が古いものがあるのではないだろうか?デバッグルーチンのデバッグになってしまうので,あとで見ることにしよう.

対象となっているリスト要素のSIMPLEEDGEがnodl_floatに入ったときには,すでにpnodは空になっている.pnumもゼロだ.nodule:operator deleteではオブジェクトはすべてゴミ箱に一旦廃棄される.これを実行しているのはTRASHCAN::DumpWasteだ.ここに入った時点ですでにpnodは空だ.nodule::Disposeでnodl_floatを実行している.ここではtoplistは一旦空になっている.明らかにここで誤っている.増設スロットEXTRA_ANCHORの動作をコピーしてすっかり同じことをやっている.これは完全な誤りだ.

nodl_floatは接続を切断するための操作であり,参照操作を行うことは予定されていない.ノードを削除する目的のときは事前に参照はすべて削除されているはずだが,そうでない場合もある.たとえば活きたまま別の位置に繋ぎ替えるときなどにもnodl_floatが実施される.従って,ここでは参照リストに関しては何もしないというのが正しい.⇒動くようになったが,DATALIST::cancelでデータカウント残が出ている.

障害が起きているのは要素を23個持ったCOMPLISTで先頭ノードを1個削除しただけでtoplistが空になっている.cancelはもともとDATALISTの関数だが,DATALISTはbottomlistを持っていないのでLISTでcancelしなくてはならない.ただし,これはいまの障害の動作とは関係ない.ゼロスロットの繋ぎ変えはelseケースで実行していたが,REFERISTのコードが残っていたため実行されなかった.

かなり難しい問題が出てきた.フロート状態のオブジェクトの参照管理は不可能かもしれない.ともかく一度バックアップを取っておこう.

◎検索ボックスをタイトルバーに出す.もし,それができればつねに検索ボックスが見えるようにすることも可能になるのだが…

nodule::ReferenceControlで(!referist || !*referist)により停止する.*referistが空になっている.これはこのノードがフロート状態になっていることを意味する.referistは参照元ノードのアドレスが格納されているアドレスだが,参照リストに保存するのは単なるアドレスで問題ないのではないか?一度生成されたオブジェクトは生成された場所から移動することはあり得ないと考えられる.ただし,この場合に問題になるのは無断で削除された場合にどうなるか?という点だ.これについては少し吟味しなくてはならない.

このシステムは自前でリサイクルシステムを持っていてdeleteでパージされたオブジェクトも実体的には(ゴミ箱の中で)存続しているので,GP例外のような致命的なエラーは発生しないと考えられる.どこにも接続していないフロート状態というのはかなり不安定な状態ではあるが,完全参照リストシステム自体は接続とは無関係に,つまりフロート状態のノードを含めて維持できるはずであるし,そうしなくてはならないと思う.⇒修正を入れてみよう.

DATALIST::findlinkという関数がダブルポインタを使っている.ダブルポインタはスロットに格納されていないと取得できないが,代用として&を付けるという便法がある.これで通るはずだ.⇒#8のTITLEBOX→#6のTREEVIEWという参照を解除しようとして,TREEVIEWの参照リストに記載がないというエラーになった.おかしい.目の錯覚だろうか?.⇒TITLEBOX→TREEVIEWではなく,TREEVIEW→TITLEBOXになっている.#8 TITLEBOX は一度も参照設定していないのに,参照リンクに値が入っている.これはかなりおかしい.BOBsYUPPER(3)という枝だ.⇒いや,やっている.InitTreeView→initializeでSetBranchからSansyoが実行されている.

これは誤動作ではないかと思う.ノードAからノードBへの参照が多重に掛かる場合があり得る.それに対処していないのでは?参照削除されたときは単純にリストから削除しているが,これは明らかに誤りだ.A(i)→BとA(j)→Bは異なるものとして扱うか,ないし参照カウントを管理しなくてはならない.もっとも確実なのは(参照元,枝番号)の対で1エントリとする方式ではないだろうか?このためには(参照元,枝番号)というタイプのクラスないし,構造体の定義が必要だ.クラスにするのも大げさなので構造体を作ってみよう.構造体などしばらく使っていないので構文を忘れてしまった.こんな↓感じでいいかな?

typedef struct _NODEREF {
     nodule *node;
     int edan;
} NODEREF;

しかし,DATALISTと言いながら,データの一致で検索する関数を持っていないとは…nodule *DATALIST::finddata(void *dp)という関数を作った.⇒これで問題は解決したように思われが,またtoplistがあってbottomlistがないという問題が再燃してしまった.さっきは,修正コードに手抜きがあったためだが,こんどは何だろう?障害はTRIBEBOX::CheckGeneSplitで起きている.TRIBEBOX::SpllitListというLISTのインスタンスだ.

見落としていたが,ARRAYクラスの派生クラスとしてREFLINKというクラスがある.これはスロットを1個だけ持つnoduleだ.リスト要素として用いることが想定されている.DATALISTではデータサイズを指定しないときはデフォルトでこのタイプの要素を持つリストを生成する.

障害はDATALIST::putを実行したところで起きている.putはもともとDATALISTの関数だが,LISTの関数でオーバーライドしないとまずい.実際,DATALISTのputではBottomlistが欠けている.不足しているLISTの関数をすべて補充して動作するようになった.それにしても,A→Bへの参照が多重に掛かるという問題などはプロトタイプのReferenceListのときから存在していたはずだが,一度も露見したことがないというのは怖い話だ.ReferenceListはかなり強力なツールなので結構使っているにも関わらず一度も発現したことはない.A→Bへの参照が複数存在するノードというのはかなり特殊で,たまたまそれらに関係するトラブルが発生しなかったということだが,「動いているから安心」など決して言えないことが分かった.⇒ダメだ!まだ何かある.

▲構成図 TEST.zelをUNDOCHAINで開いて,graph3でソートしただけでエラーになった.被参照カウントの残留だ.障害はnoduleのデストラクタで検出されている.TOPOLOGY::topologicalSortの冒頭でbaselistをキャンセルしたところだ.障害が起きているのは#43490のPAIRBOXだ.このノードの完全参照リストを見ると確かに要素が1個残っている.ダンプしてみよう.

DumpRefList PAIRBOX::Dispose #43490 ▲【p PAIRBOX 43490】
   1 edan=2 node=PAIRLIST:4647 Pindex=0 datacount=0 gene=-1
   2 edan=2 node=#28360 ノード対:MDB(2)→(0) ch=2
   3 edan=7 node=COUPLING(0) Bobject::pid=B cid=n DEL=0 #765 nam0=NAMEBOX nam1=765
   4 edan=18 node=COUPLING(1) Bobject::pid=B cid=n DEL=0 #43373 nam0=NAMEBOX nam1=43373
   5 edan=3 node=PAIRLIST:4647 Pindex=0 datacount=0 gene=-1

枝2と3にPAIRLIST #4647 からの参照がある.残っているのは枝2の方だ.PAIRLISTの枝2はbottom2,枝3はcurnodなのでbottom2が残っているように見える.PAIRLISTは独自のcancel関数を持っている.

モノシステムとヒモノシステム

環境もようやく整って,いよいよ本格的なデバッグの領域に踏み込もうとするところ.ZTシステム構成図は開発支援のためにも十分役に立つということが分かってきたので,遊んでいるVAIO機で開いて常時参照できるようにした.システム構成図は完成にはほど遠いが,「全体図は隠蔽する(見ない)」という方針になったので遠慮なく細部データを書き込んでよい.クラスの(仮想的な)代表オブジェクトの呼び方を決めておこう.一案としてプリミティブという名前を考えてみたが,しっくりこない.オブジェクティブというのはどうだろう?まだましなような気もするが…マスターオブジェクトとか…マスタリィとか?

抽象グラフ検証系では「SIMPLENODEsLINKを廃止@20201019」は一旦放棄してもう一度組み直すしかないと思う.つまり,従来方式を完全復活させそれと並立する形で非オブジェクトも対象とすることができるようなシステムだ.グラフクラスにmonoというフラグを置いて切り替えできるようにする.この値はSIMPLEGRAPHのコンストラクタで設定できるようにすればよい.デフォルトではオンだ.monoがオンとなる対象システムをモノシステム,そうでないものをヒモノシステムと呼ぶ.ヒモノとはオブジェクト(NODULEクラス)ではないようなモノだ.この方式変更に関わるのはほとんどSIMPLENODEに限られているはずだ.今日はまず,ここから入ることにする.

「SIMPLENODEsLINKを廃止@20201019」を撤廃して,「抽象グラフ検証系の複線化@20201103」を導入する.一つ問題がある.ZTシステムではすべてのオブジェクトのリサイクルを実施している.リサイクル時にはコンストラクタが呼び出されるという理解でよいのか?また,仮にそうであったとしても,この場合コンストラクタを引数付きで呼び出すということはできないのではないか?リサイクルシステムの論理は高度に難解なところがあるが,new operatorで生成するようになっているのでコンストラクタの呼び出しは掛かるようになっていると思う.

ただし,CARDLINK, MARGLINK, PARTIALNAMEだけはsetNring でWASHINGというリテラルでnewを実行している.リサイクルは delete されてゴミ箱に入っているオブジェクトだけが再生の対象となるので,システムに常設されているオブジェクトがリサイクルされる可能性はゼロだ.現行論理ではすべてのグラフオブジェクトは一度生成された後は廃棄されることはないので,この意味では安全であるとは言える.いや,仮にリサイクルされたとしてもオブジェクトをnewで生成する位置で引数付きコンストラクタを呼び出しているはずだから,問題ないと思われる.つまり,リサイクルされる場合も新たに生成される場合も完全に同一手順で生成されると考えてよい.ただし,WASHINGという機構についてはあとで検証(して文書化)した方がよい.

SIMPLEGRAPHにはunsigned int atribute グラフ属性というのを持っている.新たにmonoを増設する必要はないのでは?⇒そうすることにしよう.⇒改修は大体終わって動作しているが,「抽象グラフ検証系の複線化@20201103」を止めて元のバージョンが動作することを確認しようとしてMaeHasseDiagram→UpStreamHasseで問題が発生した.ここで使われているグラフのノードは人名カードか連結成分リストと思っていたが,ノードリストが入ってきた.連結リストとノードリストが混在しているというイメージだ.

COMPLISTとNODELISTの違いをシステム図から読み取ろうとしたが,まだ細部が書き込まれていないこともあって比較できない.前者はnlistで後者はNLIST.おそらくnlistは任意のnoduleを要素とし,NLISTは型の決まったノードのリストなのではないかと思う.NLISTにはクラス型を引数として渡しているが,図面からは読み取れない(まだまだ未完成だ).NODELISTはSIMPLENODEをリスト要素としているはずだ.COMPLISTの要素はSIMPLENODEかないしCOMPLISTだったのではないかと思う.どうもどこかで誤動作しているのではないかという気がする.⇒単純ミスだった.

▲UndoChainでソートしてPAIRBOX::repairCommonEndPointで(CheckSamePoint())により停止した.RepairCommonEndPointでも同じ理由で停止する.サンプルはモジュール構成図 TEST.ZEL.

▲ファイル→プロパティ→場所でファイル名が表示されていない.ファイル名にスペースが入っているためだろうか?

▲かなりまずい.非参照カウントの残留が発生している.UNDOCHAINで立ち上げたあと,undochainで起きた.COUPLING:TopologicalSortの入口でResetExperimentを実行して初期化しているところで起きている.まだかなりの不備があるようだ.これは直しておかないと命取りになる.まず,SIMPLENODEのデストラクタを見てみよう.いや,そうではない.参照が残っているのはCOMPLISTだ.構成図 TEST.ZELを開いて閉じるだけで起きる.今度はPAIRBOXに移った.PAIRBOX #653だ.どうもどこかでxpand(NODULEの静的変数)をリセットしているようだ.

new (NULL, XPAND_ANCHOR) LIST(sizeof(long));

を実行してもxpandに値が設定されない.extraを使っているのではないか?いや,extraにも入ってこない.暫定的に強制的にxpandを設定するようにした.これで一応nodule::ReferenceListは動作するようになったが,非参照カウントの残留は残っている.障害ノードはPAIRBOX #653で変わらない.残留が4 もある.すべてSIMPLENODEからの参照と思われる.SIMPLENODE→PAIRBOXの参照はGENEBOX::CheckInverseCycle(危険対枝グラフの循環検定)で実施されている.参照カウントの残留はTOPOLOGYのデストラクタでCleanSlotを実施する中で起きている.

CleanSlotはCOMMONHEADERSHORTマクロで定義され,すべてのクラスに備わった常備関数になっている.CleanSlotは個別スロットごとにclearslotを呼び出してスロットを解体する.この解体処理はスロット並びに従って実施されるので,解体が最下層から逐次実施されることを予定することはできないので,自ノードを参照する可能性があるノードを探索してあらかじめ参照解除を実施する必要がある.つまり,あるノードは自分がどこから参照されているかを知っている必要がある.

CleanSlotはマクロの中で定義されているが,NODULEないしnoduleのクラスメンバとして一つだけ作るということはできないのだろうか?⇒そうするためには仮想化する必要があり,仮想化した場合にはクラスごとに実装が必要になる.この構成は避けられない.Disposeの中でCleanSansyoを実行するというマナーだ.PAIRBOXのDisposeではgraph1に対してrelease(this)を実行している.

解決した.修正に抜けがあった.一つだけ不審な点がある.OUTPUTLOG_SANSYOがオンのときアプリ終了しても画面が残ってしまう.⇒CallSetCouplingPtrで無限ループしている.仮修正で入れた不正規なリンクがそのまま残っているためだ.xpandに関わる仕様を整理する必要がある.xpandは現在NODULEの静的変数としてシステムに1個だけ存在しているが,これを個別にもたせて包括的に参照リストを管理してみたい.この仕掛けは基本的にデバッグ支援のためだが,もし時間効率がそれほど悪くなければリリース版で採用される可能性もある.

いずれにしても,改変はまず現在の仕様できちんと動くようにしてから取り掛かるべきだろう.ここまでの修正もかなり大きいので一度バックアップを取るのが賢明だろう.⇒枝番号にXPAND_ANCHORを指定したとき,無動作で抜けている.nodule::insert_nodeにはそれらしき処理がはいっているが,motoが空の場合がある@20181014でスルーするようになっている.この修正は明らかに誤りと思われる.⇒無条件に実行するように修正して動作するようになった.この版を改めて保存しておこう.直前に保存した版と差し替えておく.

その前にEXTRA_ANCHRという特殊スロットの扱い方を見ておこう.これは普通の枝番号と同様にスロット配列番号として使えるリテラルだが,多分実際にはNODULEのextraスロッを使っているものと思われる.用途は2つあり,PAIRBOXでは「端点共有ノード対接続チェーン」のアンカーとして,MARGBOXでは「SymmetricActionでブロック移動実行時に使われるリストを一時的に接続する」ために使われる.これらはすべて一時的にしか使われないものなのでそれ専用のスロットをクラスに設けることはムダと考えられたためと思われる.

今回予定している「完全参照リスト」は恒常的に使われるものだが,すべてのノードに割り当てられるものなのでNODULEクラスに設置する.アクセスはEXTRA_ANCHORのやり方に準ずるものとなる.このスロット番号には別の名前を割り当てることにしよう.参照リスト専用なのでREFERLISTでよいのではないか?REFERISTとしてみよう.スロット名はreferistとしておく.@20201103@でマーキングしておこう.この仕組みは既存システムをまったく改変しないで上から乗り込むだけとし,従前システムとの切り替えスイッチは設けない.

昨日作った要対処項目リストの整理に失敗

昨日作った要対処項目リストの整理に失敗してしまった.オリジナルでは92件あったのに,82件に減ってしまっている.行の移動で何度もUNDO/REDOを繰り返しているので何か誤操作ないし誤動作があったのだろう.中間で保存している可能性もあるので読み出してみよう.⇒下書きは残っていない.!偉い!カテゴリ分けした時点で公開している.ここからやり直せばよい.HTMLのリスト形式になっているが,むしろプレーンなテキストエディタで編集したあとリストに仕立て直した方が確実なのではないか?⇒テキストエディタから戻してリストに変換しても改行が無視されてしまう.⇒やむを得ない.どっちみち行番号も消さなくてはならないのだから…

行番号が付くのを避ける方法がある.HTMLで一度リストを解除してやればよい.テキストエディタで一改行入れておくと一発でリストに変換できる可能性がある.⇒あっという間に終わった.1項目を分割しているところがあるので,93項目になった.全93項目のうち,バグ37件,不良20件,改善24件,検証5件,宿題1件,参考6件に分類された.バグは障害の発生事例,不良は仕様的な不具合,改善は改良のための提案,検証は確認を要する事項,宿題はやや長期的な課題,参考はプログラムに直接影響しない情報.この手順で一つだけ問題がある.リンクが切れてしまうという点だ.これは適宜復元するしかないだろう.

すでにフィックスしている項目は対処済み項目リストに移動する.ともかく最初の37件のバグから見てゆくことにしよう.バグ項目には「障害を再現させるための手順」が記録されていることが必須だが,ほとんどの項目はその要件を欠いているので,まずそこから始めるしかない.最初に再現可能か否かを確認するというところから始めよう.再現出来ない項目はとりあえず,後回しとするしかないが,再現可能な項目でも緊急性や難易度の観点から判断して後回しとすることもあるだろう.

いや,その前に机上に反例サンプルが10個あるので確認しておこう.BUG20-10-31というフォルダに収容した.⇒一つだけ開けてみたが再現しない.多分これは広域スプリット検定の反例ではないかと思う.「ファイルオープンテスト」を実行してみる.特に問題なく開けた.多分すでに解消しているのではないかと思う.

▲実親を基準ノードでソートしているのに直下に来ないというのはかなりおかしい 

再現できるだろうか?これは軸線図だ.UNDOCHAINでソートしているのだが…この記録は10月23日のものだが,現在のシステム図とは大分違うようだ.ごく初期のモジュール構成図と呼んでいた時期のものと思われる.図柄は多少違うが,確かに出てきた.

image

UNDOCHAINには父が3人いてそのうちUndoChainは実親,他の2人は養親となっている.この2人が養親子関係であるのにブルー表示されていないというのは,これらの父が祖父undosysの実子であるからだ.つまり,この2人の義父は同時に叔父でもあるため,傍系血族のピンク色になっている.いずれにしても血統軸線図では直系血族が最優先されるはずなのでUndoChainが軸線上にないというのは誤りと考えられる.軸線図関係はJikusen.cppというファイルがあるので少し読んでみよう.

「複数出力枝を持つ」というダンプが出ているが,これは軸線図に関係するもののようだ.このダンプはTOPOLOGY::FindJikusenAncestorのステージ【6.1】父系/母系優先を枝グラフに反映するで出ている.

VAIOに最新版をインストールしようとしてエラーになった.

image

このマシンにはゼルコバの木ベータ2はインストールされているが,ZTはまだインストールされていない.Program Files (x86)のbabalaboにはZelkova Beta2フォルダがあるだけだ.つまり,まだバージンと言ってよい.CPUもOSも64ビットだ.考えられるのは.NETのバージョンくらいだが….NETは3.5が入っている.アプリの必須コンポーネントは.NET 3.5 SP1だ.HRESULT=-2147024770=0x8007007EはDLLNotFoundExceptionだ.いや,OCXの登録に失敗しましたとある.⇒インストールに成功した.必要条件はVisual C++ 2017 Redistributable (x86)がインストールされていることだ.VAIOにはVC++ 2010 Redistributableしかインストールされていなかったため,マイクロソフトサポートからダウンロードしてインストールした.

VBの発行→必須コンポーネントにはVisual C++ “14” Runtime Libraries (x86)が必須となっているが,msiインストーラはこの手続きを踏んで生成されている訳ではないので,不足しているコンポーネントを指摘できないのだろう.「発行」を実行するとデスクトップ上にApplication Filesというフォルダが作られる.これは発行フォルダの場所にデスクトップを指定しているためだ.このApplication Filesというのはネット上のサイトに置くべきもので中に入っているものの大部分は*.deployという拡張子が付いている.どういう使い方をするものなのかはよくわからない.この方法でインストールした場合にはバージョンアップしたときの自動更新などもサポートされるのではないかと思う.いずれにしても,少なくとも64bit OSで64bit CPUならZT 2021をインストールできることが証明された.この後の課題としてVisual Studio 2019への移行というのがあるのだが,それはまた後日.

「複数出力枝を持つ」というダンプが出ているので,このサンプルには何か特殊な問題があるらしいということが察せられる.「枝」と言っているのは「グラフ」の「辺」のことで,このロジックでは抽象グラフ検証系のSIMPLEGRAPHを使っている.グラフはすべてTOPOLOGYが管理しているのだが,何を使っているのだろう?graph2だ.graph1, 2, 3があるが用途が分かるようにJikusenGaphとリネームしておこう.おそらくこの枝グラフは基準ノードから先祖ノードまでの直上経路を探すためのものと思われる.仮に下から上に矢線が向いているとすれば,出力枝とは上向きの枝ということになる.

すでにSIMPLELINKのノードを廃止してしまったが,早まったかなという感じもある.ただのベタ参照とnodule*参照では使い勝手がまったく違う.確かに早まっていたと思う.NODULEではスロットに数値を格納することはまったく不可能という訳ではなかったのだ!昔はそういうこともやっていた.ただし,4バイトのスロットのうち2バイトまでしか使えないので,-32768~32767ないし,0~65535までだ.しかし,一般の用途ならこれで十分なのではないだろうか?Windowsの座標値の範囲も概ねこのくらいだったような気がする.⇒いや,ちょっと勘違いしている.SIMPLENODEのlinkに入るのは数値ではなくアドレスだ.これには4バイトが必要だ.

スロットとベタ参照を両方用意して使い分けることも考えられる.そういうことを意識せずにプログラミングできるところがNODULEの最大のメリットだった…つまらない欲をかいてしまったのだろうか?いくらなんでもベタのアドレスとNODULEのポインタをごしゃまぜにはできない…こうなることは覚悟の上だったはずだが…失ったものはあまりに大きい.やはり,この修正「SIMPLENODEsLINKを廃止@20201019」はあまりよくなかったと結論付けるしかない.修正箇所は45箇所ある.

もう少し考えてみよう…これまでの仕組みの利点を失わないためには,装置を複線化するしかない.つまり,たとえばlinkとvalueを使い分けるという方式だ.グラフはlinkベースであるか,valueベースであるかのいずれかしかないと考えられるから,グラフにフラグを持たせてそれをSIMPLENODEが参照して切り替えればよい.これは結局対象がNODULEベースであるか?NON-NODULEベースであるかの違いということになる.このフラグを bool monoとする.グラフはデフォルトではmonoだが,生成時ないし動的に値を設定できるものとする.

▲メニューバーの検索ボックスは最初カーソルがボックス中央に出ているが,文字入力すると左端に移動してしまう.最初から左端に出している方が素直だ

システム構成図でsplitlistという名前は2箇所出てくる.TRIBEBOX::splitlistとTRIBELIST::splitlistだ.前者はすでにSplitListとリネームされているがシステムズには反映されていない.プログラムの書き換えとシステム図のメンテナンスを同時並行されるのは至難の業だ.完全に同期させるためにはプログラムのソースコードから自動生成できなくてはならない.

後者の用途は「系列間シンメトリ婚スプリット検定用リストへの参照」となっている.広域スプリット検定ではリストは使っていないはずなのだが…⇒確かに,このオブジェクトは全く参照されていない.⇒いや,使われている.関数の中でクラスメンバー変数をわざわざローカル変数に置き換えているが,意味がないと思う.相手を呼ぶときは固有名詞で呼ぶべきだ.⇒対処した.

TRIBELIST::CheckSymmetricSplitとTRIBEBOX::CheckTribeSplitはほとんど等価なのではないか?どちらもLISTを使っているが,なぜリストが固有データ部を使っているのかという理由も分かる.動機はいま上で問題になっている非monoデータの参照に関係がある.2つのスプリットリストは連結問題をグラフを使わずに解いているが,確かに実際的にはこちらの方が圧倒的に効率的だ.グラフを使った場合には,最初に枝を生成し(頂点集合がすでに生成済みないし,枝を生成するときに同時に生成),次に連結成分分解を実行しなくてはならないが,スプリットリストの方法では最初に頂点集合を作ったあとは頂点集合自体が集結して最終的には連結成分そのものになるという手順になっている.

このようなことが可能になるのは,頂点と連結成分が同種のものと考えられるためだ.つまり,頂点は区間であり,連結成分も区間になる.しかし,通常は連結成分は頂点の集合であり,頂点そのものではない.言い換えると元と元の集合が同じ形式で表現可能ならスプリットリストのような手法が適用できる.少なくともTRIBELISTのスプリットリストとTRIBEBOXのスプリットリストは共通関数によって生成されるようにすべきだろう.多分それほど難しくはないのではないかと思う.グラフを使わずにグラフ問題が解けるというのはある意味おもしろい.

◎全体図という部分図を既定で生成する.