PARTIALNAME::MakePartialListを廃止

PARTIALNAME::MakePartialListは現在使われていないようなので,廃止しておこう.さて,リンクテーブルとlookupの同期に関してかなり深刻な問題が発生している.部分図リストのレコード数がlookupで数えた場合とPDBで数えた場合で一致しないという現象だ.lookupがゼロ発進に対し,PDBは1発進になっているという違いがある.ただし,lookupのレコード番号と言うときは,1発進の数として扱っているはずだ.確かにどこかで躓きそうな感じはする.

まず,テーブルのサイズを確認しておこう.lookupはlongtableのインスタンスで,MAXPDBというサイズのlong配列を保持している.lookupはゼロ発進だから,これで問題ない.PDBはどうか?CARDTABLEはBASETABLEの派生クラスで,MAXPDBサイズのCARDLINK*配列だ.BASETABLEはARRAYという配列クラスの派生クラスで,ARRAYは指定したサイズのスロットを持つオブジェクトだ.

確かに,これはまずいような気がする.BASETABLEはtablesizeというメンバー変数を持っているが,ここには,オブジェクト生成時の配列サイズ,つまりMAXPDBが入っている.現行ではgetmaxrecnはtablesizeを返すようになっている.⇒まず,BASETABLE::tablesizeをprivateに指定して外部から隠蔽してみよう.その上でアクセス関数gettablesizeを追加して,tablesize-1を返すようにしてみる.念のため,tablesize→ tablesize2にリネームしてクラス内部での参照もチェックしておこう.⇒NODECLASS *newlinkから参照されていた.getmaxrecn,doClean, getnod, emptyrecn からもアクセスされている.

一般には,getmaxrecnまでのレコードは許容されているが,refnum >= gettablesize()でtablesize-1を返すとおかしくなるところも出てくる.たとえば,refnum >= gettablesize()をエラーとみなしているところなど…TableSizeOverなども同様の仕様になっている.その逆に,CARDTABLE::makelinkなどではrecn <= tablesizeならば適法としている.⇒まだ,見直しは完了していないが,ざっくり修正して通るかと思ったが,TOPOLOGY::MakeActiveListで起きているエラーはまだ止まらない.デバッグするしかないのだろうか?

2023/01/09のリストに戻ってみよう.登録レコード数:recordcount→ recordCount, 最大レコード番号: maxrecord→ getmaxrecn, _maxrecn としているが,テーブルサイズを押さえている関数は予定されていない.現行のgetmaxrecnはtablesizeを返しているので,実質それに該当しているのだが,maxrecordはテーブルサイズではなく,現に登録されている有効なレコード番号中の最大のものという位置付けだ.tableSizeでもよいような気はするが,最大レコードというところを強調したい.上の修正でgettablesizeに相当する関数だ.中を取って,getTablesizeとしてみよう.この関数はtablesize-1を返すことにする.現行のgettablesizeを置き換えるものだ.この関数を使っているところで,使われ方が正しいかどうかを点検してみよう.

getTablesizeはレコード番号の取れる最大値を意味している.⇒TableSizeOverという関数名は状態を示す関数としては適切ではないので,LinkTableFullに変更する.LinkTableFullはmaxrecordではなく,recordcountがtableSizeと等しくなることを意味する.⇒すでにそういう実装になっている.

カードの肩書に文字を書き込んで登録して,TOPOLOGY:MakeActiveListで(treeview->validcard != ActiveList->count)のエラーになる.TREEVIEWの側の値が間違っているものと思われる.TREEVIEW::validcardは「有効なカード数(基準カードと親族関係にあるカード数)」で多分これは画面上のカード数と一致するはずだ.この値は,①SetKinship,②validcardnum,③ExtractPartialCardのいずれかの方法で決定される.今の場合は③に該当する.⇒validcardnumはTOPOLOGYのメンバー変数,SetKinshipはTOPOLOGY:FilteringKinshipの中で,「親族範囲を確定する」ために実行される.

どうも,カードの登録更新で部分図ノードが増加してしまっているようだ.このサンプルには現在部分図が3つ登録されているが,そのうちの2番目の「3077直系血族図278点」がデフォルトになっている.編集対象のカードはこれら3つの部分図のすべてに登録されているので,部分図の登録に変化があるというのはおかしいのだが…

PartialListのカウントは278のままで変化していない.partialflagというのはCARDLINKの固有情報であり,Undo/Redoは実行されていないのだから,誤って上書きされることも考え辛い.partialflagにTRUEを設定している箇所は7箇所ある.ファイルオープン時には,InitLinkTable→ PartialLoad で既定の部分図が読み込まれる.

カードの登録でmakenewcardが掛かってくる.なぜだろう?アプリから送付されたCarddataのRefnumが空欄になっている!Personalには入っている.これはかなり由々しき事態だ.MyCardというのは,アプリに常設されているカード情報の格納域だ.MyCardはGetNewCardでロードされているはずだが…OpenFileProc→ GetNewCardでは入ってくる.mCARDDATA::Putで転記されていない!

どうもこの関数の中で例外が発生しているようだ.この関数では例外が発生することを想定していないので,voidになっている.例外をトラップしたところでGCERROR_CARDDATAPUTをスローするようにしたが,何も応答はない.この関数を呼び出しているmZelkova:mSendCardでキャッチして,GCERROR_SENDCARDで復帰している.SendCarDataにはエラー処理は入っているが,ごく一部のエラー以外は処理されていない.ERR_CARDTABLEOVERとERR_MARGTABLEOVERでは黙って復帰する.ERR_NOMEMORYの場合はアプリ終了する.

それ以外では戻り値の上位ビットを取って,その部分がSAMENAMEEXISTの場合には,黙って新規カードを作成する.これは「本人氏名と同じときは黙って新規カードを生成する」というルールによる.この仕掛けはあまり感心できない.SAMENAMEEXIST As Integer = &HFFFF0000なので,ほとんどのエラーコードと合致してしまう.NAMERECURRENCE As Integer = &HEFEF0000はなんとか識別できるのではないか?SAMENAMEEXISTのコードをFEFE0000に変えておこう.いや,これはすでに使われているので,F8F80000とする.

これで「本人氏名と同じ」とは衝突しないようになったが,どう処理すればよいか?当面,公式リリースはないと見込まれるから,エラーパネルを出して異常終了でよいのではないか?⇒異常終了ではなく,ノーマル終了とした.これで一応例外が発生していることだけは検出できるようになった.あまりユーザフレンドリでないパネルだが…

image

戻り値を持っている関数はエラーリターンできるが,void関数ではエラートラップに落ちたことはコンソールへのダンプでしか知ることができない.ほとんど見落とされてしまうので,void関数では例外をスローするようにしておこう.⇒16箇所のトラップにthrow文を入れた.

さて,問題は,mCARDDATA::Putでなぜ例外が発生しているのかという理由だ.理由ははっきりしていて,(carddata->Refnum != refnum)のときには例外をスローするという論理になっているためだ.「参照番号の改変」とされる事象だが,

いや,その前に別のエラーが発生してしまった.「本人の氏名データがありません」というエラーが起きている.

image

MakeNewCard→ Myself.Getで-5007というエラーが返っている.参照番号は3079.-5007というのはGCERROR_GETPERSONALだ.⇒修正ミスがあった.catchの{}の外にthrow文を置いてしまっていた.

例外をスローしているのは以下の一文だ.

if (carddata->Refnum != refnum) throw “mCARDDATA::Put 参照番号の改変”;

しかし,これはかなり奇妙だ.mZelkova::mSendCardでは,引数で渡されたmCARDDATA^ mycard… 以下のデータをDLL側に送付するものであり,CARDDATA card… 以下のオブジェクトはアプリ側のデータをDLL向けに変換するための受け皿として用意された関数内オブジェクトであり,まったく空の状態になっているはずだから,carddata->Refnum と refnumが一致するようなことは起こり得ないと考えられるのだが… 「参照番号の改変」という文字列はここにしか現れない.⇒この文をコメントアウトしてエラーなしで動作するようになった.

「改変」という文字列は,2019/08/13に一箇所出てくるだけだ.もちろん,「参照番号の改変」とはまったく別の文脈だ.サイトにアップロードされていない以前のログを探せば何か手がかりになる情報を見つけられる可能性はあるが,今のところ,全文検索ができていないので,それもできない.もしかすると,以前はCARDDATAを関数内ではなく,静的領域に置いていた可能性はあるかもしれない.そうなっていれば,一致することはあり得るように思われるが…

