ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Rails / OSS パッチ会オンライン 2021年1月のお知らせ

2021年1月の Rails / OSS パッチ会を 1月14日(木)にオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS への upstream にパッチを送る会です。

今回は終わった後に、有志でオンライン新年会を行います。

会には Ruby と Rails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。Google Meet あたりのテレビ会議システムを使います。

当日の招待 URL は Idobata の esminc/rails ルームで共有する予定です。

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントと (TV 会議システムによっては Google アカウント) が必要になると思います。

以下、前回の活動が関わる成果です。

ima1zumi: Reline

github.com

koic: RuboCop Rails

github.com

osyo-manga: activerecord-multi-tenant

github.com

yahonda: activerecord-oracle_enhanced-adapter

github.com

昨年末にリリースされた Ruby 3.0 や、Rails 6.1 といった話題などあるかもしれません。

その他の開催方針については以下の Gist に記していますので、ご参照ください。

Reboot Rails/OSS meetup online · GitHub

プロファイラを使ってRubyのコードをパフォーマンス改善したい

こんにちは。永和システムマネジメントの内角低め担当、はたけやまです。

作成したプログラムが想定していた速度で動かず困ってしまうこと、ありますよね? パフォーマンス改善を行う場合、プロファイラなどを使ってプログラムを計測し、どこがパフォーマンスのボトルネックとなっているかを見つけることが重要です。

Ruby プログラムをプロファイリングするための方法はいくつかありますが、今回は stackprof を使った方法をご紹介します。

stackprof を使ったプロファイリングは以下の手順で行います。

  • 計測対象のプログラムに stackprof を仕込む
  • 計測対象のプログラムを実行する
  • 計測結果からボトルネックを割り出す

計測対象となるプログラム

今回は例題として以下のライフゲームを計測してみます。

記事を読み進めるのに実際に動かす必要はありませんが、ぜひ動かしてみたい!という方は以下の手順でどうぞ。

$ git clone https://github.com/thata/lifegame-ruby.git
$ cd lifegame-ruby/
$ bundle install
$ bundle exec ruby main.rb
(Ctrl-Cで終了)

計測対象のプログラムに stackprof を仕込む

まず stackprof をインストールします。stackprof は RubyGems として提供されているので、Gemfile へ以下を追加して $ bundle install を実行してください。

diff --git a/Gemfile b/Gemfile
index dfc4a95..93fd4d1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,4 @@
 source "https://rubygems.org"
 
 gem "pry"
+gem "stackprof"

次に計測対象のプログラムへ以下のような感じに stackprof を仕込みます。out パラメータで指定されたファイル( /tmp/stackprof.dump )へプロファイリングの結果が出力されます。

diff --git a/1000_generation.rb b/1000_generation.rb
index 66a209a..41075cd 100644
--- a/1000_generation.rb
+++ b/1000_generation.rb
@@ -14,4 +14,7 @@ def start_game
   end
 end
 
-start_game
+# ブロック内の処理を計測
+StackProf.run(out: '/tmp/stackprof.dump') do
+  start_game
+end

計測対象のプログラムを実行する

次に、計測対象のプログラムを実行してプロファイリング情報を収集します。プロファイリングの結果は先ほど指定した /tmp/stackprof.dump へ出力されます。

$ time bundle exec ruby 1000_generation.rb

stackprof はサンプリング型のプロファイラ(1秒間に1000回スタックフレームを確認してどのメソッドが実行中かを調べる)なため「メソッドの実行割合」は調べることができますが「メソッドの実行時間」は調べることができません。そのため、時間の計測のため time コマンドを利用します。

計測結果からボトルネックを割り出す

出力されたプロファイリング結果を stackprof コマンドで確認します。

$ bundle exec stackprof --limit 100 /tmp/stackprof.dump

(省略)

