Refactoring English: Month 13

Michael Lynch

Refactoring English:13カ月目

原文は Michael Lynch により に公開されました。 このブログを購読する

一行サマリー

集中力について書きながら気が散ってしまう

初めての方へ

こんにちは、Michaelです。ソフトウェア開発者で、小さなインディーテック企業を経営しています。現在、Refactoring English: Effective Writing for Software Developersという本を執筆中です。

毎月、こうした振り返り記事を公開して、本の進捗や仕事全般の近況を共有しています。

ハイライト

  • 購買力平価に基づいて、本に地域別価格を導入しました。
  • 初めてのFlutterアプリを作りました。
  • 初めてのクロス言語ライブラリを開発中です。

目標の自己評価

毎月のはじめに達成したい目標を宣言しています。その結果は次のとおりです。

Refactoring Englishのサイトへ人を呼び込むゲームを公開する

この記事は賭けでした。Hacker Newsのフロントページに載らなければ新しい読者には届かず、チャンスは2026年の最初の2週間しかなかったからです。

幸い、この記事はHacker Newsで1位を獲得し、約22時間フロントページに留まりました。これは他の優れたテックライターを紹介するという、これまでの戦略の延長でもあります。この戦略が好きなのは、自分にも読者にも紹介する書き手にもプラスになる、Win-Winだと感じられるからです。

Hacker News予想ゲームは今も80%ほど完成した状態で止まっています。ほぼ完成しているのに、面白くないと感じてしまい、完成させるモチベーションが湧きません。ただ、最後までやり切ってみんなの反応を見てみたいとは思っています。

Refactoring Englishの2つの章を公開する

  • 結果:2つの章で進捗はあったが完成には至らず
  • 評価:D

皮肉なことに、取り組んでいる章のテーマはモチベーションと集中力なのですが、MeshCoreの実験に気を取られて執筆が妨げられています。年が明けてからは集中を保てるようになってきましたし、気が散ること自体が、集中力を取り戻す方法について書くための新鮮なネタになっているので、むしろ役立っています。

趣味で作る家族向け写真共有アプリの設計ドキュメントを書く

ここでも12月はMeshCoreの実験に気を取られ、思ったほど進みませんでした。設計ドキュメントは大好きで役に立つと思っていますが、書くのはとにかく退屈なので、すぐに成果が得られる別のことに逃げたくなる誘惑に常に駆られていました。

Refactoring Englishの指標

指標2025年11月2025年12月変化
ユニーク訪問者数7,6082,266-5,342 (-70%)
予約販売による収益$1,018.48$492.55-$525.93 (-52%)
スポンサー収益$48.25$48.25$0.00 (0%)
合計収益$1,066.73$540.80-$525.93 (-49%)

予約販売が落ち込んだのは、新しい読者を呼び込む新規記事がなかったためです(Hacker Newsの記事を公開したのは1月でした)。それでも、「パッシブな売上」が伸び続けているのは明るい兆しです。12月は約500ドルの予約販売がありました。訪問者数が同程度だった月と比べると、5月は241ドル、8月は361ドルだったので、数字は上向きです。本がより完成に近づき、薦めてくれる読者が増えるにつれて、毎月マーケティングの当たりを狙わなくても、パッシブな売上が伸び続けることを期待しています。

本に地域別価格を導入する

11月にブラックフライデーのプロモーションを実施したとき、ある読者からメールをもらいました。30%オフの20米ドルでも、アルゼンチンでは本としては依然として手が届かない価格だというのです。地域別価格を検討してはどうかと提案され、Steamのゲームはアルゼンチンでは米国より通常50%安く設定されていると聞き、それを目安にするのが良さそうだと考えました。

決済はStripeで受け付けていますが、ダッシュボードに地域別価格のオプションは見当たりませんでした。Stripeのナレッジベースで「Geographic pricing in practice: Why it matters and how to implement it.」という記事を見つけ、喜んだのも束の間、最後まで読んで「どう実装するか」の部分が丸ごと抜け落ちていることに気づきました。

