astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

働きたくないのでコードを自動生成してみたよ

こんにちは、開発部のNishikawaです。

今日はScala と Play frameworkを使ってコードの自動生成を行なった時の話をまとめたいと思います。

どうしてこの記事を書こうと思ったか

Webアプリケーションの開発をしていると、よく本質じゃないところに時間がかかることがままあります。

例えば、データアクセス部分とか、コントローラの実装部分です。プログラムとしての責務がちゃんと別れれば別れるほど刺身たんぽぽ感が出てくるので、心を無にしてひたすらコピペとクラス名やメソッド名などの修正を行う人が多いと思います。

しかし、心を無にして釈迦のような志のもと作業を行なっていても所詮は人間なのでミスは起こります。そうすると、釈迦のような志から一変、鬼の形相でひたすらバグを潰しさらに時間がなくなるという無駄が無駄を産む状況になりがちです。

そういうのは一人で仕事を進める上ではとても無駄だし、本当に避けたい状況です。そこで目をつけたのはコードの自動生成です。

実際どういう作戦で何をしたのか

一括りにコードの自動生成と言っても、使うにはやはり一定の条件があります。今回Webアプリケーションを作成するにあたり以下のような構成のプログラムの実装を行いました。

  • ビュー
実装はhtmlとjavascriptで行い、バックエンド側のAPIから情報を取得しレンダリングする。
  • コントローラ
REST APIの口を設けてView層からのリクエストを受け付ける
  • モデル
データへのアクセスはSlickを使用し、取得した値をプレゼンテーション層へ渡すためのモデルに移し替える

今回はコードを自動生成するにあたり以下のsbtプラグインとライブラリを使用しました。

  • sbt-swagger-codegen

https://github.com/unicredit/sbt-swagger-codegen

  • slick-codegen

http://slick.lightbend.com/doc/3.2.0/code-generation.html

  • flyway-play

https://github.com/flyway/flyway-play

sbt-swagger-codegen

これは、sbtのプラグインでswagger specからPlay framework のコントローラとroutesファイル、json返却用のモデルを生成してくれます。

slick-codegen

これはデータベースにある実際のテーブル構造からSlickのコードを自動生成してくれます。

flyway-play

これはflywayのPlay framework用ライブラリです。これを使ってSQLを管理しました。

コード生成

前提

実際にコード生成を行う前に前提として、今回コード生成に使用したプロジェクトのディレクトリ構成を記載します。

example-web      =============================================== > プロジェクトディレクトリ
├── app
│   ├── ErrorHandler.scala
│   ├── Module.scala
│   ├── com
│   │   └── astamuse
│   │       └── ・・・
│   └── infrastructure
│       └── models
│           └── postgresql
│               └── Tables.scala  ============================== > 今回自動生成したSlickコード。
├── build.sbt
├── conf
│   ├── application.conf
│   ├── db
│   │   └── migration
│   │       └── default
│   │           ├── V1.0.0__example_database_web_app.sql    == > flayway用のDDL その1。
│   │           └── V1.0.1__example_database_dummy_api.sql  == > flayway用のDDL その2。
│   ├── logback.xml
│   └── routes  ================================================ > 今回sbt-swagger-codegenで自動生成したroutesファイル。
├── docs
│   └── swagger
│       └── ExampleWebAPIController.yaml
├── project
│   ├── build.properties
│   ├── plugins.sbt
│   ├── project
│   │   └── target
│   └── target
├── target
│   ├── scala-2.12
│   │   ├── classes
│   │   ├── resolution-cache
│   │   ├── routes
│   │   ├── src_managed
│   │   │   └── main  ========================================== > 自動生成されたコードが本来格納されるディレクトリ。今回sbt-swagger-codegenで自動生成したコントローラやモデルなどのコードが格納される。
│   │   │       └── swagger
│   │   │           └── codegen
│   │   │               ├── Model.scala
│   │   │               ├── controller
│   │   │               │   └── ExampleWebAPIController.scala
│   │   │               └── json
│   │   │                   └── package.scala
│   │   └── test-classes
│   ├── streams
│   └── test-reports
└── test  ====================================================== > テストディレクトリ
    └── generator
        └── slick
            └── SlickCodeGenerator.scala  ====================== > 今回はテストコードでslickのコードを生成するためのプログラムを書きました。

