Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

GitHub Actions の ::set-output 脱出 上級編 便利ツールでカバーできないケース

::set-output 脱出 進んでますか?

github.blog

エンジニアで GitHub Actions を利用している人はだいたい知っているであろう、 ::set-output::save-state の非推奨化問題。このコマンドを利用していると2023年5月末にはエラーになってしまう予定となっています。

面倒がっているエンジニアのために、巷には便利なツールがあります。

一つは rhysd/actionlint です。::set-output のような非推奨機能を使っていると actionlint の検査に引っかかります。こちらについては過去記事で紹介したのでご覧ください。

blog.arthur1.dev

もう一つは azu/set-env-to-github_env です。このコマンドをインストールして実行すると、workflow ファイルを新しい書き方に書き換えてくれます。便利。

今日は、残念ながらこれらのツールでは引っ掛からない例とその対応策をご紹介します。

スクリプトファイルを呼び出しているところに注意

mackerelio/mackerel-agentGitHub Actions の結果を見ていると、Annotation で warning が大量に表示されていることに気づきました。利用している action の更新をしたり、actionlint を実行して非推奨な記述を見つけたりして修正したのですが、まだ ::set-output 使っているよ、という warning が表示されていました。

actionlint に引っ掛からなかったのに出るのはおかしいな、と思って、::set-output で文字列検索を掛けたところ、こんなコードが引っ掛かりました

(async function () {
  const version = "1.2.3"; // ここは本当は parse してアレコレしている
  console.log("::set-output name=VERSION::" + version);
})();

そして、この Node.js のスクリプトを Workflow 中で呼び出していました。

- run: node _tools/parse_version.js

なるほど!workflow から呼び出した外部のスクリプトが標準出力に ::set-output と書き込んでいたのに actionlint だと気づけなかった、というわけです。これは先ほど紹介した set-env-to-github_env でも無理だと思います。

解決策: actions/github-script を咬ます

先程のスクリプトを直すのには工夫がいります。新しいやり方では標準出力は利用せず専用のファイルに書き込まなければならないので、 console.log() を用いることはできません。例えば、以下のような方法で新しい方法に対応することができます。

const fs = require('fs/promises')

(async function () {
  const version = "1.2.3";
  const file = process.env.GITHUB_OUTPUT;
  if (file === undefined) {
    process.exit(1);
  }
  await fs.appendFile(file, `VERSION=${version}`);
})();

これは美しくないし、今後さらに変更があったときに見つけづらくなるなあと悩んでいたところ、 id:stefafafan さんが @actions/coresetOutput() という関数があるよ、と教えてくれました。これなら、仮にさらに新たな仕組みになったとしてもライブラリの更新に追従すればよいので良さそうです。

しかし、この npm ライブラリを利用するためだけにちょっとした js ファイルを bundle して生成するのは大袈裟です。少し調べていたところ、actions/github-script という公式の action に行き着きました。この action を経由して Node.js のスクリプトを動かすと、GitHub API や Workflow context を利用するためのライブラリを利用することができるようになります。

Workflow の yml と呼び出しているスクリプトを以下のように書き換えました。

- use: actions/github-script@v6
  with:
    script: |
      const script = require('./_tools/parse_version.js')
      await script({ core })
module.exports = async ({ core }) => {
  const version = "1.2.3";
  core.setOutput("VERSION", version);
};

すると、無事 warning が消え、かつ workflow の output を設定することができました!

まとめ

GitHub Actions の ::set-output 脱出の際には、以下の事項に気をつけましょう。

  • Workflow 定義外のスクリプトファイルを呼び出して ::set-output と標準出力に書き出しているところに気をつける
  • それが Node.js のスクリプトの場合は、actions/github-script 経由で呼び出すようにすると、setOutput() 関数が用意されたライブラリを利用可能

聞き手と共有しているコンテキストを意識して伝え方を選ぶ

自分は以下の2点を理由に、外国語を直訳したような堅い言葉で話すことがある。

  • ボードゲームの翻訳をしている
  • 日常の思考を日本語ベースで行っていないことがある

英語を翻訳していると a, an と言う冠詞に何度も遭遇する。日常会話では何も気にせずに済むのだが、ボードゲームの翻訳となるとそうはいかない。日本語に翻訳した後に、複数なのか単数なのかと言う情報が欠落する可能性があるからだ。ボードゲームに限らず一般にゲームをプレイする上で、ルールの曖昧さ・解釈の不一致はプレイへの集中を妨げる大きな障害となる。

