この記事は、
- とりあえず初心者同士で共同開発したい初心者
- 初心者をとりあえず共同開発に巻き込みたい玄人
- 「ぼっち・ざ・えんじにあ」をしすぎて何すれば良いか分からない方
- 共同開発の手法を教えすらせずに学生にプログラミングのグループワークを押し付ける教員orその被害者学生
向けの記事です
はじめに
共同開発、うまくできますか?
この記事を見ている方には
- 講義でとりあえずグループで作れと丸投げされた学生
- サークル、文化祭などでソフトウェアをグループで作りたい学生
など、事情は様々だとは思います。
どのような状況でもいいのですが、共同開発をするとき、どうしてますか?
初心者がやりがちなのは、こんな感じじゃないでしょうか:
- うまく分担できず一人がほとんどの開発を引き受けて、他のメンバーはどう参加すればいいか分からないのでアイデアだけ出して協力した気になるだけ(一番ありがちで、一番最悪)
- 一人がほとんどの開発を引き受けて、他の人にちょっとしたアルゴリズムや関数を書かせてlineで送信するも、メインシステムとのインターフェースが噛み合わず書き直し
要するに、上手いプロジェクトの分担方法が分からず、一人が森羅万象を背負ってしまうパターン。
共同開発と言えるかどうかも怪しいですが、これで「共同開発」できた気になっている人は多いんじゃないですか?
もっと色々良からぬパターンが思い浮かびますが、そろそろ本題へ。
上手に「共同」開発するには
一人の負担を大きくせず、メンバーみんながうまく仕事を分担するには、次の3要素が必要であることを提唱します:
- コード管理のスムーズさ
- プロジェクト・システムの分割性
- プロジェクト状況に関するメンバーの理解
実際にどういうことかを、 初心者にも実践できるように 、それぞれ詳しく解説します。
1. コード管理のスムーズさ
結論:GitHubを使ってください
まず、共同開発をする前提として
- メンバー全員がソースコード(プログラム)が見れる
ことが前提になります。(言葉にすると当たり前だけど、割とそれすらしていないグループワーカーもいる)
そして、
- 共有の際に、変な手間をかけずに自分の進捗をアップロードできるスムーズさ
が必要になります。
まずはよくない例を見ながら、どういうことか見ていきましょう
中央集権型
例えばもしシステム全体のコードが一人だけ管理していて、他メンバーからその人へLINEなりでコードを送信し続けると、当然管理者役の責務がパンクしますよね?
もう一つの問題点として、その管理者役以外はソースコードにアクセスできないので、他メンバーがシステムの仕様が分からず、そもそも仕事を引き受けられないという問題点もあります。
なので、ソースコードはオンライン上で、全メンバーが閲覧したり、アップロードできるサービスを利用するべきです。
ファイル共有型
だからといって、じゃあGoogle Driveならいいのかというと、前項よりは全然マシですけど、これも問題が発生します。
それだと、「今からそれ編集するから、そこ何もせんといて!!」という縛りができてしまいます。
例えばメンバーA, メンバーBが二人ともファイルFを編集したとして、どっちかを保存しようとするとどっちかが消されてしまいます。
あるいはABを合成するとしても、人力でやるのだるくないですか?
「それぐらいめんどくさがるな!甘えるな!!」という脳筋がいるかもしれませんが、「めんどくささ」は心理的な癌です。 その癌は広がり、いずれは 「めんどくさいからやらな~い」 になります。
「スムーズさ」の実現に全力をかけるべきです。
GitHubを使いましょう
GitHub
は知っていますか?
GitHub
は全世界の全プログラマーが利用しているサイトで、ほとんど全ての会社もこちらを使用しています。もし少しでもエンジニアになる気があるのであれば、必ず使いこなせるようにするべきサービスです。
もちろん無料です
実は、僕が 初心者向けすぎて玄人に怒られまくったけどバズった 記事がありますので、ぜひご覧ください:
とりあえず周りと仕事ができるGithub入門
ただ、特に講義の教員が教育を放棄して真面目な学生に落ちこぼれ無気力学生の介護をさせる無賃労働グループワークでは、とにかく難しいことはやりたくないという 無気力な メンバーもいると思います。
ぶっちゃけインストールしてちょっと設定すれば、更新するたびに
git add *
git commit -m "更新した"
git push origin main
の呪文をコマンドプロンプト(Windows)ないしターミナル(Mac)に打ち込むだけですが、それですら「むずかしいことできな~い」という人もいると思います。
その場合も、GitHub
のサイトで
ここを押すとドラッグ&ドロップで、ファイルをつっこむだけでコマンドと同じように差分更新ができるので、そういうメンバーにはこっちをやらせてみてください。これぐらいならできるはず。
2. プロジェクト・システムの分割性
ここは技術的なお話になります。
事前のモジュール設計が重要だ という話であり、意識することが「分割性」「独立性」「隠蔽」の3点あります。
分割性:困難じゃなくとも分割せよ
初心者にありがちなのは、全てのプログラムを1ファイルに書いてしまいますよね。 これ共同開発だと最悪です。
- 仕事を頼んだときに、即座にどこを編集すればいいか分かりやすい構造にするべきです
例えば、これは僕の動画の音声を他言語に変換するシステムですが、
このようにファイル分けしていると、例えば「動画を取得するパートの改修がしたい」となれば、即座に VideoGetter.py
に取り掛かればいいことがわかりますね。
基本的に、
- 一つのファイル (=モジュール) が一つの「責務」 (機能) を持つ
する分割方法が最もやりやすいといわれています。
一つの「責務」 というのが重要です
例えば上の例でいうと、 VideoGetter.py
(Youtubeから動画ダウンロード)にTranscriber.py
(音声→文字テキスト変換)の機能が混ざっていたとして、設計した本人は分かるとしても、他人からすると「おいおいどこで文字起こししてんねん勘弁してくれ」となるわけです。
独立性:
これはちょっと難しい設計命題です。
例えば、VideoGetter
(ダウンローダー)がTranscriber
(書き起こし器)の関数を引っ張ってきていて、VoiceTranslator
(音声変換器)がVideoDataMaker
(動画化システム)の機能を引っ張ってきていて...というように 依存関係 があちこちに散らばっていたら、 なかなか手をつけられない ですよね?
あるいは、例えばモジュールAとモジュールBがガッチガチに密結合(どっぷり相互依存)していたら、モジュールAを修正したいだけなのに、なんだかんだでモジュールBを修正しなければならない...となります。
もちろん他モジュールに依存すること自体は必要であればするべきですが、 しなければしないほど喜ばれます
- モジュールを独立性を高めることによって、分担しやすく、修正が格段に楽になります。
そのカギは次の「隠蔽」にあります。
隠蔽:「入口」と「出口」以外をブラックボックスに
政治界の「隠蔽」は焼き討ちにあいますが、プログラマー界の「隠蔽」はすればするほど褒められます。
「隠蔽」というのは、モジュールAの仕組みを知らなくとも、モジュールAを使える という状態です。
VideoDataMaker.py
は、「動画編集ソフトに渡す編集データ(字幕、mp4ファイル)を用意する」という責務を持っています。
例えば、他のメンバーが「VideoDataMaker.py
できたよー!」と言って、「どの関数使えばいいの?」と聞いてこのサンプルコード来たら、いやですよね?
from VideoDataMaker import make_sub, make_sub_text, make_mp4, save_mp4, save_sub
#表示する字幕の形式に変換
sub_data = make_sub_text(transcription)
#字幕データを作成
sub = make_sub(sub_text)
#字幕データを保存
save_sub(sub)
#mp4データを作成
mp4_data = make_mp4(audio, image)
#mp4を保存
save_mp4(mp4_data)
「お前がやれよぉ!?」というお話ですね。
例えば、高いカスタマイズ性が求められるライブラリなどであれば、こちらが適切の場合もありますけど、目的が明確であるプロジェクトにおいて、モジュールの使い方が煩雑であることはただただ不便です。
例えば、極端に言うと、
from VideoDataMaker import run
run(transcription, audio, image)
だけで実行できれば、VideoDataMaker
の詳細な仕組みを知らなくとも、頭空っぽで使えますよね?
これを内部構造の隠蔽 といいます。
イメージ的には、モジュールがこんな氷山だとして、
本当は水面下で色々やっているんだけど、我々が認識するべきなのはほんの一角でいい、という状態が理想です。
どうするべきか
この3点を総合して、実装前に、次のように設計すればいいかと思います。
-
細分化した責務ごとにモジュールを仮組。
まあ、ファイルを作るだけですね。
予め、各モジュールに「この関数を呼ぶよ」という指定(インターフェース)を設定しておいて、その関数にデータを入れ込むと処理後データが得られる、という仮組もしておくとよいでしょう。 -
運営モジュールを仮組
各モジュールを呼び出す運営モジュールを用意しておくと、各モジュールの独立性をもっと高めることができます。
たとえば、main.py""" 運営モジュール """ from #いろいろインポート VideoGetter(self.consts).run(video_link) df_transcription, original_language = TranscriberWhisper(self.consts).run() df_translated = LanguageTranslatorDeepl(self.consts).run(df_transcription, original_language, self.consts.get_language_code_from_full(language_destination)) VoiceTranslatorVALLEXAllPrompt(self.consts).run(df_translated) VideoDataMaker(self.consts).run(df_translated, should_make_sub=True)
のようにしたら、各モジュールは直前、直後のモジュールを指定せず、ただ関数を用意しておくだけでよくなります。
VideoDataMaker.py""" 編集データ作成モジュール """ #インポートなし!!!(独立性!!!) class VideoDataMaker: def run(...省略): ...省略
のように。
-
それぞれのメンバーが担当のモジュールを実装
自分のモジュールについて、隠蔽と独立性を極限まで高めながら実装してください。
逆に、他のメンバーが独立性と隠蔽してくれているので、自分は他のメンバーの進捗や仕様を意識せずに実装することができます。
という感じに行くと、上手に仕事を分担できるでしょう。
3. プロジェクト状況に関するメンバーの理解
2では隠蔽の重要さを説きましたが、 逆にメンバー全員の理解を求める お話です。
いくら玄人でも、知らないプロジェクトに、なんも説明もなしに協力しろと言われても基本的にしんどいです。
初心者であればなおさら。
プロジェクトに対する理解を共有しやすくなる実践を2つ紹介します
マクロな理解:UMLを描く
UMLとは、いわば プログラミングの設計図 です。
UMLには様々な種類があります:クラス図、状態遷移図、シーケンス図など...
少なくとも、クラス図(モジュール図)は描くべきです
クラス図(モジュール図); オススメ度 高
ちょっと伝統のある言語は、 一つのクラスに一つの責務をまとめる ということをしていて、クラス が実質的に モジュール になっています。
Python
では、ファイルを作ることでモジュールであることと定義されているので、わざわざクラスを定義しなくともモジュールとして関数たちを一まとめにでき、あまりクラス化しなくともよくなっていますね。
こうした理由で、Python
だけできる初心者にも分かるように「クラス図(モジュール図)」と書いていますが、世間的には「モジュール図」という言葉は存在しないです。
まずはサンプルをお見せします。これはUnity
で作った、学祭出展のゲームのクラス図です。
これを見ることで、どんなクラスが存在し、何の機能と変数を持っていて(中身の箇条書き)、何に依存しているのか(矢印) がわかります。
こうして図示することで、例えば「車の経路探索モジュール描いて!!!」と他メンバーに投げやりしても、どのクラスのデータを取得すればいいか分かり、とくに仕様についてのやり取りをしなくとも、そのメンバーは実装をすることができました。(経験談)
全体システムのアクティビティ図;オススメ度 高
これは雑談動画の書き起こしデータをデータベース化するシステムの例です。
これを見ることで、簡単に どのモジュールがどういった順序のどういった位置づけか 即座に分かりますね。
直観的に全体像を把握する最高の手段だと思います。
シーケンス図;オススメ度 場合による
入力→出力で完結するシステムはモジュールの一方通行で済むことがほとんどですが、ゲームや、リアルタイムシステムなどはこのように複数のクラスが緻密に 連絡 しあうことが多々あります。(それも少ない方がいいんですけどね)
連絡が複雑になると、各クラスの担当者が(個人開発の場合も)頭おかしくなるので、このようにあらかじめ連絡のパターンと呼び出す関数(インターフェース)を設計することはとても重要です。
ただし、一方通行だったり、そんなに複雑でない場合は、別にわざわざ描かなくとも大丈夫であると感じています。
どうやって描くの?
こんな高尚な図絶対描くのに時間かかるでしょ、と思った方がいれば、安心してください。
PlantUMLを使えば、すぐに描けます。
例えば先ほどのシーケンス図ならば
@startuml
participant GameManager
collections Roads
collections Intersections
collections TrafficLightsSystems
Roads -> Intersections : 接続
activate Intersections
Intersections -> Roads : ActivateTrafficLight()
activate Roads
create collections TrafficLights
Roads -> TrafficLights : 起動
Roads -> Intersections : TrafficLightの参照を返す
deactivate Roads
deactivate Intersections
GameManager -> Roads : 接続完了を確認
activate Roads
Roads -> GameManager : 接続完了!
deactivate Roads
GameManager -> TrafficLightsSystems : 初期化を命令
hnote over TrafficLightsSystems : 初期化処理
TrafficLightsSystems -> TrafficLights : 初期色をセット
@enduml
こんな感じで書いて、オンラインサーバーにコピペして、完成です。
もちろんPlantUMLの文法がありますが、すごく直観的なのでトップページのこの部分を見て
描きたいUMLを選んだらだいたいわかります。
もしあまり使いこなせない場合は、マウスで操作する draw.io もありますが、絶対PlantUMLの方が速いです。お任せします。
ミクロな理解:コードレビューをする;オススメ度 低
ここはちょっと意識高い系の領域になります。もちろん業務では必ずやりますが。
GitHub
のプルリクエストという機能を利用し、 他人の追加コードを品評します
何を品評するのかというと、
- コードに意味不明なところ(コメント不足、わけわからないロジック)がないか
- ヤバいことをしていないか(他の箇所と全く同じコードを書いている、そもそも間違っている...など)
らへんです。
業務では、コードを長く使うのが前提ですので、このようにしてコードをなるべく読みやすく、スッキリさせることを目的としてコードレビューを行いますが、副次的な効果として
- コードレビューを通じて他者のコードの理解が深まります。
ただ、現実的な話、もしどこかのモジュールでバグが発生したらだいたいその担当者が修正するので、あまり他人のコードを理解する必要がないといえばないかもしれません。(場合による)
この優先度は低いかもしれません。
最後に
共同開発、上手にしましょう
いいね頂けると泣いて喜びます><