Posts for: #Meta Programming

責任を扱うコミュニケーションの在り方

水曜日から継続していたメンバーのコードレビューをお昼頃に完了して、それからは自分の時間に集中して開発に取り組めた。途中、晩ご飯休憩もしながら翌2時頃までコードを書いていて issue を2つ fix した。

隔週の雑談

顧問のはらさんと隔週の打ち合わせ。ここ1-2ヶ月はあまり議題がなくて近況について雑談した。いまの開発フェーズが終わらないと、私が開発以外にリソースを割いていないので他のことに取り組む余裕はないかもしれない。開発プロジェクトにおける、議題の1つで次のインタビュー記事の内容について雑談した。

責任をすぐに相手に投げてしまわないこと。責任をこちらで負えるように日頃から信頼貯金を貯めていくことが大事です。責任を負うこと自体はたいへんだけど、仕事は楽になるんです。

でも、人は責任を負いたくないと考えてしまうんですね。気持ちは分かりますよ、責任を負うのには胆力が必要ですから。そして責任を負いたくないから「いつまでですか?」と聞いてしまう。その途端、スケジュールを決めるのは相手の裁量になる。責任から降りて、自分から立場を下にして、受発注の関係にしてしまっています。

何が事業貢献なのか分からなくなっていた伊藤直也さんが再認識したユーザーエクスペリエンスへのコミット

私自身、このインタビュー記事を読むまで相手に仕様や納期を決めてもらうことを、相手に責任を負わせるという視点をもっていないかった。しかし、その通りでもあると新たな気付きを得た記事でもあった。うちらはプログラマーという、システムを作る専門家なのでビジネス課題や解決したい業務課題について、その課題意識をもっている人 (ビジネスオーナー) から教えてもらうのを至極当然のように考えてしまう。そして、それ自体はいまの業務の在り方としてそうならざるを得ないところもある。

システム開発はそれぞれの専門職が分業によって行う。とくに規模が大きくなればそうなる。この構造そのものは一般的といえる。しかし、一緒にプロジェクトをやっていく働き方や考え方によってはその責任を分散させられるというのがこの記事に書いてあることだとわかる。そして、私自身これまで意識せずにそういった行動をいくらか取ってきているところもあり、それは「気付き」のレベルによって起こるものだとずっと考えていた。気付くからより多くの、より本質的な、より優れた改善のための行動ができる。そして、私の考え方も間違ってはいないと思うが、私は他人よりリスクを取りがちな性格があるから責任をこちらで負うという意識をあまりもっていなかった。つまり、私は自分の価値観でこうしたいと思って勝手にやっていたことを、別の見方では相手から責任を受け取って進めているという解釈もできる。

ちょうど 兵庫県知事の百条委員会の答弁 もみていて感じたことだが、私はこういったコミュニケーションを本質的に好んでいない。自分の責任ではないという議論をしても、モノゴトは前に進まない。世間では百条委員会の後も知事の説明は十分ではなかったとみられている。その所以である。誰かが責任をもってモノゴトに取り組む必要がある。その責任の所在を明確にすることも大事ではあるが、自身の責任ではないという主張だけでは物足りなさを感じる。普段の業務においてもそういう姿勢やコミュニケーションを取る人が少なからずいる。はらさんの経験でもその点においては同意していた。よくある状況だと思える。自身に責任を負うことをためらわないコミュニケーションを取る人とそうではない人の2通りがあるのだと気付いた。そして、私は後者の人とコミュニケーションを続けていると疲弊したり苛々したりすることがある。それゆえに私自身も結果に対して潔くあろうと努めるし、潔い人たちとウマがあうのだろうともわかってきた。

課題管理の文脈においては、コミュニケーションのやり取りから責任の綱引きがどのような場所でどのぐらい起きるのか。人間であれば読み取れるが、ai はその意図を解釈できるか。そういった業務の責任という概念を見える化することに意味はあるかもしれない。責任の押し付け合い、または責任分散、本質的にどうあるべきだったかをなんらかの指標をもって数値化できればおもしろいのではないかと思えた。

go における簡単な式の評価

テスト自動化のツールを作っていて、テストデータでちょっとした式の評価をやりたくて調べたらまさに次の記事で解決した。

この記事では go/types パッケージに定数や式の評価を行う機能がその使い方が紹介されている。簡単に使える。ふとサードパーティのパッケージならどうなるんだろう?とインターネットを検索したものの、自分ではみつけられなかった。chatgpt に問い合わせたら次のようなコードを紹介してくれた。そして、たしかにほとんどは正しくて意図したように動いた。次のコードでは http フレームワークの echo の定数を参照している。types.Package を生成するためのモジュールの読み込み方法について標準ライブラリはそのためのユーティリティが用意されているが、サードパーティのパッケージは用意されていなかった。

