更新日:
投稿日:

今使っているチェアがだいぶヘタってきて、そろそろダメそうなので買い換えたいがディスコンになっていて困ってしまった…。

椅子に求めるもの

個人的に椅子に求めるものは次のようなものだ。

  • 汚れに強く、清掃性が高い
  • 加水分解しない
  • 座面に程よいクッション性があり、硬さやツッパリ感がない
  • 上で胡坐をかける
  • 一番高い状態のまま机の下に入れられる

ファブリック vs メッシュ vs レザー

で、これを求めると選択肢としてはこの辺りだろう。

まずファブリック素材は汚れに弱く、清掃性も終わってる。家具屋の座面や、会社の椅子などは悲しいほど汚れているし、悪臭を放っていることさえある。あんなのは耐えられない。

次にメッシュチェアだが、こちらは突っ張っていて苦手だ。それと椅子の端っこ、太ももが乗る部分が硬くて痛い。アーロンチェアだろうがオカムラだろうが痛いものは痛い。

すると残るのは革張りになってくる。革張りの椅子は汚れが染み込まず、表面の汚れも落としやすい。また、座り心地はファブリック並みによく、親水性がないためクッションの中に湿気が溜まることがないのもメリットだ。

今の椅子

三年ほど使っていて、食べこぼしや飲みこぼし、血などの付着があったりもしたが、軽微なものはトイレクイックル、油汚れであっても食器洗い洗剤で擦れば綺麗になくなるので、汚した内容に対する状態はいい。

構造的には左右にパイプが張っていて、その間に革をハンモックのように張っている面白い構造だ。クッションと呼べるものはないが、革の伸縮によるクッション性がある。

しかし三年も使っていると革が伸びてダメになってくる。これは三代目なのだが、四代目はどうにも手に入りそうにない。

素材はニトリのNレザーで、そのためか加水分解など一切せず、非常に表面の状態はいいのが特徴だ。

更に肘置きがなく、机の下にそのまま入るのも便利だ。

ただ本来は肘置きで背もたれを固定する構造のため、肘置きがないと背もたれが前に折れてくるのが難点だ。この椅子は運搬の都合、背もたれを半分に折って小さくできるようになっているのだ。

これも初期のころは何もせずとも立っていたのだが、ついここ半年ほどで具合が悪くなってきている。

そこでガムテープで固定しているのだが、ガムテープだと緩んで機能しなくなることもあり、なんとも言えない。

背もたれが倒れているのは非常に腰によくないので、限界を感じる。

因みにこの椅子は製品としてはニトリのNシールドカルカというものだ。

次の椅子探しの旅

類似品を探して発見するリプロダクトというジャンル

ニトリ製品はOEMも多く、ニトリでディスコンになった後もホムセンなどで見かけることがあるので調べていたが、残念ながら同じものは見つからなかった。

ただ類似品はECサイト上に多くあり、調べていくうちにイームズ社の開発したアルミナムチェアのリプロダクトであることが分かった。リプロダクトというのは要は設計の権利が切れたもののコピー品のことである。

「イームズ アルミナムチェア リプロダクト」とかでググると山のように出てくる。ローバックならニトリビジネスにもオフィスチェア(EX338 GTS129 BK)として存在した。

オフィスチェア(EX338 GTS129 BK)を実際に買ってみたが、座面実測が44cmで今の椅子が50cmなので、6cmも低く、更に座面が後ろに倒れており、作業椅子としては使えそうになかった。いわゆるエグゼクティブチェアの類に近いと思うが、グランフロント大阪にあるニトリビジネスの展示などを見る限り、会議室での使用を想定してそうだった。

しかし、イームズ アルミナムチェア リプロダクトが、これほど人気のジャンルならニトリさんにもイームズ アルミナムチェア リプロダクトとしてもう一度出しなおしてほしいものである。

まぁないものは仕方がないので諦めるしかない。

家具屋巡礼

実際に座ってみないことには何とも言い難いので、実物を展示している店舗を探すことにした。

まずそんじょそこらの家具屋にはオフィスチェアがそもそも置いてない。これを置いている家具屋を探すだけで一苦労である。