さて,どこまで進んでいたのか?わからなくなってしまった.エラーはすでに解消しているが,不良の原因となった,「新規カードの生成」で起きる不具合について,原因と対策を考えておかなくてはならない.部分図モードのときに新規生成されたカードは自動的にその部分図に属するようになるというのが仕様だったような気がするので,部分図のメンバーが増加すること自体は正当だが,あちこちで不整合が発生するというのは問題だ.どんな事象が起きていたのか?

カード情報を更新しようとして,新規カードが生成されたという事象だ.不良の原因ははっきりしている.lookupテーブルが更新されていないためだ.いまのところ,ファイルオープン時に一度生成されただけで,それ以外の処理は何一つ組み込まれていないのだから当然だ.カードの生成と削除に同期してlookupを時点処理するというのが現在の方針であり,その手続きを実装しなくてはならない.

lookupではレコード番号は必ずしも連続した並びにはなっていない.場合によっては,順序が逆転していることすらあり得るし,また,そのような場合でも正しく操作できなくてはならない.逆に言えば,もし,それが可能ならレコードの追加・削除ではテーブル全体の詰め合わせや並び替えをやらなくてもよいということになる.系統並び替えにはテーブルソートが入っていたはずだから,そのような処理はそこに任せてよいはずだ.従って,lookupの保守はかなり簡単なもので済むはずだ.

いや,maxrecordというのは仮に中間に空きがあったとしても,テーブル上では必ず末尾に来なくてはならないのではないか?⇒これは解釈の問題ではないかと思う.maxrecordが有効レコードの末尾であるということは,その(リンクテーブル上の)レコード番号の最大値であるということを意味する.定義としては,これで十分なのではないだろうか?カード追加・削除時のlookup更新手続きを書いてみよう.

  1. 新規カードが追加された場合には,そのレコード番号をlookupの末尾に追加し,lookup.countを+1する
  2. 既存カードの削除では,lookup上のそのカードのセルを空欄とする.

つまり,lookupのサイズは単調に増加するものとする.⇒しかし,どこかで整序する必要があるのではないか?これは結局,MakingLookUpを実行するということに他ならない.おそらく,CommandEndとUndoProcessの出口でやっておけば十分なのではないかと思われるが,もし,RebuildCardListでlookupを参照しているとすれば,その前に実行する必要があるかもしれない.⇒MakingLookUpを実行する必要はない.系統並び替え時にはつねにテーブル並び替えを実施している.テーブル並び替えとはlookupの整列に他ならない.

レコード番号と参照番号を切り分ける作業

レコード番号と参照番号を切り分ける作業を続けているところだが,いよいよ正念場だ.リンクテーブルの構成は転々と変成してきたが,ようやく最終的なところまで煮詰まってきたと言える.最初から完全な設計からスタートしている訳ではないので,完璧に仕上げるためにはこの工程をやり抜かなくてはならない.要点を拾い出しておこう.

  1. 参照番号はカード氏名のハッシュ値によって決定され,リンクテーブルには参照番号(辞書順)でアクセスする
  2. カードを追加するときデフォルトのスロットが空いていないときは,上方に空スロットを見つけてその位置にリンクを配置する
  3. 外部からはリンク番号によってリンクテーブルに直接アクセスできない(リンクテーブルのリンク番号は公開されない)
  4. 基準ソートなどのテーブル並び替えではリンクテーブルは操作されない 
  5. 一覧画面の表示モードには①全カード,②現表示カード,③部分図カードの3つがある.これらは,①lookup,②ActiveList,③PartialListの3種のテーブルで管理されるが,①にはリンクテーブルのレコード番号が格納され,②,③には参照番号が格納されているという違いがある.外部からはこれらのテーブルのレコード番号によってアクセスする
  6. ActiveList,PartialList などからlookupで参照番号から逆引きするのは不利なので,参照番号でアクセスするための関数を整備する
  7. UNDOではリンクテーブルとlookupテーブルはバックアップされない ActiveList,PartialListは必要に応じバックアップされる

とりあえず,問題のUpdateRecordを精査してみよう.TOPOLOGY:UpdateRecordという関数は呼び出されるたびに,updateflagがオンになっているカードのレコードを1件づつ送付するという関数で,VBのGetNewTableではビルドフラグがオンのとき実行されるBuildNewTableの場合を除き,つねにUpdateTable→ UpdateRecordが実行される.セッションの開始は,リンクテーブルが管理しているupdateindexという値がゼロになっていることにより判別される.処理が何らかのトラブルで中断した場合などのリカバリの手続きが存在しない,などの弱点はあるものの,まぁ,それほど致命的な欠陥とも言えない.

TOPOLOGY::UpdateRecordの内部ではモードごとに処理を切り替えているが,実行関数はTOPOLOGY::UpdateRecord(別バージョン)で統一的に扱われている.この関数はかなり疑わしいところがある.とりあえず,関数名を変更しておこう.⇒UpdateRecordFuncにリネーム.

▲コラッツ特注版では基準ノードを変えることができないようになっているのだろうか?系統並び替えしても図面が変化しない.⇒いや,動いた.タイミングの問題だろうか?⇒少なくともダブルクリックでは作動しない.⇒いずれにせよ,思ったような動作になっていない.どこかでトンネルが崩落しているような感じだ.DLLの系統並び替えには値は渡っているのだが… いや,もしかすると,描画が遅いだけかもしれない.⇒どうも,そういうことのようだ.

▲一覧画面のカード数表示で,選択されたカード xxx/yyy のように表示されるが,yyyは一覧画面に表示されているカードで,xxxの示す選択されたカード数は,yyyの中に含まれるとは限らない.表示の仕方に問題があるのか,選択されたカードのカウントに問題があるのか?選択操作は一覧画面の範囲とは連動していなので,別の表示を考えた方がよい.

UpdateRecordFuncの最大の弱点は,更新レコードをグローバルに管理しているため,リンクテーブルにアクセスするしかないという点にある.実際には個別のテーブルにアクセスして参照番号を取り出しているが,リンクテーブルのレコード番号で個別テーブルにアクセスするというイレギュラーな構成になっている.しかも,lookupには参照番号ではなくレコード番号が格納されているという違いも見落とされている.

UpdateRecordFuncで一括処理するのではなく,個別テーブルごとに関数を立てるということも考えられるが,updatecountをどうするか?などの問題も残る.1件分のレコードを生成する関数MakeRecordは共通なので,そのカードが現在のテーブルに載っているか否かを確認できれば,それでよいのではないだろうか?個別テーブルで参照番号を検索するのはコストが掛かってしまうが,それしかないのではないだろうか?

明らかに現行方式は(一見したところ動作しているようには見えるが)間違っているので,修正が必要であることは明らかだ.「個別テーブルで参照番号を検索」するコスト増を回避するとすれば,テーブルごとにupdateindexを管理するしかないと思われる.3つのテーブルはすべて同じlongtableクラスなので,メンバーにupdateindexを追加してやればほぼ同等の処理になる.UpdateRecordFuncには引数でlong *tableを渡しているが,longtable *tableに書き直すのは容易だ.それがベストかもしれない.⇒実装してみよう.⇒実装完了した.少し動作確認が必要だが,どうすればよいか?CARDLINK->updateflagが立たないと動作しないので,データをいじらなくてはならない.

@3077というカードの所属に書き込みしたところ,FAMILYTREE:callSendCardで (_pagenum < 0)というエラーが出た.現親ページ番号が入っているpagenumが負値になっている.oyapageは実親登録なしではNOPARENTS=-1が入ることはあるのだが… エラーを無視して続行して,MakingLookUpで停止した.CommandStartFlagが立っているためだ.CARDTABLE::makelinkの出口でMakingLookUpが実行されている.コマンド処理の出口では必ずMakingLookUpを実行しているので,ここでは実行しなくてもよいのではないかと思われるのだが…

その意味ではUNDO動作中にlookupにアクセスすることがあるとすれば,それはイレギュラーな動作になると考えられる.チェックできるだろうか?callSendCardでは出口でもMakingLookUpを実行している.callSendCardではCommandStartをどこで呼び出しているのだろう?MakingLookUpを呼び出している箇所はかなりある.

  1. FAMILYTREE::callSendCard
  2. MARGTABLE::deletelink
  3. CARDTABLE::deletelink
  4. MARGTABLE::makenewlink
  5. CARDTABLE::makelink
  6. FAMILYTREE::AppendFile
  7. LINKTABLE::ImportEnd
  8. SetCardData
  9. COUPLING::OpenFamilyBase
  10. COUPLING::InitLinkTable
  11. UNDOSYSTEM::UndoProcess
  12. UNDOSYSTEM::CommandEnd
  13. UNDOSYSTEM::CommandEnd

