投稿日:

さて、世の中ではAI開発全盛期という感じで、とにもかくにも全盛期という感じになってきているので、今回は私自身の個人的な取り組みについて書いていく。

あらかじめ断りを書いておくと、世の中の人ほど活用できてはいない。

今のところは簡単なツール作りや、実装の部品作りといった、小規模用途に利用していて、開発工数の圧縮には便利だと感じている。

実際にLLMを使ってみた事例

事例1:MarkdownをYAMLに変換するスクリプトの作成

私は職務経歴書をWordで作っているのだが、WordそのままだとLLMに読ませるのはしんどい。

そこでLLMに読ませる形にしたいが、Markdownはそこまで構造的ではない。なのでYAMLにすることにした。

単刀直入にやるならWordファイルの中身をLLMに渡し、「これをYAMLにしてください」というのが恐らく一般的で、最も手っ取り早いだろう。

しかし私はそれを避けた。私の経歴書はかなり長く、LLMがどこかでミスをしていた場合、それをレビューするのは至難の業だからだ。例えば経歴の一部がLLMによって書き換えられていたりするリスクは拭えない。

なので私はLLMにWordファイルから切り出したプレーンテキストをYAMLに変換するスクリプトを書くことを依頼した。これであればレビューするのはコードそのもので良いし、静的コードは基本的に文脈を見て判断して勝手に書き換えることはしないし、仮にそんな処理があったとしてもそんなものはコードを見ればわかるからだ。

作らせたのはSESや受託系の経歴書にありがちな、死ぬほど繰り返される案件ごとの経験セクションだ。職務経歴書の中でも職務要約だとか、活かせる経験みたいな単純な文章セクションは手作業でどうにもなるので、そこは手で移植すればいいが、繰り返しはしんどい。

というわけで次のようなものを書いてもらった。これで9割はうまく変換してくれたし、後はざっと目検して行って変換にしくじっている部分を直して対応した。

なお、この作業を行ってから時期が過ぎており、当時のファイルやプロンプトが喪失した状態からVSCodeの作業履歴をもとに復元しているため、実際の条件と合致しないため、あくまでイメージとして残しておく。要はLLMに変換させるのではなく、LLMに変換ツールを作らせると間違いが少ないという話だ。

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
binmode(STDIN, ':utf8');
binmode(STDOUT, ':utf8');

my $input = do { local $/; <STDIN> };

# 複数のプロジェクトを分割(日付パターンで分割)
my @projects = split(/(?=\d{4}\/\d{2}\s*-\s*\d{4}\/\d{2})/, $input);

print "projects:\n";

for my $project (@projects) {
    next if $project =~ /^\s*$/;

    parse_project($project);
}

sub parse_project {
    my ($text) = @_;

    # 時期を抽出
    my ($period) = $text =~ /^(\d{4}\/\d{2}\s*-\s*\d{4}\/\d{2})/m;
    return unless $period;

    # タイトル(時期の後の行)
    my ($title) = $text =~ /^\d{4}\/\d{2}\s*-\s*\d{4}\/\d{2}\s*\n(.+?)(?:\t|規模|$)/m;
    $title //= '';
    $title =~ s/^\s+|\s+$//g;

    # 要員数
    my ($members) = $text =~ /要員数\s*\t?\s*(\d+名)/;
    $members //= '';

    # 役割
    my ($role) = $text =~ /役割\s*\t?\s*([^\t\n]+)/;
    $role //= '';
    $role =~ s/^\s+|\s+$//g;

    # プロジェクト概要
    my ($overview) = $text =~ /【プロジェクト概要】\s*\n(.*?)(?=\n\s*\n|\n【|$)/s;
    $overview //= '';
    $overview =~ s/^\s+|\s+$//g;
    $overview =~ s/\n\s*/\n/g;

    # 主な業務
    my ($tasks) = $text =~ /【主な業務】\s*\n(.*?)(?=\n\s*\n|\n【|$)/s;
    $tasks //= '';
    $tasks =~ s/^\s+|\s+$//g;

    # 実績・取り組み
    my ($achievements_text) = $text =~ /【実績・取り組み】\s*\n(.*?)(?=\t要員数|\n開発環境|$)/s;
    $achievements_text //= '';

    # 開発環境セクションを抽出
    my ($dev_env_text) = $text =~ /開発環境\s*\n(.*?)$/s;
    $dev_env_text //= '';

    # 出力
    print "      - 時期: $period\n";
    print "        内容: $title\n";
    print "        要員数: $members\n";
    print "        役割: $role\n";

    # プロジェクト概要
    print "        プロジェクト概要: |-\n";
    for my $line (split /\n/, $overview) {
        print "          $line\n" if $line =~ /\S/;
    }

    # 主な業務
    print "        主な業務:\n";
    print "          - $tasks\n";

    # 実績・取り組み
    print "        実績・取り組み:\n";
    parse_achievements($achievements_text);

    # 開発環境
    print "        開発環境:\n";
    parse_dev_env($dev_env_text);
}

