emahiro/b.log

Drastically Repeat Yourself !!!!

Nuxt3 で nuxtCtx.tryUse is not a function を直す

Overview

Nuxt3 の RC8 を使おうとした際に Cannot start nuxt: nuxtCtx.tryUse is not a function というエラーに当たったのでその解消方法についてのメモです。

対処方法

以下の issue に書いてあるがこのエラーの原因は rc6 -> rc7 において互換性がなくなっていたことで、@nuxt/kit を依存に追加することで解消します。

github.com

$ npm install @nuxt/kit

# package.json
{
    "@nuxt/kit": "^3.0.0-rc.8" # 追加
}

これで解消します。

余談

業務上のプロジェクトで上記と同様の手順を行ったら次に Cannot find module '@nuxt/vite-builder' というエラーが発生しました。

これを解消するに以下の issue を参考にして、強制的に vite-builder を追加しました。

$ npm i @nuxt/vite-builder --force

github.com

Nuxt3 × TailWind - Importing directly from a nuxt.config file is not allowed. を直す

Nuxt3 と Tailwind CSS を使っているときに下記のドキュメント通りに Tailwind CSS を設定すると Importing directly from a nuxt.config file is not allowed. というエラーが発生する。

tailwindcss.com

これは nuxt の設定ファイルを tailwind を適用する module に指定してるからなので tailwind.config.js から ./nuxt.config.{js,ts} を削除することで解決する。
既に issue でも言及されている。

github.com

Alexa を使ったスマートホーム化の話

Overview

戸建に住み始めてもう引っ越すことも一生のうちに何度もないだろうっていうくらい住宅の流動性が低くなったので最近になってようやくスマートホームを作ろうと思っていて、その途中経過について記載します。

今までもやろうやろうと思ってはいたんですが、いかんせん賃貸だといつか引っ越すだろうしその都度一から設定するのは面倒くさいと思ってちゃんと取り組んできてこなかったのですが、戸建も購入したことでようやく腰を据えて取り組み始めていますし、今後少しずつアップデートしていこうと思っています。

用意したもの

  • Amazon Echo (何を用意するかは自由だと思います)
    • 我が家はリビング、キッチン、寝室にそれぞれ一台ずつ置いてます。
  • Switch Bot
    • 現時点で使用してるのはカーテンと Hub、リモートスイッチです。

配線図

現在の 1F の配線図は現場以下のようにしています。2F は進行中なのでまた別で書こうと思っています。

SwitchBot Hub で接続してるのは赤外線のリモコンだけで、ルンバや FireTV は Echo とそのままデバイスリンクできます。

今使ってるスキル

今のところ以下のもののみです。それ以外のスキルもいらないので。

  • Amazon.co.jp: 今日のゴミ出し : Alexa Skills
    • キッチンの echo dot に入れています。
    • いくつか試した中で最も安定してました。
    • 初期設定が面倒くさいですが、ゴミ出し日は変更するものでもないのでとりあえず使えるレベルにはなってます。
    • 「明後日」という言葉を識別しないのがだけが不満です。
    • 他にいいのがあれば乗り換えるかもしれませんが、当面はこれでいいかなと思っています。

ハマったところ

リモコンには型番がある

SwitchBot Hub 経由で Amazon でエアコンやテレビの操作するときに「スマートラーニングモード」というものを使うのですが、そこでどのリモンを使うのか、型番指定があります。

同じ機種でもリモコンの赤外線の中身は微妙に違うらしく、スマートラーニングモードでレコメンドされた型番のリモコンを一つ一つ試してどのリモコンで意図する挙動になるのか自分で調べて設定する必要があることを初めて知りました。

設定は簡単でスマートラーニングで検出されたリモコンの型番リストがあるので、ボタンを押しながらスマートラーニングした電化製品が操作できるかを一個一個確認して操作できる型番を確認する必要があります。

Fire TV と Alexa を接続する

Alexa はデフォの状態では Fire TV を操作することはできません。「Alexa、Youtube 開いて」と何もしない状態で発すると例えば EchoShow などは Kindle Fire TV ではなく EchoShow の画面で Youtube を開こうとします。

Alexa で FireTV を操作するためには以下の設定をします。

https://www.amazon.co.jp/gp/help/customer/display.html?nodeId=G7JTYZL789TQJHKV

やってみて

だいぶ遅れてスマートホーム化と称して色々試してみましたが、今はかなり簡単にできることを初めて知りました。周辺のエコシステムの進化も含めて感動しました。

個人的には今更感ある組み合わせとかまだまだ足りないところあるなーと思いつつも、声で操作すると言うことを自分だけじゃなく奥さんが使ってくれてるのを見るのはエンジニア冥利に尽きると思いました。
ただ、1Fでやりたかったことは今回の設定で大まかに揃えたものの、1F でしかにしかないエアコン操作等を早く2Fでもできるようにして欲しい等の追加要望を受けてるので SwitchBot Hub をもう1つ買わないといけないなと思っています。

なお今回このエントリで書かれている内容は全て Youtube でそれっぽい単語で検索するとこのエントリよりも映像でわかりやすい内容が出てきますので、そちらを参考にするのが良さそうに思います。

戸建を買った話

Overview

実は今年の3月に戸建てを購入して5月から入居しているのですが、戸建を買うに至った経緯を備忘録として残しておこうと思います。

ちなみに去年の9月に賃貸マンションに引っ越していたので、わずか半年でまさか戸建を買うとは本当に思っても見ませんでした。

去年12月に入籍してその3ヶ月後に家買う、という意味不明なスピード感で人生が進んでしまっていて自分でもビビっています。

なお、当たり前ですが、このエントリでは僕の家の話を書いているので万人に当てはまるとは思ってません。あらかじめご容赦ください。

なぜ買おうと思ったか

あくまで僕の場合に限った、購入に至る意思決定のプロセスについての説明になります。

そもそもこの手の購入の話になると基本的には戸建の購入の前に「賃貸」VS 「ローン」論争があると思います。

僕自身、独身の時はずっと賃貸でもいいかなと思っていたのですが、賃貸は基本的には流動性をとった上で、掛け捨てで住居を手に入れる方法でもあります。ただ、僕はいちいち住み替えるのがめんどくさかったり、一度住むと決めると自分は余程のことがないと引っ越さない性分でもあるので、流動性を担保しておく必要があまりない、ということがありました。

また、僕自身は家は広い方がよく、同時に固定費に対してがめつい性格でもあったため、都心賃貸の家賃に対する間取りのコスパの悪さ(利便性を優先して高い家賃で狭い部屋に住む)も気にはなっており、賃貸の家賃と同額で比較すると、ローンで家を購入する方が広い間取りが手に入るし、自分達の所有物になる(資産になる)ので、購入する方がいいかなとは以前から考えていました。

上記のような考えに加えて、仕事柄、転勤というものが存在せず、それに加えてコロナ禍にあって仕事もベースは WFH に移行していたので、引っ越さないといけない、もしくは引っ越した方が良くなるようなシチュエーションもほぼ存在しなくなったので、住む場所を固定することに対するネガティブ要因はほぼ消えており、買うことのボトルネックは無くなっていました。

ついでにいうと、賃貸のみで行く場合、人生のどこかのタイミングで年齢を理由に借りられなくなるリスクがあります。日本の不動産屋は高齢者への住居の貸し出しを敬遠しがち(年齢による健康問題や収入面の不安)で、収入面をクリアしても将来的には自分の希望する物件に住めない可能性があり、これが自分にとっては将来的にはかなりストレスになるなと感じました。今回購入した物件を終の棲家とするかはわかりませんが、人生何があるのかわからないので、住居も自分達でコントロールできる変数に入れておこうと思ったので購入の選択をしました。

なぜ戸建てなのか

はい「マンション」VS「戸建」の話です。
まず前提をいうと自分は元々「マンション」を買おうと思っていました。
ただ、本当に不思議なもので所沢近辺で一番大きな住宅メーカー(当時住んでいた賃貸マンションを紹介してくれた不動産屋と同じ)に話を聞きに行った際に、自分達の状況を整理してみると戸建の方が都合が良い、ということがわかって、そしてたまたまその当日内見を勧められた新築物件を見に行ったところ、奥さんも気に入って自分も条件を満たしていたので購入を決めました。

こういう話はあんまり長く悩んでもしょうがないし、サンクコストがのしかかってくるので色々面倒だと思ってその場で決めてしまいました笑

なおマンションにせずに戸建てにした理由については以下の2つです。

  • 欲しいと思うマンションが絶妙に高い。
  • 車を持っていた。

1つ目の理由ですが、まず所沢のマンション高かったです。
自分としては10年後くらいに戸建を買うための頭金にしようと思ってまずはマンションを選択するつもりでしたが、探していた範囲の物件が思いのほか高く、10年後の売却価格を考えるとそこまでプラスにならないことがわかりました。