bundle exec ruby 1000_generation.rb  1.97s user 0.15s system 96% cpu 2.203 total
==================================
  Mode: wall(1000)
  Samples: 1756 (0.00% miss rate)
  GC: 55 (3.13%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       760  (43.3%)         760  (43.3%)     LifeGame#alive?
      1086  (61.8%)         534  (30.4%)     LifeGame#num_of_neighbors
       213  (12.1%)         213  (12.1%)     LifeGame#cell_str
       314  (17.9%)         101   (5.8%)     LifeGame#dump_cell
        48   (2.7%)          48   (2.7%)     (sweeping)
      1383  (78.8%)          45   (2.6%)     LifeGame#next_generation
      1338  (76.2%)          44   (2.5%)     LifeGame#next_generation_cell
         6   (0.3%)           6   (0.3%)     (marking)
      1701  (96.9%)           3   (0.2%)     Object#start_game
         1   (0.1%)           1   (0.1%)     LifeGame#initialize
        55   (3.1%)           1   (0.1%)     (garbage collection)
      1701  (96.9%)           0   (0.0%)     <main>
      1701  (96.9%)           0   (0.0%)     <main>
      1701  (96.9%)           0   (0.0%)     block in <main>

出力された結果を見ると、LifeGame#alive?LifeGame#num_of_neighbors に比較的時間がかかっていることが分かります。

パフォーマンス改善してみる

alive? はあまり高速化しようがなかったので、num_of_neighbors で無駄な計算を行わないようにしてみます。

diff --git a/lib/life_game.rb b/lib/life_game.rb
index fdf877a..de370e4 100644
--- a/lib/life_game.rb
+++ b/lib/life_game.rb
@@ -66,74 +66,52 @@ private
 
   def num_of_neighbors(x, y)
     n = 0
-    _x = 0
-    _y = 0
+    # セルをループさせるため、画面の端のセルを逆側の端のセルとつなげる
+    left_x = x - 1
+    left_x = @width - 1 if left_x < 0
+    right_x = x + 1
+    right_x = 0 if right_x >= @width
+    up_y = y - 1
+    up_y = @height - 1 if up_y < 0
+    down_y = y + 1
+    down_y = 0 if down_y >= @height
 
     # left
-    _x = x - 1
-    _x = @width - 1 if _x < 0
-    _y = y
-    if alive?(_x, _y)
+    if alive?(left_x, y)
       n += 1
     end
 
     # right
-    _x = x + 1
-    _x = 0 if _x >= @width
-    _y = y
-    if alive?(_x, _y)
+    if alive?(right_x, y)
       n += 1
     end
 
     # up
-    _x = x
-    _y = y - 1
-    _y = @height - 1 if _y < 0
-    if alive?(_x, _y)
+    if alive?(x, up_y)
       n += 1
     end
 
     # down
-    _x = x
-    _y = y + 1
-    _y = 0 if _y >= @height
-    if alive?(_x, _y)
+    if alive?(x, down_y)
       n += 1
     end
 
     # upleft
-    _x = x - 1
-    _x = @width - 1 if _x < 0
-    _y = y - 1
-    _y = @height - 1 if _y < 0
-    if alive?(_x, _y)
+    if alive?(left_x, up_y)
       n += 1
     end
 
     # upright
-    _x = x + 1
-    _x = 0 if _x >= @width
-    _y = y - 1
-    _y = @height - 1 if _y < 0
-    if alive?(_x, _y)
+    if alive?(right_x, up_y)
       n += 1
     end
 
     # downright
-    _x = x + 1
-    _x = 0 if _x >= @width
-    _y = y + 1
-    _y = 0 if _y >= @height
-    if alive?(_x, _y)
+    if alive?(right_x, down_y)
       n += 1
     end
 
     # downleft
-    _x = x - 1 
-    _x = @width - 1 if _x < 0
-    _y = y + 1
-    _y = 0 if _y >= @height
-    if alive?(_x, _y)
+    if alive?(left_x, down_y)
       n += 1
     end
 

再度計測してみると、以下のように若干パフォーマンスが改善しました。やったね。

  • 実行時間
    • 2.203秒 → 1.842秒
  • 全体に占めるnum_of_neighborsの割合
    • 30.4% → 18.9%
bundle exec ruby 1000_generation.rb  1.62s user 0.14s system 95% cpu 1.842 total
==================================
  Mode: wall(1000)
  Samples: 1391 (0.00% miss rate)
  GC: 53 (3.81%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       694  (49.9%)         694  (49.9%)     LifeGame#alive?
       743  (53.4%)         263  (18.9%)     LifeGame#num_of_neighbors
       196  (14.1%)         196  (14.1%)     LifeGame#cell_str
       297  (21.4%)         101   (7.3%)     LifeGame#dump_cell
      1035  (74.4%)          44   (3.2%)     LifeGame#next_generation
        40   (2.9%)          40   (2.9%)     (sweeping)
       991  (71.2%)          34   (2.4%)     LifeGame#next_generation_cell
        13   (0.9%)          13   (0.9%)     (marking)
      1338  (96.2%)           6   (0.4%)     Object#start_game
      1338  (96.2%)           0   (0.0%)     <main>
      1338  (96.2%)           0   (0.0%)     <main>
      1338  (96.2%)           0   (0.0%)     block in <main>
        53   (3.8%)           0   (0.0%)     (garbage collection)

終わりに

今回は stackprof を使ってパフォーマンスの計測と改善を行いました。

モニタリング系の SaaSとして Datadog や New Relic などがありますが、それらレポートだけじゃ良くわからないよーという時は懐に忍ばせた stackprof で華麗に計測しちゃいましょう。

参考

CurrentAttributesを使ってリクエストごとのdefault_scopeを設定する

こんにちは、永和システムマネジメントに新卒で入社して4年目になりました swamp09 です。

この記事では、以前関わったRailsプロジェクトの default_scopeユースケースと実装例の話をします。

ユーザーが記事を投稿したり、記事にコメントしたりするWebサービスがあり、Twitterのようにユーザーが他のユーザーをブロックする機能がありました。 ブロックすると、ブロックしたユーザーからはブロックされたユーザーの投稿した記事やコメントが一切非表示になります。このブロックのロジックを default_scope で書けるとコーディングが楽になりそうだなぁとプロジェクトメンバーと話していました。 ただ、リクエストごとに異なるログインユーザーのブロックされたユーザーを非表示にするのは default_scope でうまく書けない、というところで悩んでいました。

そんな時に見つけたのが ActiveSupport::CurrentAttributes でした。

ActiveSupprt::CurrentAttributes ではリクエストごとの属性をシステム全体で簡単に利用できるよう保持できます。これを使って上記のような default_scope を書けるのではと思いました。

具体例をRailsチュートリアル第6版のサンプルアプリを元にコードで示します。sample_appにはUserとMicropostというモデルがあります。

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
end

class Micropost < ApplicationRecord
  belongs_to :user
end

sample_appに追加でユーザーが他のユーザーを非表示にするためのブロック機能を追加していきます。

# Blockモデルを追加する
class Block < ApplicationRecord
  belongs_to :blocker, class_name: "User"
  belongs_to :blocked, class_name: "User"
  validates :blocker_id, presence: true
  validates :blocked_id, presence: true
end

# UserモデルにBlockモデルとの関連を追加する
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy

  has_many :active_blocks, class_name:  "Block",
                                  foreign_key: "blocker_id",
                                  dependent:   :destroy
  has_many :blocking, through: :active_blocks, source: :blocked
  has_many :passive_blocks, class_name:  "Block",
                                   foreign_key: "blocked_id",
                                   dependent:   :destroy
  has_many :blockers, through: :passive_blocks, source: :blocker
end

Blockモデルはsample_appのRelationshipモデルとほぼ同じです。

これに ActiveSupport::CurrentAttributes を用いてリクエストごとの default_scope を設定していきます。

# app/models/current.rb を追加する
class Current < ActiveSupport::CurrentAttributes
  attribute :blocking_ids
end

# app/controllers/concerns/hide_blocking_user.rb を追加する
module HideBlockingUser
  extend ActiveSupport::Concern

  included do
    before_action :set_current
  end

  private

  def set_current
    Current.blocking_ids = current_user&.blocking&.ids
  end
end

# ブロック機能が関わるControllerでconcernをincludeする
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy

  # concernをincludeする
  include HideBlockingUser

  def index
    @users = User.paginate(page: params[:page])
  end
end

# MicropostモデルとUserモデルのdefault_scopeを書き換える
class Micropost < ApplicationRecord
  belongs_to :user
  has_one_attached :image
  
  default_scope do
    if Current.blocking_ids.present?
      where.not(user_id: Current.blocking_ids).order(created_at: :desc)
    else
      order(created_at: :desc)
    end
  end
end

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy

  default_scope do
    where.not(id: Currnet.blocking_ids) if Current.blocking_ids.present?
  end
end

以上のようにして、HideBlockedUserinclude したControllerであれば default_scope を使ったブロック機能が使えるようになりました。

まとめ

リクエストごとに default_scope を指定したい、というユースケースはあまり出会うことはないかもしれませんが、ActiveSupprt::CurrentAttributes を使えば実装できることがわかりました。実際に使うかどうかは別として… CurrentAttributesのドキュメントにも注意点が書かれていますが、CurrentAttributes を使う際はグローバルな値をいろんなところからアクセスできるようになるので、慎重に扱う必要があるかと思います。

ブロック機能を追加したコードは GitHub に置きました。なにかの参考になれば幸いです。

プロジェクト参画直後にやってよかった3つのこと

エンジニア2年目に突入しましたyuki0920です。

私は、入社から1年ちょっとの間に、4つのRailsを使ったプロジェクトに携わりました。 おおよそ約3ヶ月に1プロジェクトを経験していることになりますね。

本記事では、この4つのプロジェクトにエンジニアとして携わった経験から得られた、「プロジェクト参画直後にやってよかったこと」を3点紹介します。

1.ペアプログラミングをする

まずは、主要な機能を理解することが重要かと思います。

私は、プロジェクト参画後すぐに、参画済みメンバーと小さなタスクを対象にペアプログラミングすることがあったのですが、いまふりかえると、この経験はよかったなと思います。

早期に主要な機能のソースコードや業務プロセスについての理解を深められたため、その後の改修を効率的に進められるきっかけとなりました。

このペアプログラミングは、キーボードを操作してコードを書く「ドライバ」と、ドライバの補佐をする「ナビゲータ」の役割を、時間を決めて(20分くらい)交代しながら進めました。VSCodeLive Share機能を使うと、1つのエディタを互いに共有できるので、とても捗ります。

2.プロジェクト外のアプリケーションを作って、ライブラリの動作を把握する

アプリケーションで使うライブラリは多岐にわたります。

Railsアプリケーションならば、Gemfileをみると、導入されているGemの一覧がわかります。

就職前に私が利用していたRailsの学習教材では、Gemはあまり取り扱われていなかったので、プロジェクト参画当時は知らないGemが多々ありました。

しかし、Gemと密に関わる改修を行う場合、当然ながらGemの理解が必要となります。

そこで私のオススメは、プロジェクトで扱うアプリケーションとは別のところで、Gemを使い倒してみることです。

rails new してサンプルのアプリケーションを作成し、気になるGemを導入します。 GemのREADMEを見ながらパラメータを変えてみたりと手を動かしながら試すことで、理解が深まるはずです。

下記は私が使ってみたGemの一例です。

管理画面や認証、認可のGemはいくつもの種類がありますが、どれか1種類の使用経験があれば、応用は効きやすいかと思いますので、これらのGemをとっかかりとして、使ってみるのもよいかもしれません。

3. ER図を作成する

アプリケーションの規模が大きくなると、モデルが増え関連が複雑になります。 このモデルの理解には、ER図が役立ちます。

Railsアプリケーションならば、Rails ERDというGemが便利です。 Rails ERDを使うと、コマンド1つでER図を出力することが可能です。

下記は、サンプルのアプリケーションにAuthorモデルとBookモデルを1対多の関係で実装し、Rails ERDを導入して出力したER図です。

ER図

モデルの数が少なければ脳に記憶しておける場合もあるかと思いますが、モデルが増えるにつれ記憶しておくことは難しくなるので、すべてのモデルが記載されているER図が手元にあり、いつでも参照できるのは何かと便利です。

最後に

本記事では、「プロジェクト参画直後にやってよかったこと」を3点紹介しました。

「もっとこれやっといたほうが良いよ」などあれば、教えていただけると幸いです。

ユーザーストーリーマッピングのすすめ

アジャイル事業部の山岸です。

私のプロジェクトでは、作るもの(開発するもの)を決めていくときにユーザーストーリーマッピングのワークショップを開発チームでやっています。ユーザーストーリーマッピングはシンプルな手法であるため、導入のハードルが低く、様々なプロジェクトで活用できるところが特に気に入っています。
今回は、ユーザーストーリーマッピングをよりプロジェクトで有効に機能させるために、なぜユーザーストーリーマッピングが必要なのか、何に利用するのかを説明したいと思います。今回の記事では、ユーザーストーリーマッピングそのものの説明や用語については、書籍や記事などで詳しい解説がされていることもあり、省略させていただきました。

なぜユーザーストーリーマッピングが必要なのか

プロジェクトの開始時点では、MVP(Minimum Viable Product)を意識して開発の計画を立てていたが、気付くと開発したい機能がどんどん増えていき、当初予定したリリースに全然収まりきらなくなったといった事態が発生することがあります。
こういった事象は開発当初のゴールを見失い、全体像がみえなくなってしまったときに起きることが多いように感じています。プロジェクトの目標である、作るものを最小限にして最大限の成果を実現するためには、成果が発生するまでの(全体の)流れとそこに辿りつくまでに必要な活動について、プロジェクトに関わる人たちと認識を合わせていくことが重要だと思います。 このような共通の理解を築くためにユーザーストーリーマッピングという手法が有効だと考えています。

ストーリーマップを作る前に

私はユーザストーリーマッピングからストーリーマップを作成する前に、参加者とユーザーストーリーマッピングから得られる結果の期待値を合わせるようにしています。
ストーリーマップを作ったとしても、すぐに開発可能な粒度でのユーザーストーリーが洗い出されるわけではありません。参加者が会話によってプロダクトの理解を深め、全体像が把握でき、ゴールが明確になることが重要であり、そこに重点を置いて取り組むのがよいと考えています。

ユーザーストーリーのはじまり

ユーザーストーリーマッピングは、ストーリーの開始地点を決めて、そこからユーザーストーリーを書き出していきます。まずは利用者の人物像を決めて、プロダクトが実現したい成果に向けてその利用者や関係者のアクティビティを書き出していきます。
例えば開発するプロダクトが新規のサービス(Webサイト)だった場合、ストーリーの開始時点では、利用者がプロダクトを認識していないことを前提とします。そして、利用者がどのようにして(どのような経路で)サイトを訪れるのか、それを実現するには何が必要か、などを話し合いながらストーリーを開始します。

プロジェクトによっては、機能要件が先行して洗い出していたりすることがありますが、その内容に引っ張られて、「ユーザーが〇〇サイトで会員登録する」などのプロダクトの利用シーンからストーリーを開始してしまい、ストーリーの前後関係を理解しないまま、機能の詳細に話が偏ってしまうことは避けるようにしています。

ユーザーストーリーマッピングの次は

ユーザーストーリーマッピングで書き出されたユーザーストーリーは粒が大きく、開発可能な状態とはなっていないため、ストーリーワークショップと呼ばれる、個々のストーリーの細部を詰めていく作業が必要になります。ここでは開発する機能の完成イメージを共有し、受入基準などを会話しながら確認していきます。開発に着手可能な粒度へ分解できると、見積もりや開発計画を立てることができるようになります。

ストーリーマップに立ち戻る

プロジェクトが進んでいくと開発の優先順位が変わりリリース計画を見直す必要がでてくることがよくあると思います。その場合でも一度ストーリーマップに戻って、リリースに必要なユーザーストーリーを並び替えをすることで、依存関係を視覚化し、開発チームで認識を共有することができます。
また、並び替えたユーザーストーリーは、記載された内容が同じでもあっても文脈が変わっていることがあるため、開発を着手する前には完了条件を改めて話し合う必要があります。

最後に

短いサイクルで開発とリリースを繰り返すアジャイル開発においても、開発チームが全体像と現在地を把握しながらゴールを目指す難しさが存在すると思います。ユーザーストーリーマッピングはその問題を解決する手助けをしてくれると私は考えています。
最後にこれからユーザーストーリーマッピングを学びたい方は書籍「ユーザーストーリーマッピング」をおすすめします。私はこの書籍でユーザーストーリーマッピングを学び、開発プロジェクトで取り組むときはいつも参考にさせていただいています。

Rails / OSS パッチ会オンライン 2020年12月のお知らせ

2020年12月の Rails / OSS パッチ会を 12月10日(木)にオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS への upstream にパッチを送る会です。

今回は終わった後に、有志でオンライン忘年会を行います。

会には RubyRails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。Google Meet あたりのテレビ会議システムを使います。

当日の招待 URL は Idobata の esminc/rails ルームで共有する予定です。

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントと (TV 会議システムによっては Google アカウント) が必要になると思います。

いよいよ Ruby 3.0 がリリース直前であり、Rails 6.1 も rc がリリースされているタイミングなので、そういった話題などあるかもしれません。

その他の開催方針については以下の Gist に記していますので、ご参照ください。

Reboot Rails/OSS meetup online · GitHub

ふりかえりには Continuous KPTA がオススメ

こんにちは、コッパイ星を救うため惑星探索している nsgc です。

組織活動やチームでの開発をよりよくするための「ふりかえり」。
アジャイルなソフトウェア開発を実践しているチームでは、プラクティスとして取り入れて実施している所も多いかと思います。

私が参加しているプロジェクトでは2週間イテレーションで開発しており、リリースした日の夕方に「KPTA」(けぷた)を使ってふりかえりを行っております。

KPTA とは、こちらをご覧いただくと詳しく載っていますが、ざっくりと説明しますと「続けること(Keep)」、「問題点(Problem)」、「試したいこと(Try)」の3つの項目に分けて整理する思考フレームワークである「KPT」を拡張したもので、Try から具体的な行動することを Action として抽出することに注力できるのが特徴になります。

当初、私達はホワイトボードに模造紙を貼り、その前でペンと付箋紙を用いて行なっていたのですが、昨年、同僚の @amapyon@kunitoo が開発している Web上で KPTA でのふりかえりが出来るサービス 「Continuous KPTA 」 がリリースされたので、プロジェクトでのふりかえりをそこで行なうようになりました。

それから1年ほど使ってみた上で、ぜひ皆さんにオススメしたいと思い、今回ブログに書かせてもらいました。

付箋の関連付けが簡単にできる

Continuous KPTA の素晴しい点として、画面いっぱいに広がったKPTAボード上の好きな所に付箋の配置ができ、付箋同士に線を引くことで簡単に関連を結べることです。
これにより、「ある Problem を解消するために、この Try を考えた」ということを分かりやすく表すことが出来ます。

relation
Problem から Try、Try から Action と繋がっている

もちろん、ホワイトボードや模造紙でも線は引けますが、1度線を書くと付箋の位置が動かしずらくなったり、毎回ホワイトボードの線を消したり模造紙を貼り変えるのが手間である、という理由から中々線を引きずらいと思ったことはありませんか?
そのため簡単に関連の線で引けることはとても魅力的なことと私は思います。

ふりかえり自体を見直せる

Continuous というその名前が表すように、実施したふりかえりを次に繋げていくというコンセプトがあり、過去に実施したふりかえり結果がすぐに閲覧できたり、出した意見が長く残り続けているということが可視化されています。

そのため、「最近は Problem が増加傾向になっている」とか 「長く放置されている Action がある」といった、ふりかえり自体の状態の把握が容易にできます。
このことは「解決できない Problem が多い」、「実践しにくい Action になっている」など付箋に書き出した内容を考察するきっかけになり、ふりかえりによる改善活動自体の見直しにも繋がります。

problem
付箋の上に過去の世代が。黄色の付箋は5回前から Problem として残り続けている

便利な機能がそろっている

ふりかえりの基本的なやり方が載っていたり、タイムキーパーをするためのタイマー機能、そして、どの意見にフォーカスして議論するかを参加者が選ぶ投票機能、と実際に行う時にあると便利な機能が揃っているのも嬉しい点です。

vote
赤の数字バッジが投票結果。たくさん出た Try からどれを試すかを決めるのにも使える

最後に

昨今はコロナ渦による外出自粛のため、リモートでチーム開発をしているという開発者の方も多くいらっしゃるんじゃないでしょうか。
もし、一箇所に集まれないためふりかえりがあまり出来ていない、良い方法やツールがなくて困っているというチームがありましたら、1度試してみてはいかがでしょうか?

Continuous KPTA