10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rでテスト駆動開発っぽいことをしたい

Last updated at Posted at 2018-05-30

背景

プログラム開発プロセスを見直したいと思っていた。
変数の名前やコメントの入れ方など、色々勉強&工夫はしているが、根本的な方法論が不足しているのは明らかだった。
データ分析系の仕事では、製品開発のプログラミングスタイルは個人の生産性が低く、明らかに合わない。
だが、検証やドキュメンテーションの不備は、たいてい納期直前に地獄を見ることになる。
この板挟みをうまく解消する方法はないだろうか?

そこで、最近、以下の記事を読んだ。
忙しい研究者のためのテストコードとドキュメントの書き方

これは非常にいい。Rでも実践したいと思った。

RStudioの機能を使う

まずPythonのdocstring的なものを用意しないといけない。
幸いなことに、RStudioでは、関数の中でCtrl+Alt+Shift+Rでdocstring的なもの (Rで別の名前がついてるのだろうか?) を作れる。
関数の引数を書いて、関数の中で上記コマンドを押せば以下のようになる。

#' Title
#'
#' @param x
#' @param y
#'
#' @return
#' @export
#'
#' @examples
comp_eigen <- function(x, y) {

}

ここから開発をはじめる。
以下、作業は全てRStudio上で行う前提。

docstringを書く (入出力編)

関数のやること、引数、戻り値を書く。

#' 2つの行列の固有値の内積を求める
#'
#' @param x 行列1 (matrix)
#' @param y 行列2 (matrix)
#'
#' @return 内積の値 (num)
#' @export
#'
#' @examples
comp_eigen <- function(x, y) {

}

これで入出力を定義できた。
普段はこれでいいが、もし将来的にパッケージ化する場合、英語じゃないとだめ (というか2バイト文字がだめ) っぽいので、気をつける。

docstringを書く (テストコード編)

次に、@examplesの下にこの関数を使った処理を書く。
ここで書く処理は、サンプルであり、テストコードになる。

#' 2つの行列の固有値の内積を求める
#'
#' @param x 行列1 (matrix)
#' @param y 行列2 (matrix)
#'
#' @return 内積の値 (num)
#' @export
#'
#' @examples
#' set.seed(0)
#' x <- matrix(rnorm(9), 3, 3)
#' y <- matrix(rnorm(9), 3, 3)
#' comp_eigen(x, y)
comp_eigen <- function(x, y) {

}

本当はcomp_eigen()の出力値に関する想定も入れた方が良いのだが、今回はなしで(動作確認までしかテストしないことになる)。
答えがわかったら、コメントに追記しておくと、将来的なアップデートをした際に結果を検証できるので良いだろう。
ちなみになぜこれがテストコードになるかだが、@examplesより下の部分にカーソルをあわせてCtrl + Enterを押すとその行を実行できる。
とても便利な機能だが、知ったのはつい最近だ(むしろこれを知ってればもっと前から@examplesにテストコード書いてたよ……)。

関数の中身を書き込む

中身を書き込んでいく。

#' 2つの行列の固有値の内積を求める
#'
#' @param x 行列1 (matrix)
#' @param y 行列2 (matrix)
#'
#' @return 内積の値 (num)
#' @export
#'
#' @examples
#' set.seed(0)
#' x <- matrix(rnorm(9), 3, 3)
#' y <- matrix(rnorm(9), 3, 3)
#' comp_eigen(x, y)
comp_eigen <- function(x, y) {
  x_value <- eigen(x)
  y_value <- eigen(y)
  x_value*y_value
}

中身を一通り書けたら、次のステップに移る。

テストする

関数を読み込んだ後、@examplesの行をCtrl+Enterで実行する。
結果が正しく出力されるか、どんなエラーが出るかを確認する。

> comp_eigen(x, y)
Error in x_value * y_value : non-numeric argument to binary operator

エラーが出てきた。最後の行がおかしいらしい。

デバッグする

browser()を使ったりして、途中の状態を確認しながら直していく。

よくみると、そもそもx_valueの値が思ったのと違うようだ。固有値ではなく、リスト構造になっている。

Browse[1]> x_value
eigen() decomposition
$values
[1]  1.0382130+1.21338i  1.0382130-1.21338i -0.4045975+0.00000i

$vectors
                     [,1]                 [,2]         [,3]
[1,] 0.7280958+0.0000000i 0.7280958+0.0000000i 0.1940062+0i
[2,] 0.0169427+0.2529073i 0.0169427-0.2529073i 0.3995693+0i
[3,] 0.1994380-0.6048569i 0.1994380+0.6048569i 0.8959386+0i

ここから欲しいのは固有値($values) だけだ。
ここを修正すると、同様にy_valueも修正することになる。
その後、戻り値が3つ返ってきて「あれおかしいぞ?」となり、内積を求める計算も、*でもないということに気づく。
最終的に、以下のようになる。

#' 2つの行列の固有値の内積を求める
#'
#' @param x 行列1 (matrix)
#' @param y 行列2 (matrix)
#'
#' @return 内積の値 (num)
#' @export
#'
#' @examples
#' set.seed(0)
#' x <- matrix(rnorm(9), 3, 3)
#' y <- matrix(rnorm(9), 3, 3)
#' comp_eigen(x, y)
comp_eigen <- function(x, y) {
  x_value <- eigen(x)$values
  y_value <- eigen(y)$values
  x_value %*% y_value
}

一通り動くようになる

テストコードを実行すると、以下の出力になる。

> comp_eigen(x, y)
                   [,1]
[1,] 1.688619+3.510959i

というわけで、ひとまず完成した。

リファクタする

今回はあまり必要ないと思うが、ある程度以上の複雑なプログラムを書いていると、リファクタが必要になるケースがほとんどだ。
リファクタ自体は、もともとやっている(はずな)ことなので、このプロセスにおいても当然必要だ。

完成

以上のプロセスで、めでたく「関数、ヘルプ、テストコード」の3つ組が完成した。
これなら半年後の自分もこのコードを見て絶望することはないだろう。

余談

実際には、テスト~デバッグは何周もグルグルすることになる(上記の例では、x_value,y_valueの修正と、内積の計算の修正で2周)。
何周するかは、プログラムの複雑度と、事前の設計、スキルなど様々な要因に依存する。

上記のやり方を実践した結果、今までに気になっていた点はかなり解消できた。あと完成した時に気持ちが良い。
一方で、以下のスライドのつまづきに見事にハマり始めた。レガシーコードの谷は非常に深い。
理想のプログラミングスタイルの実現はまだまだ遠いらしい。

[TDDを実践してわかったTDDつまづくあるあると自分なりの乗り越え方まとめ] (https://www.slideshare.net/keiswd/tddtdd)

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?