ググって出てくるオシャレな家具屋には確実にない。置いてあるのはニトリ、IKEA、東京インテリア家具、大塚家具くらいだ。

ニトリに期待できる品がない以上、他をあたるしかないだろうと思って、まず訪問したのが新神戸のプラスカーサだったが、こちらにはハーマンミラーのアーロンチェアやセイルチェアしかなかった。

次に向かったポートアイランドのIKEAには革張りのオフィスチェアで求めるものがなく、ファブリックが多かったと思う。向かいの東京インテリア家具には何の期待もせずに向かったがハーマンミラーとオカムラのチェアはあったものの、そもそもハーマンミラーとオカムラに求める品がない私には意味がなかった。

最後に向かったのは大阪南港にある大塚家具のショールーム、IDC大塚家具大阪南港だ。

ここは非常に品ぞろえが多かったが、リプロダクト品の扱いはなかった。予約なしで訪問したが、ここでは店員からアドバイスを受けることができた。

一番近いのはオカムラのコンテッサセコンダの総革張りとのことだった。しかし本革で価格は70万円、コンテッサの売りは肘置きということで、「肘置きが不要ならそれだけの価値はなく、更に本革はガシガシ汚す用途では不向きという。価格的に無理なのもあり、撤退することにした。

ここまでの旅で分かったこととしては、革張りの椅子にはエグゼクティブチェア、要するに社長椅子が多く作業に不向きなものが多いということだった。そして作業椅子は代替ファブリックかメッシュである。

結局次の椅子をどうするか

ひとまず世の中の椅子はファブリックかメッシュが多く、革張りがあっても社長椅子では話にならないし、作業椅子でも価格が70万では話にならない。

そこで着眼したのがゲーミングチェアだ。ゲーミングチェアは革張りが多く、ゲームをしながら使うものなので飲み食いが想定されている。肘置きは高さ調整が効いたり、外せそうな見た目のものも多く見えるので、ここにならチャンスがないかということを思ったので、次はゲーミングチェアを見ていこうと思う。

Value-DomainのダイナミックDNSエンドポイントは60秒以内に叩くとエラーが返ってくるので、これを回避するためのDDNSの仕組みを作った話。

やったこと

まず、Value-DomainのDNS APIに対し、既存のaレコードをバルクで差し替えるためのツールとしてvd-ddns_v4.plを作った。

そしてhotplug.dにPPPoEインターフェースのIPが変わったときに、このスクリプトを蹴る処理を書いた。

この記事の前提構成

OpenWrtにIPv4用のPPPoEインターフェースがある。

やったこと

  1. 今回利用するのに必要なPerl周りをセットアップする。ストレージの空きが3.2MBほど必要
  2. value-domain-dns-util/root配下とか適当な場所に放り込む
    • opkg install openssh-sftp-serverでSFTPを導入しておくとファイル移動に便利
  3. vi /etc/hotplug.d/iface/40-pppoeとかして、OpenWrtのインターフェースが変化したときのHookを作る

     #!/bin/sh
     # デバイスが存在しなければ終了
     [ -n "$DEVICE" ] || exit 0
     # リンクアップでなければ終了
     [ "$ACTION" = ifup ] || exit 0
     # インターフェース名がPPPoEのものでなければ終了
     [ "$INTERFACE" = wanppp ] || exit 0
     # pppoe-wanpppのIPv4アドレスを取得
     pppoeaddr=$(ip -4 addr show pppoe-wanppp | head -2 | tail -1 | awk '{print $2}')
     # ログに吐く
     logger -t "DDNS - PPPoE IP" $pppoeaddr
     # DDNSもどきを叩く
     /root/vd-dns-util/vd-ddns_v4.pl 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' example.com $pppoeaddr hoge fuga piyo
     # 結果をログに吐きたいが何故か動いていない
     if [ $? -eq 0 ]; then
     logger -t "DDNS - UPDATE SUCCEED"
     else
     logger -t "DDNS - UPDATE FAIL"
     fi
    
  4. PPPoEインターフェスをRestart

  5. Value-Domainのコンパネで指定したドメインのaレコードが更新されていることを確認

