どうも、文月です。
最近仕事の方でGithub Copilotを使用することとなり、あまりの便利さに自分でもIndivisualを契約するほどになってしまいました。
そんなCopilotですがCopilotの機能と言えばIDEでコードを書いている際に補完機能のCompletionsとチャットウィンドウに質問を投げるとその内容からコードを生成してくれるChatがあると思いますが、これらは色んな言語に対応しています。
C,C++,C#と言ったC言語ベースの言語にJava、Python、JavaScript、Rubyと言った主要言語もそうですがHTMLやCSSといったところも対応してくれているありがたいアシスタント君です。
そんなGithub Copilot君ですが、実はPlantUMLにも対応しているのはご存知でしょうか?
PlantUML
PlantUMLはオープンソースのコードベースで記述するUMLダイアグラム作成用の言語です。
有名なのはVSCodeのプラグインでGraphvizと合わせて最終的にPNGやSVGとして記述した内容のUMLダイアグラムを出力してくれます。
そんなPlantUMLですが、実はVisual Studio 2022の方でも拡張機能として「PlantUMLEditor」というものが提供されており、Visual Studio Codeに馴染みのないC++やC#をメインで書いているような人でも使えます。
PlantUMLEditorを導入すると拡張子を「.mdpuml」とすることでPlantUMLのファイルとして認識がされるようになり、記述した内容を元にプレビューを行ってくれるようになります。Visual Studio Codeはちょっと・・・という方はよろしければこちらでどうぞ。
さて、PlantUMLはUMLダイアグラム作成用の言語ということで、当然クラス図を書くことが出来ます。
詳しいことは以下の記事を参照していただければと思います。
PlantUML使い方メモ
本来、ソフトウェアを作成する際には設計時にクラス設計を行ってクラス図を書きますが、他社からソフトウェアを購入したけどドキュメントがほとんど残っていなかった場合や、最初はクラス図を書いたが度重なる改修を反映せずにクラス図が古いままになっていて役に立たないなど、再度コードをベースとしてクラス図を作りたくなるような場合はあるかと思います。
サンプルコード(Observerパターン)
実際にPlant UMLのクラス図を作るためにサンプルコードを作ってみましょう。
今回はC#(.NET8.0)を使用してデザインパターンのObserverパターンのサンプルコードからクラス図を作ってみることにしましょう。Observerはオブジェクト間の1対多の依存関係を定義し、オブジェクトの状態が変更されたときに依存するオブジェクトへと変更を通知するパターンです。
ここではConcreteObserverクラスとSubjectクラスを使って実装してみます、
せっかくなのでこっちのサンプルコードもGithub Copilotに任せて以下のようなコードを生成してくれました。
namespace ObserverSample
{
public interface IObserver
{
void Update(ISubject subject);
}
public class ConcreteObserver : IObserver
{
public string Name { get; }
public ConcreteObserver(string name)
{
Name = name;
}
public void Update(ISubject subject)
{
if (subject is Subject s)
{
Console.WriteLine($"{Name} has received an update! New State: {s.State}");
}
}
}
}
namespace ObserverSample
{
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
public class Subject : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private int _state;
public int State
{
get => _state;
set
{
_state = value;
Notify();
}
}
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify()
{
_observers.ForEach(observer =>
{
observer.Update(this);
});
}
}
}
ObserverではUpdate、SubjectではAttach、Detach、Notifyを定義・実装します。
処理の順としては以下のような形となります。
- Subjectのオブジェクトを生成する
- Observerのオブジェクトを生成し、Attachに渡してSubjectに登録する
・AttachはSubject内で対象のObserverのオブジェクトを通知対象として保持する
・Detachは対象のObserverオブジェクトを通知対象から外す - SubjectのStateを変更させることでSubjectのNotifyを呼び出す
- Subjectに保持しているObserverのUpdateメソッドを呼び出す
4によって各Observerに通知が行われる形となります。
実際に以下のように使ってみましょう。
using ObserverSample;
class Program
{
static void Main(string[] args)
{
var subject = new Subject();
var observerA = new ConcreteObserver("Observer A");
subject.Attach(observerA);
var observerB = new ConcreteObserver("Observer B");
subject.Attach(observerB);
subject.State = 10;
subject.State = 20;
subject.Detach(observerA);
subject.State = 30;
}
}
ビルドしてみてこれを実行してみると、結果は以下のように出力されます
Observer A has received an update! New State: 10
Observer B has received an update! New State: 10
Observer A has received an update! New State: 20
Observer B has received an update! New State: 20
Observer B has received an update! New State: 30
10と20はObserverAとBの両方をAttachしている状態でStateの値を変更しているため両方のObserverオブジェクトのUpdateメソッドが呼ばれているため両方の結果が出力されています。
逆に30についてはObserver AをDetachしているため、Observer BのみのUpdateメソッドが呼ばれるため、Bの結果のみ出力されています。
本題:Plant UMLのクラス図をCopiilotに作ってもらおう
ようやく本題です。
実際にGithub Copilotを使用してPlantUMLのクラス図を書いてみましょう。
と、いうわけで、Github Copilot Chatに対して以下のようにCopilot Chatへとお願いしてみます。
#Observer.cs #Subject.cs #Program.cs これらのクラス図をPlantUML形式で記載してください。各クラス・インターフェース間の関係も記載してください。
結果としては以下のようなPlantUMLのコードを生成してくれました。
@startuml
namespace ObserverSample {
interface ISubject {
+Attach(observer : IObserver) : void
+Detach(observer : IObserver) : void
+Notify() : void
}
interface IObserver {
+Update(subject : ISubject) : void
}
class Subject {
-_observers : List<IObserver>
-_state : int
+State : int
+Attach(observer : IObserver) : void
+Detach(observer : IObserver) : void
+Notify() : void
}
class ConcreteObserver {
-Name : string
+ConcreteObserver(name : string)
+Update(subject : ISubject) : void
}
Subject ..|> ISubject : implements
ConcreteObserver ..|> IObserver : implements
Subject --> "0..*" IObserver : notifies
IObserver ..> ISubject : observes
}
@enduml
本当はIObserverはISubjectの集約ではあるのですが、やりたいことはわかるのでとりあえずヨシ!
あと、指定しているのにProgram.csがエントリーポイントのため仲間外れにされているっぽいので以下の言葉で無理矢理にでも追加でお願いしてみます。
#Program.cs も追加してください。
@startuml
namespace ObserverSample {
interface ISubject {
+Attach(observer : IObserver) : void
+Detach(observer : IObserver) : void
+Notify() : void
}
interface IObserver {
+Update(subject : ISubject) : void
}
class Subject {
-_observers : List<IObserver>
-_state : int
+State : int
+Attach(observer : IObserver) : void
+Detach(observer : IObserver) : void
+Notify() : void
}
class ConcreteObserver {
-Name : string
+ConcreteObserver(name : string)
+Update(subject : ISubject) : void
}
class Program {
+static void Main(args : string[]) : void
}
Subject ..|> ISubject : implements
ConcreteObserver ..|> IObserver : implements
Subject --> "0..*" IObserver : notifies
IObserver ..> ISubject : observes
Program --> Subject : uses
Program --> ConcreteObserver : uses
}
@enduml
Program.csは本当はObserverSampleパッケージじゃないんだけどなぁ・・・。
結果
結果を見ていただければわかると思いますが、Github Copilot Chatを使えば既存のコードからPlant UMLのクラス図を作ることができます。
とはいえ、やっぱりAIということもあって100%うまく言ってくれるわけではありません。このあたりはまだまだ人の目が必要といった感じです。とはいえ、少なくても1から作るよりはある程度書いてくれるだけでもだいぶ違うと思いますのでプロンプトとして入力する用語にもよると思いますが、うまく利用してみてください。