Skip to content

Latest commit

 

History

History
62 lines (44 loc) · 8.32 KB

File metadata and controls

62 lines (44 loc) · 8.32 KB

Architecture & Refactoring Notes

今回のリファクタリングによるアーキテクチャの改善点まとめ

今回のコード改修は、単なるファイルの分割ではなく、「関心の分離」と「状態管理の安全性向上」を目的としたモダンな設計への移行です。フレームワーク(Angularなど)の導入なしに、標準のES Modulesとクラス設計のみで保守性の高い実装を実現しています。

具体的な改善点は以下の5点です。

1. モノリス(1ファイル)からの脱却(ES Modulesの導入)

約800行にわたりHTML、CSS、JSが混在していた元のファイルを、役割ごとに分割しました(main.js, SerialManager.js, FileManager.js, utils.js)。 これにより、見通しが改善され、特定の機能を修正する際の影響範囲がひと目でわかるようになっています。

2. グローバル変数の削減と状態のカプセル化

旧コードにおける最大の技術負債は、waitForSerialIntransferFileObj といったグローバル変数がファイル全体に散らばり、状態管理が予測不能になっていた点です。 新コードでは、これらの状態を SerialManagerFileManager のクラスインスタンス内にプライベートな状態としてカプセル化しました。これにより、外部からの意図しない書き換えや、複数処理の競合バグを構造的に防いでいます。

3. 複雑な非同期処理(シリアル通信)の安全性向上とPromise化

シリアル通信のストリーム(断片的に送られてくるデータ)を待ち受ける処理において、旧コードはループ内で常にグローバル変数をチェックする力技の構造でした。 新コードではこれを writeAndWaitFor というPromiseベースのメソッドに隠蔽しています。呼び出し側(アプリケーション層)は「この文字列が返ってくるまで待つ」という直感的な await 処理を書くだけでよくなり、非同期処理のコールバック問題や同期ズレを解消しました。(なお、別ウィンドで開かれる連携先アプリの改修がまだのため、windowオブジェクトに互換性保証用のインターフェースをあえて露出させています)

4. UI操作(DOM操作)とビジネスロジックの分離

旧コードでは、ファイルのダウンロード処理中に直接 document.getElementById を呼び出してプログレスバーを更新するなど、ロジックとUIが密結合していました。 新設計では、ファイル操作クラス(FileManager)はDOMの存在を知らず、進捗や結果をコールバック関数でUI層(main.js)へ返す設計にしています。これは、昨今のフロントエンド開発におけるベストプラクティスに沿った設計です。

5. チーム開発における「持続可能性(保守性)」の担保

コンポーネント指向やRxJSのような概念を持つAngular等のフレームワークを導入すると、開発チームの学習コストが跳ね上がる(バス係数の低下)リスクがありました。 今回の設計は、「標準的な最新のJavaScript(ES6+)の知識」さえあれば誰でも読み解き、保守・拡張ができるという点で、現在のプロジェクト規模/構成に適した技術選定としています。


今回のアーキテクチャにおける通信制御の考え方

今回のコード改修は、RxJS(Reactive Extensions)やAngularのDI(依存性注入)が提供する高度なアーキテクチャ設計の思想を、標準的なVanilla JS(ES ModulesとAsync/Await)の比較的コンパクトなコード(コアのSerialManager.js 約200ステップ)に落とし込んだものです。ストリームの分離やプロンプト同期、ハードウェア特有の泥臭いハックを安全かつ体系的に構築できています。

1. RxJSのストリーム設計とVanilla JSの対比(関心の分離)

Angular版の設計思想(責務レイヤの分離)は、今回のVanilla JSアーキテクチャでも以下のように再現・担保されています。UI層が直接ストリームの生データを触ることはありません。

概念(RxJS版の設計思想) 今回のVanilla JS(ESM)版での解決策 メリット
terminalText$
(UIライブ表示専用)
SerialManager.onDataReceived コールバック 受信データをxterm.jsに流す専用経路。プロンプト判定ロジックとは分離。
receive$
(内部バッファ・プロンプト同期用)
SerialManager内の receiveBuffercheckWaiter() 生のチャンクをクラス内に隠蔽。UI層を汚染せず、内部で文字列を結合して待機状態を解決。
exec$ / readUntilPrompt$ SerialManager.writeAndWaitFor() 非同期のPromiseとして実装。呼び出し元は await でストリームの同期待ちが可能になり、直感的。
Facade / Pipeline / Transport層 main.js / FileManager / SerialManager 役割を3つのファイルに分割。循環参照を防ぎ、単一責任の原則(SRP)を遵守。
concatMap / exhaustMap
(キューイング・直列化)
SerialManager内の queueprocessQueue() 非同期のコマンド呼び出しを内部でFIFOキューに蓄積し1つずつ順次実行。競合(Race Condition)を解決。

2. Promise(Async/Await)とキュー機構によるストリーム同期と直列化

RxJSの filtertakeUntilbuffer などを駆使して構築するプロンプト待機ロジックを、「バッファループ機構」と「Promiseの解決(resolveの保持)」の組み合わせで実装しています。 さらに、RxJSの concatMap が担う「ストリームの直列化」を、SerialManager 内部の配列(this.queue)を用いた再帰的なタスク消化ループ(processQueue)で再現しました。これにより、バックグラウンドで受信ストリームを監視しながら、衝突することなく複数の非同期送受信を実施する制御を実現しています。

3. 現実のハードウェアに対する「泥臭いハック」のカプセル化

ソフトウェアレイヤーでストリームを構築しても、相手はRaspberry Pi ZeroのUSB OTGシリアルのため、大量のデータを一気に流し込めば、バッファオーバーフローを起こしデータが欠損することがあります。これを防ぐために、旧コードにあった「泥臭いフロー制御」が必要となります。

今回の設計では、この「泥臭いハック」を、クラス設計の中に安全に封じ込めました。

  • ダウンロード時のフロー制御(moreコマンドの活用) RxJS側でチャンクを結合するだけでなく、Pi側に対して more -50 で50行ずつ出力させ、アプリ側が5ミリ秒の sleep を挟みながらスペースキー(" ")を送って次を要求する。このハードウェアの限界に対応した制御を FileManager.getFile() 内の while ループとPromiseで実装しています。
  • アップロード時のフロー制御(512バイト分割) Base64文字列を全送信するのではなく、lineLength = 512 バイトごとに区切り、改行を送り、1ミリ秒待機する。この一連のシーケンスも FileManager.saveFile() にカプセル化し、UI層には進捗(パーセンテージ)だけを通知する設計にしています。

総括:なぜこの設計なのか

RxJSは非常に強力なツールですが、本プロジェクトの規模とチームの持続可能性を考慮した場合、「学習コストの高さ」と「バス係数の低下(特定個人への依存)」という深刻な技術負債を生むリスクがあります。 本実装は、「RxJSが目指す美しいストリームの責務分離」と「Pi Zero特有の泥臭いハードウェアハック」を、標準的なJavaScriptの文法(ESM + Async/Await)で比較的コンパクトに両立させたものです。