自分で探してみてびっくりだったのですが、首都圏まで電車で30分前後の準郊外的なポジションの地価もまぁまぁ上がってて、今マンションに手を出すのも微妙...な感じになってしまってました。
間取りとしても自分は WFH なので寝室とは専用の書斎が必要で、奥さんも当面は自室が欲しいという話だったので少なくとも3部屋以上は洋室、という間取りを条件に探してましたが、ほぼありませんでした(和室込みの4LDK とか 3SLDK はあったんですけど) あったところも予算的に厳しかったのでスキップで、結局マンション微妙じゃね?と思うに至りました。

そうした中、結局これが決め手になったといっても過言ではありませんが、奥さんが車通勤であり、自家用車を所有していたので駐車場が必要でした。マンションを購入するとなると自分が探していた範囲のマンションの多くは「ローン + 管理費」にプラスで駐車場代が乗ってきます。特に管理費と駐車場代は掛け捨てなので、結局のところローンで買って、10年後に売るとなってもそれまでにかけた管理費と駐車場代で相殺してみるとあまり手元の資金はプラスにならないことがわかりました。

固定費に自分は敏感なので、住居において資産にならない固定費がかかり続けることにネガティブになったこと、また結局10年後の住み替えを考えると引っ越しが面倒くさくなると思ったので、マンションをやめて一思いに戸建てを買うことに決めました。

選定条件

最後に物件の選定条件ですが、僕はほぼ駅近で資産性があるところ、という条件のみであとは筐体(住宅本体)の性能が良いもの、くらいの条件を置いてました。正直冒頭にも書いた通り、今後のキャリアにおいてもう働く場所に縛られることはないことがわかっていたので、僕はどこに買うか?という点についてはこだわりがあまり無く、定期的に都内には出るので出やすいところで上記の条件が合うところ、くらいのざっくりしたもので十分だと考えてました。
特に以前住んでいたマンションは駅徒歩15分のところにあったのですが、やはりどこかにいくのが面倒になりがちで自分も仕事以外で都内に行くことはそれなりにあるので、できるなら駅徒歩10分圏内を重視していたくらいです。(住宅の資産性は基本立地に全て依存するので、駅近であればある程度資産性も兼ね備えている、という前提はあります。)

僕の条件は上記のようなものだったため、実はどこに住むか、という場所については、うちではどちらかというと奥さんの意向が強く、勤務先への車通勤しやすく、かつ奥さんの実家が鳩山の方で定期的に車で帰りたいという話があったので、その条件を満たす場所で僕が元々使っていた西武池袋線沿線に絞り、その沿線で便利な駅、で探しました。ちなみに条件的には東武東上線沿線も考えましたが、東武線沿いは西武線沿いに比べると条件面(主にコスト面)で折り合わずに諦めました。

結果、先ほどあげた新築の物件(建売)が西武線始発駅の徒歩10分圏内にあり、2人とも気に入った物件だったのでそこに決めました。

あと、後押ししたというほどの話でもないのですが、購入した物件は住宅メーカー自身が土地から買い上げて新築分譲として家ごと売りに出してるいわゆる売主物件というやつで、これの場合仲介手数料がかかりません。不動産売買と住宅メーカーを兼務してる企業ではある話で、覚えておくと仲介手数料は節約できるかもしれません。大体チラシに書いてあります。

購入する

購入に際しては書類を揃えたり記入したり、と面倒なことは多かったですが、年跨ぎで1年以内に引越していたので、税金関係の書類は当時の住所ではなく、前の都内に住んでいた頃の住所で取りに行かないといけない(つまり自分だと練馬区)と言うことと、健康面の申告書類には薬の名称まで記載する必要があったこと以外はそこまで手間なことはありませんでした。服用薬についてはお薬手帳持ってる人は融資申請の際に持っていくと良いと思います。僕は服用していた花粉症の薬が思い出せず自宅まで取りに行ったりしてちょっと手間でした...。
今はマイナンバーカード持ってるだけで取得できる書類等もあり、一時期よりは行政関連の書類の取得しやすさも上がっています。何より WFH なので業務の合間を使って市役所に行ったりすることもできるので、僕自身は面倒な作業と感じたことはありませんでした。

ただ一点注意したいのはローンを受けるフェーズにおいて副業をしてると若干不利になるケースがありそうと言うところです。
副業をしていて確定申告をしている場合、申告している源泉が実際の給与所得より低いと言うケースはこの業界だと珍しくないかと思いますが、ローンの審査で見られるのは実は目減りしてる確定申告の方なので、ローン審査で跳ねられる可能性があることを初めて知りました。

僕は幸い大丈夫でしたが、金利の設定やそもそも融資を受けられるのか?ということに直結するので覚えておいた方が良いと思います。

戸建購入後の話

ここからは住宅を購入してみて気づいた(というか初めて知った)話について記載します。ほとんどお金の話です。

住んですぐリフォームが必要

なんのことかとお思いかもしれませんが、これカーテンレールの話です。
今まで賃貸にしか住んだことなかったので、気づかなかったのですが、戸建ては基本的な設備以外何もないのでこういったカーテンレールなども後で自分で取り付けないといけません。住宅によっては最初から建設費用に入ってることもあるかと思いますが、あとからリフォームとしてやることになります。

なお予算は間取りにもよりますがうちは大体20万円くらいかかりました。。。

テレビがつかない

何をいってるんだと思われるかもしれませんが上記のリフォームと同じでテレビも通ってないので、自前でアンテナを購入して工事を依頼する必要があります。賃貸だと当たり前にテレビは通っていたので、初めてテレビのない生活をしました。
ちなみに、地上波デジタル/BSのアンテナ + 工事費で大体 10万円弱くらいでした。工事費がまぁまぁ高いです。

引越するなら購入した不動産屋さんに紹介してもらうと良い

引っ越しに際して業者を使うことがあると思いますが、特に戸建を購入した場合には購入した住宅メーカーもしくは不動産屋に紹介してもらうと結構安くなります。
引っ越しにかかる費用って総額の半分くらいは家電の工事費だったりするので、ここが安くなることはほとんどないのですが、引っ越しそのものの価格は自前で見積もりを取るより紹介してもらったところを素直に使う方が安くなります。
うちは引っ越し費用については半額でしかも工事費込みの見積もりで全額クレカ決済できるところを選んで今間の引越しの中で一番楽でした。 工事費、当日現金でしか受け付けてなかったりすることも多いので。。。

賃貸のことには考えられなかった営業がくる

あとから考えるとあるあるですが、生協、ヤクルトの営業は順当に来ました。あと初めて体験したところだと太陽光発電パネルの営業が来ました。

まとめ

人生で最大の意思決定と言われがちな戸建購入ですが、色々考えはしつつ、割と勢いでスパッと決めてしまいました。 まぁ住めば都とは正しくという感じで今のところ何不自由なく暮らしてます。

余談

最近の関心事はスマートホーム化と造園です。大きくはないですが縁側に小さな花壇になりそうなスペースがあるのでここに造園業者を入れようかなと思っています。最初自分達でやろうかなと思ったんですが、先人の知恵的には初回は造園業者を入れた方がいいと言われたので業者を入れようかなと思っています。

go.opentelemetry.io で spanID と traceID を取得する

Overview

タイトルの通りですが以下の Pull Request にて go.opencensus.io から go.opentelemetry.io に乗り換えたので、乗り換えた際に従来通り span と trace を取れるようにした内容についてまとめます。

github.com

 そもそも OpenTelemetry とは?

new relic の以下のエントリを参照します。

OpenTelemetryは、サービスインストゥルメンテーション(パフォーマンスを測定する方法)のオープンソースプロジェクトであり、統一規格です。Cloud Native Computing Foundation (CNCF)がスポンサーとなり、OpenTracingとOpenCensusに代わるものです。その目的は、テレメトリーデータを収集し、バックエンドプラットフォームに送信する方法を標準化することです。

ref: https://newrelic.com/jp/blog/best-practices/what-is-opentelemetry

Opencensus に変わる計装器の統一規格であり、今後はこちらに統一されていくと考えたので、slslog で使用していた go.opencensus.io の実装を go.opentelemetry.io の実装に差し替えました。

とはいえ、slslog では opentelemetry に依存しているのは span と trace の生成のみで、これ自体は opentelemetry に依存する必要はありません。ただし、今後の規格に対してあらかじめ統一しておく方がいいだろうと考えて実装しました。

Span と Trace の取得方法