備考

一般的にDNSレコードの浸透時間は更新前に浸透していたTTLに依存し、Value DomainのAPI経由で普段から操作している場合は120秒以下にならないため、最大120秒のダウンタイムが発生するが、理論上は公式のDDNS機能と大差ないはずと思われる。

aレコードのみの対応にしているのは私の環境だとIPv6は変動しないが、v4はそれなりの頻度で変わるためだ。

運用しているMastodonのv4が変わったのに気づかないまま疎通できないインスタンスが出てくることがしばしばあり、手動で対応するのが手間なのと、毎回一日くらい気づくのに遅れるので今回自動化に踏み切った。前々からやり勝ったのだが、中々腰が重く進んでいなかった。

そもそもこの手のものは監視システムで検知できて然るべきなので、おいおい監視システムの構築もしていきたいところだ。

更新日:
投稿日:

確認環境

OpenWrt 24.10.0

やり方

opkg install perl
opkg install perlbase-essential
opkg install perlbase-json-pp
opkg install perlbase-scalar
opkg install perlbase-xsloader
opkg install perlbase-dynaloader
opkg install perlbase-list
opkg install perlbase-bytes
opkg install perlbase-findbin
opkg install perlbase-http-tiny

# OpenWrtにはPerlのSSLモジュールがないので親切な人が作ってくれてるものを使わせていただく
# https://jw2013.github.io/openwrt-packages/
opkg install libustream-mbedtls
wget https://jw2013.github.io/openwrt-packages/public.key
opkg-key add public.key
echo 'src/gz pkgs-by-jw2013 https://jw2013.github.io/openwrt-packages/24.10/x86_64' >> /etc/opkg/customfeeds.conf
opkg update
opkg install perl-net-ssleay
opkg install perl-io-socket-ssl
rm public.key

増加する容量

キビバイトやメビバイトのバイト変換やトータルビバイトはLLMに換算させた欠化を書いているので誤りが混ざっている可能性がある。大まかには合っていると思うが…。

pkg size byte
perl 1.27 MiB 1,331,692
perl-io-socket-ssl 124.00 KiB 126,976
perl-net-ssleay 153.61 KiB 157,297
perlbase-autoloader 3.04 KiB 3,113
perlbase-b 106.07 KiB 108,616
perlbase-base 4.27 KiB 4,372
perlbase-bytes 1.23 KiB 1,260
perlbase-class 3.02 KiB 3,092
perlbase-config 18.55 KiB 18,995
perlbase-cwd 11.31 KiB 11,581
perlbase-dynaloader 4.50 KiB 4,608
perlbase-errno 3.22 KiB 3,297
perlbase-essential 23.79 KiB 24,361
perlbase-fcntl 7.36 KiB 7,537
perlbase-feature 2.68 KiB 2,744
perlbase-file 72.30 KiB 74,035
perlbase-filehandle 1.81 KiB 1,853
perlbase-findbin 1.98 KiB 2,028
perlbase-http-tiny 12.98 KiB 13,292
perlbase-i18n 21.21 KiB 21,719
perlbase-integer 1.12 KiB 1,147
perlbase-io 67.71 KiB 69,335
perlbase-json-pp 11.96 KiB 12,247
perlbase-list 23.14 KiB 23,695
perlbase-locale 10.98 KiB 11,244
perlbase-mime 6.50 KiB 6,656
perlbase-params 4.02 KiB 4,116
perlbase-posix 40.45 KiB 41,421
perlbase-re 285.55 KiB 292,403
perlbase-scalar 1.60 KiB 1,638
perlbase-selectsaver 1.27 KiB 1,300
perlbase-socket 19.49 KiB 19,958
perlbase-symbol 1.94 KiB 1,987
perlbase-tie 18.40 KiB 18,842
perlbase-unicore 779.16 KiB 797,860
perlbase-utf8 1.24 KiB 1,270
perlbase-xsloader 2.54 KiB 2,601
total 3.08 MiB 3,230,188
投稿日:

カスタムキャスト側でCOM3D2のプリセットを再現しようとすると、色周りの設定が数値指定できない関係で難しすぎたので、何とかしようとして見つけた方法。

