| @Mac/iPhone

iA Writer

iPad Air を買ったことで iPad で文章を書くときに何を使うか考え始めた。 iOS と iPad で iA Writer は同一アプリとなっているようで、 iPhone 用に買った iA Writer をそのまま iPad Air でも使うことができた。

iPad Air で iA Writer を使ってみて、ファイル管理っぽい機能があることに初めて気が付いた。 iOS では小さく表示されるのでそんな機能があることに気がつかなかった。それで Mac 版を試してみたところ結構良かったので、ブログなどの文章書きを Vim から iA Writer に乗り換えることにした。

iA Writer は iPhone では 6 年くらい前から使ってる。その頃は 1200 円くらいで買えたがいまはめっちゃ値上がりしてて 8000 円もする1( iPhone 用アプリで 8000 円ですよ!)。ドルベースの価格も上がっているが円安が効いていて高くなっている(ドルでの価格は 49.99 ドル)。

iA Writer for iOS は電車の中で文章を編集するのに使ってた。通勤してた頃、混んだ電車の中でパソコンを取り出せず、 iPhone の iA Writer で Vim で書き起こしたポエムの続きを書いたりしてた。

まぁまぁ便利だったのだが、やっぱりスマートフォンのキーボードでは書きづらいしまぁこんなもんかなぁと思いながら、どうしても iPhone で文章を編集したいときだけ起動するような使い方をしていた。

iPad Air で iA Writer を使ってみて、ファイル管理っぽい機能があることに初めて気が付いた。

iA Writer のファイル管理機能

スマートフォルダという機能もあって、バラバラに散らばったファイルをいい感じに整理できることにも気が付いた。 Apple Music のスマートプレイリストみたいな機能だ。 macOS の Finder の機能を転用してるのだと思う。

iA Writer のスマートフォルダ

"メモを取り、書き、編集するための集中できる環境。それ以外には何もありません。"

iA Writer のウェブサイトではフルスクリーン表示できて文章を書くことに集中できることをウリにしているが、自分にとってはファイルを一覧表示・管理できる機能(ファイラー)がめっちゃ便利だと思った。これまで Vim + memolist で書いた文章は一定期間経過したら Day One に取り込みつつ、年ごとアーカイブ用フォルダーを作って Ruby スクリプトでそこに待避していたが、そんなことをする必要なく iA Writer だけでファイルの管理ができてしまう(サブディレクトリに移動させたいファイルをドラッグ&ドロップで移動させればよい)。

Day One のカレンダーで過去の記事を見られる機能は便利だが、Day One はプレーンテキストを捨てて Markdown ベースの独自仕様リッチテキストに移行してしまったし、起動に時間がかかるのも微妙だと思っていて、シンプルにプレーンテキストのファイルとして管理できる iA Writer の方がよい気がしてきた。

Day One のカレンダー UI

iA Writer の良いところを列挙するとこんな感じ。

  1. 起動が速い
    起動してすぐ書き始められる
  2. ファイル管理機能
    Finder とテキストエディターが統合されたような使い勝手
  3. クイック検索
    Vim の Unite 検索のような機能があり、めっちゃ素早く対象のディレクトリ以下のファイルを検索できる

これまで Vim を使い続けてきたのは Unite での検索が便利すぎたからだった。メモ書きディレクトリに移動してテキトーに Vim を起動して UniteGrep すれば書きかけのファイルをぱっと探せる。

iA Writer には UniteGrep と似たクイック検索という機能があって、しかもショートカットで検索 UI を呼び出せるところが良い( + + o)。

iA Writer のクイック検索