基本的には Opentelemetry の公式手順に則るのが良いと思います。(私は Go を採用してますが、他の言語のドキュメントも充実しています。

今回はデフォルトの方法ではなく、マニュアルで trace と span を埋め込む処理を実装しました。

opentelemetry.io

ハマったところ

実装方法自体は Pull Request を参照してください。実際に実装する時にハマったところとしては "go.opentelemetry.io/otel/sdk/trace" を使って trace の exporter を生成してアタッチしておかないと Tracer が取得できない、ということでした。

どういうことかというと具体的な実装箇所として span を取り出す処理として

ctx, span := otel.Tracer("github.com/slslog").Start(ctx, label)

という実装をしている箇所がありますが、あらかじめ TraceProvider をセットしておかないと otel.Tracer で TraceProvider を取得できない(実際には空であり、spanID も traceID も空になる)ということがわかりました。

これは otel.Tracer(...) の実装を見ると、 https://github.com/open-telemetry/opentelemetry-go/blob/e99a0ac0b5397c9997449131f7efa7a19442d1bf/trace.go#L26-L28 にあるようにデフォルトの TraceProvider を取得しておりこれを前もって Set しておかないといけません。

また Span を始めるときの Opencensus の実装を見てみると https://github.com/census-instrumentation/opencensus-go/blob/49838f207d61097fc0ebb8aeef306913388376ca/trace/trace.go#L212-L274 になるのですが、 startSpanInternal の内部で色んな処理(ID の生成含む)を行なっています。

つまり OpenTelemetry で実装する場合を考えても、この Opencensus の頃の実装を踏襲する必要があります。

そこで調べてみると go.opentelemetry.io の中でも sdk/trace の内部で ID 生成を実装してる箇所がありました -> https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/id_generator.go

特にこの中の defaultIDGenerator という処理がこの ID 生成を担当してますが、これは最終的に sdk/trace 内の ensureValidTracerProviderConfig メソッドという中で default の ID を生成する処理の中で call されており、この Call 元は sdk/trace.NewTraceProvider になります。
ref: https://github.com/open-telemetry/opentelemetry-go/blob/d96e8d2912afcb0d205a80739798b9a56a7f0e70/sdk/trace/provider.go#L100

つまりここで sdk/trace を使って TraceProvider を生成し、これをある処理における TraceProvider を指定することで NewTraceProvider が Call されたタイミングでデフォルトで生成された ID(span と trace に指定されるもの)が入ることになります。これに気づくのにだいぶ時間がかかりました。

まとめ

Opentelemetry を使った初歩的な実装を紹介しました。

See Also

pkg.go.dev

pkg.go.dev

wezterm を使い始めてみた

Overview

iTerm を長年使ってきましたが、ちょっと気分を変えてみようと思って別のターミナルエミュレーターを探してて以下のスクラップを見つけて、Wezterm を使い始めてきました。

zenn.dev

余談ですが、このスクラップにも言及されてるようなターミナルエミュレーターは最近 Rust 製のものが多い印象ですね。

インストール方法

以下のインストール手順に則ってインストールします。

wezfurlong.org

Mac 版のインストール手順で自分もインストールしました。
なお、最近は brew update か go update(たまに npm upgrade) で手元のツールを一括管理したいので、自分は brew tap wez/wezterm を採用しました。

設定

設定方法も詳細にドキュメント化されています。ここまでドキュメントにしてくれる無償ツール、ということで頭が下がる思いです。

wezfurlong.org

設定ファイルは $HOME/.wezterm.lua を作って lua で記述します。 といっても lua は見た目が Javascript っぽいですし、そもそも return されているオブジェクト内の property を追加していくだけなので難しくはありません。

自分はあまりカスタマイズしない主義なので、とりあえず初期状態で以下のように設定しました。

local wezterm = require 'wezterm'
return {
        font = wezterm.font_with_fallback { // フォントの設定 (※1)
                'HackGen Console',
                'Menlo Regular'
        },
        font_size = 17.0, // フォントサイズ
        color_scheme = "Dracula", // Theme
        window_background_opacity = 0.93, // 背景の透過
}

※1. ここでは手元にインストールされていないフォントがあったときに FallBack をしてくれる property になります。ドキュメントにも書かれています -> Fonts - Wez's Terminal Emulator
ここまでターミナル側で設定させてくれるツールも少ないので優秀なツールだなと思いました。

使ってみて

とりあえずサクッと使っていますが、基本的な設定は全て iTerm を踏襲してるので、ターミナルの使い勝手はそこまで変わっていないです。
ただターミナルの軌道と環境変数の読み込みが iTerm に比べると体感ではっきり速くなっていると分かります。

まだ移行したてなので、もう少し使ってみて所感を確かめてみようと思います。

Nuxt3 で SSG ができるようになっていた

Overview

※ 筆者がたまたま Nuxt3 を弄っていて気づいた話なので最新の動向は GitHub の Discussion を追ってください。

github.com

Nuxt3 に移行を始めた当初は Nuxt3 が RC になったばかりで、Static Site Generator (SSG) が実装されていませんでしたが、先日 RC5 で試してみたら nuxt.config.js の defineNuxtConfigssr:false かつ target:static の状態でビルドができるようになっていましたので、自分のポートフォリオサイトでは SSR をやめて SSG に移行しました。

Context

あらためて調べてみると公式でも公開されてましたね(まだフルで実装はされていないっぽいです)

v3.nuxtjs.org

そもそも、元々自分が Hosting してるポートフォリオサイトは Firebase 上に構築しているのですが、Nuxt3 に移行したタイミングでは SSR モードでしかビルド & ホスティングができず、CloudFunctions のサービスを SSR 用のサーバーとして使う必要があり、Functions に実装していた固有の実装を諦めた、という背景がありました。

大したページでもなく、そもそも SSR が必要とされるユースケースはなかったので、いい機会だったので元々採用していた SSG モードにして、SSR モードのためだけに占有されていた Functions のリソース開放も兼ねて、ポートフォリオサイトのビルドを作り直しました。

SSG やってみた

公式で記載されている通り、SSG を利用するには defineNuxtConfig 内の ssr property を false に変更します。
※ nitro を使った prerender の実装についてはべっっと調べてみる予定です。

ssr: false にした状態で改めて nuxi build を実行すると以下のように .output 配下に html ファイルが作られるようになります(SSR モードの時はこれが作られませんでした)

Nuxt CLI v3.0.0-rc.5                                                                                                                                        02:20:51

 WARN  [kit] [compat] Using extendBuild in Nuxt 3 has no effect. Instead call extendWebpackConfig and extendViteConfig.                                     02:20:53

ℹ Vite client warmed up in 353ms                                                                                                                            02:20:53

 WARN                                                                                                                                                       02:20:54
(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

ℹ Client built in 1325ms                                                                                                                                    02:20:54
ℹ Building server...                                                                                                                                        02:20:54
✔ Server built in 5ms                                                                                                                                       02:20:54
✔ Generated public .output/public                                                                                                                     nitro 02:20:55
ℹ Initializing prerenderer                                                                                                                            nitro 02:20:55

 WARN                                                                                                                                                       02:20:55
(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

ℹ Prerendering 3 routes                                                                                                                               nitro 02:20:56
  ├─ / (2ms)                                                                                                                                          nitro 02:20:56
  ├─ /200 (1ms)                                                                                                                                       nitro 02:20:56
  ├─ /404 (0ms)                                                                                                                                       nitro 02:20:56
start Building server...                                                                                                                              nitro 02:20:56
start Writing server bundle...                                                                                                                        nitro 02:20:57
✔ Server built                                                                                                                                        nitro 02:20:57
  ├─ .output/server/package.json (307 B) (182 B gzip)
  ├─ .output/server/index.mjs (458 B) (257 B gzip)
  ├─ .output/server/chunks/nitro/node-server.mjs.map (57.5 kB) (4.4 kB gzip)
  ├─ .output/server/chunks/nitro/node-server.mjs (17.1 kB) (5.37 kB gzip)
  ├─ .output/server/chunks/handlers/renderer.mjs.map (15.6 kB) (2.74 kB gzip)
  ├─ .output/server/chunks/handlers/renderer.mjs (11.8 kB) (3.83 kB gzip)
  ├─ .output/server/chunks/app/client.manifest.mjs.map (825 B) (335 B gzip)
  └─ .output/server/chunks/app/client.manifest.mjs (833 B) (319 B gzip)

prerender 用の2つの html ファイルと server 用の成果物が生成されます。

SSR モードの時の成果物は以下で、HTML が生成されないので SSR と SSG では成果物が異なることがわかります。

Firebase を使っている場合、Hosting 先は .output/public 配下なので変わりませんが、SSG モードにすると index.html がそのままホスティングされます。
また Functions に SSR 用のサーバーを立ててそれ経由でしかホスティングされたサイトにアクセスできませんでしたが、HTML ファイルをポン置きするだけでホスティングできるようになったので Functions の役割は元に戻せます。

というわけで Hosting するものを html に変えて再度デプロイをしてみたところ普通 https://emahiro.dev/ にアクセスすることができました。(もうこれでいいじゃん)

Functions も役目を終えたので元に戻すために改めて firebase init functions を実行して Functions のファイルを生成しました。

あと変更してみて気づいたことですが、SSG にしてみて感じたのは表示速度が速くなっていました。
おそらく SSR モードの時は Functions を起動するまでの cold start 問題にぶち当たっていたと思われます。

余談

Functions deploy がこける問題

lint と build が predeploy で強制的にかかってしまう

firebase init functions を実行すると、firebase.json の設定も変更され predeploy で lint と build が実行されてしまいます。

// firebase.json

"functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint" // default
      "npm --prefix \"$RESOURCE_DIR\" run build" //default
    ],
    "source": "functions"
}

local では source に指定した path を指定して lint & build をしてくれるのですが、lint は成果物となる js にもかかってしまうこと、そして actions では working directory の path の package.json が使われてしまっており、source フィールドの指定が効いてないなかったことが起因して、command not found が発生して actions から deploy ができなくなっていた問題がありました。

行った対応は以下3つです。

  1. .eslintignore にコンパイルされた js が入る lib directory を記載して lint 対象から外す。
  2. predeploy の設定を ignore。
  3. actions で functions directory に入った状態で lint & build をするように変更。

Nuxt3 で FontAwesome を使う

Overview

少し前にポートフォリオサイトを Nuxt2 -> Nuxt3 にアップデートしましたが、このときに互換性がまだなかったので FontAwesome を一旦外す選択肢をしました。

ema-hiro.hatenablog.com

久しぶりに調べたらどうやら Nuxt3 でも使えそうになっていたので FontAwesome をあらためて導入したのでその備忘録になります。

fontawesome.com

導入方法

Install dependencies

Vue で使う場合は以下の手順に沿って FontAwesome に関連する依存を入れます。

fontawesome.com

Setup Plugin

これがちょっとハマったのですが、Vue で使う場合には以下の公式の Setup Library の仕組みを入れれば大丈夫そうです。これは使いたいライブラリ群をコンポーネントに入れていくものなので CreateApp してる Root の Component でこの設定をすれば大丈夫そうですが、Nuxt は少し違います。

fontawesome.com

Nuxt3 で FontAwesome を使うためにすることは Plugin で FontAwesome の設定を追加することになります。
具体的には Nuxt3 でアプリケーションを実装したときに ./plugins というディレクトリが作成されるので、この Plugin 用のディレクトリに Nuxt の設定として FontAwesome を読み込む設定を追加します。

// ~/plugins/plugins.ts

import { fab } from "@fortawesome/free-brands-svg-icons";
import { fas } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
export default defineNuxtPlugin( ( nuxtApp ) =>
{
  library.add( fas, fab );
  nuxtApp.vueApp.component( 'font-awesome-icon', FontAwesomeIcon, {} );
} );

なおこの defineNuxtPlugin は他の読み込みしたいプラグインの設定も記載します。公式のドキュメントには以下のように記載してます。

composition-api.nuxtjs.org

調べると FontAwesome に限らず、コンポーネントを提供してくれるライブラリ群を読み込む場所としてこの defineNuxt* は使われるようですね。

How to use

Plugin で FontAwesome を読み込む設定を行ったらあとは SFC ファイルで FontAwesome のコンポーネントを使うことができます。

例えば以下の Twitter のアイコンを使いたい場合は SFC に以下のように記載します。

fontawesome.com

<font-awesome-icon icon="fa-brands fa-twitter" />

まとめ

FontAwesome を使いたいケースは多いと思うので今現在(Nuxt RC5) の状況での使い方をまとめました。すでに RC もだいぶ進んでいるので今回の使い方が大幅に変わることはないと思いますが、参考になれば幸いです。

余談

Nuxt3 の rc がなかなか上がらなかったのは vue-jest 依存のせいだった。

ポートフォリオサイトを乗り換えたときには Nuxt3 の RC1 を使っていたのですが、それ以降 RC のバージョンを上げるとビルドができなくなる、という問題に当たりました。

これ自体は以下の issue で上がっている RC1 と RC3 の互換がないことが関係していたのですが、

github.com

これを解消するために RC3 以上に上げようと思っても RC のバージョンを上げるたびにエラーが発生してしまいました。

そのため、一旦 npm rm vue-jest で削除してから 202207 時点で最新だった RC5 に上げました。

See Also

squirrel という Go のクエリビルダーが便利

Overview

ライブラリの紹介記事です。

squirrel という Go の クエリビルダーが便利だったのでその紹介です。

github.com

何をしてくれるのか

ORM 的にメソッドチェーンでクエリを動的に組み立ててくれます。

実際に使うときは README を参照してクエリを組み立ててください。最後に ToSql() を呼ぶと SQL と Placeholder に指定した値の slice (interface{} 型だけど)が出力されます。
以下はサンプルです。

Sample1

import sq "github.com/Masterminds/squirrel"

query, args, err := sq.Select("*").From("users").Where(sq.Eq{"id": 1}).ToSql()

// select * from user where id = 1;

Sample2

import sq "github.com/Masterminds/squirrel"

query, args, err := sq.Select("*").From("users").Where(
    sq.And{
        sq.Eq{"gender":  "male"},
        sq.Eq{"age": 10},
    },
).ToSql()

// select * from user where gender = "male" and age = 10;

クエリを組み立てる "だけ” の機能を提供してくれるので以下のような効用があると思います。

  • database/sql もしくは sqlx でクエリを実行するときに生のSQLを書くのがめんどくさいとき。 (*1)
  • ユースケースに応じて動的に出力されるクエリを変更したいとき。

*1. クエリを文字列で組み立てるケースで十分だとも思いますが、スペースの取り扱いをミスってクエリエラーが発生したり、クエリのフォーマットが個々人で書き方バラバラになったりといったストレスはこういったクエリビルダーを使った方が低減されると思います。

何をしてくれないのか

クエリを組み立てるだけなので、実際に DB と接続して SQL を叩くのは別のライブラリを使う必要があります。
※ ただ、それがこのライブラリの良さだと考えています。

ハマったこと

In が明示的に用意されていない

SQL の In 句がクエリビルダーに用意されてないのかと思って以下の issue を参考に In のヘルパー関数を用意しようかと思いましが、

github.com

以下のような書き方で In 句が出力されることを教えてもらいました。

query, args, err := sq.Select("*").From("users").Where(sq.Eq{"id": ids}).ToSql()

まとめ

依存が少ない状態で、ユースケースに応じて動的なクエリを変更したいケースでは愚直に文字列を繋いでくことを是として考えましたが、こう言ってクエリビルダーのみの機能を提供してくれるライブラリを知りました。他にもあると思いますが、 squirrel は提供してくれている機能もシンプルでいいライブラリだなと思います。

余談

カラムを全て取得する

squirrel と関係ないですが、SQL を発行する際にすべてのカラムを select クエリに書く(= ワイルドカードをつかない)のがめんどくさいので、struct のフィールドに指定したマッピング用の付加情報タグから、カラム情報を取得する実装をメモ程度に残しておきます。

type User struct {
    ID   int64  `$TagName:"id"`
    Name string `$TagName:"name"`
}

func main() {
    user := User{}
    t := reflect.TypeOf(user)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("%v\n", field.Tag.Get("$TagName"))
    }
}