最終的にはMakingLookUpを廃止して,常時lookupをリンクテーブルと同期させておくようにしたいと考えているのだが,いまのところは必要な処理だ.⇒この問題はいまのところ保留して,停止しないようにしておこう.しかし,このアサーションには修正日付が付いていないので,元々こういう動作になっていたのではないかという気もするのだが… コラッツ問題をやっている間は,ファイルの読み書きしかやっていなかったので,穴が空いてしまったのだろう.

pagenum = NOPARENTS を実行しているところは無数にある.従って,このパラメータが負値を取るのはエラーではない.おそらく,何らかの一時的なトラップのつもりで置いたIF文を恒久化してしまったのだろう.とりあえず,oyapageがNOPARENTSのときは,停止しないようにしておこう.⇒論理は動作しているが,updateflagの様子が少しおかしい.編集したカード以外の無数のカードにフラグが立っている.PDB()->updatecountが300を超えている.このフラグはCARDLINK::updateflagupで立てるようになっている.

どこから呼び出しているのか判定できるように引数を追加してみた.⇒呼び出し箇所は14箇所もある.⇒こともあろうに,MakeCouplingでフラグを立てている.これはいくらなんでもやり過ぎではないか?⇒いや,MakeCouplingというのは2つのノードの婚姻手続きなので正当だ.しかし,このフラグはファイルがロードされた時点でリセットされなくてはならない.ないし,レコードが取り出された時点でクリアされるべきだろう.TOPOLOGY::GetRecordではMakeRecordを実行後にリセットしているが,MakeRecordでリセットすべきだろう.

しかし,それにも問題がある.MakeRecordという関数は参照番号を引数としてレコードを生成しているため,カードリンクに直接アクセスできない.MakeRecordはCOUPLINGの関数なので,COUPLINGに専用ルーチンを新設してMakeRecordと並行に呼び出すことにしておこう.

PDBのうupdateflagは計数型なので,updatecountは単なるデクリメントでなくカウントを減じるようにしておく必要がある.また,この値が負にならないことを確認する.⇒CARDTABLE::clearupdateflagという関数もあった.使われているのだろうか?2箇所で使われている.TOPOLOGY::UpdateRecordFuncの末尾とFAMILYTREE::cancelupdateだ.cancelUpdateは外部呼び出しがあるだけだ.それぞれ cleanUpdateflag,cancelUpdateにリネームした.

▲CARDLINKのupdateflagとPDBのupdatecountが同期していない.⇒CARDLINK::updateflagupはフラグが立っていないときしかインクリメントしていない.updateflagとupdatecountはCARDLINK:updateflagupの中でしか操作されていないので,完全に同期するはずだ.⇒まだ,どこか余分なところでフラグを上げているようだ.⇒NAMESORT:SortNameSubで立てている.テーブル並び替えでスワップされるとインクリメントするようになっている.

その必要はあるのだろうか?⇒CommandEnd→ RebuildCardListでソートしている.「カードテーブルを現在のソート基準に従ってソートする」という理由による.悪いアイディアではないとは思われるが…仮にソートしたとしても,全カードが移動するようなスワップになるというのは考えづらい.⇒従来,基準ソートで降順の場合には,最初に昇順でソートした後,降順で並べ直したが,参照番号が永続コードになったので不要な措置となっているはずだ.

どうもリンクテーブルとlookupの関係がうまく調整されていないのではないか?リンクテーブルからMakingLookUpしたとすれば,せっかく保持していた並びが崩れて元の黙阿弥に戻ってしまう.これは完全な誤りと思われる.ちょっと頭を冷やす必要がありそうだ.lookupというのは,①リンクテーブルの有効なレコードすべてを含む,②つねにソートされた状態で維持される,という二つの特性があると考えられる.従って,MakingLookUpでリンクテーブルから再構築するという操作は完全に誤りというしかない.リンクテーブルでは原則として,①生成,②削除以外の操作は行われないと考えてよいので,これらのイベントが発生した時点でlookupの時点処理を行う以外にない.

①生成の場合には,テーブルへの挿入,②削除の場合にはテーブルの前詰めが必要になる.ややコスト過剰だが,仕方ないだろう.現物のリンクは移動しないから,挿入は末尾に追加でもよいのだが… つねにソートを通すということであれば,テーブルは穴空きのまま運用し,ソート時に前詰めする処理を組み込むということも考えられる.この方がトータルのコストを削減することができるのではないか?こうなると,lookupというより,むしろ「ソートテーブル」と呼んだ方が間違いない.

まず,最初にMakingLookUpを止めるところから始めよう.と言っても,少なくとも一度それを生成しなくてはならない.どこでそれをやればよいか?現行ではどうなっているのかを見ておこう.InitLinkTableのステージ7だ.CheckDataLinkを引数空の実行モードで呼び出して,データの不整合を補正した後,NameSortを呼び出して最初のテーブル並び替えを実行する前に実行される.NameSortを実行するためにはlookupの存在が不可欠だから,もっとも適切な場所と言えるだろう.それ以外の場所における実行はすべて止めることにする.

新規ファイルの場合,InitLinkTableは実行されない?⇒InitLinkTableを通っているのだが,事後にもう一度OpenFamilyBaseの中で実行される.これは,「登録レコード数0」で新規カードを登録した後の措置だ.⇒特にこれで問題があるという訳ではないので,とりあえず,これでよしとしておこう.ファイルオープンのどこかで事故ってカード数ゼロとなることはあり得るので,事後に新規カード追加というのでよいと思う.ここではカードテーブルのlookupしか初期化していないが,これでよいだろうか?結婚テーブルも初期化した方が安全なのではないか?

▲カードの肩書を変更→ 登録でFAMILYTREE::CallSendCard→ CommandEnd→ TopologicalSortのとき,PARTIALNAME:ExtractPartialCardでif (PartialList && valid != PartialList->count)のエラーが発生した.valid=279 <> PartialList->count=278で1の差が出ている.なぜだろう?この関数ExtractPartialCardはリンクテーブルを直接参照している.⇒これまでは,MakingLookUpの中で部分図リストを更新していたのではないだろうか?

いや,それにしてもおかしい.行われた操作は肩書の一部を書き換えただけであり,カード削除など部分図の構成に影響するような操作は行っていない.⇒CARDTABLE::MakingLookUpでは基本クラスのMakingLookUpを呼び出しているだけだ.CallSendCardの操作に何かやり損なっている点があるのではないか?

PartialListはPARTIALNAMEクラスのメンバーだ.PARTIALNAMEを持っているのはFAMILYTREE.longtableに関してはaddという関数は作ってある.⇒一箇所漏れがあったので修正した.PartialListはPARTIALNAME::MakePartialListで生成される.部分図リストというのは,おそらく,メンテナンスしないでつねに,生成・使用されるものなのではないだろうか?部分図の操作はすべて「部分図コマンド」として実行される.⇒MakePartialListという関数は呼ばれていない.

PartialListは,TOPOLOGY::RebuildCardListとPARTIALNAME:SelectPartialMapで生成される.⇒RebuildCardListの呼び出しは3箇所あったが,いずれも2018-02-12に廃止されている.従って,現行ではSelectPartialMapが唯一の生成関数ということになる.この関数は部分図コマンドの実行時,およびUndoProcessから呼び出されている.いや,見落としていた.RebuildCardListはUndoProcessとCommandEndからも呼び出されている.

InitLinkTableで既定の部分図がPartialLoadで読み込まれている.このときのカウントは278だ.これが279に増えているということはどこかで部分図に属するカードが増えていることになる.カード登録のCommandEndでRebuildCardListが実行され,その後,UndoProcess出口のTopologicalSort→ ExtractPartialCardで不一致が発現する.RebuildCardListではlookupを経由してアクセスしているのに対し,ExtractPartialCardは直接リンクテーブルにアクセスしているという違いがある.どうも,リンクテーブルとlookupに齟齬が生じているようだ.ExtractPartialCardの論理をlookupベースに変更すると,この不一致は解消するが,今度はMakeActiveListで同種のエラーが生じる.

TOPOLOGY::UpdateRecordはかなり危うい仕掛けだ

いよいよ大詰めと言ってよい.始業時バックアップを取ろうとしたら,下記のエラーが出た.⇒開発環境が立ち上がったままだった.

image

参照番号を永久コード化する,つまりマイナンバーコード化するということにした.これは決定であり,後戻りできない.いろいろなところに副次的な影響が出てくることだろう.それにしても,CardNumlist, MargNumlistには何を格納し,どのように使われているのだろう?基準ソートではリンクテーブルを操作しないということになったので,もしかするとまったく不要になっている可能性もある.というか,NAMESORTというオブジェクトは何のために設置されているのか?

