出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

書評: 入門 監視

過去に一度目は通していたけど精読していなかったし、今後改めて監視強化したいと考えたので読み直した。

気になったところ

「監視の原則」の重要性

監視の原則や基礎を伝える上ではよい本だと改めて感じた。 特に第1部の監視の原則については今後も通用する内容であり、今後も記憶に留めておいた方がよいと感じた。

「デザインパターン2: ユーザ視点での監視」は自分の中では理解できていても、社内への周知が十分でないと感じるケースが多々ある。 「3章 アラート、オンコール、インシデント管理」は自分も実践できていない項目が多々あり、特にオンコール対応やインシデント管理の取り組みは改善の余地がたくさんある。

先日 Incident Response Meetup vol.1 に参加したとき、PagerDutyのインシデントレスポンス対応ドキュメントへのリンクがインシデント管理の章に掲載されていることを再認識した。

response.pagerduty.com

クラウドベースのサービス監視への取り組み

第2部の監視戦略について、監視対象に関する説明や取り上げている内容はオンプレミス環境を想定しているのか若干内容が古くなっている。 原則自体は変わっていないが、パブリッククラウドベースだったりコンテナベースのサービスを想定するとすぐに活用できる内容ではなくなってきている。

特に「8章 サーバ監視」や「9章 ネットワーク監視」は知っておくとパブリッククラウドにおける監視にも活用できるが、直接取り扱う情報ではなくなってきている。 「10章 セキュリティ監視」もパブリッククラウドにおいてはこれ以外にも重要な監視項目はたくさんある。

この書籍の内容は参考にしつつも、パブリッククラウド上において実践する上ではどのように取り組むべきか再考する必要がある。

情報の解像度が低い

そもそもが原則を伝える本であり、タイトルの通り監視の入門(原題ではPractical Monitoring)である。このため、この書籍だけでは具体的に監視に取り組むには情報が不足する。

「3章 アラート、オンコール、インシデント管理」よいアラートの仕組みを作るための6つの方法や、オンコール対応やインシデント管理の取り組み方が挙げられているが、実践しようとすると考えるべきことがたくさんある。付録Aには手順書の例が記載されているが、これが何かしらの問題解決に繋がるようには見えない。 「7章 アプリケーション監視」はパブリッククラウドベースのサービスにおいても重要な内容ではあるが、この章は20ページ程度と情報は足りていない。 また、この書籍では取り上げられていない、オブザーバビリティやSLI/SLOといったSREに関するプラクティスも非常に重要になってきている。

各章や節の内容が1冊の本になる程度には深いトピックを扱っているので、この書籍を起点に監視の取り組みを具体化していく必要がある。

第1回 AWSコスト削減 天下一武道会 に参加しました

コスト削減は永遠のテーマだし、これから注力しないといけないトピックでもあるので改めて情報収集のために参加した。

100社のコスト診断から見えてきた、コスト削減の王道とケモノ道

コスト削減をサービスとして提供しているdelta社の取り組み。

みんなでやることが重要であり、CTO/経営陣がタスクとして認識すること、モブコスト分析などでお祭りムードで取り組む、というのはなるほどと。 AWSコストに明るくない人にコスト感覚を養ってもらうのは非常に難しく感じており、モブコスト分析は面白い取り組みに思える。実際に取り組んでみたい。

また、取り組むなら根本的に安くするというのも非常に納得。サービスがスケールしたときにコスト増加の傾きを抑えるとか、ドメインと繋げて考えるとか、このあたりはコスト削減に取り組む上で意識しておきたい。

speakerdeck.com

節約は技術!削減は芸術!何より必要なものは覚悟!

スニダンのサービスを開発・運営するSODA社の取り組み。 NAT GatewayやCloudWatch Logs、CloudFrontなどで実践した事例。

speakerdeck.com

どこかで聞いたことのある話だな、と思ったらStartup Dayでも発表したのを聞いていたのだった。

zenn.dev

パネルディスカッション

コスト削減に取り組むタイミングとして、違和感を感じたらとか、具体的に100万円程度のコスト削減余地が生じたら、みたいな話があった。 これは身に覚えがあり、自分たちも定期的に請求詳細を確認して金額の増減に違和感があったらとか、獲得MRRに比べて削減余地が比較的大きくなってきたら、みたいな感覚がある。 また、他社事例を通してコスト削減の勘所を養うというのもまさに経験したことがあり(自分たちはAWS Configについてだった)、やはり他社事例で学ぶのも取り組みを公開するのも重要だと再認識できた。

