自分のブログが欲しいものの、既存のブログはデザインを自分で変更しづらそうに思えるし、静的サイトジェネレータもよく分からないし、できれば出先から記事を書けるようにしたい……そう思っていた矢先に、microcmsというヘッドレスCMSの存在を知りました。
これがあれば記事を書く部分については心配する必要がなくなり、記事の生成に専念できるのではと考え、ならば静的サイトジェネレータも作ってしまおうと思い立ち、勉強も兼ねてGoで書くことにしました。
本記事では特に、ポイントや反省点などについて書こうと思います。Go初心者の書いたものですので、いろいろと良くない点があるかとは思いますが、どうか大目に見ていただければ助かります。
今回作ったプログラム"microblogen"は Qman11010101/microblogen に置いてあります。
また、これで生成したブログのリポジトリは Qman11010101/qmans_diary 、サイトはこのサイト(https://blog.qmainconts.dev/)です。
ブログ全体の構成について
全体の流れ
記事の公開から生成までの流れは以下のようになっています。
microcmsで記事が公開されると、Webhookで Qman11010101/qmans_diary に通知が飛び、GitHub Actionsが動作してmicroblogenで記事が生成され、Cloudflare Pagesにデプロイされます。
microblogenによる記事生成の流れ
- 設定ファイルの存在を確認し、ファイルがなければ環境変数から設定値を取得する。APIキーなど必須のものがなければエラーで終了する。
- 記事一覧ページ・記事のためのテンプレートの存在を確認し、なければエラーで終了する。
- 古い出力ディレクトリを削除し、再度生成する。
- テンプレートディレクトリ内にアセットがあればそれをコピーする。
- microcmsから公開順に5件の記事データを取得し、JSONにして出力ディレクトリに保存する。
- 何回かに分けて公開順にすべての記事データをダウンロードする。記事一覧ページと同時に記事を生成する。
- microcmsからカテゴリ一覧を取得し、JSONにして出力ディレクトリに保存する。
- microcmsからカテゴリごとに何回かに分けて記事データを取得し、カテゴリごとの記事一覧ページを生成する。
ポイント
ここでは、Goを書く上で得た気づきをはじめとしたポイントを記します。
JSONの扱い
GoでJSONを扱う上では事前に構造体を定義しておくことがほとんどかと思いますが、これに関してはJSONを食わせると自動で構造体のコードを生成してくれる JSON-to-Go が大変便利です。
なおmicrocmsのAPIを扱うに当たっては microcms-go-sdk を使用したので、こちらのReadmeに記載されている構造体を参考に記述しました。
なお、前述のReadmeに倣ってomitempty
を書いていますが、これはMarshal時(=構造体をJSONにする時)のみ考慮されるものというのを後で知りました。microblogenの用途では不要な気がするので、いずれ消しておこうと思います。
ディレクトリのコピー
Goの標準ライブラリにはディレクトリをまるごと(再帰的に)コピーする関数が存在しないというのを知って、驚きました。代替として、otiai10/copy を利用させていただくことで解決しました。Goがシンプルな仕様を指向していることは良いと思うのですが、これくらいは標準で用意しておいてほしかったと思います。
deferが便利
遅延評価の概念はまだしっかり理解したとは言えませんが、途中に書いてもスコープの最後で実行されるというのはcloseしなければいけないリソースに便利だと思います。openとcloseが近くに書けるというのはソースコードを読む上でも負担が減ります。
個人的にはcloseを書く必要のないPythonのwith構文が一番好きなのですが、deferしてcloseする構文はそれに次いで好きです。
環境変数のデフォルト値の設定
workingDir, ok := os.LookupEnv("WORKDIR")
if !ok {
workingDir = "default"
}
極めてシンプルな普通の書き方ですが、個人的にはRubyなどのようにworkingDir := os.LookupEnv("WORKDIR") || "default"
のように書きたかったです。
変数の再宣言?
Goを書いていると、多値返却の形でエラーを返してくるコードによく会います。value, err := function()
みたいなやつです。
一方で、microcms-go-sdk にも記載されているように、趣旨が副作用で返り値はerr
だけ、というような例もあります。
err := client.List(
microcms.ListParams{
Endpoint: "article",
Fields: []string{"id", "title", "publishedAt", "updatedAt", "category.id", "category.name"},
Limit: 5,
Orders: []string{"-publishedAt"},
}, &articlesLatest)
もしこれが現れるより前に何かでerr
を使ってしまっていた場合、no new variables on left side of :=
と怒られてしまいます。
どうも多値返却だとこれが起こらないっぽいのですが、多値返却でない場合は:=
を=
にするしかないように思えます。
統一されておらず気持ち悪い気がするのですが、もっと何か良い方法があるのでしょうか……?
(追記)if節でif err = function(); err != nil {}
と書けばいいみたいです。若干変にも見えますが作法は作法と納得すべきなのでしょうか。今回みたいに関数がそれなりに大きい面積を取る場合、if節の見通しが悪くなってしまう気がするのですが……
反省点
プログラムを書く上で失敗したと思う点を記します。Go言語に限らない点が多いです。
書く前に設計をする
まずは動くところまで持っていこうと思い、雑に考えながらプログラムを書いていたところ、構造が定まらなかったせいであとになって書き直す羽目になった部分がいくつもあります。
スパゲッティコードの原因にもなるので、途中で変更するとしても事前に設計しておいたほうが楽に開発できたと思います。
用語を統一する
記事に紐づけるカテゴリについて、「カテゴリ」「タグ」の二種類の呼び方を混在させてしまっていました。混乱の原因になっていたので、少なくともmicroblogen上では「カテゴリ」に統一しました。
自分のためにコメントを書く
要所要所にメモ程度に書いておけばいいだろうと思っていましたが、関数に分けられていない似た処理が混在していることもありわかりづらくなってしまいました。その場しのぎではありますがコメントを書くことである程度見通しが良くなりました。
Goを書いてみて
小さいプログラムとはいえ、Goをこれくらい書いたのは多分初めてなのですが、それなりに良い印象を受けました。
PythonやJavaScriptといった型のない言語から入った自分でもすんなり書けますし、型推論も助かります。エディタの機能のおかげで型のメリットも享受できました。
公式ドキュメントでなくとも、日本語の情報がたくさんあります。動的型の言語からプログラミングを始めて静的型の言語も触ってみたいという人がいたら、Goを勧めると思います。
言語仕様のシンプルさについても、本当に必要なものを選んでいることは伝わってきました。最小セットで始めて必要な人がいたらアップデートで追加するというのも悪くないスタイルです。しかし、それでも痒いところに手が届かないなと思うことがいくつかありました。
Goへの批判とそれに対する反論の記事はたまに見かけては読んでいたので、Goの思想については理解しているつもりです。ただ、Battery includedを思想とするPythonから移ってきた身としては「それもできないんだ」と思ってしまうこともあります。
とはいえ、今回利用させていただいた otiai10/copy のように、有志がライブラリを作ってその不便さを打ち消そうとしていたり、公式側も必要なものは入れていくという思想だったり(現にGenericsは手のひらを返して1.18で入っていますし)、そういうところを見ているとこれからもGoという言語の利便性は上がっていくように思えました。