NumlistはNAMESORT::BuildNumListで「テーブル並び替えの前後で参照番号のリストを生成」している.また,RestoreNumListでリンクテーブルに参照番号を書き戻している.それ以上の用途はないように思われるので,完全に止めてみることにしよう.特に,RestoreNumListでは参照番号を親番号として認識しているので,完全に法令違反だ.⇒明らかにNAMESORTというオブジェクトはCardNumListとMargNumListを管理するためだけに作られているので廃止できる.

いや,そういう訳にもゆかないようだ.①ソート基準列インデックス,②ソート順,③年号タイプ,④数字タイプなどのパラメータを保持している.NameSort,NameNarabekaeなどもこのクラスのメンバー関数だ.⇒CardNumListとMargNumListの完全廃止で問題なく動作している.⇒本線に戻ることにしよう.

maxrecnum→ recordcountのリネームはすでに完了している.maxrecn→ recordCountは未遂行だが,maxrecnをローカル変数として使っているところもあるので,そちらを先にリネームしておく必要がある._maxrecnはすでに使われているので,maxrecn_としておこう.⇒クラスメンバーとしてもmaxrecnはすでに存在していない.このmaxrecnはmaxRecordにリネームしているところからすると,関数と推定される.「maxrecordのアクセス関数をmaxRecordとする」となっているが,maxrecordは新設を予定されているものであり,まだ実装されていない.その前身として,maxrecn()が存在していたのだろうか?

maxRecord()はgetmaxrecn()を置き換えるべきものであり,「getmaxrecnではmaxrecordを返す」となっているのだから,getmaxrecn≒maxRecordと考えてよいはずだ.⇒「_maxrecnをリネームしてrecordCountとする」というのがある._maxrecn()という関数は存在し,recordcountを返しているから,recordcountを返す関数がrecordCountということになる.maxRecordという関数はまだ実装されていない.とりあえず,_maxrecn→ recordCountというリネームは実行できるはずだ.⇒対処した.

ともかく,「maxrecordを新設し,最大(リンク)レコード番号を格納する」というのをやっておこう.maxrecordへの参照を返すmaxRecord()も作っておく.maxrecordは「recordcountの更新時に更新」となっている.recordcountの更新関数には,以下がある.①incmaxrecn,②decmaxrecn,③cleartable,④MakingLookUp.しかし,これらの関数ではレコード番号自体の情報は与えられていないので,これらを呼び出しているところを見なくてはならない.

incmaxrecnはAMILYTREE::AppendFile,NODECLASS *newlinkから呼び出されている.decmaxrecnの呼び出しは,MARGTABLE:deletelink,CARDTABLE:deletelink,FAMILYTREE::AppendFileから掛かってくる.これらの位置で処理するより,incmaxrecn,decmaxrecnに引数を渡して,集中処理した方がよいと思う.「maxrecordは単調に増加するものとし,(基準ソート時以外では)テーブルは縮小されない」としているので,とりあえず,decmaxrecnでは最大レコード番号を更新しない.ただし,これは実装してもそれほどコスト増要因にはならないと考えられるので,実装した方がよい.

▲カード一括削除の後の基準ソートが入らなくなっているような気がする.もう一度クリックすればソートされる.これまでは動いていたような気がするのだが…

ともかく,ここで一度バックアップしておこう.⇒さて,問題はlookupとの関係だ.lookupとは,リンクテーブル上の有効レコードの参照番号を前詰めして格納したテーブルであると言える.lookupは常時更新されるのではなく,処理の最後に一括生成されるという仕様になっているから,recordcountやmaxrecordには関与しないと考えられる.⇒それでは,なぜMakingLookUpでrecordcountとlookup.countが一致しないのか?⇒UNDOではリンクテーブルは保全されていないので,オブジェクトの再生と削除は本来なら通常手続きとして実行されなくてはならないところだが,削除はdeleteで実行されているもの,再生は手続き的に実行されているため,recordcountが更新されていないものと見られる.

この処理はUNDOSYSTEM::RestoreShadowで実行される.この操作の対象となるオブジェクトはCARDLINKとMARGLLINKに限定されている.逆に言えば,それ以外のオブジェクト(の削除)はUNDO対象となっていない.⇒UNDOSYSTEM::RestoreShadowを修正して,incmaxrecnを実行するようにした.⇒これでつねにUNDO動作と関わりなくリンクテーブルを保守することができるようになった.

いや,まだ不一致が発生している.Redoした場合のデクリメントが入っていない.RestoreShadowはUndo/Redoの共通処理になっている.<削除>の場合はoperator deleteを実行しているだけなので,通常通りの処理になっていなくてはならないのだが… recordcountは307のままだ.なぜだろう?⇒CARDTABLE::deletelinkではdelete cardの後に,decmaxrecnを実行している.これは必須である.つまり,deleteを直接呼び出しただけではアプリ的な処理は実行されない.

BASETABLEの関数でnoderefnumというのがあるが,これはレコード番号を返す関数であり,この戻り値を参照番号とみなすべきではない.しかし,かなりの箇所でこの関数は使われている.⇒仕様を変更して,リンクから参照番号を返すように改めるべきだろう.⇒いや,この関数は仮想関数でクラス実装では参照番号を返すようになっているから,問題ないだろう.⇒BASETABLE::noderefnumは純粋仮想関数とした.また,BASETABLEにgetrecnの別バージョンを新設し,親枝番号をレコード番号として返すようにした.⇒大分整理されたのではないかと思う.

ここまでできると,lookupを常時更新することも可能になるのではないだろうか?lookupに何を格納しているのか?が問題だ.レコード番号を格納するというのが最適だが,現行では参照番号になっているのではないか?⇒いや,レコード番号になっている.これでよい.しかし,テーブル並び替えではリンクテーブルを直接ソートしている.これはあまりうれしくない.リンクテーブルは操作しないという原則に反する.しかし,これでも実際にはソートしたように見えているというのはなぜだろう?リンクテーブルが辞書順にソートされているとすると,テーブル並べ替えによってそれが崩れてしまうことになる.それによってアクセス不能になるという訳ではないので,とりあえず,問題なく表示できているのだろう.X⇒勘違い.下記参照

テーブル並び替えというのは一覧表の並びに関係するものであり,一覧表には部分テーブルを表示することもあるため,一覧表示されるカードについては,別途カードリストが編成されている.lookupというのは実際のところ,どの程度役に立っているのだろう?⇒MakeActiveListなどではlookupを直接参照している.⇒いや,読み違えしている.テーブル並び替えはlookup上で実施されている.

一つマズイのが出てきた.BASETABLE::getrefnumではlookupから引いた値を返している.これは参照番号ではなく,レコード番号だ.BASETABLE::getlinkがこの関数を使っている.⇒BASETABLE:getrefnum,getlinkは廃止した.⇒TOPOLOGYにもgetrefnumという関数がある.⇒アプリ側にはリンクテーブルのレコード番号はまったく公開されていないと考えるべきではないか?

この意味ではCallGetRecordNumのような外部関数には問題がある.逆に言うと,一覧表のレコード番号を常時当てにすることはできないから,外部からカードにアクセスするには,参照番号を使うしかないということにもなる.そうなると,lookupを使うメリットも薄れてしまうような気もする.参照番号は一応辞書順になっているので,それほど悪い設計とも言えないが…

アプリから参照番号でアクセスするとき,lookupを経由すると総当りになってしまう可能性があり,その意味ではむしろリンクテーブルにアクセスした方が効率はよいと考えられる.⇒アプリからレコード番号でアクセスするおそらく唯一の関数がZ.mGetRecordNumだが,この関数はレコード番号1のカードを取り出すか,テーブル全体にアクセスする場合にのみ使われているので,lookupで応答するように改造してみよう.

lookupはゼロ発進だったような気がするが…いや,1発進でよいようだが,だとすれば一つ大きい箱を用意しなくてはならないような気がするが… ⇒テーブルサイズはMAXPDBだ.つまり,MAXまで使うとオーバーフローしてしまう.いや,間違っている.やはりゼロ発進だ.⇒CallGetRecordNumをlookup対応に修正した.動作確認するには,mGetRecordNumの呼び出しをチェックしなくてはならない.

InitializeDisplay→ SendDateAll→ mGetRecordNumというフローがあるのだが… ⇒InitializeDisplayはファイルオープン時に一度呼び出されているだけだが,動作している.⇒アプリからレコード番号でアクセスする関数を総点検する必要がある.⇒一度バックアップを取ってから…

