はじめに
テックリードというかリードプログラマというか。まあ、プロジェクトの技術的にリードする役割をやった経験を書き残そうと思います。
そこそこ大きなシステムのリプレイスプロジェクトで、プログラムの基盤や規約を作成したときに、こだわったことや困ったことを共有します。
※プログラムに特化した話で、インフラは対象外です。
ざっくりどんなプロジェクトか
まずシステム自体が以下のような課題をもっていました。
- 適切にクラス分割がされていない
- 業務処理部分がControllerに書いてあったり、Serviceに書いてあったりする
- ある機能・業務に関することがすべて書いてある巨大なクラスが存在する
- 適切に共通化がされていない
- 同じ処理がいろいろなところに書かれている
- 無理やり共通化して処理が複雑化している
- フレームワークが古いため、サポートされていない
- コードの可読性が悪い
- コメントの量が少なすぎる
- 無駄に難解な処理になっている(アルゴリズムが悪い)
上記の課題の結果、秘伝のタレのように現場に長年いるエンジニアしか改修できない、改修案件の影響調査だけで1か月かかるという状態でした。
このような状態では、企画の求めているスピード感を出せません。
骨董バージョンの言語・フレームワークを使用していたので、セキュリティ的にもよろしくない状態でした。
ざっくり課題解決方法
- Spring Bootを導入!
- 規約を作成!
- テーブル定義の見直し!
- 複雑な処理を最適化!
- テストコード導入!
テックリードとして何をやったか
ざっくりテックリードとしてやったことは以下です。
- 規約の作成
- バックエンド規約
- フロントエンド規約
- プログラム構成の整備
- 後ほど説明
- Security関連
- Spring Securityを使用した認証・認可
- 参考コードの作成
- 汎用的な共通処理の作成
- マスタ取得処理
- ログイン情報の取得
- テストコードの効率化
- 後ほど説明
プログラム構成の整備について
ざっくり説明するとパッケージの切り方やMVCのクラスの粒度や分割の仕方のことです。
みんなで一貫性のあるプログラムを書いて、保守しやすくするのが目的です。
具体的にどんな風にやったか
今回、Springフレームワークを導入したので、Controller,Service,Daoクラスの粒度や分割の仕方を整備しました。
ざっくり以下のとおり分割しました。
- Controller
- 原則1画面、1Controller
- 1画面に登録、更新、削除の処理があれば、すべてひとつのControllerに書く
- 業務処理は書かず、基本的にはServiceクラスを呼び出すだけ
- 原則1画面、1Controller
- Service
- 原則1Controller、1Service
- つまり1画面、1Controller、1Service
- 原則1Controller、1Service
- Logic
- 2Serviceにまたがって共通化したい処理があった場合、Logicクラスを作成する
- 業務に関連する処理のみ、文字列操作などの汎用Utilityは書かない
- Dao
- TBLごとにDaoクラスを作成(これは普通ですね。。)
- さらに登録・更新・検索専用のDaoと検索専用のDaoの二つを作成する
今回こだわった点としてはLogicの導入です。
別にServiceという名称でもよかったのですが、横断した共通処理ということをクラス名からわかるようにしたかったため、あえて別名として切り出しました。
SharedServiceなんて言ったりする現場もあります。
テストコードの効率化
今回のテストコードではDao層のテストもしています。
Dao層のテストを作っておくことで、複雑なSQLを簡単にテストできるようになり、DB変更に強いシステムになります。
しかし、Dao層のテストには無視できないめんどくさい作業があります。
それは。。
テストデータの準備です!
テストデータはSQLもしくはCSVで管理するのがよくある手法だと思います。
(もちろん使っている言語やフレームワークによっては、それ以外の方法もあると思います)
しかし、SQLにしろCSVにしろ管理するのはとても面倒です。
例えば、あるテーブルにカラムを追加したときに、大量のSQL、CSVに値を一つ追加するのは大変手間です。
CSVをExcelで管理したとしても、テストケースとExcelを突合するのは、結構だるい作業です。
TestUtilを作成!!
テストデータ投入めんどくささを解決するために考えたのがTestUtilです!
(名前ダサすぎ。。)
TestUtilはコード上でテストデータの準備から投入までをできるツールです。
TestUtilを簡単に説明すると以下のとおりです!
- DB定義を読み込んで、Entityクラスを自動生成
- Entityクラス自体はプロダクトコードにも使用
- DB定義を読み込んで、テストデータをInsert、Selectするメソッドをもつクラス(TestUtilクラス)を自動生成
- Insert、SelectにはJDBCテンプレートを使用
- 仮にプロダクトコード自体のORMが変わっても影響されない
- Insert、SelectにはJDBCテンプレートを使用
- TestUtilクラスには、Insertできるように初期値が設定されている、Entity(EntityTemplate)を持っている
- kotlinのコピーメソッドを使用して、TemplateEntityをコピーして変えたい値だけ変えたりできます
コード上で表すと以下の通りです。
class ATableTestUtil {
companion object {
val atableEntityTemplate = AtableEntity(
columnA = "dummy値",
columnB = "dummy値",
columnC = LocalDate.of("dummy値")
)
fun insert() {
// INSERT処理
}
fun select() {
// SELECT処理
}
}
}
class DaoTest {
@Test
fun test() {
// テストデータ投入
ATableTestUtil.insert(
// テストケースに即した値でTemplateを修正
atableEntityTemplate.copy(
columnA = "テストケースに即した値"
)
)
// テスト実行...
}
}
TestUtilのメリット
- テストデータ作成が格段に早く作れる!
- 簡単にテストデータを作れるため、保守性UP!
- DB変更時の影響箇所がコンパイルエラーでわかる!
まとめ
テックリードをして一番難しいと思ったのは、誰でも咀嚼できる規約づくりです。
規約で縛ることにより、一貫性のあるコードになりますが、規約にあてはめてしまったばかりに、逆に可読性の悪いコードになってしまう処理もあります。
個人的には規約からはずれて、こういう書き方をすればもっといい感じになるのに、みたいなもどかしさを感じる瞬間がありました。
TestUtil作成など自分のアイデアを形にしてプロジェクト、チーム全体の目的達成や効率UPのために課題解決をするのは、とてもやりがいがあり、おもしろかったです。