学生がチーム開発をやってみた(C#初心者がWPFでデスクトップアプリを作るまで)


TCU-CTRL場外乱闘 Advent Calendar 2017 24 日目の記事です。

はじめに

本記事はチームプロジェクトとしてWPFでツイッタークライアントを作った際にどのような手順で作業を行い、勉強したのかを書いた記録です。
作成したツイクラでは、タイムラインの取得、ツイート、画像つきツイート、いいね、リツイート、リプライなどを行うことができます。

最終的にできた物

きっかけ

最近部内でチーム開発が流行していて、このプロジェクトもその1つです。数多くのプロジェクトが発足しては爆発四散してゆくなか、運がいいことにこのプロジェクトは一応動くものを作ることができました。

勉強する前の実力

C++の関数、配列、ポインタ、スマートポインタ、クラス、参照をなんとなく理解していて、標準ライブラリのvector、string、algorithmを使ったことがあります。C#に関しては全く使ったことがなく、当然WPFもXAMLもMVVMも知りません。

開発環境

前提としてC#+WPF
最終的に使用した環境は以下の通り。

IDEとエディタ

Git関係

コミュニケーションツール

タスク管理

ライブラリ関係

開発記録

2017/6/27(開発1日目 - プロジェクトの立ち上げ)

プロジェクトの立ち上げ。
Slackの存在を初めて知り、チームのコミュニケーションツールとして今後Slackを使うことにしました。

2017/6/28(開発2日目 - 時報botを作ろう)

まずはじめに何をしようか話し合いました。
TwitterAPIを使ったことがある人が私のみだったため、まずはTwitterのAPIに慣れてもらうのがいいと思い、PHPで時報botを作ってもらうことにしました。
この日はPhpStormとXAMPPでPHPの開発環境を作り、Hello Worldして終わりました。
今振り返るとこれは迷走で、最初からCoreTweetでTwitterAPIを使ったほうが楽だったと思います。

2017/7/5(開発9日目 - 説明力不足)

C#に移行する事が前提ならPHPをわざわざ勉強する必要はないのでは?という意見が出て多少衝突しました。
誰も知らないC#でTwitterAPIについてを理解するのは大変だと考えていたのですが、自分で考えていたことをうまく言葉にすることができなかったため、自分の考えを正しくわかりやすく伝える力が必要だと実感しました。
最終的に他の部員の活躍によりPHPでの時報bot制作を継続することになりました。

2017/7/12(開発16日目 - 開発環境をざっくり決定)

TwitterAPIの概要をチームメンバーが理解し終えました。
また、開発言語と開発環境を決め、開発言語はC#、開発環境はVisual Studio Community 2017に決まりました。
また、8/8までにC#で何か簡単なアプリを作ってくることになりました。

2017/7/30(開発35日目 - 軽い気持ちでWPFを選択)

C#の勉強を始め、四則演算を書いていました。
C#でWindowsアプリケーションを作る手段を調べたところ、Windows FormsとWPFを見つけ、モダンなUIを作るならWPFのほうが作りやすいという情報から自分はWPFがいいのかなと考えていました。

2017/8/8(開発44日目 - 謎技術データバインディング)

この日までにC#で簡単なアプリを作ってこようという話だったのですが作ってきた人はほとんどいなかった模様。(私も含めて)
WPFを使うならMVVMというデザインパターンに沿って書くべきであり、まずはデータバインディングについて理解しようとテキストビューに文字列をバインディングする方法を学びました。
考えるより作れ!ということで以下のサイトを参考にLivetを勉強し始めました。
Livetで始めるWPF(ざっくり)入門

2017/8/21(開発57日目 - Prismを選択)

この日までに、私はCoreTweetとLivetを用いてツイートを行うだけのアプリを作りました。
また、メンバーの1人がタイムラインをREST APIを使って取得し、表示するものを作ってくれました。
サークルの強い先輩から「このままだといつまでたっても完成しない」とアドバイスを頂き、Windows Formsでゴリゴリ書くかWPFをしっかり勉強するかを考え、WPFを勉強することにしました。
素のWPFでMVVMに沿って書いていくのはかなり冗長になってしまうため、積極的に更新されているMVVMフレームワークを探したところPrismを見つけ、Prismを使うことにしました。
以降はWPFというよりはPrismの使い方を勉強していくことになります。

2017/8/22(開発58日目 - Prismの勉強方針)

基本的に下記のサイトの方針で勉強していくことにしました。
C#すら触ったことないド素人がWPFとprismをキャッチアップする方法

Prismの公式ドキュメントは全て英語で書かれているため、Google翻訳を駆使して読みました。
Chromeだと右クリックから「日本語に翻訳」をクリックすることで、リダイレクトなしにWebページを翻訳することができます。
英語はあまり得意ではないため、この機能がなかったらツイッタークライアントは完成していなかったと思います。
また、Prism公式のサンプルプログラムをGitHubからダウンロードすることができます。
このサンプルプログラムはVisual Studioのソリューションファイルであるため、ダウンロードしてきたらすぐにVisual Studioで実行することができます。
F11を連打して1行ずつステップ実行すると、どのような順番でプログラムが動いていてどのようなことが行われているのかをなんとなく把握することができます。
Bootstrapperがいろいろ初期化してくれるのかー。ViewModelLocatorとかいう謎の技術を使うと自動的にViewとViewModelを結び付けられるらしい。EventAggregatorを使うとなんかメッセージをやりとりできるらしいぞ。InteractionRequestを使うとポップアップウィンドウを出せるみたいだ。といったようにふわっと理解しながら全てのサンプルプログラムのステップ実行を行いました。
この作業は結構きついので、短期間で集中して行ったほうがいいですね。
また、NuGetを初めて使い、ライブラリの導入、削除、バージョン管理を簡単に行えることに驚きました。
NuGetすごい!

2017/8/23(開発59日目 - Prismの勉強2日目)

ライブラリのバージョンの統一ミス

解決に手間取ったエラーがあるのでメモ。どうやらプロジェクト内に異なるバージョンのPrismが存在していたため起こったようです。
この問題はプロジェクトで使うPrismのバージョンを統一することで解決できます。

Prismのプロジェクトテンプレート

Prismのプロジェクトテンプレートの存在を知りました。これを使うとPrism.Unityを使ったプロジェクトテンプレートなどが使えるようになるため、Prismを使う際のめんどうな初期作業がなくなって便利です。
Prism.Wpfのプロジェクトテンプレート

ViewModelLocator

ViewModelLocatorという謎の技術によってViewとViewModelを自動で関連付けできるようになりました。
ViewとViewModelの依存関係を疎結合に保つことでUIの自動テストを行い易い、UIの仕様変更に強いなどの理由があるようです。

MVVMについて

MVVMはデザインパターンの1つです。
MVVMフレームワークであるPrismは疎結合をテーマに作られていて、MVVMを構築するメイン要素としてデータバインディングが存在するようです。

デリゲートという概念

サンプルのUsingDelegateCommandsをステップ実行していてC#のデリゲートという文法事項について知っておく必要が出てきたためデリゲートについて勉強しました。
勉強した結果、関数ポインタの進化系のような印象を受けました。
UsingDelegateCommandsをステップ実行していて[プロパティおよび演算子をステップ オーバーする (マネージのみ)]チェック ボックスをオフにしたほうがわかりやすかったため勉強中はチェックボックスをオフにしました。
理解したあとは何度もプロパティにカーソルが飛ぶのがうっとおしいためチェックボックスをオンに戻しました。

2017/8/24(開発60日目 - 定義へ飛べない)

XAMLからコマンドの定義へ飛べないのがつらいです。
ViewのDataContextにViewModelを直書きしないでViewModelLocatorを使ってViewとViewModelを結びつけているのが原因です。
Visusl Studio使いなら以下のTweetCommandの部分でF12キーを押したくなりますが...

TweetView.xaml
<Button Command="{Binding TweetCommand}" Content="Tweet" Width="130"/>

こうなります

ViewModelに引数なしのコンストラクターを用意し、実際に依存性注入時に呼ばれてほしいコンストラクターにDependency属性を付けるとIntelliSenseの候補に表示されるようになり、定義へ飛べるようになります。

2017/8/25(開発61日目 - データバインディングを広めよう)

分からねぇって言いながら黒板でデータバインディングについて説明していました。

2017/8/28(開発64日目 - リポジトリができた)

GitLabにリポジトリを作成し、実際に作り始めました。
周囲にWPF&Prismについての知識のある人がいないため開拓者の気分です。
サンプルプログラムの使えそうな部分を組み合わせて作りたい物を目指します。

また、この日から依存性注入コンテナ(Unity)についてGoogleで調べはじめました。
DependencyInjectionの概念を理解するのに役立った資料を載せておきます。
特にDependencyInjectionを依存性の注入じゃなくてオブジェクトの注入と訳すところが分かりやすかったです。
やはりあなた方のDependency Injectionはまちがっている。

2017/9/11(開発78日目 - 依存性の注入に成功)

「依存性の注入」ってすごいパワーワードだと思いませんか?私は思います。

ついにDependencyInjectionについて理解し、Prism+MVVMで動くものを作ることに成功しました。
やったぜ!
このMVVM+Prism+UnityでDependencyInjectionについては別途記事を書きたいですね。

参考資料
DI コンテナ Unity の利用(その1)
DI コンテナ Unity の利用(その2)
DI コンテナ Unity の利用(その3)
DI コンテナ Unity の利用(その4)
DI コンテナ Unity の利用(その5)

2017/9/27(開発94日目 - 多発する問題)

コードを書けるようになってきて進捗が出るようになると、様々な問題が起こり始めます。どうやら進捗と問題は切り離せない関係のようです。

現在までの進捗

  • 認証部分を作り、AccessToken、AccessTokenSecretをソースに直書きせずに使えるようにした
  • 画像を投稿できるようにした
  • コレクションのバインディングについて学んだ
  • ReactivePropertyを使いはじめた

コレクションをバインディングする際の見た目を作るのに役立った資料
ItemsControl 攻略 ~ 外観のカスタマイズ

UIスレッド以外からの要素の操作

UIコントロールにObservableCollectionをバインドし、UIスレッド以外からコレクション要素の操作をすると例外を吐きます。

WPFのUIスレッドはスレッドセーフではないため、別のスレッドから直接アクセスすることはできません。
この時に使うのがディスパッチャーで、これを使うとUIスレッド上で処理を実行できます。
ReactivePropertyのReactiveCollectionを使うとこの処理を簡単に書くことができます。

修正前
timeline = connectable.OfType<StatusMessage>()
                .Select(m => m.Status)
                .Subscribe(status => TL.Insert(0, status));
修正後
timeline = connectable.OfType<StatusMessage>()
                .Select(m => m.Status)
                .Subscribe(status => TL.InsertOnScheduler(0, status));

並走するブランチ

チームでGitを使う経験が初めてだったためか、ブランチのネットワークグラフが路線図のごとく並走している混沌とした状態になっていました。developから伸びたブランチが1度もdevelopにマージされていません。
このブランチ運用が後のコンフリクト地獄を作り出すことになります。

難読化するソースコード

まともなコーディング規約を決めていなかったためあらゆる場所で書き方が異なる現象が起こります。
クラス内に書いてあるフィールドとメソッドの順番がぐちゃぐちゃだったり、thisがあったりなかったりして読むだけで疲れます。
タブとスペースも何故か混ざっていました。
さすがにこれはまずいと感じ、今後コーディング規約を決めていくことになりました。

2017/9/30(開発97日目 - タスクを管理しよう)

Trelloを導入し、カンバンでタスクを管理することで進捗が目に見えるようになりました。

2017/10/1(開発98日目 - Blend for Visual Studio 2017)

Blend for Visual Studio 2017を使ってUIを書き始めました。
VisualStudioを使うよりテンプレートの変更を行いやすいですね。

2017/10/4(開発101日目 - コーディング規約を追加)

この日はViewModel内部のコマンドや変数を書く順番を決めました。
また、ViewModelを書く際のコメントのテンプレートができました。
後になって考えてみるとこれでもまだ緩いですね。もっと厳密にコーディング規約を決めるべきです。

2017/10/12(開発109日目 - コンフリクト地獄)

初めて各自の進捗がdevelopにマージされました。
当然大量のコンフリクトが発生することになります。
特にVisual Studioのプロジェクトファイルのコンフリクトがひどいですね。コンフリクトが発生しているとVisual Studioで開けなくなるため、Visual Studio Codeでコンフリクトを解消する作業をしていました。
また、developから各ブランチにマージされていないので今後もマージするたびにコンフリクトが発生します。

とあるマージのコミットコメント(マージするたびに増えます)

2017/10/18(開発115日目 - 投稿できる画像の種類が増えた)

画像をjpgに変換してから投稿する機能ができました。

2017/10/19(開発116日目 - タグのゾンビ化)

自分のリポジトリからタグを削除しても誰かのリポジトリに自分が作ったタグが残っているといつの間にか復活してしまいます。
チーム開発においてタグの乱用はやめようと思いました。
また、タグの命名規則が決まりました。
これによりタグに付けたい名前を付けられなかったり、タグを探すのに時間がかかることが解消されました。

2017/10/30(開発127日目 - git-flowを使おう)

今後のブランチ運用はgit-flowを絶対に使っていこうということになりました。
プロジェクト終了直前にようやく気がつきました。

2017/10/31(開発128日目 - 最終マージ)

最終マージが完了しました。
ようやくブランチが1本になりました。

勉強してよかったこと

  • チーム開発でのGitの使い方を学んだこと
  • UWPやXamarinで使われているXAMLを理解したこと
  • DependencyInjectionについて理解したこと
  • MVVMについて多少理解したこと

まとめ

  • チーム開発は想像以上にコミュニケーションコストかかる
  • Gitを使う際はgit-flowを必ず使う
  • 勉強しながらチーム開発するのはかなりきつい
  • 全て疎結合で作ろうとすると完成しなくなるので時には妥協が必要