一撃で同じキャラを持ってこれるのでだいぶ楽。

必要なもの

  • スマホとPCを繋ぐためのUSBケーブル
  • バイナリエディタ
  • COM3D2
  • カスタムキャスト

確認環境

Env Ver
Android 16
COM3D2 2.29.4
カスタムキャスト 1.04.01

やり方

  1. スマホをUSBケーブルでPCに繋ぐ
  2. スマホ側の接続形態を充電からファイル転送モードにする
  3. カスタムキャストを開いて適当に服・体プリセットをセーブする
  4. 内部共有ストレージ/Android/data/jp.customcast.cc2/files/Presetを開き、保存したプリセットを取り出す
  5. COM3D2を開き、カスタムキャストで使いたいキャラのエディット画面を開き、服・体プリセットを保存する
  6. バイナリエディタでCOM3D2のプリセットファイルを開き、454E44AE426082を検索し、そこより先にあるバイナリコードをすべてコピーする
  7. カスタムキャストのプリセットを開き、454E44AE426082を検索し、そこより先にあるバイナリコードを、先ほどコピーしたCOM3D2のバイナリコードですべて上書きし、保存する
  8. 保存したカスタムキャストのプリセットファイルを内部共有ストレージ/Android/data/jp.customcast.cc2/files/Presetに戻す
  9. カスタムキャストを開き、キャラエディットからプリセットをロードする

備考

恐らくファイルヘッダ以外ほぼ互換だと思われるが、ヘッダのリプレースが面倒なのでボディを書き換えることで達成した。

当たり前だがMODとかは反映されない。

おまけ

データを個別に取得して弄れるようにしようとツールを書こうとしていたが、パラメーターが多いのと、フォーマットが異なるものがあり、解析に飽きたので調べた残骸として残しておく。

using System.Diagnostics;
using System.Text;

class Program {
    static void Main(string[] args) {
        string filePath = @"C:\path\to\pre_Dummymaid_20260222134142.preset";

        if (!File.Exists(filePath)) {
            Debug.WriteLine($"ファイルが見つかりません: {filePath}");
            return;
        }

        byte[] fileData = File.ReadAllBytes(filePath);
        Debug.WriteLine($"ファイル読み込み完了: {fileData.Length} bytes");

        var obj = new ParamFinder(fileData);

        var MuneL = obj.FindBodyValue("MuneL");
        var MuneS = obj.FindBodyValue("MuneS");
        var MuneTare = obj.FindBodyValue("MuneTare");
        var RegFat = obj.FindBodyValue("RegFat");
        var Hara = obj.FindBodyValue("Hara");
        var RegMeet = obj.FindBodyValue("RegMeet");
        var KubiScl = obj.FindBodyValue("KubiScl");
        var UdeScl = obj.FindBodyValue("UdeScl");
        var EyeSclX = obj.FindBodyValue("EyeSclX");
        var EyeSclY = obj.FindBodyValue("EyeSclY");
        var EyePosX = obj.FindBodyValue("EyePosX");
        var EyePosY = obj.FindBodyValue("EyePosY");
        var EyeClose = obj.FindBodyValue("EyeClose");
        var EyeBallPosX = obj.FindBodyValue("EyeBallPosX");
        var EyeBallPosY = obj.FindBodyValue("EyeBallPosY");
        var EyeBallSclX = obj.FindBodyValue("EyeBallSclX");
        var EyeBallSclY = obj.FindBodyValue("EyeBallSclY");
        var FaceShape = obj.FindBodyValue("FaceShape");
        var MayuY = obj.FindBodyValue("MayuY");
        var HeadX = obj.FindBodyValue("HeadX");
        var HeadY = obj.FindBodyValue("HeadY");
        var DouPer = obj.FindBodyValue("DouPer");
        var Sintyou = obj.FindBodyValue("sintyou");
        var Koshi = obj.FindBodyValue("koshi");
        var Kata = obj.FindBodyValue("kata");
        var West = obj.FindBodyValue("west");
        var MuneUpDown = obj.FindBodyValue("MuneUpDown");
        var MuneYori = obj.FindBodyValue("MuneYori");
        Debug.WriteLine($"MuneL: {MuneL}, MuneS: {MuneS}, MuneTare: {MuneTare}, RegFat: {RegFat}, Hara: {Hara}, RegMeet: {RegMeet}, KubiScl: {KubiScl}, UdeScl: {UdeScl}, EyeSclX: {EyeSclX}, EyeSclY: {EyeSclY}, EyePosX: {EyePosX}, EyePosY: {EyePosY}, EyeClose: {EyeClose}, EyeBallPosX: {EyeBallPosX}, EyeBallPosY: {EyeBallPosY}, EyeBallSclX: {EyeBallSclX}, EyeBallSclY: {EyeBallSclY}, FaceShape: {FaceShape}, MayuY: {MayuY}, HeadX: {HeadX}, HeadY: {HeadY}, DouPer: {DouPer}, Sintyou: {Sintyou}, Koshi: {Koshi}, Kata: {Kata}, West: {West}, MuneUpDown: {MuneUpDown}, MuneYori: {MuneYori}");
    }
}