terraformでAWS IAM ロールにポリシーを付与するためにmanaged_role_arnsを利用する

terraformでAWS でIAMロールを構築するとき、ロールとポリシーの設定方法として複数の手段がある。

  1. aws_iam_roleのmanaged_policy_arns属性を利用する
  2. aws_iam_roleのinline_policy属性を利用する
  3. aws_iam_role_policy_attachment を利用する
  4. aws_iam_role_policy を利用する
  5. aws_iam_policy_attachmentを利用する

自分は従来、aws_iam_role_policy_attachment を利用することが多かった。 IAM ロールを作り、IAMポリシーを作り、ロールとポリシーを紐付けるためにattachmentを作成するというのは直感的にもわかりやすかったため。 また、aws_iam_roleのドキュメントには aws_iam_policy_attachmentよりはaws_iam_role_policy_attachmentを推奨すると記載されており、他の方法との比較は記載されていないのでとりあえずはaws_iam_role_policy_attachmentでよいか、くらいの判断。

registry.terraform.io

構成ドリフトの発生

これで大きな問題はなかったが、最近ドリフト検知できないという問題にしばしば遭遇するようになった。IAMロールに手動で追加のポリシーをアタッチしてもterraform上では差分を検知できないので、検証のためにポリシーを付与したことを忘れて付与しっぱなしになっていた。 また、ポリシー付与が蓄積してIAMロールあたりの最大ポリシー数のクオータ(10ポリシーまで)にひっかかりterraform applyエラーになることもあった。

docs.aws.amazon.com

このような問題を回避するためにもterraformコードと実際のリソースが一致していることが重要になった。 これを解決する方法がaws_iam_roleのmanaged_policy_arns属性で、ここでポリシーarnを記載することで余計なポリシーを手動で付与すると差分として検知してくれる。これはより厳密にロールとポリシーを管理する方法としてよいと考えた。 実際試してみてよさそう。

managed_policy_arnsへの移行の注意点

aws_iam_role_policy_attachmentから managed_policy_arnsに移行する上での注意点として、この2つの方法は排他的に動作するので同時に指定できないという問題がある。 これはaws_iam_roleのドキュメントにも注意書きが記載されている。

同時に指定できないというのは1回のterraform applyにおいても同時に登場する場合も正常に動作してくれない。 具体的には1つのプルリクにおいて、aws_iam_role_policy_attachmentを削除して managed_policy_arnsに記載する場合で以下のようなdiffが生じる場合。