https://go.dev/play/p/8gjhDrGQrD7 は sqlx でマッピング用のフィールドには db タグを使うので db タグの付いたカラム名一覧を取り出す実装です。毎回 reflect が必要なのがちょっとネックなポイントだなぁとは思っています。

追記

ちなみにワイルドカードをカラム指定とreflect によるカラム取得をベンチしてみたところワイルドカードが単純なパフォーマンスは一番いいことがわかりました。これはおそらく colums に渡す slice の大きさに比例していそうです。

$ go test --bench . --benchmem
goos: darwin
goarch: amd64
pkg: sample.com/go-sandbox
cpu: VirtualApple @ 2.50GHz
Benchmark_WildCard-10             484780              2097 ns/op            1688 B/op         33 allocs/op
Benchmark_PlainColumns-10         386214              2869 ns/op            2240 B/op         45 allocs/op
Benchmark_Columns-10              316792              3630 ns/op            2776 B/op         56 allocs/op

コードは以下

package main

import (
    "reflect"
    "testing"
    "time"

    sq "github.com/Masterminds/squirrel"
)

type User struct {
    ID        int64     `db:"id"`
    Name      string    `db:"name"`
    Age       int64     `db:"age"`
    CreatedAt time.Time `db:"created_at"`
    UpdatedAt time.Time `db:"updated_at"`
}