import (
    "fmt"
    "go/token"
    "go/types"
    "golang.org/x/tools/go/packages"
)

func eval(expr string) (types.TypeAndValue, error) {
	cfg := &packages.Config{
		Mode: packages.NeedTypes | packages.NeedImports,
		Fset: token.NewFileSet(),
	}
	pkgs, err := packages.Load(cfg, "github.com/labstack/echo/v4")
	if err != nil {
		return types.TypeAndValue{}, err
	}
	if len(pkgs) == 0 || pkgs[0].Types == nil {
		return types.TypeAndValue{}, fmt.Errorf("failed to load echo package")
	}
	mainPkg := types.NewPackage("main", "main")
	mainPkg.Scope().Insert(types.NewPkgName(token.NoPos, mainPkg, "echo", pkgs[0].Types))
	return types.Eval(
		cfg.Fset,
		mainPkg,
		token.NoPos,
		expr,
	)
}

このコードで expr に次のように echo の定数を指定するとその値を参照できる。

echo.MIMEApplicationJSON

動的型付けのノリで関数も実行できたりするのかな?と試してみたら (当たり前だが) できなかった。chatgpt になぜ関数実行できないかを尋ねたら次であるとのこと。静的型付けのコードを実行するには、本来コンパイルしないといけないのだから式の評価よりもずっとやることがある。

packages.Load を使用してサードパーティパッケージをインポートできているにもかかわらず、expr からそのパッケージの関数を呼び出しても結果が取得できない原因は、go/types パッケージがサポートしているのは、型や定数のチェック、構文解析、式の評価であって、関数の実行そのものはサポートされていないためです。

go/types の Eval 関数はあくまでコンパイル時の型検査や式の評価を行うもので 関数の実行や実行時の評価 (ランタイムの処理) は行いません。これは、Eval が式を評価して型と値を返すだけであり、動的な関数の実行などはできないためです。

openldap スキーマと parser generator

この開発フェーズではあまり機能拡張を行わない方針としているが、できればやっておいた方がよい機能拡張の1つに openldap サーバーから ldap スキーマを取得する処理がある。ちょうどいま若いメンバーに実装してもらっている大きな機能のバリデーションにも ldap スキーマの情報があると便利そうなので私が対応することに決めた。

openldap サーバーで管理している ldap スキーマの定義は openldap サーバーの Schema Specificationrfc 4512 の2つをみると仕様がわかる。たとえば、次のように AttributeTypeDescription というスキーマの定義は Augmented Backus–Naur form (abnf) という記法で定義されている。rfc などのネットワークプロトコルの世界では abnf のフォーマットで仕様を説明することが多いらしい。

