つぎはぎのメニューを、一本の ngwMenu にまとめた話
同じデータから3通りに描かれていたNGWのメニューを、単一のサーバ描画メニュー ngwMenu に一本化した。標準とドロップダウンを1つの土台で出し分けるハイブリッド構成へ。150超のアプリを一切触らず、差引 −2,403 行。動くだけのコードを、読んで美しいコードへ近づける記録。
同じデータなのに、描き方が3通りあった
NGW の画面の一番上にはメニューが出る。文書管理・財務支援・人事給与といったサブシステムのアイコンが横に並び、いま開いているアプリ専用のメニューがその下に続く。ユーザーから見れば、ただのメニューだ。
中を開けると、そのメニューを描くコードは 3系統あった。
- 新しい
ngwMenu— サーバ側でHTMLを組み立てる新方式。 - 2026ドロップダウン —
ajaxNgwMenuFunc.php、1,516行。 - 旧アイコン列 —
ajaxNgwFunc.phpの中のfncMakeNgwMenu/fncMakeSubSystemMenu。
職員ごとの設定値 STAFF_MENU_TYPE で、このどれを使うかが分岐する。厄介なのはここからだ。150を超えるアプリが、画面を移動するたびに MakeNgwMenu / MakeSubSystemMenu を同期AJAX(async:false)で呼び、メニューの入れ物 #NgwMenu を毎回まるごと詰め直していた。
データ源はどれも同じだ。以前の記事で書いた NGWT_APP_MENU テーブルの JSON。同じデータを読んで、同じメニューを、3通りのコードで描いていた。 描画関数どうしは約9割が同じ内容で、権限の判定も、自治体ごとの語の置き換えも、コピペで枝分かれしていた。
つぎはぎ、という言葉がちょうどいい。今回はこれを一本の布に縫い直す話だ。
ハイブリッドとは何か — 1つの土台、2つの見え方
めざした形を先に書く。新しい ngwMenu はハイブリッドだ。何と何のハイブリッドか。
データ源はこれまで通り NGWT_APP_MENU の JSON ひとつ。職員ごとの STAFF_MENU_TYPE によって、同じデータから2つの見え方を出し分ける。
- 標準メニュー — サブシステムのアイコン列を常時表示する。下位アプリを持つサブシステムには、アイコンにドロップダウンが付く。アプリ内のメニュー(AppMenu)は本文の先頭に置く。
- ドロップダウン(DD)メニュー — HOME 画面ではアイコン列でナビゲートし、アプリを開いている間は HOME ロゴのドロップダウンでナビゲートする。AppMenu は画面の最上部に持ち上げる。

ハイブリッドの核心は、両モードが同じ土台(アイコン列=NGW_MENU)を共有していることにある。標準モードでもサブシステムにはドロップダウンが付き、DDモードでも HOME ではアイコン列を出す。どちらか一方に倒すのではない。同じ部品を、文脈に応じて出したり畳んだりする。二択ではなく、地続きの一つの仕組みにした。
いちばんわかりやすいのが文書管理だ。

アプリを開くと、DDモードはこうなる。上段の余白を AppMenu に譲り、ナビゲーションは HOME ロゴのドロップダウンに畳む。

