「.NETのクラスライブラリ設計 改訂新版」を買った
最近買った C# 系の本の中ではダントツでよい。巨大な API ライブラリを設計するときに気を付けることが具体的に書いてあって参考になる。
neuecc 氏が前書きを書いていて 2章が素晴らしいと書いてあったけどそのとおり2章が素晴らしかった。ここまででも十分におつりが来る感触はある。
以下の引用が面白かった人は購入する価値があると思います。.NET 開発者が API 設計で後悔しているところが読めるなんてそんなに知見に溢れた書籍はなかなかない。
特によかったところ
自分と同じようなユーザー向けに設計するのは簡単で、そうではない人向けに設計するのはとても難しいことです。正直に言うと、ドメインの専門家によって設計された、ドメインの専門家にしか使えないAPIが多すぎます。
1回の大規模なユーザビリティスタディの計画、設計、実施に多大な労力を費やすよりも、API開発プロセス全体で、より小さく、より範囲を絞ったスタディを何度も実施する方が、はるかに価値が高い
主流のシナリオのAPIで必要となる初期化を、最小限に抑えるように設計すべきです。理想的には、基本的なシナリオ用に設計された型を使い始めるには、既定のコンストラクター、または単一のパラメーターを持つコンストラクターのいずれかで済むようにすべきです。
フレームワーク設計原則
フレームワークは、使い方のシナリオのセットと、それらのシナリオを実装するコードサンプルから設計を始めなければならない。
単純なシナリオでは、フレームワークはドキュメントを必要とせずに使用可能でなければならない。
.NET 開発者が API 設計で後悔しているところ
この逆もまた真なりで、「ほとんど使用されない型が含まれる名前空間に、一般的に使用される型を入れてはならない」と言えるでしょう。StringBuilderは、私達が後になってSystem名前空間に含めておけばよかったと思ったものの例です。この型はSystem.Text名前空間にありますが、その名前空間にある他の型よりもはるかに高い頻度で使用されるうえ、それらの型とはあまり関係がありません。
私の「もし願いがかなうなら、やり直したいこと」トップ10のひとつは、System.Threading.Tasksにあります。私達は当初、CPUベースの並列処理に焦点を当ててこれらのAPIを設計したのですが、時がたつにつれて、その最も一般的な使い方は当初の時点よりもはるかに、I/Oベースの非同期処理に焦点を当てたものへと変わってしまいました。そして、私達が当初設定した既定値の一部は、前者の場合は非常に適切なものでしたが、後者にとっては有害なものになりました。
ブログ執筆を加速させるコマンドラインインターフェースを作成した
このブログに素早く投稿するために、個人用のコマンドラインインターフェース(CLI)を作った。
投稿: blog new
新規記事を作成するときは new コマンドを使う。引数として URL を指定するカスタムパスを渡す。
$ blog new path/to/article store /Users/ongaeshi/blog/ongaeshi.hatenablog.com/entry/path/to/article.md
- 下書きとしてはてなブログ上に空の記事が投稿される。
- URL は
https://ongaeshi.hatenablog.com/entry/path/to/article
になる。 - 対応するファイルが
/Users/ongaeshi/blog/ongaeshi.hatenablog.com/entry/path/to/article.md
に作成される。
blog new 直後の path/to/article.md。
--- Title: URL: https://ongaeshi.hatenablog.com/entry/path/to/article EditURL: ... PreviewURL: ... Draft: true --- (ここに記事を書いていく)
執筆: blog edit
$ blog edit
- ブログ記事が格納された git レポジトリをVSCode で開く。
- blog new で作成したファイルを開いて記事を書いていく。
- 記事の執筆が終わったら
draft: true
の行を削除してblog commit
コマンドを実行する。
投稿: blog commit
$ blog commit または $ blog commit -m "/path/to/article を投稿"
- 裏で blogsync push してから git commit を行う
- blogsync push する記事は以下のどれか
- まだ追加されていないファイル (
git status --porcelain
) - 編集済みのファイル (
git diff --name-only
) - main に pushしていないファイル (
git diff --name-only origin/main..HEAD
)
- まだ追加されていないファイル (
- blogsync push と git commit を同時に行うことでどちらかの実行し忘れを防ぐ。(blog コマンドを作る前はこの操作ミスが本当に多かった)
感想
ここ1か月ほど、快適にブログ執筆するためのインターフェースはどうなっているのがよいかを試行錯誤していた。 個人的には特に以下の辺りが重要だった。
- ブログのカスタムパスは必ず指定する。
blogsync post
では標準入力を受け付けずに自動で空の記事を作成する。- 空の記事がデフォルトになるので必ず下書きで作成する。(オプションからは指定できないようにする。)
- git commit と blogsync push を1つのコマンドできるようにする
自分用コマンドを作った効果は抜群で今月だけでだけですでに7本の記事を執筆できている。 今年6月までに書いた記事が10本なので1か月で半年分を追い抜きそうなペース。
よいインターフェースには利用者の力をブーストする、もしくは最大限に発揮させる力があると改め感じた。
まとめ
blogsync に薄いラッパーを被せて自分のブログ投稿ワークフローに最適なインターフェースを作った。 ユーザーにフィットしたインターフェースをデザインすることは、個人や組織の開発効率を飛躍的に向上させる。
今や、重要な内部パーツは多くがOSSとして既に存在している、あるいはLLMによって生成できる時代になった。 そのため、様々な状況に対応できるインターフェースを作成することが、これからのプログラマにとって一層重要なスキルとなりそうな予感がする。
Spotify で5分だけ音楽を聴く
例えば5分だけ休憩したいときや集中して作業したいとき、タイマーをかけるのではなく、せっかくなら Spotify で好きな音楽をかけたくなるときがある。
ところが Spotify は自動再生機能があるため、曲が終わったらそれに関連した別の曲をどんどん流してくるので気が付くと休憩時間が伸びてしまって困る。iOS のタイマーは何故かヘッドホンに流せずスピーカーに音が出るのでこれも駄目。
自動再生をその都度切るのは面倒なので、最近は「終わりの曲」を決めて、その前に大体指定時間を満たすような曲をつなげるようになった。終わりの曲には波の音を使っている。(他の曲と明らかに違うことが分かればどんな音でもよい、自分は海の音が好きなのでこれ)
「次に再生」で好きな曲をつなげて、最後に波の音を流すとなんとなくタイマーみたいな効果があっておすすめです。
blogsync new は標準入力を受け取らない&コミットしない
前回 から引き続き blog new コマンドの改善を続けている。
直接記事をポストしたいときは標準入力から記事の中身を受け取れるのは便利だけど、 自分の場合は VSCode ですぐに編集するのでまず空の標準入力を渡してその処理をキャンセルする。
system("blogsync post --custom-path #{path} #{opts.join(" ")} ongaeshi.hatenablog.com", in: IO::NULL)
そうすると記事の最初の一回目は必ず空の状態で post されることになるので、常に--draft
として作成する。(blog new から --draft
オプションは削除)
system("blogsync post --custom-path #{path} --draft #{opts.join(" ")} ongaeshi.hatenablog.com", in: IO::NULL)
blog new コマンドでは空記事の作成のみにとどめてブログへの投稿は blog commit コマンドで常に行うになった。 大分驚きが減ってすっきりしたオペレーションになったのではないか。
最終的な blog new コマンド。
desc "new PATH", "Create a new blog post with PATH" method_option :title, type: :string def new(path) Dir.chdir(BLOG_REPOSITORY_DIR) do opts = [] opts << "--title=\"#{options[:title]}\"" if options[:title] system("blogsync post --custom-path #{path} --draft #{opts.join(" ")} ongaeshi.hatenablog.com", in: IO::NULL) end end
プログラマは Obsidian の「読みやすい行の長さ」オプションを OFF にするとよい
Obsidian の不満として、長めのコードブロックがずっと読みにくいと思っていた。 フォーラムを覗いていたらよいオプションを見つけた。
When the “Readable Line Length” feature is turned on, the lines are extremely short in edit mode
コードブロックなどを Obsidian に貼り付けることが多い人は「読みやすい行の長さ」オプションを OFF にしておくと大分快適になる。
blog new コマンドと blog commit コマンドの実装
自分用の blog コマンド を改善中。 git レポジトリへのコミットと blogsync を同期させる方向で実装してみた。
blog new
引数は --custom-path
に渡すことで URL を直接指定できるようにする。
--draft
と --title
はオプション。
class Main < Thor desc "new PATH", "Create a new blog post with PATH" method_option :title, type: :string method_option :draft, type: :boolean def new(path) Dir.chdir(BLOG_REPOSITORY_DIR) do opts = [] opts << "--title=\"#{options[:title]}\"" if options[:title] opts << "--draft" if options[:draft] system("blogsync post --custom-path #{path } #{opts.join(" ")} ongaeshi.hatenablog.com") system("git add ongaeshi.hatenablog.com/entry/#{path}.md") system("git commit -m \"Add #{path}\"") end end
blog commit
blog push してから変更内容をコミットする。
desc "commit", "git commit and push blog changes" method_option :message, type: :string, aliases: '-m' def commit Dir.chdir(BLOG_REPOSITORY_DIR) do list = `git diff --name-only`.split("\n") list += `git diff --name-only origin/main..HEAD`.split("\n") list.each do |path| system("blogsync push #{path}") end system("git add .") message = options[:message] || "Commit blog changes" system("git commit -m \"#{message}\"") end end
blogsync 20.0.1 への更新
blogsync の置き場所を確認
PowerShell の type は Get-Command (いつも忘れる)。
> Get-Command blogsync C:\app\bin\blogsync.exe
ダウンロード
https://github.com/x-motemen/blogsync/releases から最新のバイナリをダウンロード。
https://github.com/x-motemen/blogsync/releases/download/v0.20.1/blogsync_v0.20.1_windows_amd64.zip
zip 展開してできたバイナリを C:\app\bin
に放り込めば OK。(go で書かれたアプリは更新が簡単・・!)
0.20.1
この辺りが特に嬉しい。
- blogsync remove が使えるようになった
- custom-path パス未指定のドラフトを更新するたびに新しいファイルが降ってきてしまう問題が解決された
NAME: blogsync.exe - A new cli application USAGE: blogsync.exe [global options] command [command options] [arguments...] VERSION: 0.20.1 (9760a4c) COMMANDS: pull Pull entries from remote fetch Fetch entries from remote push Push local entries to remote post Post a new entry to remote list List local blogs remove Remove blog entries help, h Shows a list of commands or help for one command GLOBAL OPTIONS: -C PATH Run as if blogsync was started in PATH instead of the current working directory. [%BLOGSYNC_WORKDIR%] --help, -h show help --version, -v print the version