Slickコードの自動生成

ここからは、実際にSlickコードを自動生成する方法について述べます。

Slickのコード生成をするためにsbtに以下を書き込みライブラリを追加します。

libraryDependencies ++= Seq(
  "com.typesafe.slick" %% "slick-codegen" % "3.3.0"
  ,"org.flywaydb" %% "flyway-play" % "5.2.0"
)

追加したらプロジェクトディレクトリ配下の「conf/db/migration/default」にflywayのファイル命名規則に則りDDLを作成します。

作成したら以下のコードをテストディレクトリに作成します。

注意)今回は諸々省くためにテストコードに実装を行なっております。

package generator.slick

import org.flywaydb.core.Flyway
import org.scalatest.WordSpec
import play.api.Logger
import slick.jdbc.H2Profile.api._
import slick.codegen.SourceCodeGenerator

class SlickCodeGenerator extends WordSpec {

  private val logger: Logger = Logger("verification")

  "Generated code due to access to postgresql for slick." in {
    logger.debug("Start slick verification.")

    // Basic information
    val slickProfile = "slick.jdbc.PostgresProfile"
    val jdbcDriver = "org.postgresql.Driver"
    val url = "jdbc:postgresql://localhost:5432/example_db"
    val user = "admin"
    val password = ""
    val outputDirectory = "app"
    val pkg = "infrastructure.models.postgresql"

    // Execute migration for flyway
    val flyway: Flyway = Flyway.configure().dataSource(url , user, password).load()
    flyway.migrate()

    // Execute code generate for slick
    val args: Array[String] = Array( slickProfile, jdbcDriver, url, outputDirectory, pkg, user, password )
    SourceCodeGenerator.main(args)

    logger.debug("End slick verification.")

    true === true
  }
}

以上を記載したら、ローカル環境でPostgreSQLを起動し、コードに記載した内容でログインできることを確認したのちに実行します。

$ sbt "testOnly generator.slick.SlickCodeGenerator"

実行が完了したら、プロジェクトディレクトリの「app/infrastructure/models/postgresql」にコードが生成されていると思います。

Swagger Specからのroutesファイルおよびコントローラとモデルの自動生成

次はSwagger Specからroutesファイルとコントローラ、モデルを生成していきます。

sbtに以下のプラグインを追加します。

  • project/plugin.sbt
addSbtPlugin("eu.unicredit" % "sbt-swagger-codegen" % "0.0.11")

プラグインを追加したら、プロジェクトにプラグインを有効にし、 Play frameworkのディレクトリレイアウトのプラグインは無効にします。

  • build.sbt
lazy val `example-web` = (project in file("."))
  .enablePlugins(PlayScala)
  .disablePlugins(PlayLayoutPlugin)
  .enablePlugins(SwaggerCodegenPlugin)

・・・
(中略)
・・・

scalaSource in Compile := baseDirectory.value / "app"
resourceDirectory in Compile := baseDirectory.value / "conf"
scalaSource in Test := baseDirectory.value / "test"
resourceDirectory in Test := baseDirectory.value / "conf"

// Custom Settings
// For swagger
swaggerCodeProvidedPackage := "com.astamuse.example"
swaggerSourcesDir := file("docs/swagger")
swaggerGenerateServer := true

swaggerModelCodeTargetDir := file("target/scala-2.12/src_managed/main")
swaggerServerCodeTargetDir := file("target/scala-2.12/src_managed/main")

以下を記載したら、プロジェクトディレクトリ配下に「docs/swagger」ディレクトリを作成し、そこにSwagger Specを配置します。

諸々の配置が完了したら以下のコマンドを実行します。

$ sbt swaggerRoutesCodeGen
$ sbt compile

コマンドの実行が完了すると、「target/scala-2.12/src_managed/main」配下にコードが作成されそれを含めた状態でアプリケーションをコンパイルしてくれます。

まとめると・・・

いかがslick-codegenとsbt-swagger-codegenを利用するために使った設定ファイルの内容です。

  • project/plugin.sbt
logLevel := Level.Warn

resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.0")
addSbtPlugin("eu.unicredit" % "sbt-swagger-codegen" % "0.0.11")
  • build.sbt
//SwaggerCodegenPlyginのインポート
import eu.unicredit.swagger.SwaggerCodegenPlugin

name := "example-web"

version := "1.0.0"