前提とするコンテキスト(文脈を超えて、場や間といった概念も含まれるだろう)の大きさをいかに小さくするか、それはある意味正義なのだが、ある意味では正義ではない。表現として限りなく正確だったとしても、受け手がどう解釈するかは結局受け手次第なのである。

自分はコンビニで煙草を買うとき、レジで「◯◯ひとつください」という風に、目的のモノと数量を明示してお願いする。しかし、結構な確率で「ふたつですか?」と聞き返されてしまう。自分の滑舌が著しく悪いわけでもないのに、だ。子音は同じものの母音は違うので、聞き間違えにくいように思う。

ここには、単数がデフォルトで、いちいち数量を言うやつは何個か欲しいのだろう、と言うバイアスがあるのだと思う。忙しい人が買い物に来るコンビニでは尚更のことである。

5W(When, Where, Who, What, Why)を意識して、適切な物の伝え方 (How) が選べるようになりたいと思う。そして、それはこのブログでも同様である。


余談: なぜこんなエントリを書いたのか

学園祭のWeb開発を語る: 抽象編 自分たちの責務をどう絞り、どう委嘱するか

遠い昔に学園祭の Web 開発をしていた話をするシリーズもの最終回

  1. 具象編1: 講習会と砂場遊びで支える組織
  2. 具象編2: 開発体験・保守性を投げ捨てる
  3. 抽象編: 学園祭の開発部署という組織や、学園祭Webサイトというプロダクトの性質から語る ←イマココ

ここからは、これまでの昔話ではなく、現代の Web 事情を踏まえたり、自分が昔いた組織特有ではない一般の話に寄せたりして書いていく。

もし、自分がまた大学生になってどこかの学園祭の Web サイトを作るとしたら、どういう選択をするだろうか。そんな思考実験をしてみる。

静的なサイトを作ろう

ここ4、5年でフロントエンド界隈は凄まじい進化を遂げたと思っている。TypeScript により保守性と開発体験が向上したし、React や Vue.js など、再利用可能で影響範囲のスコープを絞れる component ベースで物を作れるフレームワークも一般に浸透した。

個人的に一番偉大だと思っているのが、Next.js や Nuxt.js などに実装されている Static Generation(静的サイト生成)である。

サーバ上で HTML を動的に組み立てるのは、パフォーマンスやセキュリティ面での懸念がある。もちろん、プロには知識があるから、キャッシュを活用したり、フレームワークを適切に使って XSS や SQLi を回避したりする。しかし、それを学生に求められるかは難しいところがある。このシリーズエントリの中で講習会をやっていた話をしたが、フロントエンドとサーバサイド両方の話をガッツリやっていたらあっという間に1年が過ぎてしまう。

自分がかつて作っていた学園祭のサイトは、同じアプリケーションでお客さんに見せる部分も、学生に見せる部分(電子申請フォーム)も作っていた。お客さんに見せるところでも、ヘッダメニューなどをテンプレートで作るためにサーバサイドのフレームワークで HTML を組み立てていたのである。しかし、現代でそれを行うのはリッチなフロントエンドフレームワークを使って簡単にできる。電子申請の部分は別のアプリケーションとして分けてあげればよい。

一般的な学園祭サイトの運営において、ある程度技術を身につけて自分たちで作るのであれば、フロントエンド寄りに軸足を置いた方が良い。この状況はこの先しばらくは変わらないんじゃないか、と思っている。

もちろん、SG でサイトを作ればセキュリティ面の心配は全くない、と言っているわけではない。でも、考慮範囲が小さくなるだけで十分儲けものである。

従量課金のクラウドサービスは選択しにくい

アプリケーションの次はそれを載せるインフラの話をしよう。

今の自分が選ぶとしたら、有名なレンタルサーバを月定額で借りて、そこに静的な Web サイトをデプロイする。マネージドなクラウドサービスに載せるのも cool で個人的にはやりたいものの、それを組織が選ぶべき選択肢として提示することはないかもしれない。

一番の問題は料金面である。たかだか学園祭のサイトにマネージドサービスにするほどのトラフィックはない。システムのスケーリングを手動で行うこともないので、それをマネージドサービスが勝手にやってもらっても恩恵が薄い。

