# Code Coverage

## 不要なコードを消すことの意義

不要なコード (以下 dead code) が存在するとプロダクトにも開発体験にも悪影響を及ぼします。 例として下のようなものがあります。

* プロダクト
  * 不要な処理によるバックエンドの速度劣化
  * 不要な asset のダウンロードによるフロントエンドの体験劣化
* 開発体験
  * build 時間の増加
  * 影響範囲推定の困難化

発見が簡単なものとそうでないものがありますが、ある dead code が他の dead code から参照されている場合、 前者の発見はより困難になります。 したがって dead code を減らしていくというアクションは継続的に行わないと簡単に対応が難しい状態になってしまいます。

## dead code 検出方法

ここでは社内で作った本番環境から取得できるログをもとにした dead code 検出方法について解説します。 2023 年 6 月時点で一部の Rails のアプリケーションのみしか対応していないので主に Rails を前提にします。

### simplecov viewer

下のフォーマットで社内のエンジニアに coverage を公開しています。

```
https://internal-only-static-files.wantedly.com/coverages/repos/<app_name>/<env>/<date>/index.html
```

* `<app_name>`: repository 名 (例: wantedly/wantedly の場合は `wantedly`)
* `<env>`: `production`, `qa`, `sandbox`, `test`
* `<date>`: `YYYY-MM-DD` のフォーマット、もしくは `latest`

Ruby における実装方法の背景上、ステートメントカバレッジのみ、つまりある行が実行されたかどうかしか記録していません。 このため、各行について少なくともその一部が実行されていることのみがわかります。

#### 例

* [wantedly/wantedly](https://internal-only-static-files.wantedly.com/coverages/repos/wantedly/production/latest/index.html)

#### 注意

この手法で検知できない dead code が存在し、また逆にここで hit していないと判定されても実際には dead code でない場合があります。 したがって実際に削除する場合には Pull Request に他の根拠を示してください。 理由としては下のようなものがあります。

* 「hit していない => 不要なコード」は成り立たない
  * 定期実行 job なども測定対象だが、測定期間内に呼び出されなかっただけの可能性もある
  * Rails の場合 initializer など測定開始前に実行されるコードは検出できない
* 「検知されている => 消すことができない」は成り立たない
  * 表示に影響のない HTML 要素や無駄な処理など仕様に影響のないコードもありえる
* 正確でない/勘違いしやすい場所がある
  * Rails の view は実装の制約上 **render されたかどうか** しか確認できない
  * ActiveRecord の scope などを 1 行で定義した場合 `scope` が定義されたこととその中の `proc` が実行されたことの区別はできない

## 実装

### 計測方法

Rails では [oneshot coverage](https://bugs.ruby-lang.org/issues/15022) を利用しています。 view についてはこの手法では計測できないため、`ActiveSupport::Notifications.subscribe` を用いて render されたかどうかのみを検知しています。 詳しい実装は wantedly/wantedly に存在する view\_coverage.rb を確認してください。

ここで検出されたものを [servicex](https://docs.wantedly.dev/fields/the-system/servicex) の `EventLogger` で BigQuery に保管しています。

### 集計方法

集計期間をできる限り長くするために次のような集計方法をとっています。

* file ごとに期間を決めてその間に各行が hit したかどうかを集計する
* 期間
  * 終了時点: `<date>` の日付
  * 開始時点: 終了時点と file の内容が変わっていない最も古い日付 (ただし最大 6 ヶ月前)

#### 話を聞きに行きたい

* Slack: [#dx](https://wantedly.slack.com/archives/CQK61054H)

#### もっと知りたい

* [不要な style 検知ツール unused-classes について (internal)](https://dev-docs.wantedly.com/frontend/unused_classes)
* [oneshot coverage を誰でも使いやすくする (internal)](https://github.com/wantedly/dx/issues/572)
* [テスト環境の Code Coverage (internal)](https://dev-docs.wantedly.com/code_coverage)