func (u User) Columns() []string {
    var columns []string
    user := User{}
    t := reflect.TypeOf(user)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        columns = append(columns, field.Tag.Get("db"))
    }
    return columns
}

func Benchmark_WildCard(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        sq.Select("*").
            From("`user`").ToSql()
    }
}

func Benchmark_PlainColumns(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        sq.Select(`id`, `name`, `age`, `created_at`, `updated_at`).
            From("`user`").ToSql()
    }
}

func Benchmark_Columns(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        sq.Select(User{}.Columns()...).
            From("`user_concierge_counseling`").ToSql()
    }
}

余談ですが、暗黙的なカラムと言う話もあるのでワイルドカードをそもそも使うのが正しいのか、と言うのはユースケースに依存すると思います。

タイピングを矯正してみている

Overview

全然技術的な話ではないですが、最近自分のタイピングの癖が気になっていたのでタイピングの矯正をしています。

Context

自分は大学生になった時から本格的にパソコンを使い始めていて、その時に独学で覚えたタイピングで今まで生きてきましたが、最近異常に手が疲れることや誤植が多くなってきていることが気になっていたので、調べてみたところ、どうやらタイピングの基本となる両手のホームポジションの位置に変な癖があることがわかりました。

これが原因となって特に右手なのですが、異常に動かす回数が多くそれが右手のタイピング疲れを誘発していたっぽいです。

コードを書くだけだと実はそこまでタイピング速度は必要ないのですが、wiki や資料を書くことが増えてきて、誤植が気になることも多くなってきたので、一念発起してタイピングの癖を矯正してみています。

やり方

www.e-typing.ne.jp

このタイピング練習 & 速度計測サイトで今の実力を測りつつ、正しい指の配置を訓練しています。

大体指の使い方に慣れたらいくつか例文を叩いてみて正しいホームポジションと指の流れを訓練しています。

やってみて

実際まだ初めて三日しか経っていないので全然上達してませんが、少なくとも気づいた自分の癖として、右手の薬指と小指が全く使えていないことがわかりました。もともと小指が短いというのもあって、タイピングするには割と小さい手なのではあるのですが、それでも薬指のポジションを調整してかなり使うようにした結果指の流れはだいぶ改善されました。

ただやはり慣れていないのがあって、仕事の中でやるとストレスがものすごいです。手足を縛られたような感覚でタイピングしてることが多いです笑

スピードは全く上がってこないですが、ミスタイプは減ってきている実感があるので、もう少し辛抱して続けてみようかなと思っています。

おしまい。

Nuxt2 ⇒ Nuxt3 に上げた話

Overview

タイトルの通りですが、Nuxt2 で書かれていた自分のポートフォリオサイト (emahiro.dev) を Nuxt3 に移行したのでその移行手順とハマったポイントについて記載します。

なおこのブログを書いている現在では Nuxt3 系はまだベータのままです。 (一応 RC にはなってるので現時点から大幅な破壊的変更が入ることは少ないと思われます)

また前提として自分は Nuxt2 系の最初のテンプレートの状態を少し変更したくらいのページ、つまり Plain な Nuxt のアプリケーションをアップデートしてるに過ぎず、実際には使っている依存の関係で一筋縄ではないかないケースがあるかと思います。

このエントリではあくまで Nuxt2 → Nuxt3 に Plain な状態でも上げるだけでいくつかつまづいたのでそのハマったポイントと解決策を備忘録として記載します。

準備

Nuxt2 系の最新まで上げる

まず最初に Nuxt2 系で書かれていたポートフォリオサイトを Nuxt2 の最新まで上げることにしました。

各種依存ライブラリを最新に上げる

Nuxt2 にあげたら次に npm audit fix して必要な各種依存のバージョンを上げます。

$ npm audit fix

まず Nuxt を最新にして依存周りも全部調整してビルドが通る状態にしてから Nuxt3 への移行を開始します。

Migration to Nuxt3

Nuxt2 → Nuxt3 への移行は 公式のマイグレーション手引き に従って作業を進めます。

Nuxt Bridge

まずは nuxt-bridge に移行します。これは Nuxt2 → 3 への架け橋的なポジションで公式のページには何がサポートされていて、何がサポートされてないのかのマトリクスがあります。

この Bridge の立ち位置はよくわからないんですが Nuxt2 系のエコシステムから Nuxt3 にすぐには移行できない理由があるケースにおいて、Nuxt2 系のコードをある程度残しつつ Nuxt3 の恩恵を受けるためのものなのかなと思いました。

そもそも SSG だったり、これは欲しいやろな〜と言った機能群については現時点ではまだ Nuxt3 においてサポートされてませんが、やはり Vue3 に対応した部分は使いたいと思われるのでそのために使われるものなのかなと考えています。

ただし、今後 Nuxt3 が正式リリースされた後にあった、どこまでこの Bridge がサポートされるのかわからなかったので、私は基本的には Bridge に上げた後、すぐに Nuxt3 の最新版にポートフォリオサイト全体を移行しました。

やり方はほとんど公式に書いてある通りなので詳細は割愛し、進める過程ではまったところをまとめます。

Named export 'isWindows' not found が発生する

https://github.com/nuxt/framework/issues/1697 に issue が上がっていたのでこちらを参考にしました。

これを解決するために set-env を追加します。

npm add -D set-env

ref: https://www.npmjs.com/package/std-env

これを install する際に core-js がないとエラーが発生したので、依存に追加します。

npm insatll --save core-js

core-js をインストール後にビルドができるようになっていればマイグレーション完了です。

Nitro サーバーが起動しない

Nuxt-Bridge の段階では SSG を使うことができ、ポートフォリオサイト自体も元々 SSG を使っていたので SSG を ON にしておきます。

import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
  nitro: false,
  ssr: false,
    target: 'static',
})

ref: https://github.com/nuxt/bridge/issues/27

SSG を On にした状態でビルドが成功したら Nuxt-Bridge への移行が完了です。

Nuxt3

Nuxt3 の移行にするために Nuxt-Bridge を外して Nuxt3 に移行ます。

※ 既に 202207 時点で Nuxt3 は RC4 まで来ています。

手順としては https://www.npmjs.com/package/nuxt3 に書いてある通りに install し、nuxt-bridge を package.json から削除します。

Nitro を動かすには SSG モードを OFF にする

Nuxt3 はこのエントリを書いてる時点では SSG を正式にサポートしてません。そのため Nuxt3 でアプリケーションを起動するには SSG モードをオフにして SSR を強制的に使用するほかありません。

import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
  nitro: true,
  ssr: true,
    // target: 'static',
})

SSR を強制されると言うことは、Nuxt3 で動いているアプリケーションを動かすためには、Node のランタイムがセットで必要になります。

これにより後述しますが、Firebase を動かす際に Firebase Functions をこの Node のランタイムで使用することになりますので、Firebase Functions で独自の実装だったり、API などを書いてるケースでは Functions を SSR ランタイムで占有されることになってしまいます。

Cannot start nuxt: Cannot find module '@nuxt/vite-builder' が出る

npm add -D @nuxt/vite-builder で vite-builder を追加する。

FontAwesome が動かない

※ 現時点での話になります。

元々ポートフォリオサイトでは FontAwesome を使用してましたが、この FontAwesome が原因で TypeError: Invalid value used as weak map key 発生しました。

調べると RC の状態でも FontAwesome を使うことができるやり方は見つかりましたが、結果として現時点では FontAwesome を使うことを諦めました。

https://zenn.dev/itsuo/articles/24e4f16eb0a190 あたりが参考になりましたが今無理して導入しなくてもいいかなと。

ちなみに公式でも Nuxt3 対応は進んでいるっぽいです → https://www.npmjs.com/package/@fortawesome/vue-fontawesome

FontAwesome を一時的に諦めたら Nuxt3 RC でもビルドが通るようにはなりました。

Nuxt3 のビルド方式に Firebase の設定を合わせる

Nuxt3 からはビルドの成果物置き場が変わります。

Nuxt2 ではビルドの成果物が dist というディレクトリになりましたが、Nuxt3 系では .output と言うディレクトリが成果物の置き場になります。

Firebase を使っている場合は、Hosting 対象を以下のように変更します。

"hosting": {
-    "public": "dist",
+    "public": ".output/public"
}

また SSR モードを ON にしている場合、Functions の src も .outout に出力される、SSR 用のディレクトリに変更します。

"functions": {
+    "runtime": "nodejs16"
+    "souce": ".output/server"
}

Firebase の対応