特に、従量課金体系の場合は要注意である。ケチって WAF を入れないでいたら DDoS 攻撃喰らってクラウド破産、みたいなことが起こらないように運用しなければならない。そういうところにちゃんと興味があって学園祭のサイトを運用しようとしている人は本当に少ないのではないだろうか。

自分が学生の頃を思えば、監視という業務を本当に疎かにしていたと思う。Availability の目標値どころか Observability も 0 であった。

これは Twitter などで公になっていることなので書くが、自分がいた学園祭実行委員会の Web 担当部署が昨年、所有しているドメインを失効させオークションにかけられてしまう、という事故を起こした。Webサイトは閲覧不可能になり、独自ドメインのメールで学内外の人と連絡していたのもできなくなっただろう。そして、ドメインを再度手元に戻すために多くの負担を強いられた

こういった変化するものを、人とシステムの力で観測できる状態にするという取り組みに手が割けないのであれば、従量課金のサービスなんてとても使っていられないと思う。これは仕方ない面もあって、企業は週5日働いてくれる社員を雇って運用するのだけれど、学生の本分は遊び勉強なので、それほど恒常的に時間を割けない。

料金のトラッキングができないのであれば、定額でサービスを提供できる方が学園祭のサイトとしては嬉しいと思う。これは、サービスの可用性と料金の予測可能性とどちらが大事ですか?というトレードオフの関係になっている。

もちろん、サーバを持つこと自体もセキュリティ面での懸念が大きいだろう。(そのため、権限が大きくないレンタルサーバを候補に上げた。)クラウドサービスを使うことの否定はしていなくて、無料枠で確実に収まり勝手に料金が増えなければ良いと思う。

まとめ

プロダクトそのものの価値にどれぐらい commit したかも大事だが、その裏側でどんな根拠を持ってこういう意思決定をした、と自信を持って言えることも重要だ。自分が新卒採用の面接担当だったらそういう話を学生から聞きたいと思う。

zenn.dev

上のエントリを読んで「めちゃくちゃ良いな」と思って始めたこのシリーズ投稿もこれでおしまいにする。学園祭 Web 界隈の益々の発展を祈っている。

僕は技術エントリを自分のために書いているわけではない

blog.arthur1.dev

おかげさまでたくさんの方に反応していただきました、ありがとうございます。

まだ全然読みきれていないけど、少しずつ丁寧に咀嚼していこうと思います。


技術エントリは自分のために書いている、というコメントが多かったので一つだけ。

僕は技術エントリを自分のために書いているわけではないです。断定は怖いので少し逃げると、主目的ではないです。

自分の理解を整理・収納するのは他の場所でやっていて、チラシの裏にメモ書きしています(これは比喩)。他人に見せなくて良い代物なので、気が楽ですし、スピーディーに書き留められます。後世の自分が見て、記憶の引き出しを開けられる程度の情報もそこにあります。

他人に読んでほしいから、図を用意したり、サンプルコードを用意したり、言葉を丁寧に選んだりするわけです。そして、誰かが一生懸命運用維持してくれるサービスでブログを書くわけです*1

では、なぜ他人に読んでほしいのか。

一つは、Web が記憶拡張装置、ひいては人間の創造を支援するプラットフォームであるからです。Web が好きだから、その存在意義のために自分ができることをしています。ただ、生み出したものが一生誰にも読まれないならば、それはなくても同じことです。いつか読まれるかもしれない、というツッコミはあろうかと思いますが、さて、それはどれぐらいの確率で起こる事象でしょうか。

もう一つは承認欲求という言葉で片付けられるかもしれないし、誰かに必要とされたい、という表現もできるかもしれません。これは人間として比較的高次元な欲求です。自己実現のステージにまで辿り着いている人はすごいですね。

技術エントリを書くことで、副次的に文章力が上がるとか、伝える力が上がって嬉しい、というのはあるかもしれません。ただ、今の自分はそこに問題意識を持ち priority を上げて取り組んでいるからエントリを書いている、というわけではないです。

*1:これははてなブログへの褒め言葉であり、ポジショントークです

Windows の OS 名をレジストリから取得するのはやめよう

3行

  • Windows の OS 名をレジストリから取得すると、Win 11 が Win 10 として表示されてしまう
  • 例えば、WMI を使って取得する方法が正攻法っぽい
  • Go から WMI を触るサンプルコードもあるよ

Win 10 が Win 11 として Mackerel に登録されている

自分は OSS の動作確認のために Windows を使いたくなったときに、手元の Macbook から別のホストにリモートデスクトップで繋いで使っている*1。また、繋ぐ前に WOL マジックパケットを送ってスタンバイ状態から復帰させるようにしている。

ここで、手元にマシンがない状態だと WOL によりちゃんと起動したかどうかがわからない。そこで、mackerel-agent をインストールしていて、Mackerel 上でメトリックが取れているかどうかでマシンの生存確認をするようにしている。

そんな中、ふと気づいた。ホスト OS は Windows 11 のはずなのに、Mackerel の Web コンソールには Windows 10 として表示されていることに。

Windows なんもわからん……と思いながら、詳しい人が教えてくれたら良いなという楽観的発想でまず issue を立てた。

github.com

mackerel-agent の実装はどうなっているのか

mackerel-agent が Windows の OS 名やバージョンを取得している実装は以下の部分である。

https://github.com/mackerelio/mackerel-agent/blob/7417ef2be05e88c815ddc8a4002ba32a6b74d16c/spec/windows/kernel.go#L25-L44

osname, _, err := windows.RegGetString(
        windows.HKEY_LOCAL_MACHINE, registryKey, `ProductName`)

これらの情報はレジストリから取得していて、例えば OS 名であれば HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProductName から取得した値を利用している。

つまり、先ほどの不具合は、OS が Windows 11 でもこのレジストリの値が依然として "Windows 10" であることによるものだった。

この問題については Windows の Q&A フォーラムでもスレッドが立っていた。

learn.microsoft.com

Windows 10 は最後の OS だ」と言い切ってしまったが故にこのような混沌が生み出されたのかもしれない。

どう直すか

レジストリを使った取得方法では正しい OS 名を取得できないため、実装を改めなければならない。改修方針案はいくつかあった:

  • OS 名は、レジストリ ProductName と CurrentVersion の両者から判定する
    • つまり、ProductName が Windows 10 で CurrentVersion が m.n.o 以上だったら Windows 11 とする、といった感じ
  • PowerShell のコマンド Get-Computerinfo を実行し、パースする
    • ConvertTo-Json コマンドも利用すれば program-friendly な JSON 形式で OS 名を取得可能
  • WMI の Win32_OperatingSystem クラスにある Caption というプロパティ値を OS 名とする

レジストリによる取得の延長的な改修に関しては、mackerel-agent の該当実装部分にやたらと分岐が増えて負債となり得るので、避けたいと思った。Windows 11 がこんな状況では、Windows 12 以降どうなるか先が思いやられる。仕様が現実に追いついていない不安定なものに依存したくはない。

PowerShell のコマンドを実行する方式もぱっと見悪くはないと思ったのだが、チーム内で話したところできれば避けたい、という結論になった。これは、PoweShell の動作が重いこと、ConvertTo-Json を利用できない OS がありそうなことなどによる。

そこで、3つ目に挙げた WMI (Windows Management Instrumentation) を利用する手法に着目した。WMI とは Windows OS を管理・監視するために作られたインタフェースのようだ。WQL という SQL-like な言語を使ってシステム情報を問い合わせることができる。

Win32_OperatingSystem クラスの中に Caption というプロパティがあり、「オペレーティング システムのバージョン」が含まれているようだ。試しに Windows 11 のホストで値を取得すると"Microsoft Windows 11 Pro" と結果が返ってくる。これこそが求めていた文字列である。

learn.microsoft.com

Go のプログラムから WMI に問い合わせる

ここまでできたら、あとは mackerel-agent から WMI というインタフェースをどう触るか、ということを考えれば良い。mackerel-agent は Go 言語で開発されているので、この要件は「Go のプログラムから」と読み替えてもよくなる。

実は mackerel-agent には、すでに go の WMI ライブラリ github.com/StackExchange/wmi が利用されている。しかし、このリポジトリを見るともうメンテされていないようなので、 fork 先の github.com/yusufpapurcu/wmi を使うことにする。

こうして、自分で実験的なアプリケーションを作ってみた。

github.com

//go:build windows

package windows

import "github.com/yusufpapurcu/wmi"

// cf.) https://learn.microsoft.com/windows/win32/cimwin32prov/win32-operatingsystem
type Win32_OperatingSystem struct {
    Caption    string
    Version    string
    CSDVersion string
}

type WinVer struct {
    OSName  string
    Version string
    Release string
}

func GetWinVer() (*WinVer, error) {
    var dst []Win32_OperatingSystem
    q := wmi.CreateQuery(&dst, "")
    if err := wmi.Query(q, &dst); err != nil {
        return nil, err
    }
    winVer := WinVer{}
    for _, v := range dst {
        winVer.OSName = v.Caption
        winVer.Version = v.Version
        winVer.Release = v.CSDVersion
        break //nolint
    }
    return &winVer, nil
}

今回利用した wmi ライブラリで特徴的なのが、結果を格納するために作る構造体の構造からそのまま WQL クエリが作られるところだ。つまり、Win32_OperatingSystem という構造体の名前やフィールド名を任意に変えることはできない。

GitHub Actions に Windows の runner があるので、これを利用してテストを動かしてみた。OSName が Microsoft Windows Server 2019 Datacenter として取得できていることが確認できる。

mackerel-agent にこれを組み込むにはまだ考えることがあるので未着手だが、近いうちに取り組もうと思う。

(2022-02-03 追記) 上記方針での改修が取り込まれた mackerel-agent v0.75.0 がリリースされました

まとめ

Windows の OS 名を取得したい時には、レジストリを参照するのではなく WMI を利用しよう。もしくは、「もっと良い方法があるよ」という有益情報をお持ちのWindows に詳しい方はぜひ教えていただきたい。

*1:そのために家のネットワークに VPN で繋げるようにしてあるし、OS も Windows Pro をわざわざ買っている。

next-gen Slack App で AWS のサービスと接続するとき気をつけたいこと3選

年末から Slack App の次世代プラットフォームを試してみています。現在はオープンベータ版となっており、有料の Slack ワークスペースでしか利用できません。

api.slack.com

次世代プラットフォームはこれまでの Slack App を取り巻くエコシステムと大きく変わっています。全部は紹介しきれないのですが、これまでと変わったところを挙げておきます:

  • Slack App を動かすためのインフラを Slack 側が用意してくれる
  • 開発用にローカルにサーバを立てて、Slack のアプリから参照し動作確認できる
  • アプリの設定(この読み取り権限が必要、など)を TypeScript のコードとして表現できる
  • CLI が用意されていて、これを叩いてデプロイなどが可能

試しに作ったもの

今回はお試しとして「ask-anywhere」という Slack App を next-gen platform を利用して作りました。

はてなの Slack ワークスペースには「#ask-hogehoge」という形でいろんな部署に問い合わせることができるチャンネル群があります。ここで、例えば、総務のチャンネルで質問したら、実は経理部門管轄の話で別のチャンネルに案内された、という様子をよく見かけます。

質問を投げるとどの窓口チャンネルに書けば良いか教えてくれるインフォメーションセンターのような Slack App があれば便利かなあと思って作ってみました。動作イメージは以下の通りです:

また、自然言語で入力された質問の解釈には Amazon Lex を利用しました。Alexa と同じ技術を使って、テキストや音声での会話を行う Bot を簡単に作れるサービスです。

aws.amazon.com

構成図は以下の通りになります:

next-gen platform の Slack App で気をつけたいこと

Slack のインフラ上で完結できれば簡単だったのですが、AWS のサービスとの接続を考えると面倒なことがいくつかあったので紹介します。

OIDC できない

GitHub Actions から AWS のリソースにアクセスするときにはアクセスキーの漏洩を嫌って OIDC 認証するのが一般的だと思います。

zenn.dev

これと同様のことを next-gen platform でもやりたかったのですが、残念ながら今はできません。Lex bot との会話をするための IAM ユーザを作り、アクセスキーとシークレットを発行する必要があります。

また、こういった秘匿情報は App にハードコーディングせず、環境変数に格納することが推奨されています。

api.slack.com

deno の公式 aws-sdk がない

next-gen Slack App のランタイムは現在 deno しか選べません。

Amazon Lex とやりとりするために aws-sdk を利用したいのですが、現在 deno 用の公式 aws-sdk は用意されていません。issue は立っているものの Open の状態です。

仕方ないので非公式のものを利用します。今回の実装では、先ほどの issue の discussion 中で紹介されていた aws_api for Deno を利用しました。

import { ApiFactory } from "https://deno.land/x/aws_api@v0.6.0/client/mod.ts";
import { LexRuntimeV2 } from "https://aws-api.deno.dev/v0.3/services/lexruntimev2.ts?docs=full";

...

const api = new ApiFactory({
  credentials: {
    awsAccessKeyId: env["aws_access_key_id"],
    awsSecretKey: env["aws_secret_access_key"],
  },
  region: "ap-northeast-1",
});
const lex = api.makeNew(LexRuntimeV2);
// lex bot に入力したテキストを認識させる
const response = await lex.recognizeText({
  ...
});

外部との通信がそのままではできない

deno はデフォルトの設定だとネットワークアクセスができないことをご存知でしょうか?deno で動く Slack App でも同じようなことが言えて、外部にアクセスする際には許可するドメインを manifest.ts ファイルの outgoingDomains に記す必要があります。

export default Manifest({
  ...
  outgoingDomains: [
    "aws-api.deno.dev",
    "runtime-v2-lex.ap-northeast-1.amazonaws.com",
  ],
  ...
});

sdk による通信など、どのドメインとやりとりしているか自分で分からない場合は、エラーログを見てちまちまとリストに足していけば良いです。例えば、以下のようなエラーが出たら outgoingDomains に aws-api.deno.dev を足す、という感じです。

slack next platform Requires net access to "aws-api.deno.dev", run again with the --allow-net flag

まとめ

next-gen platform の Slack App は(有料ワークスペース限定ですが)現在無料で利用できます。自前でバックエンドの Lambda や ECS を立てたりする必要がないので、インフラ面に関しては気が楽ですね。

一方で、ユースケースによってはやっぱり他のクラウドサービスとも接続したいと思うこともあるでしょう。そんな時には本記事が参考になれば幸いです。

ベータ版のうちに遊ぶと色々面白いと思うので、ぜひ次世代プラットフォームの波に乗っていきましょう。

選択は多目的最適化の重みづけによって説明されたい

軽音楽部の学生のシールドケーブルの巻き方がやけに小さかったのを見て小言を言ったという記録。


ケーブルというのは円柱の形をしている。

(ここでプリングルスのパッケージを手に取り、ひねる。)

円柱を曲げるには無理やり力を入れなければならない上、このように皺が寄ってしまう。

これと同様に、巻くという行為によって少なからずケーブルへの負担がかかる。

すなわち、ケーブルをまっすぐ伸ばしたまま保管するのが、ケーブルへのダメージを限りなく抑える方法として最も良いということになる。

しかし、実際問題そうやって保管している人はほぼいない。そのままでは取り回しにくいという理由もあるが、こうすると保管に必要なスペースが大きくなってしまうという問題がある。

保管に必要なスペースを限りなく小さくしようとしたときに、皆さんがやっているように半径を小さく小さくして巻く、という手法が考えられる。

つまり、保管スペースを小さくしたいというのと、ケーブルに負荷をかけたくないというのはトレードオフの関係にある。

(面倒なのでとりあえずリニアな関係ということにしておく、本来は入れ物に入らないというのはあってはならないことなので制約を指示関数化したものになるはずだし、負荷がリニアなこともないだろう。)

この2つの指標を共に、両者同じぐらいの重要度だということにして最小化しようとすると、直感的には「入れ物に入るギリギリの大きさで巻く」のが最も良いということになる。

本当は持ちやすさとかもっと複雑に考えなければならない軸はあるが、まずはこの2つがトレードオフの関係にあるということを意識してケーブルを巻いてみたらどうだろうか。


こういった話を(もう少し簡単にして)高校生にした。

僕は単一原因論的に物事を決めるような大人になって欲しくないと常々思っている。世の中の言説は、ある点が満たされないからこの主張は間違っている、という雑な否定に満たされている。

多角的に物事を見た上で、最終的には軸ごとの重みづけをどうするか、という議論に帰着すると思う。しかし、ある人にとってその重みのパラメータは完全に固定してしまっている。(もしくは、ある軸の評価関数が完全に制約と化してしまっている。)ある軸に全振りしていることに気づいていないのだ。

いかにそういった仮定・思い込みから一歩引けるかというのを、普段の仕事でも考えている。一方で、そういった思考を他人に促すというのはとても難しい。