GetRecord(long num, char *rstr, int range)はおそらくレコード番号だと思う.GetRecordはrangeにより3つの処理に分岐する.①GetAllRecord,②GetActiveRecord,③GetPartialRecordだ.GetAllRecordはGetCardBaseRecordを呼び出している.この関数はlookup対応だ.しかし,GetActiveRecordから呼び出されるTOPOLOGY::GetRecordはリンクテーブルを対象としている.⇒この関数はlookup対応に書き直す必要がある.GetPartialRecordはPARTIALNAME::GetPartialRecordを呼び出している.ここではPartialListのレコード番号が使われているのでこのままとする.

GetActiveRecordというのは,一覧画面の表示範囲:系図画面上のカードを表示というモードだ.いや,参照しているテーブルは引数で渡されたもので,今の場合はActiveListを指しているので,間違ってはいない.参照番号からレコード番号への変換はgetrecnが使われているが,デフォルトの位置でマッチングしない場合は,リンクテーブル全体のブラインドサーチになっている.これはlookupを使った方が効率的なのではないか?また,レコードは通常デフォルト位置より上に配置される(参照番号の昇順)ことになるので,先に上方を検索し,見つからない場合のみボトムから探すとした方がよいのではないか?

SendUpdateDataもそうかもしれない.この関数はTREEVIEW:sendUpdateDataを呼び出しているが,レコード番号は参照されていない.⇒紛らわしいので使っていない引数は廃止することにしよう.SendUpdateData(long recn, int range, int len, char item[][MAXTEXTFIELD])の引数のうち,recnとrangeは使われていない.

ImportTableFuncは一覧表のレコード番号が使われている.⇒ブロック内変数に変更した.AutoMergeTest(long recn, int type)というのもある.AutoMergeTestはSingleMergeTestを呼び出している.⇒SingleMergeTestは「現在系図画面上に表示されているカードを対象にカード合併包括テストを実施する」ものなので,対象領域はActiveListである.現行でよいのではないかと思う.

COUPLING::getUpdateRecordでは戻り値でレコード番号を返している.実行関数はUpdateRecord.戻り値のレコード番号は対象領域により,lookup,ActiveList,PartialListとなっているので問題ないが,果たしてこのインタフェースで連続レコード処理できるのだろうか?mGetUpdateRecordを呼び出しているのは,UpdateRecordだけだ.UpdateRecordはUpdateTableのループの中で戻り値が0以下になるまで回している.引数はrangeだけなので,単なるトリガーだ.⇒mGetUpdateRecordは書き込みではなく,レコードの取り出し関数.

TOPOLOGY::UpdateRecordという関数はかなり特殊なことをやっている.これはじっくり解析しないと誤るかもしれない.この関数では内部状態を持っていて,updateindexをインデックスとして押さえている.対象領域は指定されたテーブルを対象としているのでよいようにも思われるが,かなり危うい仕掛けになっているような気がする.また,lookupに格納されているのはレコード番号であり,それ以外のテーブルは参照番号であるという点にも留意する必要がある.いずれにしてもかなり危ないような気がする.

参照番号のマイナンバーコード化

とりあえず,仕掛りとなっている recnとrefnumの切り分けというのが最大の課題だが,その前にいくつかペンディングを片付けておこう.

再現手順5でUndo→Undoしたとき,RestoreNumlistを実行すると,大量の「カード参照番号の重複」エラーが発生する.⇒この関数ではCardNumListとMargNumListに保全された参照番号をリンクテーブルに書き戻している.⇒手順5で最初に基準ソート→ Undoではエラーは発生しない.その後,一括削除→ 基準ソート→ Undo→ Undoで発生する.つまり,障害は基準ソートをUndoした時点ではなく,一括削除のUndoの時点で発生している.これは,一括削除時にNumListがバックアップされていないことを意味するのではないだろうか?

確かに,CardNumListとMargNumListをバックアップしているのは,基準ソートの場合だけだ.基準ソートをUndoした時点ではエラーは発生しない.Numlistが復元されているためだ.ただし,このリストは削除後のものであるため,短いものになっているので,テーブル上で更新されていない部分との間に矛盾が生じている.カード削除の場合などでもつねにバックアップを取るようにしておけばよいのかもしれないが(部分図リストなどはそうしている),時間コスト的にはかなり不利だ.

一番合理的なのは,参照番号をユーザから見えないようにしてしまうことではないだろうか?一覧画面の第一列にはつねに1から始まる通し番号を振っておけばよい.実用的にはこれで何ら差し支えないような気がする… つまり,参照番号はある種の「パーマネントコード」のようなものになる.コラッツ特注版では「氏名」を数値として解釈し,それに準ずるハッシュ値を生成しているが,将来的には「名前」でハッシングすることになると思う.さらに,データベース化ということまで考えると,やはり,参照番号の永久コード化というのは必須なのではないか?

もし,そうであるとすれば,RestoreNumlistという処理は完全に廃絶されることになるのではないだろうか?基準ソートという操作自体が廃絶されるとすれば,Renumberという機能も無用なものになると考えられる.⇒方向としてはこれが正しいのではないかと思われるのだが… その方向に一歩踏み出すことにしよう.Numlistを復元しないとすれば,保全する必要もなくなると考えられる.「参照番号を永久コード化する@20230109」で止めてみよう.

参照番号の隠蔽というのはよいとは思うが,「カード番号」というのは便宜上いずれにしても必要だ.一覧画面の通し番号でもよいが,一覧画面では一部カードのみを表示する場合があり,その番頭を採用するとなると,今度はカード画面との不整合が発生するようになる可能性がある.これを整合的に操作するのは意外に難しいと思う.むしろ割り切って,参照番号というのはマイナンバーカードのようなものとして公開してしまうという方が整合性が取り易いかもしれない.というか,それしかないような気もする.つまり,「参照番号のマイナンバー化」だ.

これが修正をミニマイズするもっとも賢明なやり方ではないか?これで昨日のチェックするとの項目1~3が片付いたので,いよいよリスト項目7のrecnとrefnumの切り分けに掛かることにしよう.これは前回途中までやって挫折したところだ.一度バックアップを取ってから始めよう.⇒ログを遡って読み直しておく必要がある.⇒この取り組みは2022-12-30頃から始まったものだ.要点をピックアップしておこう.

  1. 結婚リンクと親番号の不一致→ 暫定的に停止しないように措置した
  2. 人名テーブルと結婚テーブルについては,IsReferenceを使わない ⇒ この修正は撤回されたものと思われる
  3. maxrecnumをrecordcountにリネーム→有効レコードカウント
  4. recordcountは lookup テーブルの count に等しい
  5. maxrecordを新設し,最大(リンク)レコード番号を格納する
  6. recordcountの更新時に,maxrecordも更新する.
  7. maxrecordは単調に増加するものとし,(基準ソート時以外では)テーブルは縮小されない
  8. getmaxrecnではmaxrecordを返す
  9. maxrecordのアクセス関数をmaxRecordとする
  10. getmaxrecnを廃止して,maxRecordに統一する ⇒ getmaxrecnを廃止@20221226⇒_maxrecnにリネーム
  11. _maxrecnをリネームしてrecordCountとする
  12. CHECKREMAINSANSYOを一時停止 ⇒ 現在は有効
  13. longtableのaddを使う@20221226 ⇒ FIX
  14. setmaxrefnumでレコード番号を禁止@20221227 ⇒ FIX
  15. 並び替えではリンクテーブルは操作しない@20221228
  16. 構造的にオブジェクトの一部として完全に一体化している下位オブジェクトは,スロット接続ではなく,アンカー接続とするのが適当なのではないか?特に,参照リストは通常の参照管理とは別扱いとしなくてはならないのだから,その方が適切であるような気がする.
  17. @20221229で「枝番号と親番号が一致しない場合を許容する」という仕様変更 ⇒ この修正は破棄されたのではないか?
  18. ①UNDO処理,②参照管理のいずれか,ないし両方が未サポートの場合でも正しく動作しなくてはならない
  19. CheckMargLinkは静的情報とリンクの整合性をチェックしているので,CARDLINKのHUBOSからの参照が解除されているため,不一致が起きるのはやむを得ない ⇒ フラグを見て除外する⇒対処済み

まず,メンバー変数のリネームから始めよう.

  1. maxrecnumをrecordcountにリネーム→これはリンクテーブル上の有効レコードカウントを意味する.recordcountはlookup.countと一致しなくてはならない.recordcountはBASETABLE::cleartableで初期化され,incmaxrecnとdecmaxrecnで操作される.

  2. maxrecnをリネームしてrecordCountとする.
  3. maxrecordを新設し,最大リンクレコード番号を格納する.テーブルは穴空きの疎な状態で管理されるので,上限を押さえておく必要がある.
  4. recordcountの更新時に,maxrecordも更新する.
  5. maxrecordは単調に増加するものとし,(基準ソート時以外では)テーブルは縮小されない.
  6. getmaxrecnではmaxrecordを返す.
  7. maxrecordのアクセス関数をmaxRecordとする.

  8. getmaxrecnを廃止して,maxRecordに統一する