一部を上記の Bridge, Nuxt3RC 対応でも触れてはいますが、現状 Firebase に対応させようとすると現時点ではちょっとめんどくさいことが多いです。

特に現時点では SSG が Nuxt3 で対応していないために、Fuctions を SSR のランタイムとして動作せる必要があったりとちょっとばかり制限があるように感じます。(そんなことない、というマサカリは飛んできそうですがw)

本音を言えば簡易的なポートフォリオサイトなのであれば、HTML をそのまま出力してしまって Hosting にポン置きしたいなと考えています。

Firebase 本体にも issue としてはいくつか上がっておりこれから改善されることを期待して待っていたいと思います。

公式のビルドコマンドでは Firebase Deploy が通らない

これが一番困りました。現状 Firebase10.9 以上?だと nuxi build (*1) のみで生成した SSR のランタイムは動かないです(デプロイ時に転けます)

*1.nuxi は Nuxt3 用のコマンド

issue としては以下が上がっていました。

これの具体的なワークアラウンドとしては Nuxt3 の SSR で利用されている Nitro サーバー向けにビルドをする、というものがあります。

issue には具体的に以下のようなビルドコマンドが記載されていましたのでこれをそのまま叩くと手元から Firebase Deploy が通るようになります。

NITRO_PRESET=firebase nuxi build

ただしこれだけだと Functions のデプロイはまだうまくいきませんでした。

Functions のビルド結果が入っている .output/server 内部の package.json をベースに node_modules をインストールしておくことが必要でした。

このため、package.json には firebase 向けに以下のように Firebase 向けの script を用意しました。

"scripts": {
+    "fb:build": "NITRO_PRESET=firebase nuxi build",
+    "fb:start": "NITRO_PRESET=firebase nuxi build && firebase emulators:start",
+    "fb:clean": "cd .output/server && rm -rf node_modules && npm i && cd ../../"
}

Actions の設定は以下のようにしました。

steps:
   - name: Checkout Repo
     uses: actions/checkout@master
 
   - name: Setup Node
     uses: actions/setup-node@v3
     with:
       node-version: ${{ matrix.node }}
 
   - name: Install Dependencies
     run: npm install
 
   - name: Build for firebase
     run: NITRO_PRESET=firebase npm run build
 
   - name: Clean for firebase
     run: |
       cd .output/server
       rm -rf node_modules
       npm i
       cd ../../
 
   - name: Deploy to Firebase
     uses: w9jds/firebase-action@master
     with:
       args: deploy
     env:
       FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

See Also

zenn.dev

リードをやってみる意味について

Overview

少し前に Tech Lead から一人のエンジニアになったエントリを書きましたが、自分の視座の変化として書いた「リードをやってみる意味」についてまとめてみました。

ema-hiro.hatenablog.com

前程

何度か書いてますが、現職における (Tech) Lead のポジションというのはプロジェクトを推進することを責務として置かれています。

細かいところは前回のエントリーに書いてある内容を参照してください。

Memo

雑に Slack で会話した内容をまとめただけなので箇条書きで書いていきます。

Lead をやる価値

  • 事業の解像度が上がる。

    • 裁量が大きければ責任も大きくなるが、裁量が小さい時ほど不満が溜まりやすいのである程度裁量を持って自分で「意思決定すること」の訓練をできること。
    • 意思決定をするためには、現場レベルでは考えていなかったような情報を知らないと意思決定できないので必然的に「実装」レイヤー以外の情報に目を触れる機会が増える。
      • 実は「裁量に応じて責任も増える」が「裁量に応じてアクセッサブルな情報も増える」というのはやってみての気づき(※1)
    • コストやバジェット、人的リソースのアロケーションのされ方(嫌な表現をすると奪われ方)まで目の当たりにするので、事業はそもそもどうやって成り立ち、どうやって進めているのか?のリアルを体感できる。実装だけしてる時には意識してこなかったことが関心事になる。
    • 経営レイヤーと話す機会が増えるので、意思決定の基準(優先順位とも言える)を知ることができる。
      • あれこれわがまま言わない。適切なステークホルダー管理のものに物事を進めることができるようになる。
  • マネジメントの基礎を学べる。(ピープルマネジメント以外)

    • 事業活動というのは決まったロードマップに則って、リソースをどう確保して、何に投資するのかの順番を決め、実行していくことで、それを最小範囲で訓練できる。
  • 兵站がなぜ重要かを改めて理解できる。

結局のところ、業務で取り扱うすべての施策についてその意味や背景を強制的に理解することになる(というか自分自身が理解して、メンバーに説明して納得してもらわないといけない)ので必然的に解像度は向上しますし、意思決定に関わる機会が増えるので「決めてケツを持つ」訓練にもなります。
そしてこれらは自分のキャリアにレバレッジをかけてくれることにもなるので、キャリアにおける選択肢を大幅に広げてくれます。全ては自分に返ってくるものでもあるのでチャンスがあるなら積極的に引き受けてみることをお勧めします。

※1. もちろん原則 Lead が手に入れられる情報というのは現場レベルでもオープンにされ、アクセス可能であることが望ましいが、そもそもその情報を取りに行く機会が増える、という意味。

結婚式をやった話とそのお金について

Overview

5/28 に結婚式を上げました。
結婚式当日は参加した方にも楽しんでもらえて、僕らもすごく楽しい会でやってよかったなと思います。
また、今までは参加側だったので結婚式というものを主催側になることで、一気に当事者意識が上がってどこにどれくらいコストがかかっているのか?、どうしてそんなにコストがかかるのか?というカラクリがわかって興味深かったので、このイベントをするにあたってのお金の話もセットにしてざっくりまとめてみました。

方針

結婚式をするにあたって何を重視するかは家庭によると思いますが、我が家では重視したいところは以下の2つでした。

  • 家族にはちゃんと感謝を伝える場を設けること。
  • 友人とはワイワイしたいこと。

家族も交えて披露宴一回にするプランもありましたが、家族が置き去りになってしまうケースもあるなと思った(友人へお酒注いで回ったり、自分達も友達と写真撮ったりと忙しくて多分ちゃんとコミュニケーションとれないだろうな〜と予想してた) ので、イベントとしては親族メインと友人メインで分けることだけは先に決めてました。

特に入籍を決めたのがコロナ禍というのもあり、両家の両親同士ですら実際に会ったことはなかった状況だったので、両家挨拶もかねてちゃんとしておきたいな、と思っていたこともあります。 (ちなみに両家挨拶は奥さんの実家で Zoom を使ってリモートで行ました。)

親族とのイベントは挙式と会食に分けていたので、友人たちとの結婚披露パーティーは割とカジュアルにしたいなと思っていたところ、今は 1.5 次会プラン(披露宴ほど畏まってなく、また二次会ほどラフでもないという感じ)というのがあり、ちょうど良いなと思ったのでこちらを選択しました。

会の形態にはこだわりを持っていた一方で、逆に妥協できるポイントは以下でした。

  • 場所。
  • イベントの人数。

この「場所」を妥協できるポイントとしておけたのが、スケジュール的にも家計的にもかなり助かりました。教会へのこだわりやディズニー挙式などに二人ともあまり興味がなかったので、場所のコストを抑えつつ、また日程的にも柔軟に対応できました。

コロナ禍というのもあり、ワクチンが普及しているとはいえ、また新株の流行など状況がどうなるかはわからなかったので大体的に人も呼ばず、あくまで小規模開催にしたいなとも思っていたので、準備そのものへの負担は大きかったものの、4ヶ月という短期間で準備できたのはこのおかげもあると思います。

実際のコストポイント

典型的な結婚イベントに関して全部やろうとなった時の主だったコストポイントは以下でした。

  • 結婚式準備
    • ドレス・タキシードの貸出
    • 前撮り
    • 会場の事前準備
      • ウェルカムスペースや式中にやるミニゲームの準備、エンドロールムービーの作成依頼など。
    • 花嫁の事前準備
      • エステやネイル、髪のセットなど。
  • 結婚式当日
    • 式場
      • 挙式・親族会食
      • 1.5 次会
    • テーブル装架
      • 挙式・親族会食
      • 1.5 次会
    • 食事
      • 挙式・親族会食 (* 親族の人数分)
      • 1.5 次会 (* 招待分)

ざっと出しましたが、これら全てベースのものがあり、より「良い」ものを選びたければオプションで追加料金が乗っかってきます。

これの量がなかなかでした。基本ベースで満足することはないし、一生に一回のことだから(基本的にはねw)と言って妥協するラインが難しくなるので、オプションを選択することが普通になります。また、自分達で全部やると流石にしんどいのである程度業者にお任せすることも増えます。結局主催側として、着飾ったりリソースをアウトソースするだけ積み上げ式でコストが増えていく構造になっているので、最初の見積もりと最終見積もりの差額がリアルに100万円くらいあってびっくりしました。