lazy val `example-web` = (project in file("."))
  .enablePlugins(PlayScala)
  .disablePlugins(PlayLayoutPlugin)
  .enablePlugins(SwaggerCodegenPlugin)

resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"

resolvers += "Akka Snapshot Repository" at "http://repo.akka.io/snapshots/"

scalaVersion := "2.12.2"

libraryDependencies ++= Seq(
  ehcache
  ,ws
  ,guice
  ,"com.typesafe.play" %% "play-slick" % "4.0.0"
  ,"com.typesafe.slick" %% "slick-codegen" % "3.3.0"
  ,"org.flywaydb" %% "flyway-play" % "5.2.0"
  ,"org.postgresql" % "postgresql" % "42.2.5"
  ,"org.scalatestplus.play" %% "scalatestplus-play" % "4.0.0" % Test
  ,"com.h2database" % "h2" % "1.4.197" % Test
)

unmanagedResourceDirectories in Test +=  baseDirectory ( _ /"target/web/public/test" ).value

testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "exclude", "SLICK_CODE_GENERATE")

scalaSource in Compile := baseDirectory.value / "app"
resourceDirectory in Compile := baseDirectory.value / "conf"
scalaSource in Test := baseDirectory.value / "test"
resourceDirectory in Test := baseDirectory.value / "conf"

// Custom Settings
// For swagger
swaggerCodeProvidedPackage := "com.astamuse.example"
swaggerSourcesDir := file("docs/swagger")
swaggerGenerateServer := true

swaggerModelCodeTargetDir := file("target/scala-2.12/src_managed/main")
swaggerServerCodeTargetDir := file("target/scala-2.12/src_managed/main")

以下に躓きやすいところを記載しておきます。

なぜPlay frameworkのディレクトリレイアウトのプラグインを無効にするのか。

Play frameworkのディレクトリレイアウトを利用するとコントローラのパッケージが「app/controllers」配下に固定されるので、コード生成したコントーラを読み込んでくれないためです。(もしかしたら他にやり方があるかもしれない)

なぜPostgreSQLを使ってコード生成するのか

h2を使って生成を試みたが、なぜかうまくいかなかったので泣く泣くPostgreSQLを利用しました。今後はh2で生成できるようにするつもりです。

今後に向けての課題

今回はSwaggerでの生成部分については「target/scala-2.12/src_managed/main」にコードを吐き出しましたが、Slickについてはそれができませんでした。 理由はh2を利用してのコード生成ができなかったためです。

なぜ、h2が使えないと「target/scala-2.12/src_managed/main」にコードが吐けないかというと、このディレクトリは毎回コンパイルのたびにコードを生成するためのものを配置するようにできております。そのため、何度コンパイルしてもコード生成を成功させてコードを配置しなければならないのですが、flywayを使っている以上、重複してDDLを実行するとエラーになります。そのため今回のようにコードの生成を「app」配下にし固定することでこの問題を回避しました。

今後はここの技術課題を克服してDAO部分も完全にコード管理しないようにしていきたいと思います。

まとめ

今回はコード生成に向けて色々と作り込みを行いました。この方法はあくまで少人数・・・というか個人で何かものを短期間で作らないといけない場合に有効だと思っております。

ピーキーな実装が求められるところについてはあまりお勧めできませんが、人数に関係なくコード実装量を減らしてくれる上、コードの管理量も減るため、試しに採用してみてはいかがでしょうか。

以上

Google Guice について調べたあれこれ

こんにちは。エンジニアのNishikawaです。

今日はGoogle Guiceについて色々調べて実際に使ってみたので、そのことについて書きます。

Google Guiceとは

Google GuiceはGoogleが開発しているDI用のフレームワークです。結構昔から存在しているフレームワークで初出は2007年とWikipediaにはあります。

最新は2018.11.14 時点では4.2.2です。

Wiki

https://ja.wikipedia.org/wiki/Google_Guice

GitHub

https://github.com/google/guice

Google Guiceは、後ほど紹介しますが結構簡単にDIができるようになっています。そのためScala界隈だとPlay Frameworkに使われています。

Play Frameworkを初めて使った人のなかにはDIの仕組みに戸惑った人も中にはいるかと思いますが、この記事を少しでも参考にしていただければ幸いです。

どうやって使うのか

