UML作成ツールと共同開発での運用【アドカレ2021 14日目】

プログラミング マネジメント アドベントカレンダー2021

この記事は、デジクリアドベントカレンダー14日目の記事です。

1. はじめに


こんにちは。情報工学科4年、AL18036の片岡凪です。 デジクリでは、15期部長をやっていたり色んな創作を広く浅くやったりしています。

今回は UML(Unified Modeling Language;統一モデリング言語) というプログラミングの設計図について、その作成ツールPlantUMLと共にご紹介します。
詳しい見方や使用方法は別の記事に任せ、共同開発でこれらを運用した際に感じたメリットについて書き連ねていく所存です。

プログラマやプログラムを見るSEなどの仕事をされる方は読まれると良いかもしれません。

2. UMLとは


Wikipediaには以下のように記されています。

  • UMLは、主にオブジェクト指向分析や設計のための、記法の統一がはかられた(Unified)モデリング言語(Modeling Language)
  • UMLはソフトウェアを中心とするシステムの仕様を記述し、視覚化し、構築し、文書化するために設計された。

UMLには、以下の通りいくつかの種類があります。
- クラス図 - パッケージ図 - オブジェクト図 - アクティビティ図 - ユースケース図 - シーケンス図 - コミュニケーション図

Imgur @taku_maru (2017) 「よく聞くUMLって何?」より引用

右上のユースケース図のように、非プログラマと開発物の共通認識を取るために使用されるUMLもあります。 これとは別に今回は、左上のクラス図や右中央のシーケンス図といったプログラミング自体が円滑になる図に絞って詳解したいと思います。

2.1 クラス図


小規模でないプログラムを作るとき、プログラムは複数に分割して書くと都合が良いです。 1つ1つの小さいプログラムに集中して開発できたり、複数人で分担して開発できたりとメリットは様々です。

クラスとは、この分割した1つ1つの小さいプログラムだと思ってください(厳密には滅茶苦茶ちがいます)。 クラス図は、そんなクラス同士の関係を可視化してくれる図です。

以下は、「カメラで部屋の散らかり具合を定量化するプログラム」をクラス図に起こしたものです。

Imgur

詳しい見方はこのあたりの記事を参考にしてください 遠藤貴大(2020)【一通り理解しよう】UMLのクラス図の描き方を解説

ざっくり説明すると - ©のある四角が1つ1つのクラスを指す - 矢印は「根本が使用」「先が被使用」のような関係を示す - +のつく変数やメソッド(≒関数)だけを他のクラスで直接使用する

といった感じです。

私が経験した共同開発では、プログラミングを始める前にガッとクラス図を作成することで、ぐんと開発しやすくなりました。 プログラムの作成後に人に説明するために作図しても良いですし、プロトタイプを作って開発物のイメージを掴んでから作図するのも良いかと思います。

以下、クラス図によってどんなメリットが生まれたかを書いていきます。

2.1.1 要・不要の擦り合わせができる

クラス図を作る際は、どんなプログラムが必要かを考え、クラスと変数とメソッドに名前を付けます。 このとき、メソッドの中身を書く必要はなく、低コストでプログラム全体の概要を掴むことができます。

概要を掴んではじめて 本当に必要だったもの本当は不要だったもの が見えてくることは多々あります。 逆に行き当たりばったりで一発書きすると、不要なコードを量産することが多くなります。~~私の卒業研究が今そんな感じです。~~

共同開発でクラス図を作ることで、各々の得意分野で助言し合いつつ、低コストで多くの要・不要を擦り合わせることができます。

2.1.2 開発規模が見通せる

クラス図で必要な全てのプログラムを書き出すことで、プログラム全体の開発規模がわかり、開発スケジュールの作成が容易になります。 場合によっては、身の程に合わない大規模なプログラムに着手し、爆死してしまうことを未然に防ぐことができます。

また、各々のプログラムにどれほど時間をかけられるかもわかります。 かかる時間は個々人の得意不得意によって異なるため、共同開発では最適な役割分担を行うために役立ちます。

2.1.3 プログラム全体を見渡した説明が容易になる

上のクラス図には、作図後に必要になった仕様についての落書きが沢山あります。

クラス図が無い開発では、追加仕様を考える際に、コードを開いて該当部分を探して考えて...ということを繰り返す必要があります。 場合によってはプログラムを書いた人に内容を聞く必要があり、これは非常に高コストです。

クラス図ではクラスと変数とメソッドのシンプルな関係が見られるため、プログラム全体を見通した追加仕様の試案も容易です。 共同開発者に説明するのも容易になります。

シンプルな図が永久に残るため、昔の自分のコードを思い出すために作図したり、コードを後輩に継承するために作図するのも良いですね。 プログラムの保守性が上がるともいえます。

2.1.4 書かれていない他人のプログラムを利用できる

クラス図によってクラス名、変数名、メソッド名を予め決めておくと、中身を記述する前に他人のプログラムのクラス、変数、メソッドを利用することができます。 これによって並行した開発が容易になり、開発が円滑に効率的に進みます。