大きく壊さず、一歩ずつ縫い直す
150超のアプリが依存している土台を触る。一気に全部を差し替えれば、必ずどこかで事故る。安全に一歩ずつ進めた。コミットの順番が、そのまま作業の順番だ。
- 一本化 — ヘッダーの「旧メニューを使う」
false分岐を撤去し、常に新方式を通す。 - 旧AJAX撤去 —
ajaxNgwMenuFunc.php(1,516行)を丸ごと削除。呼び出していた再充填JSは、互換のため no-op 化。 - 未使用コード除去・分離 —
ajaxNgwFunc.phpから旧アイコン列ビルダーを取り除き、アプリ内メニューを別モジュールへ切り出す。 - リネーム — 移行期の仮称
menu2を、正式名ngwMenuへ。 - DD改善 — HOME とアプリ内で挙動を分け、AppMenu の置き場所を整える。
- 体裁調整 — 本文とのすき間、アイコンの収まりを詰める。
一番効いたのは、2番の no-op 化だ。
150超のアプリを、1行も触らずに済ませる
普通なら「メニューの呼び出し方を変えたなら、呼んでいる150箇所を全部直す」となる。それはしなかった。
新しい ngwMenu はサーバ側で描画される。ページを開いた時点でメニューはもう出来上がっている。アプリ側が遷移のたびにメニューを詰め直す必要は、もうない。
各アプリが呼んでいる MakeNgwMenu / MakeSubSystemMenu の中身を空にした。関数はそのまま残るので、150超の呼び出し元は1行も変えなくていい。呼ばれても何も起きないだけだ。副産物として、async:false の同期往復——画面遷移のたびにサーバの応答をブラウザが待っていた、あの引っかかり——も根こそぎ消えた。コードが減って、体感速度まで上がった。
AppMenu は、静的に吸収してはいけなかった
一本化の途中で一度手が止まった。アプリ内メニュー(AppMenu)だ。
上段メニューはサーバ静的描画に寄せられた。ならば AppMenu も同じように吸収すればいい。そう思った。間違いだった。AppMenu は、いま選択中の行・モーダルかどうか・登録モードか・その人の権限といった、画面のその瞬間の状態に依存して動的に変わる。サーバ描画にまるごと寄せると、この動的更新が壊れる。
吸収ではなく、モジュール分離にとどめた。AppMenu は新設した ngwAppMenuFunc.php(+808行)へ独立させ、動的AJAXのまま残す。上段メニューは唯一・サーバ描画。アプリ内メニューは動的・独立モジュール。役割で2つに分ける。 これが今回の設計判断の芯だ。
同じデータ源・3系統の描画、という Before は、同じデータ源・役割の違う2モジュール、という After になった。
フラグは、触れない場所にあった
新旧を切り替えるフラグ _USE_NGWMENU2_ は、DB(NGWT_CONSTANT)から読んで define される。ところが、それを受ける設定ファイルは www-data 所有かつ .gitignore の中だった。こちらからは編集できない。
フラグを消したいのに、フラグの定義に手が届かない。しばらく唸った。そして発想を変えた。分岐そのものを無条件化して、フラグを不要にする。 切り替えの必要がなくなれば、触れないフラグがどこにあっても関係ない。つぎはぎを消すというのは、こういう外堀も含めて、である。
数字で見る
| 項目 | 値 |
|---|---|
| 差引行数 | −2,403 行(+948 / −3,351) |
| 変更ファイル | 13 ファイル・6 コミット |
ajaxNgwMenuFunc.php | 全削除 −1,516 行 |
ajaxNgwFunc.php | 2,160 → 637 行 |
ngwAppMenuFunc.php(新設) | +808 行(AppMenuの移設ぶん) |
追加のほとんどは AppMenu を切り出した新モジュールだ。実質はほぼ純減で、差引 −2,403 行。消したのは重複と未使用コード。残したのは動くために本当に必要なものだけ。
念のため、消す前に旧メニュー一式は tag menu-legacy-2026-07-13 と branch として退避してある。戻れる状態を作ってから、戻らなくていい形に進んだ。
AIとの協働作業での学び
つぎはぎは一度に生まれない。良かれと思った継ぎ足しが積み重なって、いつの間にか一枚布から遠ざかる。今回の3系統も、それぞれの時点では正しい判断だったはずだ。だからほどくときも一気には壊せない。false 分岐を消す。旧AJAXを消す。呼び出し元を no-op で無害化する。名前を正す。戻れる粒度に切って、一歩ずつ。 AI と組むと、この「小さく安全なコミットに割る」計画づくりと、「150箇所を触らず一括で無害化する」横着の効くやり方が、格段に回しやすい。
今回いちばん確かめたかったのはこれだ。新しい NGW は、動くだけでは足りない。 同じデータを3通りに描いていたコードは、動いてはいた。ユーザーは困っていなかった。それでも、読んで美しくないコードは次の誰かを必ず困らせる。次の誰かとは、半年後の自分である。
……というのを、私は ajaxNgwFunc.php の2,160行を上から読みながら痛感していた。書いたのは私だ。文句を言う相手がいない。つぎはぎを、一本の布へ。動くだけのコードから、読んで美しいコードへ。