詳しい使い方は公式のユーザガイドを見ていただければと思いますが、とりあえず使うには以下のポイント(DIの基本ですが)を抑えれば大丈夫です。

  • 注入する依存性の作成
  • 依存性の注入先の作成
  • 依存性と注入先のバインド

以上をScalaコードに落とすと以下になります。

import com.google.inject.{AbstractModule, Guice}
import javax.inject.{Inject, Singleton}


class Dependency {
  def print: Unit = println("Hello!")
}

@Singleton
class Receiver @Inject()(dependence: Dependency) {
  def execute: Unit = dependence.print
}

object Executor {
  def main(args: Array[String]): Unit = {
    val injector = Guice.createInjector(new AbstractModule{})

    val instance = injector.getInstance(classOf[Receiver])

    instance.execute
  }
}

DIの旨味のないコードになっておりますが、使うとしたらこれが最小の構成なのではないかなと思います。

Guiceは「Guice.createInjector」メソッドでインジェクタを作成する際にAbstractModuleを継承したクラスのインスタンスを必要としており、ここに依存関係の設定を記載することで、インスタンス生成時にそれらのクラスをバインディングした状態でインスタンスを生成してくれます。

この際、上記のように特に設定を記載しない場合は勝手に存在するクラスをバインドしてくれます。

実行すると以下のようになります。

Hello!

上記のコードをもう少し汎用的にしてみます。先ほど自動でバインディングされていた「Dependency」クラスをトレイトに変更し、それを継承した「DependencyImpl1」と「DependencyImpl2」クラスを作成します。トレイトには実装は何もせず、Impleに対してメソッドを実装します。

mainメソッドにはそれぞれの依存関係の設定を記載したインジェクタを作成し、そこからインスタンスを呼ぶようにします。それが以下のコードです。

import com.google.inject.{AbstractModule, Guice}
import javax.inject.{Inject, Singleton}


trait Dependency {
  def print: Unit
}

class DependencyImpl1 extends Dependency {
  def print: Unit = println("Dependency1 - Hello!")
}

class DependencyImpl2 extends Dependency {
  def print: Unit = println("Dependency2 - Hello!")
}

@Singleton
class Receiver @Inject()(dependence: Dependency) {
  def execute: Unit = dependence.print
}

object Executor {
  def main(args: Array[String]): Unit = {
    val injector1 = Guice.createInjector(new AbstractModule{
      override def configure(): Unit = {
        bind(classOf[Dependency]).to(classOf[DependencyImpl1])
      }
    })

    val instance1= injector1.getInstance(classOf[Receiver])

    instance1.execute

    val injector2 = Guice.createInjector(new AbstractModule{
      override def configure(): Unit = {
        bind(classOf[Dependency]).to(classOf[DependencyImpl2])
      }
    })

    val instance2 = injector2.getInstance(classOf[Receiver])

    instance2.execute

  }
}

ここで注目して欲しいのが、インジェクタ呼び出しの際に記載している「AbstractModule」です。ここで「configure」メソッドをオーバライドしており、この中に依存関係の設定を記載しております。

上記を実行すると以下のようになります。

Dependency1 - Hello!
Dependency2 - Hello!

現場でどうやって使ったか

DIは現場ではよくテスタビリティやメンテナンス性を上げるために利用されます。

今回は、アプリケーション開発を行う上でBigQueryに接続するために作成したDAOの部分でDIを利用しました。

簡単な構成

f:id:astamuse:20181114124932p:plain

考え方としては、DAOは発行するクエリだけを保持して下位のレイヤーに私検索を行うようにしようと考えました。 その過程でSQLの実行環境が時と場合によって変わることを想定し、ExecutionEnvironment トレイトを作成しました。 今回は実際の環境ではBigQueryを、テストではH2を使用したかったため、このExecutionEnvironmentトレイトを継承した「GoogleApiServicesBigQueryExecutionEnvironment」クラスと「ScalikeJDBCExecutionEnvironment」を作成しました。

実装したBigQueryに対するクエリ発行のコードは以下のようになります。(コード自体は色々と長いのでDIに関係しそうなところだけ記載しております)

trait ExecutionEnvironment {

  def find(query: QueryTrait): List[Map[String, Any]]

  def count(query: QueryTrait): Long

}

trait EnvironmentConfig {

  ...

}

trait QueryTrait {

  ...

}