昔行ったクラス図を利用しない共同開発では、他の人が書かないと自分が書けないという状況が生まれたり、同じ機能のメソッドが異なるプログラムに存在して干渉してしまったりしました。

2.2 シーケンス図


シーケンス図は、各々のプログラムがどのタイミングで起動・停止するかを表す図です。

以下は、「Youtubeのコメントを取得し、その感情情報を解析し、情報に合わせたコメントを返信するプログラム」を作成する際に作図したシーケンス図です。

Imgur Imgur

シーケンス図では、クラス図ではわかりにくい「プログラムを呼び出すタイミング」の表現が可能となります。特にmain関数の記述などに役立ちます。 呼び出しタイミングがわかることによってはじめてわかる要・不要にも気付くことができます。

3. PlantUMLで作図を便利に


ImgurPlantUMLでのシーケンス図作成方法より引用

ここからは、上述のUMLを簡単に綺麗に作図するPlantUMLというツールについて説明します。 直近の3つの図も、このPlantUMLによって作成した図です。

環境構築や記述の方法は以下の記事を参照してください。 かなり詳細でわかりやすいです。 @opengl-8080 (2020) PlantUML使い方メモ

公式リファレンスはこちら PlantUML 概要

以下、PlantUMLのメリットについて書いていきます。

3.1 プログラムとの双方向の移植が容易

PlantUMLは、Javaで作られていることもあってかクラス図の記述方法がJavaに似ているため、プログラムとの双方向の移植が容易です。 プログラムを書いてから移植して作図するのも、作図してから移植してプログラムするのも低コストで行うことができます。

VSCodeのようなエディタのショートカットを駆使すれば爆速で移植できます。 ショートカットについては以下の記事が網羅性があっておすすめです。 @12345 (2021) VS Code の便利なショートカットキー

Ctrl+D や Ctrl+Shift+L といった複数選択や Ctrl+Shift+Alt+矢印 といった矩形選択、Ctrl+矢印・End・Homeといった空白文字などを基に選択位置を移動する機能は特に利用頻度が高いです。

上のクラス図とシーケンス図は次のようなソースコードで作図されています。

3.1.1 クラス図のソースコード


@startuml
scale 1.5
skinparam DefaultFontName メイリオ
skinparam classAttributeIconSize 0

header 
Group 2d : al18036 Kataoka Nagi
2020-11-16 Version 1.1
Edit: GUIManagerの変数名と順序, calc_absolute_entropy()の返り値
end header

title
**Class Diagram**
+ public
~- private
end title

class RoomEntropyChecker {
  - SLEEP_SEC: Integer = 5000

  - loop_gui(): void
}
note left
  --Folder Tree--
  kodo2a
  |_ src
    |_ room_entropy_checker.py
    |_ gui_manager.py
    |_ img_exception.py
    |_ camera_img_extractor.py
    |_ absolute_entropy_analyser.py
    |_ relative_entropy_analyser.py
    |_ post_processing.py
    |_ Makefile
  |_ dist
    |_ previous_entropies.txt
end note

RoomEntropyChecker ..> GUIManager

class GUIManager {
  - PRAISE_STR: string = "How beautiful your room is!!"
  - NORMAL_STR: string = "Endeavor putting your room in order."
  - WARN_STR: string = "How dirty yor room is...."
  - img: int[][]
  - binary_img: int[][]

  + init_gui(): void
  + update_gui(): void
  - print_img(img: int[][]): void
  - print_exception(): void
  - reprint_absolute_entropy(): void
  - reprint_relative_entropy(): void
  - to_entropy_level(relative_entropy: float): int
  + destroy_gui(): void
}
note left
Implement print_exception
if possible
end note

GUIManager ..> ImgException
GUIManager ..> AbsoluteEntropyAnalyser
GUIManager ..> RelativeEntropiyAnalyser
GUIManager ..> CameraImgExtractor
GUIManager ..> PostProcessing

class ImgException {
  - exists_creature(img: int[][]): bool
  - is_dark_room(img: int[][]): bool
}
note left
Implement
if possible
end note

class CameraImgExtractor {
  - open_webcam_stream(): void
  + exists_webcam(): bool
  + read_img(): int[][]
  + calc_binary_img(): int[][]
  + release_webcam_stream(): void
}
note left
open_webcam_stem()
is in constractor
end note

class AbsoluteEntropyAnalyser {
  + calc_absolute_entropy(img: int[][]): float
}

class RelativeEntropiyAnalyser {
  - previous_entropies: float[]
  - relative_entropy: float

  - new_entropies_log_if_needed(): void
  - load_previous_entropies(): void
  + calc_relative_entropy(img: int[][], absolute_entropy: float): float
  - update_previous_entropies(): void
  + close_log_file(): void
}

class PostProcessing {
  + release_webcam_stream(extractor: CameraImgExtractor): void
  + close_log_file(analyser: RelativeEntropiyAnalyser): void
  + destroy_gui(manager: GUIManager): void
}