AttributeTypeDescription = "(" whsp
      numericoid whsp              ; AttributeType identifier
    [ "NAME" qdescrs ]             ; name used in AttributeType
    [ "DESC" qdstring ]            ; description
    [ "OBSOLETE" whsp ]
    [ "SUP" woid ]                 ; derived from this other
                                   ; AttributeType
    [ "EQUALITY" woid              ; Matching Rule name
    [ "ORDERING" woid              ; Matching Rule name
    [ "SUBSTR" woid ]              ; Matching Rule name
    [ "SYNTAX" whsp noidlen whsp ] ; Syntax OID
    [ "SINGLE-VALUE" whsp ]        ; default multi-valued
    [ "COLLECTIVE" whsp ]          ; default not collective
    [ "NO-USER-MODIFICATION" whsp ]; default user modifiable
    [ "USAGE" whsp AttributeUsage ]; default userApplications
    whsp ")"

AttributeUsage =
    "userApplications"     /
    "directoryOperation"   /
    "distributedOperation" / ; DSA-shared
    "dSAOperation"          ; DSA-specific, value depends on server

openldap サーバーに対して ldap スキーマを取得する方法は How can I fetch schema information from the server? の faq に書いてある。search base に対してサブスキーマサブエントリを返す dn を取得する。openldap サーバーの場合 ldap の root ツリーに対応するのは cn=Subschema がデフォルトとなる。

$ ldapsearch -H ldap://localhost -x -LLL -b dc=example,dc=com -s base subschemaSubentry
dn: dc=example,dc=com
subschemaSubentry: cn=Subschema

この dn にスキーマ情報の属性が格納されているのでそれらを取得する。このスキーマ情報は operational attributes として管理されているので + という記号が operational attributes 属性群をまとめて取得するキーワードになっている。

$ ldapsearch -x -LLL -b cn=Subschema -s base '(objectClass=subschema)' +

これでスキーマ情報を取得できる。先に書いた AttributeTypeDescription の実際のスキーマ情報は次のような内容になる。こういったスキーマのテキスト情報をたくさん取得できる。

( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )

go の parser generator 調査

この手のものは abnf から parser をコード生成するのが一般的なやり方かな?と、まずは go で使えそうな parser generator について調べてみた。意外と go 製の parser generator はみつからなかった。私が発見できたのは次の3つぐらい。

最後の antlr は java 製のツールだけれども、go のソースコードを出力できるので go 実装の parser をコード生成できるという意図で対象としている。goyacc は abnf の文法を yacc に変換しないといけない。yacc で実装した ldap スキーマの parser を公開しているのもみかけたが、yacc は違うなと思って除外した。antlr も abnf から専用の文法に変換しないといけないから不採用にした。

それで消去法的に bnf (ebnf) を扱える gocc で parser をコード生成できないかを調査してみた。しかし、半日ほど bnf を書いて実際にコード生成してみて不採用とした。ebnf の options 記法として [...] に gocc が対応していないようにみえた。この記法がないと ldap スキーマの文法定義が煩雑になる。issue のコメント をみると、lexer は対応したが、parser は対応していないといったコメントがある。コード生成しようとするとエラーになるので未対応なのかもしれない。bnf でこの options 記法相当のものを自分で文法定義すると、1つ2つなら簡単に変換できるが、数個の組み合わせがあると途端に文法の複雑さが大きくなるように思える。abnf から bnf に変換する過程で文法が複雑になってしまうとその後の保守ができなくなる懸念が生じるので断念した。

今回の gocc の採用は却下したが、軽く触ってみて gocc 自体の感触はよかった。シンプルな bnf で表現できるものであれば機会があれば採用してもよいと思える。gocc example をみればわかるが、bnf を書きながら単体テストのコードを実行して動作検証を小さく簡潔にできる。これは parser のコード生成の開発サイクルは速くできそうに感じた。こういう小さく単体で動くツールは好み。

最終的な結論としては parser generator は使わず、自前で parser を実装することを決めた。

scim 調査に着手

22時に寝て24時に起きて4時に起きて6時に起きた。実家だとやることないので寝るのも早くなる。早く起きているので始業も7時半ぐらいになる。早起きは三文の徳。

リモートワークのタグを新設

神戸のオフィスに行かなかった日は day off というタグを付けている。名前の通り、お休みしたということを表す。この定義に従うと、実家に帰ってリモートワークをしたときもお休みになってしまうため、それを区別するように remote work というタグを作った。

scim 調査

id 連携の文脈で System for Cross-domain Identity Management (頭文字をとって SCIM、すきーむと呼ばれている) という標準がある。基本的には rest api とスキーマを扱うように仕様が決められていて、それに準拠したサービス間で id 連携を標準化する狙いがある。id 連携と同じ用途を表す用語として id プロビジョニングという用語もあるが、多くのクラウドサービスでは id プロビジョニングのために scim 対応していたりする。

たまたまサイボウズさんが okta と scim 連携に対応しているプレスリリースをみかけた。

その延長で調査していたところ、go の scim ツールを提供していることに気付いた。しかもこれを作っているのが Maki さん。これはソースを読んでおこうと思った次第。Maki さんはメルカリに所属していたと思うのでこれは副業でやっているのかな?

scim-server は scim ライブラリを実際に使うときの参照実装としてアプリケーションの開発者に開発の雰囲気を伝えるための実装になっている。これ自体をプロダクトのサーバーとして使うわけではない。sqlite を使ってユーザー/グループの crud な操作と検索機能を提供している。

scim ライブラリのソースコードも軽くざっと読んでみた。github.com/lestrrat-go/sketch という go でスキーマを記述してコード生成する Maki さん製のツールがある。これを起点に scim のプロトコルやリソースの仕様にしたがって go のコードでスキーマを定義し、リソースに関する go のコードと scim スキーマを自動生成している。sketch というツールに関連して他にも Maki 製のメタプログラミングライブラリを多用していて、scim の標準化されている部分のエンドポイントやリソースのインターフェース部分をすべてコード生成している。

go generate やコード生成の実際の応用例として非常に参考になる。このライブラリは scim のプロトコル仕様に関する開発者と、そのエンドポイントの実際の処理 (バックエンド) の開発者を明確に分離するという開発体制を期待している。scim に関するところは Maki さんが独りでコード生成を多用してオープンなプロトコル仕様の開発を担当し、そのバックエンドをサイボウズさんの開発者が実装するという分業体制を想定しているようにみえる。

scim 対応のアプリケーション開発のプロトコル部分を外部に委譲するといった設計になっているが、scim が十分に安定していてプロトコルの仕様が変わらないのであれば理に適っているとも考えられる。バックエンドの開発者はいくらか sketch の学習コストを強いることになる。そのため、アプリケーションはその学習コストを支払ってもコード生成のメリットが上回るだけの規模や特性を要求する。おそらく scim はそれだけの価値があると判断されたのだと推測する。

私はメタプログラミングが好きな方なのでこういうやり方もあるんだなと設計の参考になった。またコード生成の要件があったときにソースを参考にしようと思う。