感想

冒頭にも書きましたが、色々と準備は大変でしたがやってよかったです。(特に奥さんはウェルカムスペースの準備したり新郎側の衣装もメルカリで探してくれたりと本当に大変だったと思います。)

基本的には一生に一回しかしないこと(2度あってたまるか…)でもありますし、今の時代だとあえて結婚式をしない選択もある中で、あえて短期間でもコロナ禍でも、両親とお世話になった方に”ちゃんと”お知らせをする機会を設けられた、ということはまぁ振り返るとないよりはあったようがよかったかな、と思う次第です。

当日は Lovegraph で撮影した写真褒めてもらったり、何より基本的には祝い事な訳でポジティブな言葉をたくさんもらえる時間だったりした訳なので自己肯定感は爆上がりしました。こんなに上がることもそうそうないのでちょっと不思議な感覚でふわふわした気持ちになってました笑

めんどくさいことは避けがちですが、今回はチャレンジしてみてよかったなと思います。一生の思い出になりました。

余談_1: 結婚式のコスト構造

結婚式(挙式+披露宴)の平均費用って大体300万円前後らしいです。

もちろん何人招待するのか?にも依存しますが、大体平均40人くらいを呼んで上記のコストポイント全てを含んでのこの価格に収まるらしいです。

この価格を高いと見るか、安いと見るかは人それぞれ感じ方はあると思いますが、結婚式全体のコストを考えると半分くらいは「建物代」(*) とのことで、式場代を会場利用者で負担しあってる構造になるので、実際はこの半額くらいで全部揃うとのこと。

我が家の結婚式では今回はスマ婚というところに依頼しました。

smakon.jp

スマ婚さんは自前で式場を持っていないので、この建物代が利用料に乗っかってきません。式場も場所貸し事業であるので空きがあります。スマ婚さんはこの空いてる式場を使わせてもらうことになるので、式場利用料がほとんどかからない(実際我が家で利用したところは会食とセットというプランを利用したので利用料自体は無料でした)ということになります。 ラクスルのモデルに似てますね。

※ 式場を建てる建設費。各種人気な結婚式場を利用する場合には、この式場の利用料に式場所有側の建物の建設費回収分の金額が乗っかってきます。

余談_2: メルカリすごい

コスト構造のところにも書きましが、主催側として妥協できないライン、かつ手間をかけずにアウトソースしたい箇所を増やせば増やすだけコストがかかります。

一方で、結婚式というのは基本的には人生で1回しかないことなはずで、どうしても購入しないといけない衣装(例えば新郎側ならウェイングカラーシャツとかカフスなどのウェディング小物といったもの)を買ったとしても一回しか着ないのに数万円単位のお金がかかることになり、言ってしまうと非常にコスパが悪いです。

かけられるコストには限界があるので、どうしようか悩んでいたところ、この一回しか着ない、という前提に立つと一度着たものは誰かが必要としてくれるので二次流通市場に結構な量が出店されてることを奥さんが嗅ぎつけて、全てメルカリで揃えてもらいました。

花嫁の衣装類小物類、ブーケなども今は普通にメルカリで格安で出品されているので、サイズさえ合えば多少手間はかかりますが、新品じゃなと嫌だ、みたいなこだわりがないのであればコストを抑えることができます。実際これで数万単位でコスト削減できました。

メルカリは本当にすごいサービスだなと思いましたし、奥さんにはすごく感謝してます。

ちなみに言うまでもありませんが、僕らも結婚式後に使ったものを買った金額そのままでメルカリに出品しました笑

余談_3: 体験を買うときはモノを買うより財布の紐が緩くなる

一生に一回、という建前もあるのでしょうが、やはり人生で基本的には一度しかしないものに対してどこまでコストカットするのか?という思考は働きづらいです。
夫婦で最初に喧嘩するポイント、などとも言われますが、実際オプションとか見てみると、なんでもいいや、くらいに思っていた自分から見ても、「んーここはお金かけてもいいかもしれない」と思うポイントがいくつかありました(テーブル装花とか)

ドレスやタキシードもやはりベースで選べる範囲のものと、オプションかけて選べるものでは全然見栄えが違ったりして、なるほどうまい商売だなと思ったりもしました。

我が家は幸いにも基本的な予算の範囲をあらかじめ取っていたことと、花嫁準備は奥さん持ちでやる、と言う握りをしていたのもあり、基本予算範囲内なら基本的には妥協するな、という方針を決めたのでそこまで揉めなかったんですが、「体験を買う」という行為自体はその体験が人生に占める回数が少なければ少ないほど、妥協できなくなり、コストは膨れていく一方なんだな〜と痛感しました。

ちなみに予算で言うと諸々精算したら大体トントンくらいに収まりました。途中大丈夫かなこれ...とも思っていたんですが、終わってみるお金の話は穏やかにまとまっていたと思います。最初に予算を握っておくの大事。

i/o timeout エラーをハンドリングする

Overview

HTTP のりクエスをしたときに遭遇する dial tcp $IP: i/o timeout をハンドリングする方法について、そもそもこれがどうして発生するのか?と併せて調べた備忘録です。

http.httpError と net.Error

Go で API クライアントを実装する際に遭遇する Timeout エラーのパターンとして http package 内で発生するのか net package 内で発生するのかで2種類が存在(※) します。
この2つはそもそも発生するユースケースが違います。
http.httpError の方は Context deadline exeeded なので Context の生存期間が切れてそれ以上処理を継続できなくなったために発生するエラーです。一方で net.Error は i/o timeout なのでコネクションの Timeout に起因するエラーになります。
net.Error の方が一段深いレイヤー(net.Conn のレイヤー)で発生したエラーをハンドリングするエラーになります。

※ net.Error は Interface なので net package 内のその他定義されてるエラーも必要な interface を実装していれば net.Error として振る舞うことができます。

net.Error で発生する Timeout のハンドリング

net.Error は interface になっている のでこの interface を実装してるエラーであれば Timeout() を call して Timeout かどうかをハンドリングできます。

そのため以下のように net.Error でキャストします。

resp, err := http.DefaultClient.Do(r)
if err != nil {
    if ne, ok := err.(net.Error); ok && ne.Timeout() {
            // Timeout 時の何かしらの処理
        }
    }
    return nil, err
}

余談: 実装を追っかけてみた

そもそも最初の疑問は timeout のエラーにも関わらず、http package を探しても見つからなかったので、どういうケースで i/o timeout がエラーが出力されるのかわからなかったので、調べることにしました。調べ方ですが、まずはどこでエラーが発火してるのか調べ、順々に呼び出し先を追っかけていきます。

エラー自体は net pacakge の timeoutError になります → https://cs.opensource.google/go/go/+/master:src/net/net.go;l=600

よく読むと timeoutError は net.Error を満たす interface を実装してるので net.Error の Timeout メソッドでハンドリングが可能になります → https://cs.opensource.google/go/go/+/master:src/net/net.go;l=598-602;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b

では実際どこで呼ばれることで i/o timeout が発火するのでしょうか?この timoutError が発火する先を遡って探していきます。

timeoutErrror が定義されているのは https://cs.opensource.google/go/go/+/master:src/net/net.go;l=596;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b になります。

この errTimeout が呼ばれてるところを探すと dial.go の partialDeadline で呼ばれてることがわかります → https://cs.opensource.google/go/go/+/master:src/net/dial.go;l=138;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;bpv=1;bpt=1

さらにこの partialDeadline を遡ります。そうすると同じく dial.go 内部の dialSerial 内で呼ばれてることがわかります → https://cs.opensource.google/go/go/+/master:src/net/dial.go;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;bpv=1;bpt=1;l=523

この dialSerial がどこで呼ばれてるか遡ると、その1つに dial.go 内の dialParallel の内部で呼ばれてることがわかります → https://cs.opensource.google/go/go/+/master:src/net/dial.go;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;bpv=1;bpt=1;l=449

最後にこの dialParallel がどこで呼ばれてるのか調べると dial.go 内の DialContext の中で呼ばれてることがわかります → https://cs.opensource.google/go/go/+/master:src/net/dial.go;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;bpv=1;bpt=1;l=376

実はこの DialContext は http.DefaultClient を使うときに自動的に指定される DefaultTransport の内部で呼ばれている defaultTransportDialContext ので呼ばれています。

defaultTransportDialContexthttps://cs.opensource.google/go/go/+/master:src/net/http/transport_default_other.go;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;bpv=1;bpt=1;l=15

DefaultTransporthttps://cs.opensource.google/go/go/+/master:src/net/http/transport.go;l=43-54;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;bpv=1;bpt=1