▲MakingLookUpでlookup->countをrecordcount に代入していたところを,一致しない場合には停止するようにしたところ,停止した.recordcount の 237に対し,lookup->countは307で70点の差がある.これはrecordcountのカウントに誤りがあるためと考えられる.⇒UndoProcess→ MakingLookUpでは不一致は避けられないのではないか?⇒一括削除のUndoなので,数字が一致しないのは当然のように思われる.recordcount はBASETABLEのメンバー変数だが,テーブルは基準ソート以外では保全されず,つねにMakingLookUpで再構築するようになっている.とりあえずは,現行論理を踏襲するしかない.

ここで二つのことが考えられる.①基準ソートではリンクテーブルは不変であるとすれば,リンクテーブルを保全する必要はないのではないか?カードの生成・削除はそれぞれのオブジェクトの操作で復元できるはずであり,リンクテーブルはUNDOから切り離すことが可能なのではないか?②UNDOがきちんと動作することが保証されるのであれば,PDB,MDBを保全する代わりにそれぞれのlookupを保全することが考えられる.リンクテーブルはテーブルの最大サイズ分のデータを保持しなくてはならないが,lookupなら有効データが前詰めされているので,かなり有利なのではないだろうか?

もし,これができればMakingLookUpでlookupを再構築する必要がなくなると考えられるのだが…⇒NAMESORTというオブジェクトがあり,CardNumlistとMargNumlistを管理しているが,これらは実質的にlookupと同内容なのではないだろうか?つまり,データとして重複しているのではないか?NAMESORTというのはFAMILYTREEの保持するオブジェクトだ.⇒とりあえず,UNDOでリンクテーブルを保全しない@20230109としてみたが,問題なさそうだ.

再現手順で障害がまったく発生しなくなった

「カード参照番号の重複」というエラーが出ていた.これは,UndoProcess→ RestoreNumlistの実行によって引き起こされている.現行版では基準ソート時のRenumberを止めてあるので,Numlistの更新は不要と考えられるので,暫定的に停止することによって解消した.これでかなり落ち着いたのではないかと思う.⇒現在,CheckDataLinkを無動作にしていることもあるが,現在実行している再現手順ではまったく障害が発生しなくなっている.

重点的に見直す必要があるのはUNDO処理に他ならないが,UNDO中にはある程度までは不整合が生じるのは避けられないので,とりあえず,出口検査だけに絞っておこう.⇒問題なく動作している.再現リストの手順をチェックしておこう.テストは起動直後に実行するという規定になっているが,連続的にテストしてもよいだろう.⇒まったく問題なさそうだ.さて,気になる点をチェックしておこう.⇒いや,まず,ここでバックアップを取っておこう.

  1. 現行では基準ソート時のRenumberを停止している.これを復活させたときの動作を確認する ⇒ 将来的には参照番号をユーザから完全に遮蔽することも考えられる.最初のころの仕様では,系統並び替えを実行すると基準ノードがテーブルの先頭に来るようになっていたような気もするのだが… 参照番号をユーザからアクセスできない内部コードとしてしまうのであれば,基準ソートという操作そのものがなくなることになる.⇒いずれにしても,Renumberを復活させて機能していることを確認しておくことは必要だろう.
  2. UndoProcessでNumlistの更新を停止しているが,その是非を確認する ⇒ 現行ではRenumberしていないので,UNDOでNumlistを更新しないというので特に問題はないが,なぜエラーが発生しているのかを突き止めておく必要はある.現行通り,参照番号をユーザに公開するという仕様が存続する場合には,Numlistの更新も必要になってくる.
  3. 基準ソート時にRenumberを実施することの得失を考える ⇒ 基準ソートすると飛び飛びの参照番号が通し番号になるというのは仕様的には妥当である.基準ソート時にはRenumberを実施するという方向で調整すべきではないか?
  4. 検査ルーチンをCheckRoutine.cppに集約し,①副作用のある検査ルーチンと②副作用のない検査ルーチンを峻別する ⇒ 大体整理できたのではないかと思う.名前がCheckで始まる関数は検査ルーチンに限定した方がよい.
  5. 現行のNODULE::slottypeはオリジナルの仕様で動作している これでフィックスすることの可否 ⇒ 問題が起きなければ,これでよいと思う.これは当初設計が妥当ということを意味する.
  6. 現行のUndoProcess→ UNDONODE::CountupReferenceで行っているUndoCopyの動作の可否 ⇒ 参照復元操作の見直し@20230104の破棄 ⇒ 当初設計に沿った仕様であり基本的には妥当である.
  7. recnとrefnumの相違を確立する ⇒ アプリからはrecnでアクセスする場合とrefnumで検索される場合がある.一覧表の操作はすべてrecnで行うのが原則である.アプリからアクセスするときのrecnとは,lookupテーブルのレコード番号ないし,xxxリストと呼ばれる各種テーブルのレコード番号であり,リンクテーブルのrecnとは対象が異なる.
  8. CLEARTABLEでは参照番号エラーを無視する@20221230 ⇒ CLEARTABLEでもすべての参照番号エラーを検査できるようにする.最終目標の「完全にシームレスなシステム」を実現する.
  9. CheckStaticMargDataは実動作のとき引数がNULL ⇒ 引数がNULLで検査ではなく,実動作の場合はNULLとし,ダンプを見ればどこでエラーが発生しているかが見えるようにすべきだ.
  10. 仮修正をフィックスする ⇒ 作業完了した

フィックスした仮修正

  1. MARGLINK::CheckMargLink 夫/妻カード結婚リンク不在で停止→ 例外をスローするを廃止⇒停止しない
  2. LINKTABLE::CheckDataLink,LINKTABLE::CheckDataLink 副作用のある動作を抑制
  3. MARGLINK::CheckMargDataLink,CARDLINK::CheckCardDataLink 修正版に切り替え
  4. COUPLING::InitLinkTable 参照番号が上限を超えている⇒廃止
  5. UNDOSYSTEM::UndoProcess 選択リストと実カウントの不一致⇒停止しない
  6. BASETABLE::getnewRefnum ++maxrefnum⇒emptyrecnを返す
  7. BASETABLE::emptyrecn getmaxrecn+1を返す⇒maxrefnum+1がレンジ内ならmaxrefnum+1を返す.でなければ空スロット番号を返す
  8. その他

ここでバックアップ.⇒保留中の修正をフィックスする.

    1. 参照復元操作の見直し@20230104 ⇒ オリジナルの仕様でFIX
    2. 参照リンクであるか否かの判定@20221228 ⇒ 廃止
    3. getGenerationの代わりにGetGenerationを使う@20221222 ⇒ getGenerationは絶対座標系では使えない ⇒ 確定
    4. CommandEndに処理結果を渡す@20221224 ⇒ コマンドの実行結果をCommandEndに渡す必要がある.「アプリケーションの処理結果が負の場合は,コマンドを削除して復帰」 ⇒確定
    5. setRefnumはCARDBASEで実行する@20230107 ⇒ refnumはCARDDATAとPERSONALでシェアしている ⇒ 確定
    6. 並び替えではリンクテーブルは操作しない@20221228 ⇒ ClearTableで結婚リンクの参照番号がテーブルサイズを超えた場合をエラーとしない
    7. 参照番号は必ずしも通番ではない@20220214 ⇒ CARDTABLE::makelinkでカードリンクの参照番号がテーブルサイズを超えた場合をエラーとしない
    8. ChangeRealReferenceの引数righthandを廃止@20210323 ⇒ かなり昔の修正 ⇒ 確定

 

今日こそはシューティング

だらだらと引きずってしまったが,今日こそはシューティングしなくてはならない.どこかで修正ミスを入れてしまったようだ.

printshort関数の中で例外が発生している.呼出履歴ではどこから呼び出しているのか分からない.引数の中に無効なアドレスが入っている.⇒CheckDataLinkで起きている.⇒つまらないミスがあった.

対象カードの参照をクリアしてしまっているのだから,UNDONODE:UndoCopyで親参照リンク不在が発生するのはやむを得ない.問題は,そのエラーを出したあと,ループの外でもう一度CheckDataLinkを実行すると,状態が変化して別のエラーが出てしまうという点だ.CheckDataLinkは副作用のある検査ルーチンだが,引数空で呼び出した場合には実動作は行わないようになっているはずなのだが… どこかに見落としがあるのだろうか?