class Query extends QueryTrait {
  ...
}

class GoogleApiServicesBigQueryExecutionEnvironment @Inject()(envConf: EnvironmentConfig) extends ExecutionEnvironment {

  override def find(query: QueryTrait): List[Map[String, ANy]] = {
    // BigQuery用のクエリ発行処理
    ...
  }

  override def count(query: QueryTrait): Long = {
    // BigQuery用のクエリ発行処理
    ...
  }

}

class GoogleApiServicesBigQueryEnvironmentConfig extends EnvironmentConfig {

  // 接続設定用の処理
  ...

}

case class TestModel(id: Long, name: String)

@Singleton
class TestDAO @Inject()(environment: ExecutionEnvironment) {

  def findById(id: Long): List[TestModel] = {
    // クエリオブジェクトの作成
    val query: Query = ...

    val result = environment.find(query)

    ...

    // 後続のデータ整形処理
  }
}

これに対してテストを行うときはBigQueryではなくh2上で行うのでScalikeJDBCで行いますので以下のようになりました。(こちらもさっきと同じでDIに関するところのみ)

import org.specs2.mutable.Specification

class ScalikeJDBCExecutionEnvironment @Inject()(envConf: EnvironmentConfig) extends ExecutionEnvironment {

  override def find(query: QueryTrait): List[Map[String, ANy]] = {
    // ScalikeJDBC用のクエリ発行処理
    ...
  }

  override def count(query: QueryTrait): Long = {
    // ScalikeJDBC用のクエリ発行処理
    ...
  }

}

class ScalikeJDBCEnvironmentConfig extends EnvironmentConfig {

  // 接続設定用の処理
  ...

}
class TestDAOSpec extends Specification {

  "TestDAO" >> {

    "IDでレコードを検索する" >> {

      val injector = Guice.createInjector(new AbstractModule{
        override def configure(): Unit = {
          bind(classOf[ExecutionEnvironment]).to(classOf[ScalikeJDBCExecutionEnvironment])
          bind(classOf[EnvironmentConfig]).to(classOf[ScalikeJDBCEnvironmentConfig])
          bind(classOf[TestDAO]).asEagerSingleton()
        }
      })

      val instance= injector.getInstance(classOf[TestDAO])

      val result = instance.findById(1)

      // assertion
      ...
    }

  }

}

上記の仕組みにより、本番ではBigQueryを、テストではScalikeJDBCを使用することができるようになったため単体試験がやりやすくなりました。

使ってみての感想

今回Google Guiceを使って見た感想としては、とても簡単という印象でした。設定についてはコードで書いてあげればいいので従来のXMLをたくさん書くみたいなことをしない分、実装もテストもしやすいというメリットがあると個人的には思います。

また、GuiceはPlay frameworkでも使用されているので利用方法を習得することは無駄じゃないと思っております。

学習コストも比較的低かったので、実装のパートナーに説明し展開することも簡単にできました。

以上がメリットです。

デメリットについては、今回プロジェクトで使ってみてとくにありませんでした。

もう少し使い倒せば粗も見つかるのでしょうか、普通に使う分には問題なかったので皆様も新規開発等で短期間で開発を行わないといけなく、クラス設計をする上で悩んだ場合は導入の検討をしてみるのはいいかもしれません。

以上

平成最後の夏、Playとscalaと私の記録。

f:id:astamuse:20170815181256p:plain

おはようございます、chotaroです。
我が家のエアコンは、平成最後のお仕事を終えました。
平成最後の秋は音楽の秋にする予定です(๑•̀ㅂ•́)و✧

さてこの夏、2つほどの機能を持つ小規模なWEBアプリケーションを、Playで組んでリリースしました。 Playに関しての詳細は公式ドキュメントや、弊ブログ内の記事が詳しいので割愛します。

何かとはじめてのことばかりで、初々しく甘酸っぱい経験になりました。

本エントリはその甘酸っぱさを忘れないうちに形にしておくための試みです。

概要

つくるもの

メインの画面は2つ。

  • URLパラメータをもとに検索結果一覧を表示する画面
  • 検索結果の詳細情報を表示する画面 また、詳細情報の画面から、問い合わせを送る事ができる仕組みと、アクセスログを専用のテーブルに保存する仕組みを実装しています。

つくるひと