class ParamFinder {
    private Byte[] FileData;

    static byte[] CreateBinaryStringBytes(string text) {
        using var ms = new MemoryStream();
        using var writer = new BinaryWriter(ms, Encoding.UTF8);
        writer.Write(text);
        writer.Flush();
        return ms.ToArray();
    }

    /// <summary>
    /// バイト配列内から指定パターンの最初の出現位置を返す (見つからなければ -1)
    /// </summary>
    static int FindPattern(byte[] data, byte[] pattern) {
        int limit = data.Length - pattern.Length;
        for (int i = 0; i <= limit; i++) {
            bool match = true;
            for (int j = 0; j < pattern.Length; j++) {
                if (data[i + j] != pattern[j]) {
                    match = false;
                    break;
                }
            }
            if (match)
                return i;
        }
        return -1;
    }

    public ParamFinder(Byte[] fileData) {
        this.FileData = fileData;
    }

    public int FindBodyValue(string subject) {
        byte[] signature = CreateBinaryStringBytes(subject);

        int sigOffset = FindPattern(this.FileData, signature);
        if (sigOffset < 0) {
            throw new Exception($"エラー: データセクションのシグネチャ '${subject}' が見つかりませんでした。");
        }

        using var ms = new MemoryStream(this.FileData, sigOffset, this.FileData.Length - sigOffset);
        using var reader = new BinaryReader(ms, Encoding.UTF8);
        reader.ReadString();
        reader.ReadString();
        reader.ReadInt32();
        reader.ReadInt32();
        reader.ReadString();
        reader.ReadInt32();
        reader.ReadInt32();
        return reader.ReadInt32();
    }

}
更新日:
投稿日:

私は2020年の末あたりからLet's EncryptのDNS-01 challengeをValue-Domainで達成するためのスクリプトであるvalue-domain-dns-cert-register (vddcr)の開発を行ってきていたのだが、メンテが辛くなってきたので、今回Shellscript(Bash)Perlでvalue-domain-dns-utilとして作り直した。その経緯と、開発中の学び、そして最後にどうやって作っていったかについて書いていく。

背景

一年半ほど前からTypeScriptに限界を感じてきたことや、当時の誤った単体テストへの偏執により、vddcrはメンテしたくない状態になってしまっていた。

そこで昨夏、つまり2025年の8月に式年遷宮、つまり全体的な書き直しを行ったのだが、個々の処理をする部品を作ったまではいいものの、それらを繋ぎ合わせるところで飽きてしまい、放置していた。vddcrはこの時点で事実上の放棄状態だった。

しかし、自宅サーバーの運用をしていく中で別の目的でValue-DomainのDNS APIと関わらなければならない必要が出てきたため、今回、vddcrの置き換えも兼ねて新しく作り直すことにしたのだ。

具体的には複数ドメインに対するDDNSをバルクでやりたい要求が生まれたが、Value-DomainにあるDDNS APIは一度に1ドメインしか処理できない上にレートリミットが60秒に1回と極めてキツく、複数ドメインに対して行うには余りにも遅延が大きかった。