つまりStripeは地域別価格を推奨しておきながら、実際にはその機能を提供していないのです。Stripeは他のすべての決済サービスを除けば最悪の決済サービスだ、ということを改めて思い知らされました。

そこでそのアルゼンチンの読者には、一度限りの対応として、手動で割引価格のカスタム決済リンクを作成しました。その過程で、価格をアルゼンチン・ペソ建てにすれば為替手数料を払わずに済むことにも気づきました。価格を22,000 ARS(約15米ドル)に設定したところ、価格にもチェックアウト体験にも満足してもらえたようでした。

その読者は、せめてブラジルやインドのように開発者数は多いが購買力が比較的低い国向けには、地域別価格を公開してはどうかと提案してくれました。

Stripeにネイティブ対応がなくても、手動でやったことを自動化するのはそれほど難しくなさそうでした。Sebastien Castiel氏が自身のコースで地域別価格を実装した記事を読み、そこからWes Bos氏の同じテーマの投稿にたどり着きました。

Sebastien氏は多くの技術的詳細を共有していましたが、その解決策はReactに大きく依存しており、私のサイトは素のHTMLとJavaScriptでできています。また、割引コードに頼る方式だったのも気に入りませんでした。多くの顧客が「自分は特別な割引を受けられていない」と感じてしまうからです。

数時間かけて、適正価格をその場で判定してStripeのチェックアウトリンクを動的に生成するクラウド関数を使った仕組みを実装しました。しかし、すべてを事前に計算しておけばサーバーサイドのロジックは不要だと気づき、そのクラウド関数は削除しました。

私の実装は次のような流れです。

  1. Stripeがサポートするすべての国/通貨のリストを手動で取得する。
  2. World Bankからデータを取得し、リストにある各国の購買力平価(PPP)を計算するスクリプトを書く。
  3. 各国の購買力を米国と比較して割引率を算出する。
    • 例:ブラジルのPPPは米国より54%低いので、54%割引になる。
  4. PPPが米国と15%以内の差しかない国は除外する(割引が小さすぎて手間に見合わないため)。
  5. 割引がマイナスになる国は除外する。
    • そうしないと、ルクセンブルクの顧客は2倍の価格を払うことになってしまう。
  6. 割引は最大75%に制限する。
    • そうしないとエジプトでの価格は4米ドルになり、手数料を引くと手元に残るのは3.50ドル程度になってしまう。
  7. リストに残った各国について、国別のStripe priceオブジェクトとStripe決済リンクを自動生成する。
  8. すべての国をサイト上のHTMLドロップダウンに配置する。

ユーザーは自分の国を選ぶだけで、その国用のStripe購入リンクが有効になり、自国通貨で支払えます。

性善説で運用しているので、IPジオロケーションやVPN対策はしていません。最安の選択肢を選ぶことを抑止するため、各国の割引率は非表示にしています。また、各国の現地通貨で価格を設定していること自体にも利点があり、もし不正に他地域を選んでも、自国通貨でない分だけ為替手数料で損をすることになります。

ただ、数字はあまり正確ではないと感じています。厳密なPPPで計算すると、米国で30ドルのものはエジプトでは4ドルに相当しますが、エジプトでプログラマー向けの正規の書籍が4ドルで買えるとは思えません。

Wes Bos氏が同じことをしたときは、読者に公正な価格を教えてもらっていました。私も同じようにしてみます。コメントを残すかメールで、あなたの国で開発者向け書籍の一般的な価格帯(現地通貨建て)を教えてください。

初めてのFlutterアプリを作る

12月に「My First Impressions of MeshCore Off-Grid Messaging」を公開しました。この技術にはワクワクしましたが、クライアントがすべてクローズドソースだと知ってがっかりしました。

そこでMeshCoreの探求は一旦休止しようと思ったのですが、MeshCoreコントリビューターのFrieder Schrempf氏が、私の投稿に興味深い視点で返信をくれました。

このトピックについて、あなたの考えの多くに共感します。個人的にはMeshCoreの価値はプロトコルにあり、ファームウェアやアプリなどのソフトウェア実装にはそれほどないと考えています。[…] MeshCoreがプロトコルとして成功し、広く使われるようになれば(今のところそうなりつつあります)、適切にメンテナンスされたオープンソース実装が続いて現れるでしょう(少なくとも私はそう願っています)。