メイン担当者はこんな感じのスキルセット

  • java 2年半
  • swift3 1年
  • スクラッチでのwebアプリ開発経験なし
  • データベース設計の経験なし

こんなことやりました

DB設計

要件から、エンティティ図を出して、そこからEDMに落とし込んで、というプロセスで設計し、edmを作成。
が、一人ではそもそもエンティティ図の段階で限界があったので経験値豊富な先輩にフォローしてもらってなんとか形に。

Scalaの採用

Javaのリリースサイクルが大幅に変更されるにあたり、既存アプリケーションも大きな影響を受けざるを得ませんでした。
(開発リソース上の問題もあり、モジュールシステムの検証も十分にできてない状況ではありますし。)

せっかく小規模なアプリ開発案件なのだから、実験的にやってみてもいいのではないか、ということでscalaを採用してみました。

Play frameworkの採用

公式で提供されているプロジェクトテンプレートを用いれば動くものが手っ取り早く用意できるので、上述のscalaのトレーニング的な意味も込めて、採用しました。

感想

良かったこと

scala

細かいところを詰めるとわからないことだらけではあるが、javaよりも記法に柔軟性があり、どちらかといえばswiftライクで個人的にとても好印象。
(あと単純に新しい言語を学ぶのはすごく楽しい。)

Play framework

最高です。ドキドキしました。

  • htmlからscalaのfunctionを呼び出せる
  • 実装の柔軟性が高い
  • routesによるurl定義がかなり可読性高くわかりやすい
  • webpackのように常時実装とコンパイル、画面の表示結果を確認しながら開発が可能なので、上記の確認も高速でできる

今までmavenやらtomcatやらjspやらの世界にいたのでなんかこう、アツいものがこみ上げてきます。最高。
自分一人で組むならこれ以外の選択肢はない、気がします(ただし、実装者に関する課題点は多々あるのと、多人数で進めるときのテスト戦略は別途検討が必要な印象。)

しんどかったこと

DB設計

とりあえず議論しながら作ってみよう、で進めたがうまく議論が回らず時間ばかりかかり、失敗。
泣きそうになりながら「楽々ERDレッスン」を読んで考えたりしてみましたが、一向に具体的な正解が見えませんでした・・・

最終的に原理原則を知った上でたたき台を作って、それをベースに議論を突き合わせる、でなんとかEDMまでたどり着きました。
先輩には多謝です。ありがとうございました

保守性の問題

WEB自体はスムーズにできたものの、自分ひとりで組んでしまったので怪しい部分がいくつかあります。
箇条書きするとこんな感じです。

  • 実装の構造がやや煩雑
  • コードに他人の目が入ってないので、冗長なコードになっている可能性がある
  • 動きを確認しながら進めていたので、現状は問題ないが、単体テストの充実度低めで改修負荷がやや高い
  • (私がこっちにほぼかかりっきりになってしまったので、並行しているプロジェクトのコードも同様に属人化してしまっている)

また、playや基盤の問題として次のようなことがあります。

  • テンプレートhtmlへの変数の当て込みがControllerのfunctionを把握してないと書けないので、デザイナーさんやフロントエンドの人にとって難しい印象
  • ユーザも絞られているため、最低限の実装のみ具備。→セキュリティや次フェーズの改修、となったタイミングで検討が必要

もう少しうまく作ることでそれぞれ解消できるかもしれず、自分の経験値的な壁を感じました。

今後のこと

この夏の経験を踏まえた学びとしてはこんな感じです。

個人

とにかく、より多く0から作ってみる事が必要

  • セキュリティ観点
  • 例外のハンドリング
  • 実装の責務配置、手スタビリティを考慮した構造化

より美しく、かつ強固なアプリケーションを組めるように経験や知識を得る必要がありそうです。

チーム的な話

  • 技術的な負債の解消が前提になる
  • 改修決定時のタイミングなどでにペアプロでテストコードを充実させるなど、コモンセンスを広げる施策を打つ必要がある
  • いくつか輻輳している状況であっても、レビューとテストファーストの文化を大事にしていく必要があるかも(git-flowとか、サイクルに沿った運営を徹底していくべき)

課題点も含め、得られたものが多く、平成最後の夏は良い夏でした。
いざ実践。

それではこのあたりで失礼いたしますm(_ _)m

Copyright © astamuse company, ltd. all rights reserved.