これからわかるのは i/o timeout は package が示す通り http のレイヤーではなく、一つ下の connection を管理するレイヤーで発火したエラーであることがわかります。Transport は DialContext で指定された値で接続されるので、timeout を発火させない場合はこの DialContext で指定する Timeout の値を増やせばいいことになります。

追記

Go1.19 で net.Error でキャストしなくて良くなる?

教えてもらいました。

実際に Go 1.19 のリリースノート(予定) を見ると、net package の箇所に以下のように記載してあります。

When a net package function or method returns an "I/O timeout" error, the error will now satisfy errors.Is(err, context.DeadlineExceeded). When a net package function returns an "operation was canceled" error, the error will now satisfy errors.Is(err, context.Canceled). These changes are intended to make it easier for code to test for cases in which a context cancellation or timeout causes a net package function or method to return an error, while preserving backward compatibility for error messages.

Go1.19 以降は net.Error でキャストして Timeout を呼ぶ必要はなく、context.DeadlineExceeded でハンドリングできるのは良いですね(http と意味合いが重なってしまうような気もしましたが)

Tech Lead から1人のエンジニアに戻った話

Overview

4月から2年間の Tech Lead の役割を降りて 1 エンジニア(最近だと IC って言うのが正式名称なんですかね)に戻りました。

元々飽き性な人間なので、同じ役割をずっと担っていると慣れとダレが生じてしまってチームに悪影響があったので去年くらいから一旦1エンジニアに戻してもらうことはマネージャーと相談していた内容で、色々と状況が許したのでようやく accept されました。

Tech Lead やってみての振り返りも書こうと思いましたが、以下にまとめてありますので割愛して、一人のエンジニアに戻ってみての振り返りをします。

medium.com

エンジニアに戻ってみて変わったこと

Tech Lead から 1 エンジニアに役割を移してみてに変わったなと思うことは以下の二点です。

  • リソース(=兵站)を意識するようになったこと。
  • 事業のあらゆるところに優先順位が存在することを理解し、ものごとをものごとを進めるようになったこと。
    • 優先順位に基づいた意思決定がされ、原則現場レベルで変更できる幅は非常に小さい。
    • 変更も可能だが、優先順位の変更コストはめちゃくちゃ高く、変える場合は変えたい側に説明責任が存在する。

まず1つ目の兵站についてです。むしろこれが一番変わったと言っても過言ではないかもしれません。

Tech Lead になるまでにも小さな案件からそれなりに大きな機能まで設計してきたことはありましたが、Tech Lead を担うようになって、自分が関わるプロジェクトにどれくらいのリソースが割り当てられて、それが実際のプロジェクトの推進にどう影響するのか?ということを目の当たりにし、思い通りにコトが進まないことも多いなと感じる機会も多くありました。

特に事業全体から見て優先度の高い案件をこなしてるときは、比較的優先的に兵站が確保されますが、そうでない時のマネジメントには結構苦労しました。

iOS の開発をしたいときに別案件でリソースが確保されない、みたいなケースはあるあるだと思います。自分が iOS を書ければ話は早いのかもしれませんが、実際そうじゃないケースの方が多いと思うのでこればっかりはどうしようもありません。

2年前くらいまではリソースが優先的に配分されることが多かったのですが、ラスト半年くらいは本当にやりくりに頭を悩ませました。それもあってか、事前準備だったり、ステークホルダーを最小にしたり、進め方や開発スタイルでカバーする癖がきました。結局のところないものねだりしても状況は好転しない + リソースは常に不足するものなので仕組みや進め方で”なんとかする”しかないんですね。

余談ですが、兵站は単純なリソースだけではなく、補給線も含めたリソース確保のパイプラインの役割もあるので、自分の手をあけるためという名目のもと、採用回りも積極的に顔を出すようになりました。元々そんなに積極的な方ではなかったですが、ここ半年は今まで真面目に関わってこなかった領域として「現職に誘う」というムーブを取れるようになったことは自分の中では1つ変わったポイントでした。

次に2つ目ですが、この役割を通して何事にも「優先順位」があり、その優先順位にしたがって全ての判断されている、という言語化すると当たり前すぎることを理解した上で業務を進めるようになったことです。

上述したリソースの話に関連して、リソースは優先順位が高いものからアロケーションされていくものであるので、自分たちのプロジェクトに割り当てられたリソースの中でどう回すのか?言うなれば与えられた手札でどう戦うのか?という思考をベースに物事を考えるようになりましたが、都度事業全体のロードマップを俯瞰してどこにどれくらい配分されそうで、その配分を変えるように依頼するにもプロセスが必要で、その説得プロセスを選択するコストの妥当性に目が行くようになりました。

ざっくりいうと、「優先順位を変える説明コストはめちゃくちゃ高くつくので、むやみやらとリソース欲しがらない」と言うことです。

このような考えを持ちつつも、現場レベルでリソースの上限を決めてできることを狭めてしまうことはプロダクト開発全体として見ると悪手であり、良いプロダクトが開発できないことに繋がってしまうので、現場だけで視野を完結させてしまうのは考えものです。

一方で、繰り返し述べているようにリソースは常に有限・不足しており満ち足りてることなどなく、特に人的リソースのアロケーションのリードタイムは想像以上に長いし水物である、と言うことを理解をしないと、リソース不足でやりたいことが実現できないというだけでものごとは前進していないという状況が続いてしまいます。

結局さくさく進んでいた時期と全然進まなかった時期を両方を経験して学んだことは、あくまで現場レベルでできることは、実現したいことに対してギャップが存在することを説明し、どれくらいのリソースがあればできるのかは説明すること、そしてリソースが確保されない事情も あらかじめ 理解した上で説明責任を果たし、現場レベルでできることを進める、ということ以外にないんだなーと考えるに至りました。

できる幅を進んで狭くすべしというわけではないですが、スコープとマイルストーンがなぜ説明する時に必要なのか、今できることで達成したいアウトカム(の一部)は実現できないのか?など現場から逆算して事業について考える機会をもらったのは Lead ポジションをしてみてよかったポイントだと思います。

エンジニアに戻って変わらなかったこと

逆に変わらなかったことはなんなのか?というを振り返ってみると以下でした。

  • コード結局そんなに書いてない。
  • 全体の整理 & ボール拾い役になる。

結局自分の性分なのかな、と思うこともありますが、コードを書く時間よりもその前の全体の状況整理だったり、そもそも何がやりたいんだっけ?というところの割り出し、ドキュメント整備、スケジュールの見積もり、もし厳しい場合は締め切りに間に合う範囲でストーリーを実現できる最小スコープを考えることに時間を割くことが増えています(※現在進行形)

こぼれ落ちそうな要件だったり、作業を一旦引き受けて決めたり誰かに回したり、作業としては泥臭いことをひたすらやるということを続けています。

これを続けてるといわゆるアジャイル?と呼ばれてる手法を実践しようとすればするほど、そこからは離れていくような感覚すら味わいました。どの粒度が個人やチームに適した形なのかはそれこそケースバイケースなのではっきりとはわかりませんが、自分が抱えてる案件レベルで言うともはやガントチャートなしにスケジュール出すことも、スコープ決めることもできませんし、この情報をなしにそれこそリソースアロケーションの交渉はできないなと思ってます。 (逆にできる人はどうやって説明してるのか?その方法を教えてほしさすらあります)

チームの中で若干ウィークポイントになりそうなところ、ボールが宙に浮いているところを見つけてカバーし、また次のボールを拾って〜みたいな動きは相変わらずで、結局疲れるけどできるから仕方ないか、みたいな感じで続けていました。

正直こればっかりやってると疲れるので、僕よりもっとこのボール拾いをできる人を連れてきたい気持ちが日に日に強まっています笑

まとめ

長々と文章を書きましたが、結局 Tech Lead のポジションをやった前後で事業に対する解像度は変化したこと、一方で現場に戻ってもやってることは大して変わらず、結局のところ自分の得意な領域は「ボール拾い」であったことがわかった、ということが書きたかった内容です。

事業全体を見たり、数字を読み解いたり、そこから仮説を立てて意思決定していくことはまだまだ道半ばでできませんが、現場にかかる意思決定がどういうプロセスでされていて、その背景、コンテキストを理解しようと努めることができるようになったのは個人のキャリアを考えても、ポジティブなことが多かったので、このエントリを読んだ方で、もしチャンスがあるなら積極的にこういったポジションを受けてみてもいいのではないかな?と思います。

うまく行くにしろ行かないにしろ、前後で事業や会社に対する解像度は多少変化しますし、変化した結果、できることやわかることが増えると、様々なところを構造的に見ることができるようになって面白かったりします。この面白さばっかりはうまく説明はできませんが、やって損はないと思います。ちなみにしんどいことはめちゃくちゃ多いと思います笑