Day One にも似たような検索機能はあるものの、検索窓を呼び出すショートカットがない。いちいちマウスカーソルを動かして🔍マークをタップしないと検索できない。検索窓はマウスやトラックパッドに手を持ち替えることなく表示されないとダメだと思う(一つ前の記事参照

デバイス間のファイル同期は iCloud を使っている。複数の端末で同時に開いてもコンフリクトが起こることはほぼなくファイルが同期される。敬虔な Apple 信者でいる限りファイルの同期のために Evernote や Notion を使う必要はない。

いまは Obsidian や Roam Research のような新手のソフトもあるようだが、自分の場合は iA Writer で満足している。使っていないが Wiki Link 記法も使えるのでファイル同士の繋がりを作ることもできるようだ。

職業プログラマーになって以来、テキストエディターは Vim 一択だろうという気持ちでいたが、 GUI でファイル検索・管理できるという一点で iA Writer を気に入ってしまった。コードを書くならいまでも Vim が良いが、ブログの文章や個人用メモは完全に iA Writer に乗り換えてしまった。世間的に大人気な Notion を使っていたらこのようにエディターを他のものに乗り換えることはできなかったのでプレーンテキスト万歳だ。


ピアソン版の『達人プログラマー』に GUI のファイル検索だと目的のファイルが見つかるのに時間がかかるので grepfind でファイルを探す方が高速だ、という記事がある(第3章)。 Unix 哲学的な話で、小さな機能を持つソフトを組み合わせて使う方が効率的だという話だった。 grepfind で検索して xargs -o vim するより、テキストエディターの中で一覧表示出来る方が便利だ。少なくとも普通の人にとっては。何でもシェルでやるのが良いという時代は終わったのかもしれない。

とはいえ iA Writer は macOS の仕組みの上で達人プログラマー的なやり方を継承しているようにも感じる。例えば先ほどのスマートフォルダは macOS の Finder にある機能だ。ひょっとしたら UI が同じだけでまるっきり別の実装がしてあるかもしれないが、 OS や SDK が提供しているシンプルな GUI を組み合わせて便利なものを作るのが今風の達人プログラマーなのかもしれない。


  1. Mac 版は同一ライセンスになっておらず Mac App Store から別途購入する必要がある。 8000 円は高いので一か月くらい悩んで買った。 

| @労働

可也山.jpg

プロダクトマネージャーになって 5 年ちかくが経った。最初の 2 年くらいはエンジニア気分が抜けず、 Vim を開いて何かやったりということがあった。ただ 3 年目くらいからはエンジニアっぽいことは一切やらず、プロダクトマネジメントだけをやるようになってきたと思う。ようやく自己紹介をするときによどみなく「プロダクトマネージャーです」と言えるようになってきた。

現在は登山アプリ・サービスの会社で仕事をしていて、割と頻繁に山に行ってドッグフーディングしている。なのでユーザー(山に登る人)の課題感が大体わかっているつもりだ。

もしいまの環境を変えることになったとして、自分は登山アプリ以外のプロダクトマネジメントができるのだろうかとふと思った。山が好きだから(ドメイン知識があるから)できているのか、それともプロダクトマネジメントのスキルが身についてきているのか。

これまで B2C 、 C2C 、 B2B2C など様々なサービスの開発に関わってきた。正直 ATI (圧倒的な当事者意識)が高い方ではなかった。なのでそんな自分がプロダクトマネジメントできるとは思ってもみなかったが、登山アプリの会社に就職して登山を好きになり、当事者意識が高まってプロダクトマネジメントを生業とするに至った。なのでいまの環境を離れてしまえばプロダクトマネジメントはできない可能性がある。趣味と仕事が重なる領域以外でも自分のプロダクトマネジメントスキルが活かせるのかが気になっていた。

しかしそもそも自分はソフトウェア(デジタルプロダクト)自体が好きなのだということに気がついた。あのプロダクトはこういう戦略で成長したとか、ソフトウェアの背景にある作り手の思想とか、そういうことを考えるのが好きだ。

ベンチャー企業のなかには、そのプロダクトがユーザーのどんな問題を解決しているのか作っている側も分からないまま突っ走っていることがあるのではないかと想像する。いまの職場でも、ユーザーがどの部分に最も価値を感じているのかを理解するまでにはだいぶ時間がかかった。

ある程度のシェアを獲得して、今後さらに規模を拡大したいというフェーズでは、ユーザーがプロダクトのどの部分に最も価値を感じているのか、ユーザーがプロダクトに期待している価値は何かをはっきりと理解する必要がある。ひょっとすると作り手の思い込みでユーザーが必要としていない機能を作っているかもしれない。プロダクトの価値を再定義し、機能を整理する必要性が出てくる。 0 → 1 のプロダクトマネジメントはではなく、 1 → 10 のプロダクトマネジメントだ。自分はこのような役回りが好きだし、こういった仕事もプロダクトマネージャーの気づかれにくい重要な役割の一つだと思う。

| @ブログ

普段は Vim などのテキストエディターで文章を書いていて、ブログの投稿画面にはできあがった内容をコピペするだけだったので、投稿画面の使いやすさやは気にしたことがなかった。画像のドラッグ・アンド・ドロップ・アップロードや、オン・ザ・フライでプレビューをできるようにしたが、テキスト自体の書きやすさを改善しようとはしてこなかった。

大筋はテキストエディターで書いたとしても、最後に推敲したり、推敲していて気が付いたおかしなところの修正は投稿画面で行うことが多い。やっぱり投稿画面の書きやすさは重要だ。特に長大な内容を編集しているときにテキストエリアが狭いととても使いづらい。ある程度大きさがあり、画面内に大量の文字が表示されるテキストエリアが好みだ。

Lokka が開発されていた 10 年以上前は解像度の小さいディスプレイが主流で、 Lokka の管理画面は小さいサイズのウィンドウで閲覧することを想定したテキストエリアのサイズになっている。今日の高解像度ディスプレイで見ると不便なので画面一杯にテキストエリアが広がるようにした。

投稿画面のレイアウトを改善

iPad からも投稿しやすいように投稿画面のレイアウトも修正した。本文のテキストエリアが広がったためスクロールしないと「スラッグ」以降の入力欄にアクセスできなくなったので、ある程度横幅のあるウィンドウのときにはこれらを右側に配置するようにした。

加えて、フォームを書きかけで保存したかどうかがはっきりせず、未保存の内容があるのに保存せずページから離脱してしまうことがあったので、変更点があるときは背景色を変えてわかるようにした。これにより記入内容を保存せずページを離脱してしまい、内容が失われるという悲劇を回避できるようになった。やり方は適当に検索して出てきた Stack Overflow を参考に、ページを読み込んだ時点で JavaScript でフィールド内の要素を JSON.stringify して data attribute に格納し、その後各フィールドの input イベントを監視して変更があるかどうかをチェックしている。

class FormObserver {
  constructor() {
    this.initializeFields();
    this.observeFieldsChange();
  }

  initializeFields() {
    const fields = Array.from(document.querySelectorAll('div.field'));
    for (const field of fields) {
      const inputElement = field.querySelector('input[type="text"], textarea, input[type="datetime-local"], select option:checked');
      if (inputElement === null) {
        continue;
      }
      field.dataset.serialize = JSON.stringify(inputElement.value);
    }
  }

  observeFieldsChange() {
    const fields = Array.from(document.querySelectorAll('div.field'));
    for (const field of fields) {
      const inputElement = field.querySelector('input[type="text"], textarea, input[type="datetime-local"], select');
      if (inputElement === null) {
        continue;
      }
      inputElement.addEventListener('input', () => {
        if (field.dataset.serialize != JSON.stringify(inputElement.value)) {
          inputElement.dataset.changed = 'true';
          field.classList.add('edited');
        } else {
          inputElement.dataset.changed = 'false';
          field.classList.remove('edited');
        }
      })
    }
  }
}

input イベントを監視すると動作がもっさりするのではないかと心配したが、そんなことはないようだ。普通に使えている。

いまのところ管理画面の HTML レンダリングはサーバーサイドで Ruby で行っているが、これ以上凝ったことをやるなら React などを使って JavaScript で HTML を組み立てる方式にしていく方が効率が良さそうだ。

| @技術/プログラミング

MySQL だけでお手軽に全文検索ができるということを知らなかった。 MySQL 5.6 から入っていたようだった。 Tantivy および Tantiny を使ったやり方を以前記事に書いてサイトで実装しているが、 MeCab によるトークナイズでは二文字の熟語がセットになって四文字になっているようなパターンを取り逃すことがあった(「関連記事」は「関連」と「記事」に分割され、「関連」や「記事」というキーワードで検索したときにはヒットするが「関連記事」で検索するとヒットしない)し、記事追加時の検索インデックス更新処理が不要( MySQL にレコードが追加されたときに勝手に更新される)なので試してみることにした。

やり方は以下の記事を参考にした。

最初にデータベースに全文検索用のインデックスを作成した。

ALTER TABLE `entries` ADD FULLTEXT INDEX index_entry_fulltext(title, body) WITH PARSER ngram;

その後、検索部分のコードを書き換えて以下のようにした。

class Entry < ActiveRecord::Base
  scope :search,
        ->(words) {
          return all if words.blank?
          where('MATCH (entries.title, entries.body) AGAINST (? in BOOLEAN MODE)', words)
        }
end

めっちゃ簡単。

このブログは記事数が 1500 記事くらいなのでぶっちゃけ LIKE 検索でも実用的な速度( 100msec 以内)で結果を取得できるが、 FULLTEXT インデックスを使うと 10msec 程度で結果を取得できる。

ただし Tantivy と比べて劣る点もあって以下は注意が必要。

  1. なぜかわからないが Vim で検索すると何もヒットしない。また Rails で検索すると Rails について触れていない記事もヒットする。 ngram によるインデックスというのはこんなものなのかもしれない。検索ワードが日本語のときはいい感じに結果が表示される。
  2. 複数のテーブルにまたがるデータを一個の検索インデックスにまとめることができない。例えば Tantivy のインデックスは記事のタイトル、本文、カテゴリー、タグをインデックス対象としているが、 MySQL の FULLTEXT インデックスだとテーブルごとにしかインデックスを作れないので(当たり前)、複数のテーブルにまたがる検索をするときにはテーブルを JOIN するしかない。 OR マッパーを使っている場合には利用しづらい。

1 の問題に関しては、 MySQL 5.7 からインデックス生成時の PARSER に MeCab などを指定できるようになったのでそうすると回避できるかもしれない。ただし MeCab のインストールや設定を行う必要があるので要注意。

2 の問題に関しては全文検索システムを入れた方が良さげ。 Tantivy であれば非常に簡単に導入できる。

現状、このサイトでは右上の検索窓から検索したときのインクリメンタルサーチとアーカイブページでの絞り込みは Tantivy を、インクリメンタルサーチの結果で必要な情報が得られなかったときの「全文検索する」と 404 Not Found ページの検索は MySQL の全文検索を使うようにしている。

二つの検索

| @技術/プログラミング

ブログのアクセス数を集計してランキング(人気記事一覧)を表示している。

シェルスクリプトでログを集計して頑張っているが、ボットからのアクセスを除外など結構やることが複雑化してきた。また最近は主にロシア方面からのスパマーによるアクセスが多く、全然いま読まれる要素がない記事がランキング上位に入ったりしてた。スパマーは以下の 2 記事が好きなようだ。

Google Analytics でアクセス数を見るとこれらの記事は上位に入ってこないので、 Google はちゃんとスパマーからのアクセスを除外しているのだろう。

というわけで Google Analytics の API からアクセス数を取得してみることにした。

しかし調べてみた感じ、あまり情報がない。 Google の公式ドキュメントは Java とPython と Go と PHP と JavaScript のサンプルしかない。

Google が公開している Ruby のライブラリはあるが、ドキュメントがえらく貧弱で勘で使うしかない。

使い方を紹介しているブログもあるにはあるが、この Ruby 製のライブラリはアルファ版とベータ版しかなくてころころ仕様が変わるようだ。先人の情報通りに動かしてみたら全然動かなかった。

API の仕様や上述のライブラリのコードを読みつつ以下のようなコードを書いたところいい感じに使えるようになった。 Ruby で Google Analytics の API にアクセスしたいと思っている人には参考になるんじゃないかと思う。

↑のコードでは metrics は screenPageViewstotalUsers を取得している。 dimension は pagePathpageTitle だ。ほかのが必要であれば変えてあげればよい。これを Rake タスクから呼び出して必要な情報を得るようにしている。

API 呼び出しについては Google が提供している Query Explorer で確認するとよい。

また Analytics API は利用開始前に設定が必要。 Quickstart ページで API を有効化し、 GCP に IAM を作成して credential をダウンロードして Google Analytics 側でこの IAM への API アクセスを許可する必要がある。コード書く前にこの辺でくじけそうになるだろうけど頑張ってほしい。

| @技術/プログラミング

松浦福島初崎海岸のドクロのような岩

Mac の Homebrew のライブラリ群を久しぶりにアップデートした。 tmux と fish のバージョンを上げたら tmux が動かなくなってめっちゃ焦った。いろんなものを同時にバージョンアップするとどっちに原因があるのかわからなくて困る。結局、 brew reinstall tmux で事なきを得た。

次に VPS の Ubuntu のバージョンが古くなっていたのでアップグレードした。ついでにいろいろ気になってたところ(ログローテートがうまく動いていないところとか Nginx の設定ファイルの配置など)を直して回った。

OS のアップグレードに伴って Ruby の再インストールが必要になり、 Ruby 再インストール後にアプリケーションを deploy しようとすると mimemagic gem が yank されていたりでライブラリのアップデートが必要になった( MimeMagic は脆弱性があって Mercel に変更しないといけなかったが、変え忘れていたところがあった)。これによって引きずられるように gem のアップデートが必要になり、うっかり capistrano3-puma を v5 系にしたところ、 puma の起動ができなくて困った。どうも puma の v5 ではデーモン化オプションが削除されているようで、 capistrano で puma を再起動させたりはできないようだった。いろいろ面倒くさそうなので capistrano3-puma も puma も v4 系に固定して凌いだ。

Archives ページの npm パッケージも古くなってたので、 React や React Router 、 Webpack 、 Babel など各種ライブラリのバージョンを上げた。 React Router の v5 系から v6 系へのアップデートは結構大変だった。以下を読みながらやった。

withRouter などは React Router から機能が消えるのでそれをラップする関数を自分で書いてコンポーネントに mixin するような感じだった。以前に比べたらマイルドになっているとはいえ、 JavaScript 界隈はアップデートについて行くのが厳しい。

職業プログラマーじゃなくなったので開発環境の維持管理などがおろそかになりがちだし、 Vim やシェルのショートカットを忘れてしまうことがある。 Vim やシェルの操作は特殊技能のようなものなので忘れるともったいない。たまに触って忘れないようにしておきたい。

そういえば温かくなって庭の雑草が伸びてきたので庭の草むしりもやった。ゴールデンメンテナンスウィークだ

| @WWW

スパムコメントがめっちゃ多いのでいろいろ対策はしている。詳しくは以前書いた。

にも関わらず、相変わらずスパム投稿が来る。昨日は 5 件ほどスパム投稿が来たのでいい加減頭にきて、スパマーの餌食になってる記事のコメント欄を閉じることにした。スパマーはバカなのか、特定の記事にしかコメントしてこない。自分のブログの場合は以下の 2 記事がターゲットにされていたのでこの 2 記事だけコメントを受け付けないように設定した。

恐らくスパマーの間でやりとりされるカモリストがあって、過去にスパムコメントを投稿して反映された実績みたいなのがやりとりされているんだろう(オレオレ詐欺のカモ一覧みたいな感じ)。とりあえず上記 2 記事のコメント欄を潰したところ、スパムコメントは来なくなった。

どう見ても日本語のブログにロシア語のスパムコメントを投稿して何の意味があるというのだろうか。日本語のブログの書き手にロシア語が通じると思っているのだろうか。前の記事にも書いているけど、機械的な投稿はできないように reCAPTCH をはじめとしたいろいろな対策が行われているので bot が投稿しているとは考えにくく、ロシアの超暇な人間が手作業で信号機や横断歩道の写真を選びつつ投稿しているのだと思われる。自分がその仕事をやらなければならなかったとしたら、その仕事の意味のなさに発狂すると思うんだけど、そういう作業をしている人がいる現実に恐怖を覚える。