私もFrieder氏に同意し、「それなら自分でMeshCoreのオープンソースの概念実証アプリを書いてみてはどうか」と考えました。

実は、すでにMeshCoreの概念実証アプリは存在していました。公式MeshCoreアプリの開発者であるLiam Cottle氏が、公式版のプロトタイプとしてMeshCore用のウェブアプリを以前に作っていたのです。公式の(プロプライエタリな)MeshCoreアプリを作る際に非推奨とされましたが、プロトタイプのソースコードは依然として公開されており、必要な機能のほとんどを備えていました。

そのプロトタイプをモバイルに移植するのはどれくらい難しいだろうかと考えました。MeshCoreはBluetoothアクセスやオフラインモードが必要なため、ウェブアプリとしては使いづらいからです。Flutter(Googleのクロスプラットフォーム・モバイル開発ソリューション)については、まあまあ良い評判を聞いていました。LLMなら、あまり手をかけずにウェブ版プロトタイプのコードをFlutterへ移植できるのではないかと思いました。

LLMにプロトタイプのFlutter移植を3段階でやらせる計画を立てました。

  1. Playwrightを使ってプロトタイプのウェブアプリのエンドツーエンドテストを書く。
  2. エンドツーエンドテストをそのまま維持して機能の同等性を担保しつつ、プロトタイプの実装をFlutterウェブアプリに移植する。
  3. FlutterプロジェクトにAndroidビルドを追加する。

うまくはいきましたが、どのステップも想定よりぎこちないものでした。

  • プロトタイプのエンドツーエンドテストを書く前に、セマンティックなHTMLとARIA属性を使うように変換する必要がありました。入力ラベルの多くがただの素の<div>だったからです。
  • Playwrightのテストをそのまま維持することはできませんでした。Flutterはウェブアプリ向けにセマンティックなHTMLを出力しないからです。Flutter独自のHTML方言を作り、すべてをHTML canvas上に描画します。Playwrightの要素ロケーターの多くは何とか動きましたが、テストにFlutter特有の変更を数多く加える必要がありました。
  • LLMを使っても、FlutterでAndroidパッケージをビルドする方法を理解するのに長い時間がかかりました。AndroidのビルドシステムであるGradleはNixOS上でバグが多く、謎のエラーで失敗し続けたのですが、結局ホームディレクトリにキャッシュされた古いデータが原因だとわかりました。
  • FlutterではBluetooth経由の通信が驚くほど面倒です。ウェブ(少なくともChrome)ではnavigator.bluetooth.requestDeviceを呼ぶだけで基本的に無料で使えるのに、Flutterではプロプライエタリなサードパーティ製ライブラリを使い、デバイス選択UIも自前で作る必要があります。

数時間でサクッと作れる週末プロジェクトだと思っていました。30時間と200ドル分のLLMクレジットを費やした末、ようやく動くようになりました。

実機のAndroidデバイスで動作する私のMeshCore Flutterアプリ

しかし、私のFlutter実装がプロトタイプと同等の機能に達した日に、Redditで共有しようとしたところ、誰かがちょうどmeshcore-openというFlutterによるMeshCoreクライアント実装を共有しているのを見つけました。私と同じアイデアでしたが、出来ははるかに優れていました。

先を越されたことにがっかりしましたが、同時にほっとしました。Flutterを少し触った経験から、できるだけ早くFlutterから離れたかったからです。私が作りたかったのは概念実証だけで、誰かが引き継いでくれることを期待していたので、今や機能豊富なオープンソースのMeshCoreクライアント実装が存在することを嬉しく思っています。

MeshCoreに必要なのはクロス言語ライブラリかもしれない

MeshCoreのFlutterアプリを作っているとき、MeshCoreのデバイス-クライアント間メッセージをパースする低レベルなロジックを実装する必要がありました。MeshCoreのピアツーピアプロトコルを定義した公開仕様はありますが、それすらかなり緩いものです。さらに、MeshCoreファームウェアが動作するデバイスがコンパニオンクライアント(例えばAndroidアプリ)とBluetoothやUSB経由で通信する方法については、もう一つの文書化されていないプロトコルがあります。