diff --git a/main.tf b/main.tf
index bb13f0d..0f3a7d6 100644
--- a/main.tf
+++ b/main.tf
@@ -1,19 +1,18 @@
 resource "aws_iam_role" "this" {
   name               = "sample-role"
   assume_role_policy = data.aws_iam_policy_document.assume_lambda.json
+
+  managed_policy_arns = [
+    "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+  ]
 }
 
 data "aws_iam_policy_document" "assume_lambda" {
   statement {
     actions = ["sts:AssumeRole"]
     principals {
       type        = "Service"
       identifiers = ["lambda.amazonaws.com"]
     }
   }
 }
-
-resource "aws_iam_role_policy_attachment" "this" {
-  role       = aws_iam_role.this.name
-  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
-}

このとき、terraform applyすると aws_iam_role_policy_attachment が削除されて、IAMロールにポリシーが何も付与されない状況が発生する。 ただし、terraform apply後もう1度terraform applyするとmanaged_policy_arnsが反映されるようになる。

$ terraform apply
aws_iam_role_policy_attachment.this: Refreshing state... [id=sample-role-20240123143715911100000001]
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_iam_role_policy_attachment.this will be destroyed
  # (because aws_iam_role_policy_attachment.this is not in configuration)
  - resource "aws_iam_role_policy_attachment" "this" {
      - id         = "sample-role-20240123143715911100000001" -> null
      - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" -> null
      - role       = "sample-role" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

...

$ terraform apply
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_iam_role.this will be updated in-place
  ~ resource "aws_iam_role" "this" {
        id                    = "sample-role"
      ~ managed_policy_arns   = [
          + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
        ]
        name                  = "sample-role"
        tags                  = {}
        # (8 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

このため、一時的にIAMロールが正常に動作しないタイミングが発生することを許容するなら2度applyを実行すれば話は簡単。

これが許容できない場合はremoved blockを利用し、一時的にリソースを管理外にするとよい。 具体的には、1回目の変更では aws_iam_role_policy_attachmentを削除しつつ、removed blockでterraformの管理外にする。

diff --git a/main.tf b/main.tf
index bb13f0d..ae5f752 100644
--- a/main.tf
+++ b/main.tf
@@ -1,19 +1,22 @@
 resource "aws_iam_role" "this" {
   name               = "sample-role"
   assume_role_policy = data.aws_iam_policy_document.assume_lambda.json
 }
 
 data "aws_iam_policy_document" "assume_lambda" {
   statement {
     actions = ["sts:AssumeRole"]
     principals {
       type        = "Service"
       identifiers = ["lambda.amazonaws.com"]
     }
   }
 }
 
-resource "aws_iam_role_policy_attachment" "this" {
-  role       = aws_iam_role.this.name
-  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+removed {
+  from = aws_iam_role_policy_attachment.this
+
+  lifecycle {
+    destroy = false
+  }
 }
$ terraform apply
aws_iam_role_policy_attachment.this: Refreshing state... [id=sample-role-20240123145145692300000001]
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

 # aws_iam_role_policy_attachment.this will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "aws_iam_role_policy_attachment" "this" {
        id         = "sample-role-20240123145145692300000001"
        # (2 unchanged attributes hidden)
    }

Plan: 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│ 
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - aws_iam_role_policy_attachment.this
│ 
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.
╵

2回目の変更では managed_policy_arnsで指定する。 こうすれば、2回目の変更はapplyで差分がなく、正常に実リソースとコードを一致させることができる。 aws_iam_role_policy_attachmentはIAMロールとIAMポリシーの関係を表現するためのリソースで、実際にAWS上に何かしらのリソースを構築するわけではないのでimportブロックでterraform管理に含める必要がないこともポイント。

diff --git a/main.tf b/main.tf
index ae5f752..0f3a7d6 100644
--- a/main.tf
+++ b/main.tf
@@ -1,22 +1,18 @@
 resource "aws_iam_role" "this" {
   name               = "sample-role"
   assume_role_policy = data.aws_iam_policy_document.assume_lambda.json
+
+  managed_policy_arns = [
+    "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+  ]
 }
 
 data "aws_iam_policy_document" "assume_lambda" {
   statement {
     actions = ["sts:AssumeRole"]
     principals {
       type        = "Service"
       identifiers = ["lambda.amazonaws.com"]
     }
   }
 }
-
-removed {
-  from = aws_iam_role_policy_attachment.this
-
-  lifecycle {
-    destroy = false
-  }
-}
$ terraform plan
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Incident Response Meetup vol.1に参加しました

自社の障害対応が場当たり的になっているのでもう少しどうにかしたいと思って参加した。 やはりどこの会社も事前準備がきちんとできており、障害対応に対する心構えとか、障害対応時に余計な判断をさせない対応はすばらしいと感じた。

incident-response.connpass.com

システム障害対応学んでいきたい勢におくるインシデントコマンダー超入門

インシデント対応は「非定型的で非計画的」なのでインシデントコマンダーの育成が難しいという表現が心に残った。 復旧要件を定める必要があるほどの大規模障害は数えるほどしか経験ないけど、確かにそういうときほどインシデントコマンダーの役割が重要になるので、自分自身がもっと障害対応に強くなりたいし社内での対応強化に向けて取り組みたい。

オンコール担当がインシデントコマンダーを担う仕組みづくり

非定型な作業だからこそ、定型作業となる部分をslack botなどの仕組み化に取り組むのはものすごく正しい。 ビジネスサイドとのコミュニケーションが重要だからこそ用語や説明が丁寧なのもよい取り組みに見える。

www.docswell.com

最速でサービス復旧をするための備え

事前準備が重要というあたりまえの話ではあるのだけれど、役割や対応フローが細かく定められており、これが事前準備かと感心させられた。

speakerdeck.com

Wantedlyの障害対応文化とそれを支える基盤

障害対応に向けた心構えが非常に整備されており、こういった心理的安全性を担保してくれていると比較的安心して障害対応に取り組めるだろうなと感じた。 こういった心構えを支える取り組みは自分はまったくできていないので特に参考になる。

直接関係ないけど、WantedlyはHoneybadger使っているんだなと気になった。自社以外で使っているところほとんど見なかったので。

2023年のふりかえりと2024年の抱負

2023年のふりかえり

できたこと

実用性を意識した個人開発 & 腕力で実装する

もともとの目的である、課題を解決する実装をやりきる点については満足とは言えないが、ひたすら実装することは今まで以上に取り組めた。 特に、成果をOSSとして公開するところまでもっていけた(最低限利用できる状況にすることができた)のはよかった。

ec2id AWS EC2インスタンスのNameタグからインスタンスIDを取得するツール。

github.com

tfcvars Terraform Cloudのworkspaceに変数を登録する、または変数を取得するツール。

github.com

tflint-ruleset-formatter tflintのプラグイン、terraform fmtでは検知されないフォーマットの問題を検知する。

github.com

awsresq AWS上のリソース一覧を取得する

github.com

ghpr GitHubの特定条件に合致するプルリク一覧を取得する

github.com

一方で、すべてがcliツールと偏っていることには気になっている。 これは実用性を意識した結果、まず自分が困っているところに開発リソースを振り分けた結果なのでしょうがない。一方で、別の目標であるデータと仲良くなることが上手くいかなかった原因でもある(詳細後述)。

また、開発したソフトウェアはすべて自身がオーナーとなるソフトウェアであり、社外のOSSに対するコントリビュートという意味ではほとんどできなかった。2023年にGo言語の開発を重視したのもterraform-provider-awsで未実装なさまざまな機能を実装したいというのも理由の1つだったが、結局terraform-provider-awsにコードでコントリビュートはできなかった。この原因の1つに個人開発KPIの設定がある(後述)。

全体的に見れば個人開発にある程度のリソースを投入してそれなりの成果を得られたものの、上記の見直しも含めて、今後どのような開発に取り組むかはもう少しバランスを取りたい。

できなかったこと

細かなアウトプット

ブログ記事のアウトプットはもっと時間を掛けるつもりが、あまり上手くいかなかった。 このメインブログはこのふりかえり記事を含めて5件、会社ブログ1件、Qiitaは3件、Zennは12件だった。特に今年の後半は一切書けなくなったので、もっと気軽に書ける場所をとサブブログを立ち上げて、20件書くことができた。 2022年までに比べると件数は増えたのでよかったが、あまり計画的に書けていないし、開発の知見をまとめるという意味でもあまり上手くいっていない。目標として立てる前よりは書いている、というのを上手くいったと言うのはあやしいところ。それとも高望みだろうか。

昨年もそうだったが、これを書こうと思っていたネタを書けずに下書きで終わらせているのはよろしくない。アドベントカレンダーネタも結局検討だけして書かずに終わってしまった。 ソースコードだけでなくブログも書ききることを意識したい。

データと仲良くなる

構造・非構造に限らずデータの取り扱いにもっと取り組む想定が期待していたよりはできなかった。 書籍としては検索システム

や実践Redis入門

を読んだり、実務でもRedisやMySQLのアップデート、MLOps基盤の構築、といった隣接領域に取り組むことはできた(elasticsearchは来年になりそう)が、時間の制約などもあり、結局今まで通りインフラエンジニアとして基盤の取り扱いだけで終わってしまった。業務で扱う以上はしょうがない面もある。 そういう意味でもやはり個人開発でwebサービスを立ち上げて、そこでデータの扱いに取り組むべきだった。

その他気になっていること

個人開発KPI

個人開発を継続する上で、わかりやすい指標がGitHub のContributionグラフだったので、毎日草を生やすことを当面の目標に取り組んだ。 これ自体は比較的上手くいった。しかし業務でのコントリビューションを除外するためにPrivate Contributionsをオフに、Activity overviewをオンにした結果、自分のリポジトリであってもプライベートリポジトリに対するコミットは優先度が落ちることになった。この結果がOSSのcliツールばかり開発することを後押ししてしまった。

GitHub Contributionsの設定が上記2つしかなく、特定の企業向けコミットだけを除外することができなかったのでしょうがなかったが、これはKPIの設定が不適切だった。 このあたりは計測方法を変更して、より自分が開発したいことが開発できるように修正したい。

Generative AIの活用

ChatGPTやGitHub Copilotなど、AI活用が盛り上がった1年だったが、自分は上手く使えていない。 もちろん上記ツールは利用しており、苦手なフロントエンド/TypeScriptまわりを書くときや、英語文書を見直すとき、似たようなコードを何度も書くときにはそれぞれ活用している。しかし世の中の盛り上りほどは使えていない。 これは世の中が過度な期待の中にいるとも言えるが、自分の場合はまだ活用の余地があるし、世の中の上手い使い方を取り込めていないという感触が強い。

このあたり自分の老害感が強くなっていると危惧している。 生成AIが今後ますます重要になることは間違いないので、最低限でもキャッチアップは続けたいし、新しい技術は積極的に利用していきたい。

職場での振舞い

2023年は会社でリーダーシップやマネジメントの面で頑張るよりは、個人の成果を重視することを意識してきた。 そしてそれは一定効果はあったと思う。個人開発で成果は出せたし、業務においても多くのアウトプットを出すことができた。

しかし、会社における組織的な取り組み・改善は上手くいかなかった。これは自分が積極的にアクションを取らなかったので当然といえば当然。 しょうがない面はあるが、結果的に見れば優先度を下げた弊害はあったように思える。ここは見直す必要がある。 また、リーダーシップとマネジメントの両方の優先度を落とすのはよろしくなかった。マネジメントに費す時間を減らすことは会社との合意事項ではあったが、リーダーシップを発揮すべき場面であまり率先して行動を取れなかったのはよろしくなかった。 このあたりは改善したい。

インプット

勉強会に参加したり、技術書を読んだり、他者ブログを読んだりする量は意識的に減らした。特に勉強会への参加はあまり行わなかった。 これは勉強会に参加する時間を個人開発の時間に当てたかったこと、技術書を読んでよりよい方法や考え方を学ぶよりも今持っている手段で対処して前に進む方法を優先したかったことが理由としてある。

悪くはなかったが、勉強会に参加しなかった時間がそのまま個人開発に取り組めたわけではないし、技術書もなんだかんだ参照していた。 なので意識的にインプットを絞る必要はなさそう。今後もインプットは継続して質の良いアウトプットが出せるようにしたい。

健康

健康が最重要なのは言うまでもない事だし、ありがたい事に大きな怪我や病気になることはなかった。しかし理想的な状態でもなかった。 運動は継続できたが、5-6月に運動量を減らしたらあっという間に体力が落ちたことにびっくりした。やはり何よりも継続することを意識したい。

そして運動以上に睡眠が上手く取れなかった。もともと睡眠障害持ちで上手くいっていなかったが、この1年は(も?)良くなかった。唯一の改善点はアクティビティトラッカーとしてvivosmart5を装着するようになったことで、これで自分の休憩の取り方が下手だと再認識できた。 それを踏まえての改善はまだまだ上手くいっていないが、もっと上手に休めるようになりたい。

2024年の抱負

2024年は意識することを絞って取り組みたい。

行動と思考をアウトプットする

アウトプットを継続することは今年も課題だが、特に「行動」と「思考」をアウトプットすることを意識する。 1年をふりかえってみて記録が全然足りていないと感じたので、後からふりかえって自分のアクティビティを簡単にでもトレースできるようにしたい。

行動のアプトプットは単に何をしたか。 ツールやサービスをリリースしたでもよいし、調べたことでも試したことでも何でもよい。 大抵の場合は1日何もしていませんでした、なんて日は存在しないので書き易いはず(その記事に価値があるかは別として)。

思考のアウトプットは何を考えたか。 書籍やブログ、勉強会等に参加したら何かしら感想を持つと思うのでそれを書けばよいし、ツールやサービスをリリースしたら何を解決したくてリリースしたのか、または機能を実装したらどうしてそのような設計にしたのかなど、無限に思考・判断が含まれているはず。これを言語化する。

アウトカムベースで個人開発に取り組む

2023年は今までに比べてよい個人開発ができたと思う。一方で「実用性を意識した」という点が上手くいっていないケースが多くあることを感じた。 それを強く感じたのがGitHub ContributionsをKPIとして利用しており、開発したいことに着手できない、あまり必要のないものに時間を割いているとき。これは個人開発KPIにも記載したが計測方法がよろしくなかった。 この方法はあくまでアウトプットを計測しており、自分の課題を解決できているかを計測できていなかった。

これはまさにビルドトラップにはまっていると感じた。 もちろん個人開発なので手段が目的になってもいいし、継続することが最重要なのでアウトプットをKPIに設定することも間違いではないと思う。しかし、自分のやりたい事を考えれば、課題を解決できているかやアウトカムを達成できているかをもっと重視したい。

go-tfeで単体テスト

go-tfe概要

go-tfeはTerraform Cloud (およびTerraform Enterprise)のためのGo SDK。 go-tfeを利用することで、Terraform Cloudの各種APIを利用したツールを実装することができる。

github.com

go-tfeのテスト

go-tfeを利用したツールを実装するときに単体テストをどうするかが悩ましいところ。 go-tfeには単体テストに相当するものがなく(?)、すべてE2Eテストになっている。 また、Terraform Cloud APIをやりとりするclientはinterfaceが定義されていないので、mockを生成してテストに利用することができない。

github.com

// Client is the Terraform Enterprise API client. It provides the basic
// connectivity and configuration for accessing the TFE API
type Client struct {
    baseURL           *url.URL
...
}

リクエスト先のbaseURLを書き換えることはできるので、Terraform Cloud自体のmockを生成すればテストの実装が可能になるかもしれないがこれは厳しい。 terraformもTerraform Cloudも機能追加が活発なので、そのAPIを利用するならテストの担保はしておきたい。 できれば単体テストとしてテストを実装したいので、いきなりE2Eテストは避けたい。

mockの利用

go-tfe自身は利用していないが、go-tfeの各種サブ機能に関するmockコードは生成されている。 GoMockを用いてmockが生成されているので、go-tfeを利用したアプリを実装する場合はこのmockを利用して単体テストを書くとよさそう。

ただし、前述の通りclient自体にはinterfaceは定義されておらず、mockコードにもclientに関するコードは存在しない。 このため、go-tfeを用いたツールを実装するときにはclientを引数などには含めず、サブ機能ごとのインスタンス(こちらはinterfaceが定義されている)に依存した実装にするとよさそう。

例えば、workspace名とVariables APIを引数としてVariableListを返すmyFuncの単体テストは以下のようになる。

package test

// func myFunc(ctx context.Context, workspace string, variables tfe.Variables)

import (
    "context"
    "testing"

    "github.com/golang/mock/gomock"
    tfe "github.com/hashicorp/go-tfe"
    "github.com/hashicorp/go-tfe/mocks"
)

func TestListVariables(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockVariables := mocks.NewMockVariables(ctrl)
    ctx := context.TODO()

    var items []*tfe.Variable
    mockVariables.EXPECT().
        List(ctx, "w-test-no-vars-workspace", nil).
        Return(&tfe.VariableList{
            Items: items,
        }, nil).
        AnyTimes()

    variableList, err := myFunc(ctx, "w-test-no-vars-workspace", mockVariables)

    if err != nil {
        t.Errorf("expect no error, got error: %v", err)
    }
    if len(variableList.Items) != 0 {
        t.Errorf("expect no variables, got variables: %v", variableList.Items)
    }
}

Emacs環境のアップデート

自分のメインの開発環境(エディタ)としてはEmacsを利用し続けている。部分的にはVSCodeやIntelliJなどを使いつつもEmacsメインであることは10年以上変わっていない。 とはいえ、最近はあまりelispまわりの整備が行えておらず、良い開発環境にはなっていなかった。 さすがにこのままではよろしくないし、かといってVSCode等への完全移行もできそうにないのでEmacs環境を整備した。

バージョンアップデート

今まではUbuntu 22.04のデフォルトである27.1を利用していた。その前も基本的にはOSのデフォルト提供されているバージョンを変えてはおらず、最新のバージョンを積極的には採用していなかった。 また、macOSなどUbuntu以外の環境でもUbuntuで利用できるバージョンを合わせていた。 これであまり不自由していなかったが、ふとEmacs User Suervey 2022を見たときに結構みんな新しいバージョンを利用しているのだなと知った。 slackのemacs-jpでも比較的新しいバージョンについて話題にしていることが多く、実は新しいバージョンに追従した方がよいのかも、と考えるようになった。 新しいバージョンを導入しても大きなトラブルが発生する頻度が少なく、それでいて新機能を試せるのであればメリットの方が大きいので。また、自分の手元でも確認してみたいと思うことは多々あったので。

あまり手間を掛けるつもりはなかったが簡単に導入できるならとEmacsを28系にアップデートすることにした。 Ubuntu環境は ppa:kelleyk/emascを、macOS環境はhomebrew caskをそれぞれ利用してインストールした。 これによりUbuntuでは28.1をmacOSでは28.2にアップートできた。 どちらも特に導入の手間を掛けずに実現することができた。

まだ最新バージョンを利用することのメリットや機能検証なんかはできていないが、これからそういった情報も継続収集できればよいかなと思う。

leafの導入

パッケージマネージャとしてel-getを利用していたが、autoloadまわりが上手くいっていないケースがあった。 このせいでいくつかのパッケージが利用できていない(または手動でロードする必要がある)状態で、これが今までEmacsを使っている中で一番の課題だった。 適切な設定を行えばおそらくel-getのままでも解決できるとは思うが、せっかくなのでとパッケージマネージャごと変更することにした。

use-packageだとelpa/melpa以外からのインストールができないということで leafを導入することにした。 2023年1月時点で、el-getを直接利用するよりも(国内の)ユーザは多そうで参考となる設定が公開されているところも地味に助かるところ。

qiita.com

まだ全部のパッケージをleafで置き換えられてはいないが順調に更新できており、今まで動作しなかったパッケージも動作するようになったのでかなり助かっている。 合わせてパッケージの移行や設定見直しなども行っているのでもう少し時間が掛かりそうだが、少しずつでも開発環境が便利になっていくのは非常に体験が良い。

init-loaderの継続

init-loaderを導入したときは、どちらかというと .emacsファイル1つでは管理しにくいからinit-loaderを利用してファイル分割した方がよい、という考えの人が多かった印象。 自分もその考えに賛成で積極的に分割していきたいと思いinit-loaderを導入していた。

最近ではあまり分割し過ぎない方がよい、またはinit-loaderを利用する必要はないという考えもあるようで、今回の見直しでinit-loaderをどう扱うか再検討した。 再検討してみたが、やはり自分は目的に応じてファイルが分かれている方が好みだなと思い init-loaderの利用は継続することにした。 init-loaderがあるとEmacsの起動が遅くなるという話もあり、高速起動できた方が魅力的ではあるとは思いつつも、自分にはあまり恩恵はないくファイル分割を実現できた方がよいと考えた。 ただし、それでも分割し過ぎの面はあると思うので過度な分割には注意していきたい。

また、他の人はinit-loaderの利用を止めてしまったのかなと公開している人の.emacsファイルを確認してみたら、まだまだ使っている人も多いみたい。 あまり心配せず、自分の好みを優先させてよさそう。

フォント

自分はフォントにあまりこだわりがなく、以前Emacs環境を整備したときにたまたまRictyが流行っていたのでRictyを採用していた。 その後Rictyフォントの生成はansible化していたので環境移行時にもセットアップの手間を感じず使い続けてきた。

今回環境の見直しを行う中で、Rictyのサイト上にて他のフォントを推奨する案内があることに気が付いた。 前述の通りあまり困ってはいなかったが、他のフォントを推奨するのであれば変更しようということで見直すことにした。

rictyfonts.github.io

いろいろ確認してみたところ白源がよさそうということでこれを利用してみることにした。 1週間ほど使い続けているが、特に違和感なく利用できるのでこのまま継続する予定。

github.com

custom-set-variable対応

いつからか、勝手に init.elファイルに追記・更新されるようになってしまった。 自分は .emacs.dをそのままリポジトリ管理しているのでファイルが勝手に更新されるとうれしくない。

これを制御して書き込みを完全に止めることは難しいらしく(なんだそりゃ)、別のファイルに追記させてそれを読み捨てるという方法がよく採用されるらしい。 あまり良い方法には思えないが、手軽に他の対策を取ることもできないのでこの方針を採用する。

qiita.com

まとめ

おそらく Emacs環境の大幅見直しをしたのは7-8年ぶりくらい。 まだ大きな変更と言えるほどの変更は入っていないが、それでも自分にとって現状把握と全体の見通しを改善できた。 最近のIDEのように表示をリッチにすることは考えていないが、こまかな改善は継続的に取り入れていきたい。

github.com