sub parse_achievements {
    my ($text) = @_;

    # 実績項目をパース(タイトル + 説明の形式)
    my @items;
    my $current_title = '';
    my $current_desc = '';

    for my $line (split /\n/, $text) {
        $line =~ s/^\s+|\s+$//g;
        next if $line eq '';

        # 新しい項目タイトル(短い行で次の行に説明がある)
        if ($line =~ /^(.+?)$/ && length($line) < 30 && $line !~ /。$/) {
            if ($current_title) {
                push @items, { title => $current_title, desc => $current_desc };
            }
            $current_title = $line;
            $current_desc = '';
        } else {
            $current_desc .= ($current_desc ? "\n" : '') . $line;
        }
    }

    if ($current_title) {
        push @items, { title => $current_title, desc => $current_desc };
    }

    for my $item (@items) {
        print "          - $item->{title}: |-\n";
        for my $desc_line (split /\n/, $item->{desc}) {
            print "              $desc_line\n" if $desc_line =~ /\S/;
        }
    }
}

sub parse_dev_env {
    my ($text) = @_;

    my %sections;
    my $current_section = '';

    for my $line (split /\n/, $text) {
        $line =~ s/^\s+|\s+$//g;
        next if $line eq '';

        if ($line =~ /^【(.+?)】$/) {
            $current_section = $1;
            $sections{$current_section} = [];
        } elsif ($current_section) {
            # カンマやスペースで分割し、バージョン番号を除去
            my @items = split /,\s*/, $line;
            for my $item (@items) {
                $item =~ s/^\s+|\s+$//g;
                # バージョン番号を除去(数字とドットのパターン)
                $item =~ s/\s*[\d.]+\s*$//;
                $item =~ s/\s+\d+(\.\d+)*$//;
                push @{$sections{$current_section}}, $item if $item =~ /\S/;
            }
        }
    }

    # 順序を維持して出力
    my @section_order = ('言語・FW', 'インフラ', 'ドキュメント', 'CI/CD', 'VCS');

    for my $section (@section_order) {
        next unless exists $sections{$section};
        print "          $section:\n";
        for my $item (@{$sections{$section}}) {
            print "            - $item\n";
        }
    }

    # 定義順以外のセクションも出力
    for my $section (keys %sections) {
        next if grep { $_ eq $section } @section_order;
        print "          $section:\n";
        for my $item (@{$sections{$section}}) {
            print "            - $item\n";
        }
    }
}

入力に使ったワードファイルのフォーマットは以下のような内容だ。

[企業情報A]
[案件情報1]
[案件情報2]
...
[企業情報B]
[案件情報1]
[案件情報2]
...

ツールの想定漏れにより一部手修正したり、ツールを作らせるほうが面倒な部位などは手作業で直しているが、低いレビューコストと最低限の手作業で冠水でき、LLMに読ませて分析させられる経歴書を作れたので、結果としてこれはよかった。

事例2:adiaryのMarkdownパーサーへの脚注記法の追加

これは重い腰を上げてadiaryのMarkdownパーサーを脚注記法に対応させたでも書いた内容だ。

端的に言うと既存コードを読ませて部品程度の機能を書いてもらった。

そのまま愚直に書いてもうまく動かなったので、結合する部位などは適当に手直ししているが、これもLLMを使ったコーディングが役に立った一例だ。

まとめ

現状そこまでバチバチに使えているかといわれると、そこまで使えていないのが正直なところだとは思う。

例えばサブエージェントやスキルといったものや、MCP、RAG、ファインチューニング、Loraといったものは活用できていない。

まぁ徐々に使えるようになっていければいいのかなぁというところで、程々にやっていきたい。

投稿日:

今までLLMを使う場合、文書校正や整理、ERP辺りが多かったが、そろそろコード作成にも必要だなと感じたので取り組んでみた結果の初回の雑感。

Claude Opus 4との直接対話

LLMエージェントを使わない、チャットインターフェースでの直接対話で行ってみたこと。これはClaude Opus 4で行っている。

簡単なボイラープレートやプログラムが関の山

正直、LLMとの単純な対話で作れるのは3カラムのハンバーガーメニュー付きのような画面のボイラープレートや、WebでJSを使った画像判定スクリプトあたりが関の山だと感じている。

それ以上のものも作れる可能性はあるが、要件定義とコードレビューが大変なので厳しい気がしている。

プログラムの変換は苦手

まず私はTampermonkeyで5分ごとにAPIをポーリングし、結果をパースして条件に応じてOSに通知トーストを出す、400行ほどのスクリプトを作っている。

そこで、このソースコードを丸っと渡して、C#.NETに変換してほしいと頼んでみたが、これは失敗した。根本的にビルドが通らないコードが出てきて多少の修正でどうにかなるレベルでもなく、全くダメだった。

ファイル構成もよくなく、ModelやControllerレベルではファイル分割されているものの、1ファイルの中に複数クラスが納められていたり、何ともな結果だった。

TSDocを書いているため、上手く推論できればInterfaceやClassも作れると思ったが、これは難しいようだった。

特定の設定方法を書くのは得意

OpenWrtの特定の設定を書かせることは得意だった。これはそのまま適用できた。やはりスコープが限定されているのが得意だと感じた。

Claude Codeを少しつついてみた感想

ファイル保存などの手間がいらなくなる

当たり前だがローカルマシン上に結果を出力するため、チャットインターフェースのように頑張ってファイルを保存したり、ディレクトリを切る必要は全くなくなる。

ボイラープレートの作成は得意

PHPを利用したMVC構成で簡単なブログをフルスクラッチで作ってほしいといえば、それらしい形のものは出してくれた。

動くかどうかは全く試していないが、大まかなスケルトンを作って貰って、そっからいじっていくベースとしては使えるような気がした。

やっぱりプログラムの変換は苦手

adiaryのテンプレートエンジン部分をPHPに書き換えてほしいと依頼してみたが、やはり動かないものが出てきた。adiaryの設計が極めて複雑でコンテキストが読み取りづらいのはあると思うが、やはりこの手の作業は苦手なようだ。

現状で見えてきたこと

そこまで大して使ったわけではないが、とりあえず所感として。

恐らく小規模でコンテキストの薄いコードを書かせるのが筋がよさそう。これは複雑な要件をLLMに伝えるのは難しいし、考えるのも大変なのと、コード変換も400行レベルでも厳しいと感じたからだ。

つまり、既存システムの移行は苦手なのではないかと思っている。なのでWordPressをGoで作り直すみたいなことは相当難しいと思う。逆にSOLID原則やClean Architectureのような、スコープが狭く責務が明確なものは作りやすいのではないかと感じた。

また仮にLLMが全て書いてくれるとしても、人がレビューしないとバグがあった時に当たりをつけるのが大変とか、知らない仕様が紛れ込んだりとかもあるため、LLMに書かせすぎるべきではなく、あくまで補助ツール程度に留めておくのが良いと考えている。

LLMの制約を味方にする開発術という記事を見た感じ、複雑なタスクを段階的に分解し、LLMの処理可能な単位に分解することが重要だと感じている。つまりこれは疎結合のほうが向いているということだ。また標準化されていて、属人性がないコードのほうが制約が少なくなるので、LLMもやりやすくなるだろう。これは標準化されておらず、属人性が高いコードは往々にしてカオスで、判断軸がなく、LLMの思考がぶれるからだと思われる。

結局どうしていくか

正直まだどう実用化していくかの展望は見えていない。

何はともあれ使い続けていくことが大切な気はしているので、個人的にはClaude Codeを使い続けていきたい。少なくとも面倒なボイラープレートを書く部分については非常に優秀なので、大まかに作らせて微調整するみたいな用途では間違いなく活路がある。こういうのは引き出しが多ければ多いほど活用できるだろうから、基礎を忘れないように自学していくことも引き続き重要で、LLMに教えてもらうのもいいだろう。適切に使えばLLMからは多くの学びを得られる。

更新日:
投稿日:

ローカル環境用の開発ドメインをhttps化する時に使えるやつ
例えばローカル環境に複数のサービスがいて、それぞれをhttps://*.example.com/のようなドメインで管理したい時に使える

確認環境

同じことをすればLinuxとかでも応用できると思う

Env Ver
nginx 1.19.8
mkcert 1.4.3
Windows 10 Pro 19043.1415

手順

  1. mkcert の導入とワイルドカード証明書の作成
    choco install mkcert
    # mkcertを認証局として登録
    mkcert -install
    # 証明書を作成するドメインを列挙
    mkcert example.test *.example.test
    mv _wildcard.example.com+1.* C:/nginx/conf/.ssl/
    
  2. nginxの設定に証明書を記載

    server {
        server_name  dev.example.com;
        listen 443 ssl;
    
        ssl_certificate     ssl/_wildcard.example.com+1.pem;
        ssl_certificate_key ssl/_wildcard.example.com+1-key.pem;
        ...
    }