例えば10ドメイン更新しようとすると10分もかかるわけで、これでは使い物にならないというので、DNS APIを直に叩いて一気に書き換える必要性が出てきたのだ。どうせ作るならvddcrが担っていたDNS challengeの機能も包含できる形にしたかったので、Value-DomainのDNS APIを便利に叩くためのツールキットとしてvalue-domain-dns-utilを作ることにした。

つまりvddcrの純粋な後継ツールというよりは、vddcrの役目もこなせるツールキットを開発し、vddcrと同等の機能を有するツールを実装サンプルとして同梱したわけだ。

Shellscriptにした理由

ものの数時間でPerlに直したので過去の話となった。移植性の関係でbashはイマイチだった。

ぶっちゃけそんなに大した理由はなくて、単に目の前にターミナルとテキストエディタがあってパッと組めたからという部分が大きい。実際ベースはパッと作れた。まぁ作って半年も放置していたのが…。ただまぁPerlの方が保守性は高いだろうし、移植性もあるだろうとは思うから、気が向いたら書き直すかもしれない。

ちなみに別言語に移植することの検討はvddcrでも行っていたが、最終的には候補になかったShellscript(Bash)での実装に落ち着いた。そもそもこの課題の存在を忘れていたので、検討すらしていなかったのである。

value-domain-dns-utilについて

今回作ったvalue-domain-dns-utilは、vddcrの単純な代替ではなく、Value-DomainのDNS APIを叩くのに便利なShellscriptの関数群だ。DNS challenge以外にも用が生まれたので、このような汎用的な形になっている。

DNS APIから現在のレコードを取得する関数や、取得したレコードから先頭一致のパターンマッチでレコードを取得する関数、レコードリストへの追加や置換をする関数、そして編集したレコードをDNS APIに送り戻す関数を備えており、DNSに関する一連の編集処理ができるように便利に作っている。

結果として、Value-DomainのDNS APIを叩く汎用機能があれば、それを流用してLet's EncryptのDNS-01 challengeもできるし、当然複数ドメインに対するDDNSをバルクでやることも可能になる。

学び

開発中に発見も多くあったので記していく。なんかこういうのまとめた記事かページ作りたいね。

echoでドルマーク始まりのシングルクォート文字列を作ると、その中の改行コードが展開される

例えば以下のようになる。これは一行で改行展開を書けて変数に入れられるので、インデントされているコードなどで物理的にへし折りたくないときなどに有用だし、文字列としてのコードが見えるのでわかりやすい部分もあると思う。

入力

#!/bin/bash

# ドルマーク始まりのシングルクォート文字列は改行が展開される
echo $'1:aaa\nbbb'

# つまりこれと同じ
echo "2:ccc
ddd"

# ダブルクォートの中の\nは展開されない
echo "3:eee\nfff"
# ドルマーク始まりのダブルクォートも展開されない
echo $"4:ggg\nhhh"

# 一行で改行展開を書けて変数に入れられるので便利
hoge=$(echo $'5:iii\njjj')
echo "$hoge"

出力

1:aaa
bbb
2:ccc
ddd
3:eee\nfff
4:ggg\nhhh
5:iii
jjj

変数の最後の一文字を削る

これは後方一致除去(${parameter%word})と呼ばれるものらしく、詳しくは調べていないが"hoge"に対してfugaを繋げ、"hogefuga"としたい場合に便利だ。

例えば以下のように書ける。

入力

#!/bin/bash

hoge='"hoge"'
fuga='fuga'
result="${hoge%?}$fuga"'"'

echo $result

出力

"hogefuga"