動作不良が起きているのは,MARGLINK::CheckMargLinkで,対象結婚リンクは#1060で共通だ.エラーが「親参照リンク不在」から「親番号不在」に変わっている理由は,getoyanum(0)の値が,0から-1に変わってしまったためと考えられる.⇒getoyanumはMARGLINKの関数であり,refnum = margbase.kodomo[index]で得られる参照番号refnumのカードが実在し,かつ,card->cardbase.oyanums[i]で得られる結婚リンクの参照番号が当の結婚リンクの参照番号と一致すれば,i を返すという動作になっている.margbase.kodomo[index]の値が零のとき,ないし,上の操作で該当する親を見つけられなかったときは,NOPARENTSという負値を返すようになっている.

2回目のときは,cardbase.carddata.parentsがゼロになっている.どこかでこの値を書き換えているのだろう.CheckDataLink→ CheckCardLinkの呼び出しで,引数に呼び出し元の識別文字列が入っていなかった.このため,「検査中」という情報が伝わっていなかったため,実動作が発生していた.⇒UNDO操作中に不整合が発生するのは避けられないが,「カード参照番号の重複」というのはちょっとイレギュラーなのではないかと思う.

これは,カードリンクテーブル上に同一参照番号を持ったカードが2つ存在することを意味する.なんでこんなことが起きているのだろう?レコード番号!1565と!3403に@1565という同じ参照番号を持ったカードが存在している.!1565は#2602,!3403は#3196だ.どちらが悪いかと言えば,!3403の#3196ではないか?

検査ルーチンの引数で実動作するかしないかを決めているが,検査のときにNULLとしているのは致命的敗因だ.むしろ逆にするべきだった.今からでも遅くない.多少リスクはあるが,書き換えることにしよう.関係するルーチンは以下の5種だ.

  1. LINKTABLE::CheckDataLink
  2. MARGLINK::CheckMargDataLink
  3. CARDLINK::CheckCardDataLink
  4. MARGLINK::CheckMargLink
  5. CARDLINK::CheckCardLink

終わった.ミスっていなければよいが… UNDONODE::UndoCopyというエラーが出ているのはよいが,UNDONODE::UndoCopyでUNSOSYSTEM::UndoCopyを実行した後に,新しいエラーが発生する.実際にはUNDOBASE::UndoCopyを実行しているだけだが,この関数ではイメージをコピーしているだけだ.対象ノードは#3196のCARDLINKで参照番号は@3403,これに対し,shadowに入っているイメージの参照番号は1565だ.明らかにこれは何かが間違っている.現物とイメージの連結関係が崩れるということは考えづらいので,どこかで参照番号が変化していると考えるしかない.NameNarabekaeではRenumberを実行しているが,現在のコードでは止めてある.

イメージは一度複写された後は上書きされることはないと考えられるので,現物が書き換えられているのではないかと推定される.CARDDATA::refnumはpublicなのでどこからもアクセスできる.これをprivateにするのは難しいし,やってもあまり意味がないような気がする.⇒参照番号を書き込んでいる場所は12箇所ある.⇒setRefnumという関数を作っておこう.⇒この際なので,setRefnumをCARDDATAではなく,CARDBASEのメンバー関数とし,PERSONALが保持しているrefnumも同時に更新してしまうように変更しておきたい.

ただし,外部とのインタフェースではCARDDATAとPERSONALは独立に渡されるので,「外部では参照番号を更新しない」という原則を立てておく必要がある.実際には,そのようなことは起こり得ないとは思われるが… CARDLINKには既存関数として同様の処理を行うsetrefnumという関数があるが,廃止してsetRefnumに統合することにする.⇒整理できた.⇒基準ソートの場合には,UndoでRestoreNumListが実行されるが,この際に異なる参照番号が書き込まれている.


Your Daily Epsilon of Math Calendar

Your Daily Epsilon of Mathの投稿は今のところ3日坊主にならずに,続いている.「急にグループの感じが変わった!!」というコメントが付いた.これまでの主流は,最新論文の読解とか,高級な問題の解題などかなりレベルの高いところを狙っているものが多かったので,「レベルを落としているんじゃないか?」と内心危惧していたのだが,肯定的に評価されたものと受け止めておこう.「数学の問題を解くのはこんなに楽しい」という雰囲気になってくれればうれしいのだが…

この投稿を続けるのは確かにかなりの(時間)コストが掛かる.最初に仕事に掛かる前に応答しなくてはならないが,前日分のコメントに応答し,今日の分を投稿するのは一仕事だ.もちろん,自力で解けなかった問題に関しては提供されたヒントを元にネットで記事を拾い出して勉強しなくてはならない.これを一年続ければ,かなりの数学力が付くことは間違いなさそうだ.これは英語の勉強も兼ねている.⇒お茶が切れてしまったので買い出しにゆくつもりだったが流れてしまった…

どうも不良が再現できなくなってしまった.2022/12/30の再現リストまで戻ってみよう.検査ルーチンを止めてしまっている可能性もあるが,包括的な検査はあちこちに入っているので,見逃されることはないと思う.⇒5番目のテストで「カード参照番号の重複」エラーが出た.

▲基準ソート→ Undo→ 一括削除→ 基準ソート→Undo→ UndoでEraseTreeViewの冒頭のCheckDataLinkでカード参照番号の重複エラーが出た.現象は多少異なるが,同列のエラーと思われる.参照番号は48なので,これまでに確認されていたものの再現と言ってよいだろう.⇒この不良はUndoProcessの中で発生している.復元対象オブジェクトはCARDLINKで参照番号は3403,親番号は3403,undotypeはUNDO_MAKE(つねにUNDOノードを新規生成する).このタイプはUNDOBASE::CommandEndでコマンド実行中に変化があった場合にのみ保全される.UndoRestoreが実行されている.

UNDONODE::UndoCopyの最初のループの中で「親の重複登録」が発生している.このエラーは対象ノードの参照スロットをクリアする操作の中で起きている.障害が起きているのはMARGLINKへの参照でCARDLINK:#3196を4番目の子どもとして持っている.⇒このエラーは無視してよいのではないか?CheckMargLinkは静的情報とリンクの整合性をチェックしているので,CARDLINKのHUBOSからの参照が解除されているため,不一致が起きるのはやむを得ない.

親リンクが空のときは,「親の重複登録」ではなく,「親参照リンク不在」を出すようにした.本当はUNDO動作中のフラグを見てもよいのだが,明示しておいた方が安全だ.⇒UNDOBASE::UndoCopyを実行する前の検査で,「親の重複登録」と「親番号不正」が検出されている.⇒この時点では.まだイメージのコピーは実行されていない.


前方視界不良という状態が続いている

前方視界不良という状態が続いている.五里霧中というほどのものではないが,かなり見通しが悪くなっている.どういう状態に持って行こうとしているのか,それをはっきりさせる必要がある.最終目標は,すべてのフェーズでつねに完全な参照管理が確立しているという状態,暫定的にはCLEARTABLEフェーズでは例外も認められるが,最終的にはその壁も崩されなくてはならない.UNDOの前後で崩れていないことは必須だ.系統並び替えに入る時点でトポロジーに関わるような操作はすべて完了していなくてはならない.

完全な参照管理とは,参照リスト管理がきちんと動作していること,参照カウントが正しく管理されていること,特にオブジェクトのデストラクタの中で整合性が維持されることが求められる.もう一つ重要なポイントは,①UNDO処理,②参照管理のいずれか,ないし両方が未サポートの場合でも正しく動作しなくてはならないという点だ.

今日の訪問者数は午後8時現在で700人を突破している.この統計が正しいとすれば,これまでのレコードになりそうだが,なぜそんなに急増しているのか?その理由が分からない.Googleアナリティクスには反映されていないので,真偽のほども不明だ.

EraseTreeViewの入口では整合しているのだが,描画リストのクリーンアップで不良が発生する.⇒いや,違う.CheckDataLinkではこの参照エラーは検出できない.CheckNringCountを仕掛けたら入口ですでに不良が発生していた.⇒やはり,UNDOだ.問題はバックアップイメージの復元を行うUndoRestoreにあると言ってよい.UndoRestoreでは,UndoCopyでオブジェクトからの参照を一旦すべてクリアしてから,イメージを復元し,その後,CountupReferenceで参照カウントの再計算を実施している.

とんでもないミスを冒していた.仮修正でコメントもなしに止めていた行があった.復活させたら動作するようになった.多分,これで完全になったという訳ではないとは思われるが… 参照処理はもう少し徹底した修正が必要と思われるが,別のエラーが出てきたので,先にそちらを片付けることにする.⇒再現できなくなってしまった.

われ 男の子 意気の子 名の子 剣の子

