食器棚に汚れた食器が並んでいるというのは

「フェーズ」はプロセスフローを適当に区切ってそれぞれの区間に名前を(勝手に)付けたものだ.1996年からゼルコバの木の開発に取り組んでいるが,「フェーズ」という概念を使い始めたのは2017年頃からと思われる.グローバル変数PHASEには「ビルド時の誤動作を防止するため@20170712」という説明が付いているしコメントには「処理モード,デバッグ用」とあり,便宜上のものと考えられていたフシがある.しかし,秩序ある解体が可能となるような完全にクリーンなシステムを構築するためには,この概念が不可欠であることが次第にわかってきた.

これまでに少なくともINITIALSTATEという区間はコア骨格木(接続関係のみで連結されたモジュール構造の核となる部分)だけが存在する状態であり,その次のINITIALIZINGという区間ではノードは追加されているが,「参照」は発生していない状態として再定義された.INITIALSTATEはアプリ起動時の初期状態で,ファイルがクローズされたときにはこの状態に戻る.INITIALIZINGはデータベースに接続して系図データがロードされた状態,INITIALIZEDはすべてのデータが展開されて系統並び替え(TOPOLOGICALSORT)を待つだけの状態だ.

「初期状態」が完全にクリーンなものになっているということはデバッグの成否と密接な関りがある.デバッグでもっとも肝要なポイントはその事象が再現できるということであり,再現可能なバグは原理的には必ず解決することができる.しかし,バグを再現する条件がすべて明らかになっていたとしても,「初期化」が不十分であればその検定の成功は覚束ない.初期状態が不定では「決定性」という条件が満たされないからだ.すべてのオブジェクトは生成時にそのオブジェクトクラスのコンストラクタによって完全にクリーンアップされる(べきである).ゴミ箱に入っている廃棄オブジェクトのリサイクルでも形式的にはnewで生成されるので,必ずコンストラクタが実行される.

ここまではよいのだが,ファイルがクローズされてコア骨格木の初期状態に戻るときにはこのような汎用的な仕組みが存在しないため個別にクリーンアップを実行しているものの,現状ではきわめて不十分なものであることが判明した.たとえば,longtableという長整数配列オブジェクトはすべて使いっ放しの状態になっていた.ファイルを再オープンするときには通常それぞれのモジュールの初期化を行っているので特に問題は発生していなかったが,今回発現した(!card->selectflag)の問題はこの欠陥が露呈したものだ.初期化が完全ならもちろんこのような問題は発生しないが,仮に食後に下げた食器を流し台に積み上げておいて調理の直前に洗って水切り台に移しそのまますぐに使い回すというのでよいとしても,食器棚に汚れた食器が並んでいるというのは頂けない.

現行ではこのようなクリーンアップに用いる汎用関数として,以下の3種,①clean, ②clear,③Clearが使われている.①のcleanは基底クラスNODULEの仮想関数で,主にコンストラクタから呼び出されることを想定している.②clearはinitializeなどの初期化の場面で使用されるが,初期化の範囲がモードによって異なる場合には,③Clearを全パートのクリアとし,②clearをその下位関数として使うなどのように使い分ける.

INITIALSTATEに戻るときなどに必要なクリーンアップを行う関数として,以下のような仕様の仮想関数Cleanを用意することにする.Cleanでは(1)自オブジェクトの固有データ部をゼロクリアする,(2)自オブジェクトに接続しているすべてのオブジェクトにつき,関数Cleanを再帰的に実行する.(3)この関数は共通ヘッダ部で汎用的に定義される.関数Cleanを実行すると,そのオブジェクトの下流のすべてのオブジェクトの固有データ部がゼロクリアされる.ZTシステムではすべてのオブジェクトはスロット部+固有データ部(だけ)からできているので,これより徹底したクリーンアップはあり得ない.実装してみよう.

現行ではNODULEクラスの共通ヘッダマクロには以下の3種がある.

  1. COMMONHEADERBASIC classname, classid, getclassize, getcid, VSLOT, _SLOt, SLOT, _slotsize, _vslotsize, dataptr, datalen, CleanSlot, setpid
  2. COMMONHEADERSHORT ①+operator new/delete
  3. COMMONHEADERPART ②+CleanSansyo, Dispose, dump

Clean()は現行のCleanSlotに近いので,COMMONHEADERBASICで定義してみよう.⇒実装した.CallSetCouplingPtrでCouplingをdeleteする直前でCoupling->Clean()を試してみたところ,すべてのlongtableのcountがきれいにクリアされた.一応マクロをコピーしてステップ実行して動作を確認しておこう.⇒動作は問題ないようだが,大きな問題がある.固有データ部を完全クリアすると,COUPLINGの保持するウィンドウハンドルのような環境パラメータまで失われてしまう.

このような維持しなくてはならないパラメータがまだ他にもあるのか?それともCOUPLINGだけの問題なのかを切り分けるためCOUPLINGを除外してその下流全体をCleanしたところ,データベースをオープンするところでBCMDTABLEのエラーが発生した.cmdtableが存在しないというエラーだ.BCMDTABLEはC++のベタクラスで,QUICKDBのメンバー,QUICKDBはKAKEIZUのメンバーだ.これらはKAKEIZUの固有データ部にあるので,全クリアすれば当然消えてしまう.

一般のオブジェクトはほとんど全クリアして問題ないと思われるので,全クリアしないオプションを選択できるようにしてみよう.まず,

virtual void NODULE::doClean(void){ memset(dataptr(), 0, datalen()); }

という関数を作り,COMMONHEADERPARTでもそれを別途実装する.デフォルトのdoCleanを使いたくない場合,COMMONHEADERSHORTを適用すれば,固有のdoClean関数をカスタマイズできる.doCleanの実装を強要するためには,NODULE::doCleanは純粋仮想関数としておくのがよいのかもしれない.⇒いや,それもあまり意味がないのではないか?noduleクラスに実装すればエラーは発生しなくなる.⇒どちらでも大勢に影響はないので,nodulle::doCleanを実装するようにした.

系統並び替え中,TRIBELIST::GoDownStream→ CARDLINK:DownStream…でNAMEBOX::makeProxyがgettreeviewで空というエラーを出した.gettreeviewはBobjectの関数で,以下の行でゼロ復帰している.if (metrix) return metrix->treeview;

これはかなりまずいと思う.metrixはクラスMETRIXのインスタンスでMETRIXはクラスmetrixから派生している.このmetrixの中にTREEVIEWへのべた参照が入っている.これはまずい.metrixからオブジェクトへの参照を排除しなくてはならないが,暫定的にこの行を止めておこう.この辺りは相当古い化石のようなコードだ.

nodule::Disposeで(!LIFE || DELETED == 1)で停止した.LIFEやDELETEDはNODULEのヘッダ部にあるので,今回の修正で影響するとは考えられないのだが… 障害ノードはREFLINK #4334でSIMPLENODEを参照している.SIMPLEGRAPH:DecompConnectedComponentの冒頭で連結成分リストをcancelしているところだ.~ARRAY()→CleanSlotで起きているようだ.⇒nodule:Disposeがダブって実行されている.⇒コンパイルエラーを避けるために入れた仮修正が残っていた.

ARRAYの派生クラスで「テーブルをクリアしたことにはならない@20201127」という理由でCleanSlotをARRAY<>::CleanSlotに修正しているが,これは誤りだ.この誤りはBASETABLEではすでに補修されているが,REFLINKとTRASHCANに残っている.⇒対処した.

COUPLINGもCOMMONHEADERSHORTを使うようにした.doCleanからはinitializeを呼び出すようにした.これで完全にクリーンなコア骨格木を樹立することができた.⇒INITIALSTATEからINITIALIZINGに遷移するまでの間に複数回INITIALIZEDが掛かってくる.COUPLING:EraseFamilyTreeの入口でCHAOTICSTATEをセットしている.⇒現フェーズがINITIALIZEDより大きい場合にのみ更新するようにした.

カード削除,新規カードその他系図木のトポロジーが変化するような操作を行ったときにはフェーズが一旦INITIALIZINGになったあと,INITIALIZEDをパスしてTOPOLOGICALSORTにジャンプする.むしろ,INITIALIZEDとすべきだろう.⇒このようなケースでは一律

if (PHASE > INITIALIZED) topology->SetPhase(CHAOTICSTATE);

としておこう※.※⇒(INITIALIZED=CHAOTICSTATE)

複数カードを選択して1枚ずつ削除するとき,2枚目でTREEVIEW:setSelectedNameのエラーが出た.「選択領域表示状態での主選択カード切り替えは不可」とされる.カード削除→TREEVIEW:CleanSansyoでsetSelectCardが実行されている.⇒動作的には問題ないように思われる.実際のところこれ以外どうしようもない.暫定的に引数のNAMEBOXが空のときは停止しないようにしておこう.

複数選択してまとめてカード削除したら,nodule::ReferenceControlで「参照元ノードリスト不記載」になった.FAMILYTREE:DeleteAllCardで一括削除しているが,実際には1点づつの削除を反復しているだけなのだが… カード一括削除でつねにエラーになる訳ではなく,何か特殊な条件があるようだ.⇒多重カードの関係かもしれない… #61と#62を一括削除して再現する.このケースを追いかけてみよう.

2枚目のカード#62CARDLINKのデストラクタでCleanSlotを実行しているところだ.エラーが起きているのはCARDLINK::HUBOS[0]というスロットだ.#61と#62は親子関係なので,#61の結婚リンクが入っている※.#61が削除されたからと言って,この結婚リンクが自動的に消滅するというものでもない.配偶者や子どもがいれば本人が削除されても結婚リンクが存続する場合はあり得る.※⇒これは事実誤認

参照リスト管理は死んだ後まで付いてゆく仕組みなので間違いようがないと思われるのだが… CARDLINK#1238→MARGLINK1#563という参照なので,#563の参照リストを追いかけてみることにしよう.⇒不記載となっているのは#1238[7]→#563だが,スロット7からの参照というのは明らかに登録されていない.このカードは親を2組持っていて,一方の結婚リンクが抹消されたあと,結婚ページの移動を行っている.これはかなりまずい.おそらく,このような操作を行っているところは他にもかなりあると思われる.⇒このケースについては解決した.

▲まとめて選択→一括削除してTRIBEBOX::setTribeRealnodeでエラーになった.上記と類似したことが起きているのではないか?選択範囲は以下のような感じだ.

image

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA