📚
Ruby でプロンプトエンジニアリングを試してみた
ChatGPTに与える呪文
コーティングでプロンプトエンジニアリングをする際に、重要なのは要件と制約です。
制約を変えると、どのようにアウトプットが変わるか試してみます。
コードレビュー呪文①
以下の呪文(プロンプト)をChatGPTに与えてレビューさせてみます。
あなたは優秀なプログラマーです。
以下の要件と制約に従いコードレビューをしてください。
# 要件
- ruby 3.1.2 で記述している
- フレームワークに Rails 7.0.4 を使用している
- テスティングフレームワークに rspec を使用している
# 制約
- モダンでシンプルな記述にすること
- KISSの原則を守ること
- 複雑な処理を避けるために高階関数は使用できることとする
- リーダブルなコードにすること
- メソッドは動詞始まりにすること
- メソッドの責務は小さく分割すること
- 単一責任の原則を守ること
- 命名は言語に則り適切に行うこと
- 条件分岐は早期returnなどを使ってネストを浅くすること
- 多重ループ処理を極力避けるロジックにすること
- テスタビリティであること
- マジックナンバーは使わないようにすること
class Tag < Firestore::FirestoreRecord
COLLECTION_PATH = 'tag'.freeze
@collection_path = COLLECTION_PATH
def self.find_row(id)
firestore.col(self::COLLECTION_PATH).doc(id).get.fields
end
def self.find_to_create(tags)
return false if tags.blank?
tags = tags.uniq
tags.each do |tag|
next if tag.blank?
ret = find_row(tag)
next unless ret.blank?
data = {}
data[:name] = tag
data[:created_at] ||= Time.current
data[:updated_at] ||= Time.current
ref = firestore.doc "#{self::COLLECTION_PATH}/#{tag}"
ref.set(data)
end
true
end
end
コード部分には以下のリポジトリのコードを与えています。
ChatGPTからのフィードバックは以下になります。
以下のレビューは、提供されたコードを見直し、要件と制約に従った変更を提案しています。
class Tag < Firestore::FirestoreRecord
COLLECTION_PATH = 'tag'.freeze
@collection_path = COLLECTION_PATH
def self.find_row(id)
firestore.col(self::COLLECTION_PATH).doc(id).get.fields
end
def self.find_or_create(tags)
return false if tags.blank?
unique_tags = tags.uniq
unique_tags.each do |tag|
next if tag.blank?
existing_tag = find_row(tag)
create(tag) if existing_tag.blank?
end
true
end
private
def self.create(tag)
data = {
name: tag,
created_at: Time.current,
updated_at: Time.current
}
ref = firestore.doc("#{self::COLLECTION_PATH}/#{tag}")
ref.set(data)
end
end
変更点:
1. メソッド名を find_to_create から find_or_create に変更し、意味を明確にしました。
2. find_or_create メソッド内で、tags.uniq を unique_tags という変数に代入しました。これにより、タグの一意性がより明確になります。
3. タグの作成処理を create というプライベートメソッドに分割しました。これにより、メソッドの責務が小さくなり、単一責任の原則に従います。
このコードは、Ruby 3.1.2、Rails 7.0.4、およびRSpecの要件に準拠しています。また、制約に基づいた最適化が適用されています。
確かによりリーダブル、テスタブルになり、レビューの結果に文句はありません。
次は制約を少し増やしてみます。
コードレビュー呪文②
増やす制約は以下です。
- 開放閉鎖の原則を守ること
- リスコフの置換原則を守ること
- インタフェース分離の原則を守ること
- 依存性逆転の原則を守ること
- 疎結合なコードを書くこと
あなたは優秀なプログラマーです。
以下の要件と制約に従いコードレビューをしてください。
# 要件
- ruby 3.1.2 で記述している
- フレームワークに Rails 7.0.4 を使用している
- テスティングフレームワークに rspec を使用している
# 制約
- モダンでシンプルな記述にすること
- KISSの原則を守ること
- 複雑な処理を避けるために高階関数は使用できることとする
- リーダブルなコードにすること
- メソッドは動詞始まりにすること
- メソッドの責務は小さく分割すること
- 単一責任の原則を守ること
- 開放閉鎖の原則を守ること
- リスコフの置換原則を守ること
- インタフェース分離の原則を守ること
- 依存性逆転の原則を守ること
- 疎結合なコードを書くこと
- 命名は言語に則り適切に行うこと
- 条件分岐は早期returnなどを使ってネストを浅くすること
- 多重ループ処理を極力避けるロジックにすること
- テスタビリティであること
- マジックナンバーは使わないようにすること
class Tag < Firestore::FirestoreRecord
COLLECTION_PATH = 'tag'.freeze
@collection_path = COLLECTION_PATH
def self.find_row(id)
firestore.col(self::COLLECTION_PATH).doc(id).get.fields
end
def self.find_to_create(tags)
return false if tags.blank?
tags = tags.uniq
tags.each do |tag|
next if tag.blank?
ret = find_row(tag)
next unless ret.blank?
data = {}
data[:name] = tag
data[:created_at] ||= Time.current
data[:updated_at] ||= Time.current
ref = firestore.doc "#{self::COLLECTION_PATH}/#{tag}"
ref.set(data)
end
true
end
end
ChatGPTからのフィードバックは以下になります。
以下のレビューは、提供されたコードを見直し、要件と制約に従った変更を提案しています。
class FirestoreTagRepository
COLLECTION_PATH = 'tag'.freeze
def initialize(firestore)
@firestore = firestore
end
def find_row(id)
@firestore.col(COLLECTION_PATH).doc(id).get.fields
end
def find_or_create(tags)
return false if tags.blank?
unique_tags = tags.uniq
unique_tags.each do |tag|
next if tag.blank?
existing_tag = find_row(tag)
create(tag) if existing_tag.blank?
end
true
end
private
def create(tag)
data = {
name: tag,
created_at: Time.current,
updated_at: Time.current
}
ref = @firestore.doc("#{COLLECTION_PATH}/#{tag}")
ref.set(data)
end
end
変更点:
1. クラス名を Tag から FirestoreTagRepository に変更しました。これにより、このクラスがFirestoreに依存していることが明確になり、依存性逆転の原則に従います。
2. Firestoreの依存性をコンストラクタインジェクションを通じて注入するようにしました。これにより、テスタビリティが向上し、疎結合なコードが実現されます。
3. クラスメソッドからインスタンスメソッドに変更しました。これにより、オブジェクト指向プログラミングの原則に従います。
4. メソッド名を find_to_create から find_or_create に変更し、意味を明確にしました。
このコードは、Ruby 3.1.2、Rails 7.0.4、およびRSpecの要件に準拠しています。また、制約に基づいた最適化が適用されています。
変更点の1から3のフィードバックが追加されています。
1からソースコードを生成するにはまだ難はあるけど、リファクタ、または、レビューでの使用は全然使える印象です。
次にテストコードを生成してみます。
テストコード生成呪文
あなたは優秀なプログラマーです。
以下の要件と制約に従いコードからテストコードを生成してください。
# 要件
- ruby 3.1.2 で記述している
- フレームワークに Rails 7.0.4 を使用している
- テスティングフレームワークに minitest を使用している
# 制約
- モダンでシンプルな記述にすること
- KISSの原則を守ること
- 複雑な処理を避けるために高階関数は使用できることとする
- リーダブルなコードにすること
- メソッドは動詞始まりにすること
- メソッドの責務は小さく分割すること
- 単一責任の原則を守ること
- 開放閉鎖の原則を守ること
- リスコフの置換原則を守ること
- インタフェース分離の原則を守ること
- 依存性逆転の原則を守ること
- 疎結合なコードを書くこと
- 命名は言語に則り適切に行うこと
- 条件分岐は早期returnなどを使ってネストを浅くすること
- 多重ループ処理を極力避けるロジックにすること
- テスタビリティであること
- マジックナンバーは使わないようにすること
class Tag < Firestore::FirestoreRecord
COLLECTION_PATH = 'tag'.freeze
@collection_path = COLLECTION_PATH
def self.find_row(id)
firestore.col(self::COLLECTION_PATH).doc(id).get.fields
end
def self.find_to_create(tags)
return false if tags.blank?
tags = tags.uniq
tags.each do |tag|
next if tag.blank?
ret = find_row(tag)
next unless ret.blank?
data = {}
data[:name] = tag
data[:created_at] ||= Time.current
data[:updated_at] ||= Time.current
ref = firestore.doc "#{self::COLLECTION_PATH}/#{tag}"
ref.set(data)
end
true
end
end
ChatGPTからのフィードバックは以下になります。
以下は、提供されたコードに基づいてMinitestテストケースを作成しました。要件と制約に従って記述されています。
require 'test_helper'
class TagTest < ActiveSupport::TestCase
class FirestoreMock
def initialize(path)
@path = path
end
def col(path)
self
end
def doc(id)
self
end
def get
self
end
def fields
{name: 'tag1', created_at: Time.current, updated_at: Time.current}
end
def set(data)
true
end
end
def setup
@firestore_mock = FirestoreMock.new('tag')
@tag_id = 'tag1'
@tags = %w[tag1 tag2]
Tag.instance_variable_set(:@firestore, @firestore_mock)
end
def test_find_row
result = Tag.find_row(@tag_id)
assert_equal({name: 'tag1', created_at: result[:created_at], updated_at: result[:updated_at]}, result)
end
def test_find_to_create
assert_not_predicate [], :present?
assert_equal false, Tag.find_to_create([])
assert_predicate @tags, :present?
assert_equal true, Tag.find_to_create(@tags)
end
end
このテストケースは、Tagクラスの.find_rowおよび.find_to_createメソッドの振る舞いを検証しています。
また、FirestoreMockクラスを使って、Firestoreとの通信を模倣しています。
このテストケースは、Ruby 3.1.2、Rails 7.0.4、およびMinitestを使用して記述されています。
動かしてまで試してはいませんが、パッと見良さそうです。
少なくともここからブラッシュアップして、更にリファクタリングする土台としては十分そうです。
まとめ
ここまでの結果でChatGPT、レビューやテストコード生成などでは十分な戦力になる印象です。
正直私はここまでとは思っておらず舐めてました。衝撃の結果です。
現状の用途としてはサポート的なペアプロパートナーのような位置づけになるかと思います。ただ非常に優秀なパートナーです。シニアレベルのプログラミングスキルはあるでしょう。
部分的に変なコードを出力することはありますので、全面的に使えるという訳ではないですが、ペアプロ用途では十分です。
凄まじい技術革新が起こったみたいなのでしばらくは注視が必要ですね。
でないとあっという間に置いていかれそうです。
Discussion