昨日はプチ宴会でお休み.今日が仕事始めだ.通常なら100本くらい入ってくるメールが今日はたったの2本しかなかった.昨日はそれでもまだ30本くらいはあったのだが… 昨日は飲み会(と言っても飲めるのはほとんどわたしだけ)から帰ったあと,Your Daily Epsilon of Math を投稿した.三日坊主で終わるのか,365本まで投稿して皆勤賞をもらえるかどうかは今のところ分からないが.まぁ,続けられるだけやってみよう.高校の卒業アルバムを弟が発掘してくれていたので,持ち帰った.わたしにも「少年時代」というのがあったのだ!アルバムの余白の寄せ書きの中にわたしの手で書いたこんな短歌が挿入されていた.

われ 男の子 意気の子 名の子 剣の子
詩の子 恋の子 ああ,もだえの子

誰の歌だろう?わたしが書いたのだろうか?確かに,この気分はわたしのものだが,わたしのボキャブラリの中には「剣の子」というのはなかったような気もする.気になって部屋に戻ってから調べたら,与謝野鉄幹の歌だった.確かに,これを読むとわたし自身の中に(父親から引き継いだ)「明治」のフレーバーが残っているのを感じる.

https://www.facebook.com/age.baba.5/posts/pfbid02RVMSuXJXTro94cRXvQ2j6NRJqVW8Nwtmf3Kurm8UnAgAD89xVu89jLk6vAYuBhA6l

▲極小反例307.ZELを開いて,再現リストの項目5の手順でUndoProcess→ UndoRestore→ UNDONODE::UndoCopy→ UNDOBASE::UndoCopyを実行後に置いたCheckDataLinkでカード参照番号の重複エラーが検出される.⇒動作確認のために動作をアレンジしている箇所があったが,外しても同じ.UndoRestoreはループ中の動作なので,一時的な不整合が生じる可能性は考えられるが,UndoProcessの出口でも同じエラーが発生する.

この2つのカードリンクはオブジェクトとしては別物で,PDB[48]に入っているのは「47」というカード,PDB[7937]に入っているのは「16129」というカードだ.参照番号はいずれも48.しかし,ファイルを開いたときのカード数は307点しかないのだから,7937などという場所に有効なカードが存在すること自体間違っているのではないだろうか?⇒いや,名前でハッシングしているので,リンクはテーブル上では離散的な配置になっているから,あり得ないことではない.⇒実際「16129」というカードは存在する.

ファイルオープン時にはこのカードは一覧表の先頭にある.参照番号は7937だ.「47」というカードの参照番号は48で,テーブルの末尾にある.ここまでは問題ないようだ.⇒いや,これは一度基準ソートした結果の場合だ.起動時では「16129」はテーブル先頭から5番目の位置にある.また,「47」は下から5番目だ.⇒基準ソートでは,この順序が逆転しているだけで,位置的には正しい.つまり,「47」が上から5番目,「16129」は下から5番目だ.

Undoでこの順序は元に戻るが,参照番号が変化してしまっている.「47」は7937に,「16129」は48に入れ替わっている.どうも,この最初のUndoがデタラメな動作になっているような感じだ.参照番号は確かにソート→Undoになっているが,中身の順序がバラバラに壊れている.Renumberというのは必須の動作だったのだろうか?⇒いや,ちょっと混乱しているかも.参照番号は離散値ではあるが,整列している(起動時は降順)が,名前順にはなっていない.頭の方を拾い出すと,

8113, 24421, 8021, 32561, 16129, 7811, 7793

のようになっている.末尾の並びは,下から,

23, 31, 35, 41, 47, 61, 71, 83, 16469

だ.ソートは問題なく動作している.Undoも正しく動作している.従って,ここまでの動作には問題はない.カードの一括削除の対象に入っているのかどうかが問題だ.「47」は残っている.参照番号は7937.「16129」は削除されている.どうも,このカード削除の動作がおかしい.カード削除によって参照番号の入れ替えが発生しているように思われる.元々の状態は,「47」が#48,「16129」が#7937だ.

別の不良が見つかった.最初にソート→Undoという手順を省いて,いきなり一括削除→ Undoでは順序の問題は発生しないが,「被参照カウント不一致」が大量発生する.こちらのバグの方が易しそうに思われるので,先にこちらを片付けておくことにしよう.このエラーはEraseTreeView→ ~NAMEBOX→ CleanSlot→ clearSlotで発生する.

NODULE::slottypeという関数は少し書き換えているので動作確認が必要だ.nodule::CheckSansyoCountという検査関数はNリング上のすべてのノードの参照カウントをチェックする.この関数の中で参照ではないと判断されたリンクが参照リストに記載されている可能性があるので検出できるようにしたところ,TRIBEBOXの参照リストにTRIBELIST→ TRIBEBOXという接続リンクが入っているのが見つかった.

TRIBEBOX *starttribeは「基準ノードの属する始系列への参照」となっている.TRIBELISTはNLISTの派生クラスであり,LISTNODEクラスのオブジェクトのリストということになっている.NLIST→ LIST→ DATALISTの継承関係があるが,listtopは「リスト先頭ノード接続用スロット」となっている.つまり,TRIBELSITの先頭TRIBEBOXはlisttopに接続しているはずだ.いや,starttribeというのはこれとはまた別のスロットだろう.これは結局,TRIBELISTでは先頭TRIBEBOXが接続され,かつ,そのオブジェクトへの参照も保持している,つまり,同じ値を持つ複数のスロットがあるということではないだろうか?

このような重複リンクは合法であり,それを禁止するとかなり窮屈な仕様になってしまうような気もするが,DATALIST::listtopが接続であるとすれば,むしろ,アンカー接続として地の部分にスロットを設けた方がよかったのではないだろうか?これはかなり厄介な外科手術になってしまう可能性があるので,ここではパスするしかない.ベタ参照にしてしまうと管理上いろいろと厄介な話も持ち上がってきそうな気がする…

参照リストのリストノードは参照元リンクと参照元のスロット位置が入っている.従って,重複リンクのいずれかを誤認しているという訳ではないが,スロット番号だけでは接続用であるか参照用であるのかを判定することはできない.いや,そんなことはない.やはり,今の場合「スロットタイプ不整合」が成立しているのではないか?DATALISTsTOPLIST=1であり,TRIBELISTsSTARTTRIBE=2のはずだから,TRIBELIST[2]は参照でなくてはならないはずだ…

スロット2というのは,LIST::LISTsBOTTOMに相当する.従って,スロット2は接続ではなく,参照でなくてはならない.slottypeが間違っているのだろうか?⇒@20221229で「枝番号と親番号が一致しない場合を許容する」という仕様変更を行っているので,参照を接続と誤認する事態が発生しているように思われる.⇒この変更は,「参照カウント不一致」を伴う「UndoCopyで参照元ノードリスト不記載」に対処したものと思われるが,やはり問題がありそうだ.

slottypeの変更を破棄し,オリジナルに復帰することにした.これで,このエラーは発生しなくなったが,slottypeの変更が必要になった時点まで巻き戻さなくてはならない.その前に,また別のエラーが出ているのでそちらを先に片付けることにする.

▲ブロック削除→ Undo→ Redoで「被参照カウントの残留」が発生する.⇒UndoProcess→ ~CARDLINK→~noduleで起きている.#1612のCARDLINKだ.おそらく,削除範囲に含まれるカードだろう.残留カウントは2.nodule::TakeSansyoでは,参照カウントが残っている場合には,nodule::takeSansyoで除去できるようになっているのだが,補修できなかったようだ.

それ以前に最初のUndoを実行した時点でnodule:ReferenceControlの参照リストx参照カウント 不一致が発生している.これはまずいのではないか?⇒UndoProcessの出口でTopologicalSortを実行する直前ですでに被参照カウント不一致が量産されている.すべてMARGLINKだ.⇒参照リストの長さの方が,参照カウントより2以上小さいという状態になっている.これらはおそらくUndoによって復活したリンクだろう.参照カウントは強制的に調整していたのではないだろうか?いや,そもそも,この値は一致しないということもあるのではないか?

フェイスブックに日替わりでDaily Epsilonの問題を投稿するのを日課とする

Facebookの数学物理etcグループにAMSのYour Daily Epsilon of Mathの日替わりの問題を毎日投稿することにした.投稿しておけば,誰かが回答を付けてくれるだろうという虫のいい算段だ.昨日の初回投稿には,数分後にレスが付いた!問題のレベルはほぼ高校数学の範囲内だが,わたしには手の出ないようなものもあるので,大いに助かる.毎日勤勉に投稿できるかどうかは保証の限りではないが,まぁ,飽きるまでは続けてみたい.多少なりとも認知症予防の効果は期待できそうだ.