Refactoring English: Month 13

Michael Lynch

Refactoring English:13か月目

ひとことで

集中について書きながら気が散った1か月

初めての方はこちら

こんにちは、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. 世界銀行のデータを取得し、リストにある各国の購買力平価(PPP)を計算するスクリプトを書きます。
  3. 米国に対する購買力から各国の割引率を算出します。
    • 例:ブラジルのPPPは米国より54%低いので、54%割引になります。
  4. PPPが米国と15%以内しか違わない国は除外します(割引が小さすぎて手間に見合わないため)。
  5. 割引がマイナスになる国は除外します。
  6. 割引は最大75%までに制限します。
    • そうしないとエジプトでの価格は4米ドルになり、手数料を引くと手元に残るのは3.50ドルほどになってしまいます。
  7. リストに残った国ごとに、国別のStripe priceオブジェクトとStripe決済リンクを自動生成します。
  8. すべての国をサイト上のHTMLのドロップダウンに並べます。

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

性善説で運用しているので、IPによる位置判定やVPN対策はしていません。各国の割引率はあえて表示せず、一番安い国を選ぶ動機を減らしています。また、各国の現地通貨で価格を設定していること自体が抑止力にもなります。もし実際とは違う地域を選んで不正をしても、為替手数料でかえって損をするからです。

ただ、数字はまだしっくりきていません。厳密なPPPで計算すると、米国で30ドルのものはエジプトでは4ドル相当になりますが、エジプトでプログラマー向けのまともな(海賊版でない)本が4ドルで買えるとは思えません。

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

初めてのFlutterアプリを作る

12月に「MeshCoreオフグリッドメッセージングのファーストインプレッション」を公開しました。技術自体にはワクワクしたのですが、クライアントがすべてクローズドソースだと知ってがっかりしました。

そこでMeshCoreの探求は一旦止めようと思ったのですが、MeshCoreのコントリビューターであるFrieder Schrempf氏が、私の記事に対してこんな興味深い視点を寄せてくれました。

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

私もFrieder氏の意見に賛成し、「じゃあ、オープンソースのMeshCoreアプリの概念実証を自分で作ってみようか」と思いました。

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

このプロトタイプをモバイルに移植するのはどれくらい大変だろうかと考えました。MeshCoreはBluetoothアクセスやオフライン動作が必要なため、ウェブアプリとしては使いにくいからです。Flutterについては、クロスプラットフォームのモバイル開発ソリューションとしてそれなりに良い評判を聞いていました。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アプリを動かしているところ

実機のAndroid端末で自作のMeshCore Flutterアプリを動かしているところ

ところが、私のFlutter実装がプロトタイプと同等の機能に達したその日に、Redditを見に行くと、誰かがmeshcore-openをシェアしていました。FlutterによるMeshCoreクライアント実装で、私がやろうとしていたこととまったく同じアイデアを、はるかに高い完成度で実現していたのです。

先を越されたのは残念でしたが、正直ほっともしました。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はまだxtensaアーキテクチャ向けにコンパイルできません。MeshCoreデバイスの多くが使っているアーキテクチャです。
  • MeshCoreファームウェアのプロジェクトの多くが使っているPlatformIOは、Zigをサポートしていません。
  • Dartのffigenは、ZigがCのABIをサポートしているのでZigでも動くかもしれませんが、Cでさえ動かすのがやっとでした。
    • Pythonのcffiも同様です。

まとめ

できたこと

学んだこと

  • 進行中のプロジェクトを最小限にする
    • AIのおかげで新しいプロジェクトを始めるのはかつてないほど簡単になりましたが、それを本番レベルに仕上げる際のボトルネックは依然として自分自身です。結果として、公開前に自分のレビュー待ちとなっている進行中のプロジェクトがたくさん溜まってしまいます。コンテキストスイッチやタスク管理の面で、精神的なオーバーヘッドが大きくなっています。

来月の目標

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

お願い

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

原文は Michael Lynch により に公開されました。

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