PostProcessing ..> CameraImgExtractor
PostProcessing ..> RelativeEntropiyAnalyser
PostProcessing ..> GUIManager

@enduml

3.1.2 シーケンス図のソースコード


@startuml out/SequenceDiagram_CommentAnalyser.png
scale 1.5
skinparam DefaultFontName メイリオ
' 可視性の変更
skinparam classAttributeIconSize 0

''''''''''''''''''''''''''''''''''''''''''''''''''

title
**Youtubeコメントの解析 / 返信**
end title

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

actor User
participant YoutubeAutoComment.java
participant ReplyGenerator.java
participant EmotionAnalyser.java
participant MeCab
participant PolarDictionary
participant Timer.java
participant API.java

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
activate YoutubeAutoComment.java
YoutubeAutoComment.java -> ReplyGenerator.java: ReplyGenerator()
activate ReplyGenerator.java

''''''''''''''''''''''''''''''''''''''''''''''''''

ReplyGenerator.java -> EmotionAnalyser.java: EmotionAnalyser()
activate EmotionAnalyser.java

''''''''''''''''''''''''''''''''''''''''''''''''''

EmotionAnalyser.java -> MeCab: getCommentSurfaceList(): List<String> 
activate MeCab
deactivate MeCab

''''''''''''''''''''''''''''''''''''''''''''''''''

EmotionAnalyser.java -> PolarDictionary: positiveLevelMap.put\n(String: wordPolarInfo[0].trim(), int: wordPositiveLevel)
activate PolarDictionary
deactivate PolarDictionary

''''''''''''''''''''''''''''''''''''''''''''''''''

YoutubeAutoComment.java -> Timer.java: Timer()
activate Timer.java

''''''''''''''''''''''''''''''''''''''''''''''''''

YoutubeAutoComment.java -> ReplyGenerator.java: generate(comment: String,\n positiveLevel: double): String

''''''''''''''''''''''''''''''''''''''''''''''''''

ReplyGenerator.java -> EmotionAnalyser.java: getPositiveLevel(comment: String): double
EmotionAnalyser.java -> EmotionAnalyser.java: evalFunc(positiveCnt: int,\n negativeCnt: int): double

''''''''''''''''''''''''''''''''''''''''''''''''''

YoutubeAutoComment.java -> API.java:  commentsInsert(credential: Credential, parentId: String, commentText: String): Comment
activate API.java
deactivate API.java

''''''''''''''''''''''''''''''''''''''''''''''''''

YoutubeAutoComment.java -> Timer.java: scheduleAtFixedRate(Runnable command, long initialDelay, long period)

''''''''''''''''''''''''''''''''''''''''''''''''''

activate User
User -> User: Read comment on Youtube
deactivate User

@enduml

3.2 修正が容易

PlantUMLはコードで編集できるため、修正のために消しゴムで消して描き直したり、Excelやdraw.ioのようにマウスをクリックする手間がありません。

配置も自動調整してくれるため、大きい修正が入って描き直しの量に絶望することもありません。

失効させるコードをコメントアウトで残せるのも強みです。

3.3 gitで差分管理が可能

PlantUMLはコードでクラス図を表現できるため、gitで差分管理を行うことができます。 すなわち、いつ誰がどんな編集を行ったかを記録したり、記録した過去のコードに戻したりすることができます。

画像データは容量が大きいので、gitignoreを活用してコードだけを差分管理するのが良いでしょう。

3.4 動作が軽快

CEDEC2021にて、PlantUMLの動作が軽快であるとの発表がありました。 伊藤崇洋 (2021) 「NieR Re[in]carnationの安定リリースを支えたバックエンド開発ノウハウ」

また、PlantUMLを導入する前は、ER Fluteというツールでポチポチ入力を行っていたそうです。 ここで作られていたER図は厳密にはUMLではありませんが、PlantUMLは書き様によってUML以外の図に応用することもできます。

3.5 図が綺麗

PlantUMLで作成した図は手描きの図と違って見やすく、誰でも読める出来になります。 イメージ齟齬を恐れる共同開発にとって、読み間違いの回避は重要です。

また、図が綺麗なため、開発に使用した図をそのままプレゼン資料や論文の図に用いたりもできます。

4. おわりに


PlantUMLの魅力をおわかりいただけたでしょうか。 開発の際に是非使ってみてください。

本記事のように、便利だと感じた技術やツールはどんどん書き残していきましょう。

類似ツールとの比較の際に参考になったり、組織や共同開発者に導入を検討させるために役立ったりします。 就活や実務で信頼を勝ち取るのにも有効かもしれませんね。

アドカレのような発信の場に投稿して盛り上げることで、同僚が持つ面白い考えや技術に触れることもできます。 このような発信の場がいつまで続くかわかりませんが、私や読者の皆様が卒業後に観測してニチャアとできることを心から願っています。

ご拝読ありがとうございました。

執筆者:片岡凪 @calm_IRL