事実上のリファレンス実装はMeshCoreファームウェアですが、そこではピアツーピアのプロトコルロジックとデバイス-クライアント間のプロトコルロジック、UIロジックが混ざり合っており、実装はコードベースのあちこちに散らばっています。

例えば、MeshCoreクライアントはBluetooth経由でMeshCoreデバイスから連絡先リストを取得できますが、生のバイト列を連絡先にデシリアライズしなければなりません。メッセージをデコードするライブラリがないため、各MeshCoreクライアントやライブラリがそれぞれ独自の実装を抱えています。

それらの実装について気づいたことは次のとおりです。

  • どこかの信頼できる場所で定義された定数を参照するのではなく、32のようなマジックナンバーを使わざるを得ない。
  • どれもパーサーに自動テストがない。
  • 高水準言語に不要な低レベル作業を持ち込んでいる。例えば、誰もがoutPathoutPathLenという変数を保持している。これは配列が自身のサイズを知らないC言語の実装の名残だ。JavaScriptやPython、Dartのような言語では、配列のサイズを手動で追跡する必要はない。
  • データを厳密にチェックしていないため、負のパス長や地球の範囲外のGPS座標といったゴミデータを平気で通してしまう。
  • すべてがflagsフィールドを無視している。flagsはどのフィールドが埋められているかを示すはずなのに。少なくともピアツーピアメッセージではそうあるべきなのだが、デバイス-クライアント間メッセージでは意味がないように見える。

最初はprotobufCap’n Protoのようなプロトコルライブラリを使ってロジックを書き直すことを考えましたが、現時点でサードパーティのライブラリを後方互換性を保ったまま統合する方法が思いつきませんでした。

そこで、MeshCoreのデバイス-クライアント間プロトコルのコア実装をCで書いたらどうかと考えました。言語固有のバインディングを追加すれば、DartやPython、JavaScript、その他どんな言語で書きたい場合でも、まったく別々の実装を用意する必要がなくなります。

そこで自分でMeshCoreクライアントライブラリを作り始めました。

ライブラリはまだ概念実証としてデモできる段階ではありませんが、もうすぐです。

MeshCoreのメンテナがこのアイデアを気に入らない可能性は十分にあり、彼らの賛同がなければ事実上立ち行かなくなります。それでもやってみたのは、クロス言語ライブラリを書いたことがなく、興味深い経験になると思ったからです。

最後にPythonからCのコードを呼んだのは20年前で、そのときはSWIGを使わなければなりませんでした。当時は苦痛で場当たり的な感じがしましたが、今は80%くらいはマシになったようです。

コア実装はCではなくZigで書きたくてたまらなかったのですが、障害が多すぎました。

  • Zigは、ほとんどのMeshCoreデバイスが採用しているxtensaアーキテクチャへのコンパイルにまだ対応していません
  • ほとんどのMeshCoreファームウェアプロジェクトが使っているPlatformIOは、Zigをサポートしていません。
  • Dartのffigenは、ZigがCのABIをサポートしているのでZigでも動くかもしれませんが、Cでさえ動かすのが大変でした。Pythonのcffiも同様です。

まとめ

何ができたか?

学んだこと

  • 同時進行のプロジェクトは最小限に抑える
    • AIのおかげで新規プロジェクトを始めるのはかつてないほど簡単になりましたが、それを本番で使える形に仕上げる際のボトルネックは依然として自分です。その結果、公開前に自分のレビューを待っている進行中のプロジェクトが大量に溜まっています。頻繁なコンテキストスイッチやタスク管理に精神的なコストがかかります。

来月の目標

  • Refactoring Englishの3つの章を公開する。
  • 2025年の年次レビュー(8年目)を公開する。

手伝ってほしいこと

あなたの住む国では、開発者向けの書籍に30米ドルは高いですか?もし高いと感じるなら、『Designing Data-Intensive Applications』のようなプログラミング書籍に、あなたの国ではいくらくらい払うのが普通か(現地通貨で)教えてください。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント