先日、拙作の二画面ファイラー Footloose2 を紹介する記事を書きました。
アプリの説明を中心に、使い方やおすすめ設定など主に機能面にフォーカスした記事です。これを読んで実際に使っていただいた方がいればとても嬉しいです。
さて、前回の記事はアプリに興味を持っていただきたいと思って書いたので、使っている技術や制作の流れなどユーザーに関係のない部分には触れていませんでした。しかしながら、せっかくなのでそこら辺についても記事にしたいなあという想いもありました。
そこで本記事ではそういった「裏側」の話を書こうと思います。まあポエムみたいなものです。ざーっと読み流してもらえればと思っています。
二画面ファイラーの思い出
二画面ファイラーというのはとても個性的で楽しいアプリなのですが、世間一般を見ているとやはりちょっとマイナーというか、知る人ぞ知る的な種類のアプリだと思います。
ターミナルで動く二画面ファイラー「Midnight Commander」
自分は今でこそ Mac ユーザーですが、大昔は Windows 使いで、Explorer の使いにくさにストレスを感じているエンジニアのひとりでした。そんな時に出会ったのが あふw です。
最初にこのアプリを使ったときの驚きは今でも忘れられません。最初はなんだかすごくとっつきにくいなあと思ったのですが、使いこなせるようになると劇的に業務が効率化されもはや手放せないツールになってしまいました。世の中にはこんなに面白いツールがあるんだなあとしみじみ思ったのを覚えています。
FDclone や Midnight Commander、だいなファイラー や 内骨格 など、いろんな二画面ファイラーを触ってみてどれもユニークで面白く、ほんとにワクワクしましたね。
その後 Mac に鞍替えするのですが、Mac には あふw がないため一気に仕事の効率が落ち、これはヤバいぞと焦ったのをすごく覚えています。なんとかしなきゃと思い、わざわざ Parallels Desktop を購入したほどです。
Parallels は Windows のアプリを仮想的に Mac で動かせることができたので、しばらくの間はあふwを Mac で動かして何とかしのいでいました。しかしやはりネイティブアプリではないためだんだんと足が遠のいていき、やがては標準の Finder に落ち着くことになります。
うーん、使いにくいな…と思いつつも、なんとなく惰性でだらだら使い続けてあっという間に時間が経ってしまいました。しかしながら あふw のようなアプリを Mac でも作りたいという想いはずっと持っていて温めていまして、それをようやく実行に移して形にしたのが5年前の Footloose でした。
あらためていま二画面ファイラーを探してみると結構たくさんあって驚きますね。以前はこんなになかった気がするんですが…
単純に見つけられなかっただけなのかあるいは5年前はなかったのかは分かりませんが、ずいぶんたくさんの選択肢があり、そしてそれはとても良いことなのは間違いありません。いい時代だな〜
なぜ作り直そうと思ったか
前の記事でも書きましたが、一生懸命作って愛着もあった Footloose ですが、それほど使わないまま塩漬け状態になっていました。当時使っていた Intel Mac があまりに古すぎて満足行くパフォーマンスが出なかったのが主な原因ですが、作りきったという事実に満足してしまったのも一因だったかなと思います。燃え尽き症候群みたいなものだったのかもしれませんね。
いつかは作り直すのもいいかなと思いつつ、前述の通り二画面ファイラーは世にたくさんあるのでもはやわざわざ自分で作る意味はないかなとも感じており、長らく放置してしまいました。
Rust を勉強し始めたのはそんな中でした。
わたしはフロントエンドエンジニアとしてずっと仕事をしてきたので、プログラミング言語というと基本的には JavaScript がメインです。バックエンドの知識もないわけではないですが業務としてはフロントエンドをずっとやってきました。Ruby などに手を出した時期もありましたがあくまで興味の範囲内でした。
そんな中でなにか新しい言語をひとつ身につけたいなあと漠然と思っていて何となく調べ始めて気になった言語のひとつが Rust でした。最近よく聞くなあ、めっちゃ速いらしい、独特の言語仕様ですごく難しいらしい、などなど色々な情報を目にする中で、自分の中でなにかむくむくと膨れ上がるのを感じました。
ちょ、ちょっとやってみようかな…
いま思うと理由は不明ですが当時なぜか Udemy に興味を持っていて、ちょうどセールだったので Rust のコースをふたつばかり購入し観始めたのがスタートでした。メモリ管理を中心に解説されていて、自分にはかなり敷居が高く正直よく分かりませんでしたが、それも悔しいので分かった気になって次に書籍を買いました。合わせて公式ドキュメントも読み進めつつ、ネットでいろんな初心者向け記事を探してはむさぼるように読む毎日。全然わかんねえよ…これホントにオレにできんのか…などなど頭を抱える日々が続きましたが、しかし…
お、面白い…面白いぞ Rust!
Rust、面白いです。おそらく10%も理解できてないと思うけど、十分面白い。JS とは発想がぜんぜん違うのが楽しい。Result 型や Option 型は最初はなんでこんな面倒な仕組みが必要なんだろうと思ったけど理解できてしまえば「コレ JS にも欲しい!」となり、冗長に感じた match 文も慣れてくると美しい。所有権や借用も Linter の力を借りてなんとか書けている。学べば学ぶほど目から鱗といった感じでああもっと書けるようになりたいなあと思いました。
そんな時にふと、Footloose を Rust で書いたら勉強にもなるし面白いんじゃないかと思ったのです。Footloose は Web アプリとして作られていて、クライアントは React です。ファイル操作を行う必要があるのでサーバーは Node.js で書いていたのですが、ここを Rust に置き換えたら面白い。この思いつきが Footloose を作り直そうと思った直接のきっかけでした。
結局それから一年ほど Rust を学びながら試行錯誤を繰り返し、作っては捨て作っては捨てをしながらようやっと完成にこぎつけたわけです。いや〜長かったです。でも楽しかった。
Footloose2 の技術スタック
さて、ここからが本稿の本題です (前置きが長い)。
クライアント
Footloose2 のフロントエンドは React で作っています。特にフレームワークは使っておらずプレーンなコードになっています。React Compiler は今だとちょっと気になりますが、作り始めた当初は知らなかったため未導入です。
ファイラーなのでエントリの一覧表示・操作がメインの機能ですが、最初は単純に全エントリを描画してブラウザのスクロール機能を利用して閲覧させるという至ってシンプルな設計でした。しかし百やそこらの数だったら問題ないのですが、これが例えば万の単位くらいになってくるとやはりちょっとパフォーマンスに問題が出てきます。
memo 化するなどして再レンダリングを防いだとしても props の shallow comparison は走るので、さすがにコストが馬鹿になりません。カレント行や選択行には className や aria 属性を付けたいので、そうなるとカーソルを動かすたびにループが回ることになりもっさり感が出てしまいます。
最終的にこの部分は見えている範囲だけをレンダリングする仮想スクロールにしました。最初は TanStack Table などを使っていたのですが、いろいろ考えなければいけないことが出てきたので自前で実装しました。
状態管理には jotai を使っています。二画面ファイラーなので同じ目的の atom が左右ひとつずつあり atomFamily を多用しています。かなり細かく atom 化したので気がつくと atom 地獄のような状態になっており、ここはちょっとミスったかなあ…と思っています。derived atom も多用したのでデータフローが追いにくくなっており、ここら辺も含めてリファクタの余地があるなと思っています。
キーボードショートカットの実装は Mousetrap を使っています。かなり古いライブラリですが、自分はやっぱこれが一番使いやすいんですよね〜。hooks 系のライブラリも使ってみたんですが、めぐりめぐってやっぱり Mousetrap に落ち着いちゃったという感じです。Gmail ライクなシーケンスも使えますしね。一部のキーコンビネーションはブラウザで予約されていてキーイベントを捕捉できないということを初めて知ったのも良い思い出です。
あとどうしても使いたかった migemo。これの JavaScript 実装である jsmigemo も使っています。これはもう完全に個人的なこだわりですね (笑)。
サーバー
前述の通りサーバーは Rust で書いています。リソースの配信は Web サーバーで、コマンドの実行は WebSocket サーバーで行っています。クライアントから WebSocket のメッセージが送られてきてサーバーでそれを受けてファイル操作等のコマンドを実行するという仕組みです。
Rust についてはとにかく勉強しながら書いたので、作っては捨て作っては捨てということを繰り返しました。色々なクレートを試して最終的に axum を使って構築しています。設計上非同期処理が中心になっているんですが、さすがに初学者に非同期処理はちょっと敷居が高かったようで試行錯誤を繰り返しました。
Tokio (非同期処理のクレート) を使っていますが最初はよく分からず変なコードをたくさん書いては調べて直して…といった感じでした。改めて見直してみると (あまり大きな声では言えませんが) ちょっとこれはどうなんだろう…と思うところもあるので、今後の課題として自分では考えています。
前作ではコピーやファイル圧縮といったファイル操作は基本 JavaScript で書いていたのですが、これがパフォーマンス低下の原因にもなっていたので、今回はシステムコマンドを直接コールするという設計に変えました。プラットホームに依存してしまいますが、処理的には軽く設計としてもシンプルになったと思います。
苦労した点は、進捗を持ったコマンド処理ですね。サーバーで非同期実行しつつ、定期的にクライアントに進捗を報告するのがちょっと難しくて苦労しました。ここら辺は Tokio の理解が足りなかったのが原因です。しっかり勉強しないとですね。
ファイラーなのでディレクトリを監視して変更が入ったらそれを表示に反映するわけですが、これは単純にタイマーを回して実現しています。最初は notify クレートを使っていたんですが、例えば自身や親ディレクトリがまるっと削除された場合にそのことを感知できないため導入をやめました。ポーリングモードすればこれを実現できるんですが、それだったら自分で書いちゃってもいいかなと思った次第です。タイマーで定期的にディレクトリのエントリ一覧を取得するだけなので簡単かなと思ったんですが、色々と考えないといけないことも多くそれなりに大変でした (苦笑)。ちなみに前作では npm の chokidar を使っていて、やっぱり同じ問題にぶつかってポーリングモードにした記憶がありますね。
Rustについて
前日の通りわたしはフロントエンドエンジニアなので基本的に TypeScript などの LL をメインに使っています。システム言語は Rust がほぼ初めてです。C++ を少しかじったことがあるんですがあまりの難しさに挫折しました。
実は Go か Rust かで迷ったのですが、特別な理由はなくて、なんとなく Rust を選んじゃいました。うろ覚えですが、ちょっとカッコいい印象受けた気がしますね。
Rust を仕事で使う事はないので本を片手に独学で勉強しました。少しずつ少しずつ理解が進んでいくあの感じ、好きですね。勉強してる時は自分が知っている言語とのあまりの違いにびっくりすることもしばしば。新鮮な驚きですごくワクワクしました。この言語仕様、JS にもあったらいいのになと思うことがたくさんあったし、逆もまたしかりでした。とにかく全然違うので、新しく学ぶ言語として最高の選択をしたと思っています。
最初は所有権や借用など独特の言語仕様が難しく感じましたが、慣れてくると非常に論理的で気持ちいいと感じるようになりました。一方で難しかったのがライフタイムです。理論的には分かるんですけども、ジェネリクスが絡んでくると一気に難しくなって正直このあたりいまだに理解が追いついていません。あと動的ディスパッチもまだよく分からないですね。全般的にトレイトが絡んでくるとちょっと難しいと感じます。
特に苦労したのがテストです。どうやら automock というのを使ってテストするというのがセオリーらしい…と見よう見まねでやっていたんですが、かなり難しい。JS のテストに慣れているとかなり凹みますね。
Rust の好きなところは Result 型や Option 型、Enum を match 文できれいに網羅できるところ、Enum に値を持たせられるところ、シャドーイング、ワイルドカードパターン、ソースコードとテストが一箇所にまとまっているところ、ドキュメントテストなどなど…。いやあ Rust の個性がことごとく気持ちいいですね。すごく美しくて楽しいです。
まだ初心者の域を出ないので、先は果てしなく長いですが、コツコツコツコツ書き続けて、いつか「わたし Rustacean なんです」と堂々と言えるようになりたいですね (今はまだちょっと言えない)。
AI について思うこと
昨今、AI を使ったバイブコーディングやエージェンティックコーディングが当たり前になりました。わたしの勤める会社でも AI にコードを書いてもらいそれをレビューするということが日常になっています。気づくと自分でコーディングする機会がめっきり減り、AI に頼り切っていることに気づきます。わたしは仕事をしながら学んでいくタイプで、JavaScript 等もそうやって身につけてきたので、今のように AI が実装するという状況ではちょっとスキルを上げにくいなあと感じています。
とはいえ AI の方が早いですから。自分で試行錯誤しながら時間をかけて実装していると会社的には望ましくないでしょう。とにかく、AI を使え、AI で効率化しろ、AI で早く実装しろ、というのが当たり前に組織でも求められています。つまりバイブコーディングしないで人間が実装する、というのはもはや贅沢なんですね。最近それを強く感じるようになりました。
こういった状況は学習者にとってはなかなか致命的です。AI に書かせても自分のスキルにはなりません。今回 Rust を勉強するにあたって AI で作ることももちろんできましたが、それだとまったく勉強にならないためやめました。Footloose2 はクライアントもサーバーも、実装コードからテストまですべて自分で書いています。バイブコーディングは一切していません。
もちろん GPT に死ぬほど質問はしましたけれどコード自体はすべて自分で書いています。すごく大変で時間もかかりましたが、だからこそ Rust をちょっとだけ書けるようになったんだと思います。AI に任せていたら今もまだまったくのよちよち歩きだったと思いますね。
実はテストくらいは AI に書いてもらってもいいかな…とも思ったんですが、やっぱり自分で書くことにしました。Rust のテストについて何も知らないのにいきなりそこを AI で書いちゃだめでしょという感じです。おかげで Rust のテストの書き方を学ぶことができました。正直ちょっと自分にはかなり難しく苦労しました。それでも自分で書いたのでテストコードすらなかなか愛着がありますよ。いま見てみると相当ひどいテストですけども…
ちなみに大きな声では言えないですがフロントエンドのテストはまだないです…。これに関しては、わたしがフロントエンドエンジニアということもありよく知っているので、後日 AI に書いてもらうのでもいいかな〜と思っています。
おわりに
いろいろと AI について書きましたが、AI を否定してるわけではないですよ。むしろ個人開発で AI を使いこなせるようになればもっともっと楽しくなるだろうなとワクワクしています。ただ今回の開発は勉強が大きな目的のひとつだったのでバイブコーディングしなかったのは正しい選択だったと思っています。
ちなみにこの記事も自分で書いています。自分は文章を書くのが (遅いですが) 大好きなので、こんな楽しいこと AI に取られてたまるかい、という感じです。
そしてコードを書くのも大好きです。自分はコード書いてないと死んじゃうですよね。AI に仕事の大部分を任すことになっても、隙を見て自分で手を動かすことを止めることはないと思います。
長文失礼いたしました。それではまた。👋