sedで改行を改行コードに置換(\n\\n

これは見つけた当時sedの記事にも書いているが、以下のようになる。

入力

cat <<'EOF' | sed ':a;N;$!ba;s/\n/\\n/g'
aaa
bbb
ccc
ddd
EOF

出力

aaa\nbbb\nccc\nddd

[]ではGlob展開できないが、[[]]では出来る

今のところこういった形式でGlobを書くことはないのだが、Sonnet 4.6にテストコードを書かせている中で発見したので書き留めておく。

入力

#!/bin/bash

hoge='aaabbbccc'

[ $hoge = *"bbb"* ]
result1=$?
[[ $hoge = *"bbb"* ]]
result2=$?

echo $result1
echo $result2

出力

1
0

あとがき

LLMとの作業分担

今回の開発でもLLMを活用したのでその辺り。

今回の開発では、8ada152~93d58ccのコミットを行った。最も古いコミットである8ada152ではテストコードを除き、私がフルスクラッチで書き、テストコードはClaude Code(Sonnet 4.6)に書かせた。

次の029a951では、私が書いたコードをClaude Code(Sonnet 4.6)に全体的に直してもらって私がレビュー・微修正するという開発形態をとった。それ以降のコミットはコメントなどの微修正なので、再び私が手動で書いている。

こうした分担により、設計の根底にある信条のようなものには私の色が濃く出ているが、実装上の問題点はClaude Codeによって大きく改善されている。特に029a951では、typoや単純な実装ミスの修正に加えて、bashの文字列処理に不慣れなことで生まれたエスケープシーケンスの山が整理された。jqの-rオプションの有無がまちまちだった部分も統一され、可読性がだいぶ向上したと思う。

とはいえ、Claude Codeの言うことを鵜呑みにはしていない。こちらから明示的に指示して直させた箇所もある。例えばテストコードのappend_recordreplace_recordのアサートは元々Globで書かれていたのだが、より厳密にするために全文マッチに直させた。別にレコードの順序がどうなっていようとValue-Domain APIは気にしないどころか勝手にソートされるので実用上の意味はないのだが、こういうのは人としての拘りである。

昨今は過去の記事でもちょいちょい言及している通り、LLMを使った開発をそこそこしていて徐々に馴染めてきているので、その点も含めてよい経験になった。

抽象化した今回の設計と、過去の歪な設計を振り返って

抽象化して再利用性を高めた今回のツールキット方式と、単体テスト大正義とばかりに変な作りになってしまった前回の方式の振り返り。

過去のvddcrでは単体テストへの誤った偏執によりモジュールが具象的な単位で分割され、非常にメンテナンス性が悪く、再利用性もないものになっていたが、今回はちゃんと抽象化して、責務ごとに機能が切れたと思っている。このことによって再利用性が高く、使いやすいものが作れたと考えている。大した規模ではないとはいえ、部品を組み合わせて処理を組むには程よくいい感じだろう。

先ほどのことや、単純に機能を削ったこと、TypeScriptの複雑すぎる型管理や非同期処理から解放されたこともあり、コードもだいぶ見通しがすっきりした。ツールではなく、ツールキットという形で作ったことも奏功し、Value-DomainのDNS API周りのスクリプトは大分作りやすくなったと思う。将来的にバルクのDDNSスクリプトを書くのも楽になるはずだし、重い腰を上げて作れてよかった。

式年遷宮で起きる変遷について

式年遷宮をする以上、元の状態には戻れないという話があり、それが何故起きるのかみたいなところが分ったような、分からないような、そんな話。

しかし世の中には式年遷宮で全く違う姿になるものがある。今回作ったのはまさにそんな感じで、なんというか作者の気持ちがわかった気がする。

分かりづらい例えだが、FF14の外部ツールであるConcept Matrix(CMTool)がAnamnesisに変わったときは、全く別物になった気さえしたし、こういった式年遷宮によって元あったことは一応できるにはできるけど、複雑になったり、なんかかゆいところに手が届かなくなったり…みたいなのは他でもちょいちょいあると思っていて、今回vddcrをvalue-domain-dns-utilにしたのもまさにそうだと思う。

という背景があるのもあり、完全互換を目指したvd-dcr.shを同梱することで、この不便さを少しはなくそうとした。いやまぁ自分のためにやってるので、自分のためのことではあるのだが…。とはいえ、以前のと比べるとプラットフォーム別の互換性は落ちていると思うので、現時点だと動かない環境は増えている気がする。

あとがき2:結局Perlに変えた

動かそうとしてた環境の一つのシェルがashで、bashが使えないことに気が付いたのでSonnet 4.6に頼んでサッとPerlに変えてもらった

関連記事