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

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

失敗するということはどのようなことか

突然ですが。 みなさんはソフトウェア開発でどれくらい失敗をしていますか?

失敗することについて、私見をまじえつつ考察してみたいと思います。

プロジェクトで失敗していますか?

仕事でソフトウェア開発をする場合、プロジェクトという単位で開発をすることが多いと思います。 プロジェクトは、その性質から基本的に一点物、その開発は常に初めて経験するものです。

初めてですから、成功も失敗もすべてを予想できるわけではありません。 解決したい領域が自分の不慣れなものであれば、どうすれば成功するのか予測することはいっそう困難なものになります。

これでは何かしら失敗をすることは約束されたようなものですから、心置きなく失敗してもよさそうなものです。

そもそも失敗とは?

しっぱい【失敗】

やりそこなうこと。 目的を果たせないこと。 予期した効果をあげられないこと。

三省堂 スーパー大辞林

プロジェクトで実現したかったことが、開発したソフトウェアで実現できなかったとき、失敗と言うことができそうです。 実現したかったことが実現できなかったのですから、これは本来はネガティブな結果です。

一方でよく耳にするように、失敗することでうまくいかない方法を見つけることができたのだから無駄ではない、という考え方があります。 この立場に立つと、うまくいかいないソフトウェアを開発するためにかけた資源をどう評価するかという問題は残るものの、失敗そのものは無駄だったというわけではありません。

はやく成功に到達するために、はやく失敗するというのは、変化の早い環境の中ではむしろ当然のものという理解も広まってきています。

失敗は、無理に取り除くのではなく、成功のための踏み台ととらえるのがよさそうです。

ただし、それとは別で区別したいものに、避けられたはずの誤りというものもあります。 思い違い、思い込み、確認不足によるミス。 これらはプロジェクトの性質によるものでなく、むしろプロジェクトへの取り組み方によるものでしょう。

失敗することとは別に、誤りをおかさない工夫はしておきたいものです。

いつ失敗するか?

失敗は避けるものでないとして。 では、失敗するならいつがよいのでしょうか?

これは先ほど棚上げした、失敗したことにかけた資源をどう評価するか、という話にもつながりそうです。

最初に考えられるのが、失敗が許される枠を用意して、その中で失敗をすること。 本番に影響を与えず失敗できるように、いわば練習期間を用意して、うまく行く方法やうまくいかない方法を見つけることに当てる。

わたし個人としては、これには異論もあるとは思いますが、自分の能力が資源であり能力を伸ばし効果的に発揮するという点で、プログラマはアスリートと変わらない部分があると考えています。

練習せずに本番にのぞむアスリートがいないように、プログラマにも結果の成否にとらわれない枠があってよいのではないか、と感じています。 個人が自分の能力を伸ばすことに努めるのはもちろんのこと、プロジェクトとしてもチームとしての能力を伸ばすための時間があってよいのかもしれません。

一方で。 できあがった時点では成功なのか失敗なのか、判断がつかないものもあります。 例えば、これはわたし自身が最近経験したことですが、よいユーザインタフェースに改善したはずが、逆にユーザに戸惑いを与え不必要な操作を増やしてしまう結果になってしまったものがありました。 実際にサービスを提供してみないと、何が正解なのか本当にはわからないものです。

ユーザに損害がおよぶ事態はさけなければなりませんが、時には実世界で成否をはかることも必要で、プロジェクトはそんな状況も加味しておくべきなのかも知れません。

うまく失敗できているか?

失敗とは何で、いつなら失敗できるか考えてみましたが、もう一つ他にも重要な要素がありそうです。 失敗することの得手不得手です。

正直なところ。 わたしは、失敗することが下手です。 失敗すれば凹むし、めげるし、ふさぎます。 不必要なほどダメージを受けます。 ダメージを受けるというよりも、自分自身にダメージを与えているというのが正解かもしれません。

失敗できる条件がプロジェクトに用意されているとしても、自分の気持ちとして失敗をゆるせるかはまた別の話。

感情は、本人以外が直接働きかけることはできません。 ですがそれでも、関わり方を工夫することはできるかも知れません。

以前、ソフトウェア開発を委託する立場だった時期がありました。 そのとき心がけていたこととして、報告される失敗に対して深刻な印象を与えないようにする、というものがあります。

深刻になり失敗してしまったことに心がとらわれていると、課題の解決のための力が削がれてしまいます。 当時はそこまで意識していたわけではないのですが、解決のために深刻にならないように、しかし真剣に真摯に取り組めるように、感情の余計な負担を減らしたかったのだと思います。

失敗したらどうするか?

共有しましょう。

なぜそうなっているのか。 その背景を知らないでいると、不用意に「改善」してしまい、失敗によって得られていた知見を失うという誤りをおかす危険があります。 その結果、その知見によって防がれていた課題が再び現れ、重大な事故につながることもあります。

失敗することが、うまくいかないことを知るための行為であるなら、その知識を活かさない手はありません。

ここでもまた「失敗を共有したらあげつらわれる」ことがあると、心理的な負担や障壁になってしまいます。 そうならないためにも、心理的に失敗できる環境が重要なのだと思います。

失敗のために

高い頂には広い裾野が必要です。 そして失敗は、成功のための裾野だと思います。

失敗だけが裾野ではありませんが、失敗もまた裾野を広げる役に立っているように思います。

過ちをおかすのではなく、裾野を広げるために、積極的に失敗することができたらと思います。

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

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

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

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

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

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

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントが必要になると思います。

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

koic: activerecord-oracle_enhanced-adapter

github.com

Oracle enhanced adapter の CI が master ブランチで落ちていたところ、私の手元で進めていた部分までを push して、続きを yahonda さんが kamipo さんとディスカッションしつつ、問題の解決まで進めてくれました。

osyo-manga: RuboCop

github.com

RuboCop でマルチバイト圏のソースコードを扱う場合の警告へのハイライト範囲の修正をされているパッチで、osyo-manga さんがパッチ会に問題点を持ってきてくれ、後日 PR を開いてくれました。このパッチへのエピソードについて以下の記事にも記しています。

koic.hatenablog.com


開催中の RailsConf や、最近の Ruby / Rails まわりの動向に関する話題などあるかもしれません。

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

Reboot Rails/OSS meetup online · GitHub

開発環境と金の弾丸

昨今の IT エンジニア募集企業では『開発 PC を選べる』というのが一つの潮流にある気がしています。永和システムマネジメント (以下 ESM) でも自分のパフォーマンスが最も出せる PC を選ぶといった制度が古くからあり、今回はそれにまつわるストーリーを書いてみます。

  • Apple の法人購入するときには、キーボードの選択に気をつけよう
  • 法人契約で間違えて購入したとき
  • 16コア、64GBは正義。コアとメモリは積めるだけ積め

以上のショートショート3本立てです。

Apple の法人購入で気をつけること

ESM では定期的に買い替えのタイミングを設けており、これはそんな MacBook Pro を買い換えるときのとある日の出来事です。

ESM で MacBook Pro を購入する際には Apple で法人契約購入するのですが、先日弊社メンバーで立て続けに起きた、ちょっとした注意点を共有しておきます。

舞台は以下のツイートです。

数日後、同僚にもまさかの同じことが起きました。

言い訳じみた原因としては、法人での購入画面への注意力散漫といったものだったと思われます。

MacBook Pro の構成をカスタマイズするときに、デフォルトで『日本語 (JIS) 』キーボードが選択されていたので、US 配列が良い場合は『英語 (米国) 』を選択し直す必要があります。

(注: 以下は US 配列キーボード「選択後」の画面です)

f:id:koic:20210402095901p:plain

CPU やメモリのカスタマイズと異なり、購入金額が変わるわけではないので、会社の予算に気を取られて見落とさないよう気をつけると良いです。

Apple の法人購入で間違えて購入した場合

一定期間の間であれば、交換してもらえます。私も諦めかけていたところ、バックオフィスのメンバーから交換の流れを教えてもらって US キーボードへの転身をとげました。 とりわけ Apple 社に迷惑を掛けてしまったあたり反省しかないのですが、おかげで満足なキーボードで生活できています。

あきらめる前に、お勤めの会社で交換可能か確認をとってみましょう。

16コア、64GBは正義。コアとメモリは積めるだけ積め

「買い替えてどうなったの?」というところで、OSS での開発を例に挙げると、RuboCop のフルビルドにかかる所要時間がおおよそ半分程度になりました。

RuboCop では test-queue を使ったテストの並列化をしているため、コア/ハイパースレッディングとメモリが増えればそれだけビルドの待ち時間が減るという公式です。

github.com

アクティビティモニタでみるとこんな感じで、すべてのコアが働いていることが確認できます。

f:id:koic:20210402095836p:plain

PC スペックとフルビルドの Before / After を並べてみます。

⏳ Before (1:54.53)

  • プロセッサ: 2.7 GHz クアッドコアIntel Core i7
  • メモリ: 16 GB 2133 MHz LPDDR3
% time bundle exec rake
Files:         567
Modules:        88 (   13 undocumented)
Classes:       529 (    2 undocumented)
Constants:     882 (  869 undocumented)
Attributes:     31 (    0 undocumented)
Methods:       990 (  867 undocumented)
 30.52% documented
Starting test-queue master (/tmp/test_queue_1738_17060.sock)

==> Summary (8 workers in 42.8440s)

    [ 1]                         1960 examples, 0 failures, 4 pending        80 suites in 24.9043s      (pid 1774 exit 0 )
    [ 2]                         2626 examples, 0 failures, 6 pending        87 suites in 24.9056s      (pid 1775 exit 0 )
    [ 3]                                     117 examples, 0 failures         1 suites in 42.8359s      (pid 1776 exit 0 )
    [ 4]                                    2309 examples, 0 failures        81 suites in 42.8358s      (pid 1777 exit 0 )
    [ 5]                                    2996 examples, 0 failures        87 suites in 42.8372s      (pid 1778 exit 0 )
    [ 6]                         1436 examples, 0 failures, 1 pending        78 suites in 42.8379s      (pid 1779 exit 0 )
    [ 7]                                     637 examples, 0 failures        53 suites in 42.8379s      (pid 1780 exit 0 )
    [ 8]                                    3490 examples, 0 failures        85 suites in 42.8312s      (pid 1781 exit 0 )

Starting test-queue master (/tmp/test_queue_1738_18680.sock)

==> Summary (8 workers in 41.1013s)

    [ 1]                                     117 examples, 0 failures         1 suites in 41.0932s      (pid 1860 exit 0 )
    [ 2]                                    1518 examples, 0 failures        78 suites in 41.0933s      (pid 1861 exit 0 )
    [ 3]                                    2480 examples, 0 failures        86 suites in 41.0947s      (pid 1862 exit 0 )
    [ 4]                                    2259 examples, 0 failures        86 suites in 41.0952s      (pid 1863 exit 0 )
    [ 5]                         2786 examples, 0 failures, 5 pending        87 suites in 41.0958s      (pid 1864 exit 0 )
    [ 6]                                    2066 examples, 0 failures        79 suites in 41.0960s      (pid 1865 exit 0 )
    [ 7]                                     591 examples, 0 failures        46 suites in 41.0957s      (pid 1866 exit 0 )
    [ 8]                         3754 examples, 0 failures, 6 pending        89 suites in 41.0892s      (pid 1867 exit 0 )

Running RuboCop...
Inspecting 1261 files
.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

1261 files inspected, no offenses detected
bundle exec rake  515.00s user 34.02s system 479% cpu 1:54.53 total

⏳ After (1:11.55)

  • プロセッサ: 2.4 GHz 8コアIntel Core i9
  • メモリ: 64 GB 2667 MHz DDR4
% time bundle exec rake
Files:         567
Modules:        88 (   13 undocumented)
Classes:       529 (    2 undocumented)
Constants:     882 (  869 undocumented)
Attributes:     31 (    0 undocumented)
Methods:       990 (  867 undocumented)
 30.52% documented
Starting test-queue master (/tmp/test_queue_27867_17060.sock)

==> Summary (16 workers in 26.9534s)

    [ 1]                                     111 examples, 0 failures         1 suites in 12.8340s      (pid 27900 exit 0 )
    [ 2]                                    1527 examples, 0 failures        43 suites in 12.8344s      (pid 27901 exit 0 )
    [ 3]                                     117 examples, 0 failures         1 suites in 26.9430s      (pid 27902 exit 0 )
    [ 4]                         1029 examples, 0 failures, 4 pending        47 suites in 26.9427s      (pid 27903 exit 0 )
    [ 5]                                    1529 examples, 0 failures        51 suites in 26.9428s      (pid 27904 exit 0 )
    [ 6]                                    1699 examples, 0 failures        48 suites in 26.9439s      (pid 27905 exit 0 )
    [ 7]                                     959 examples, 0 failures        46 suites in 26.9438s      (pid 27906 exit 0 )
    [ 8]                                      68 examples, 0 failures         1 suites in 26.9437s      (pid 27907 exit 0 )
    [ 9]                                    1470 examples, 0 failures        50 suites in 26.9435s      (pid 27908 exit 0 )
    [10]                                     269 examples, 0 failures        21 suites in 26.9434s      (pid 27909 exit 0 )
    [11]                                      34 examples, 0 failures         1 suites in 26.9432s      (pid 27910 exit 0 )
    [12]                                     920 examples, 0 failures        45 suites in 26.9430s      (pid 27911 exit 0 )
    [13]                                    1473 examples, 0 failures        49 suites in 26.9428s      (pid 27912 exit 0 )
    [14]                         1171 examples, 0 failures, 1 pending        50 suites in 26.9431s      (pid 27913 exit 0 )
    [15]                         1630 examples, 0 failures, 1 pending        49 suites in 26.9428s      (pid 27914 exit 0 )
    [16]                         1565 examples, 0 failures, 5 pending        49 suites in 26.9414s      (pid 27915 exit 0 )

Starting test-queue master (/tmp/test_queue_27867_18680.sock)


==> Summary (16 workers in 28.2100s)

    [ 1]                                     111 examples, 0 failures         1 suites in 13.5583s      (pid 27954 exit 0 )
    [ 2]                                    1590 examples, 0 failures        48 suites in 13.5583s      (pid 27955 exit 0 )
    [ 3]                                    1316 examples, 0 failures        46 suites in 13.5583s      (pid 27956 exit 0 )
    [ 4]                         1231 examples, 0 failures, 5 pending        50 suites in 13.5582s      (pid 27957 exit 0 )
    [ 5]                                      68 examples, 0 failures         1 suites in 13.5581s      (pid 27958 exit 0 )
    [ 6]                                     256 examples, 0 failures        21 suites in 13.5578s      (pid 27959 exit 0 )
    [ 7]                                     895 examples, 0 failures        47 suites in 13.5578s      (pid 27960 exit 0 )
    [ 8]                                     931 examples, 0 failures        44 suites in 13.5579s      (pid 27961 exit 0 )
    [ 9]                                    1565 examples, 0 failures        48 suites in 13.5578s      (pid 27962 exit 0 )
    [10]                         1595 examples, 0 failures, 4 pending        45 suites in 13.5578s      (pid 27963 exit 0 )
    [11]                                     117 examples, 0 failures         1 suites in 28.2005s      (pid 27964 exit 0 )
    [12]                                      34 examples, 0 failures         1 suites in 28.2004s      (pid 27965 exit 0 )
    [13]                         1153 examples, 0 failures, 1 pending        49 suites in 28.2002s      (pid 27966 exit 0 )
    [14]                                    1596 examples, 0 failures        50 suites in 28.2003s      (pid 27967 exit 0 )
    [15]                                    1613 examples, 0 failures        50 suites in 28.2000s      (pid 27968 exit 0 )
    [16]                         1500 examples, 0 failures, 1 pending        50 suites in 28.2001s      (pid 27969 exit 0 )

Running RuboCop...
Inspecting 1261 files
.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

1261 files inspected, no offenses detected
bundle exec rake  447.86s user 30.88s system 669% cpu 1:11.55 total

16 コア、64GB の開発環境は正義。金の弾丸が効いたことがわかります。

余談ですが、会社としては (業務がまわれば) Linux や WSL という選択肢もある中、私が MacBook を選んでいる理由は、Rails アプリケーション開発が仕事のベースにあるため、Rails の作者である DHH が選択している MacBook を使うことにしています。

これは『開発 PC を選べる』の根底にある「自分のパフォーマンスが最も出せる PC を選ぶ」への観点として、開発環境の構築にハマらないように、もしハマった時は The Rails Doctrine にある「数は安全性を生む」による解決が働きやすいことへの期待があるためです。

qiita.com

以上、金の弾丸によって開発効率を上げた一例の紹介でした。

もっとコアがあれば開発効率の向上に活かせるのに予算が足りないよ、、、ということであれば組織の予算をハックすることを検討できるかもしれません。PC の必要経費は年々上がっているので、時代背景とあっているか上長と掛け合ってみましょう。人件費に比べれば PC は安いもので、何より開発者にとって快適な開発環境はすこぶる良いものです (健闘を祈る!) 。

最後にいつものです。

agile.esm.co.jp

読みやすいテストコードについて考える

こんにちは、@kasumi8pon です。

アプリケーションの機能の追加開発をするときには、既存のテストに追加や修正を行う必要があります。また、既存のテストは現状の仕様を理解するのにも役に立ちます。そのため、テストコードが読みやすいと追加の開発がとても進めやすいです。ところが、わたしが開発しているアプリケーションのテストコードには、理解しやすく拡張もしやすいコードと、そうではないと感じるコードの両方が存在しました。よい機会なので、両者を比較しながら読みやすいテストコードについて考えてみたいと思います。なお、サンプルコードは RSpec と FactoryBot を利用して記載します。

内容を表す適切なラベルがついているか

メソッドの戻り値をそのままテストしているなど、テストの内容が自明である場合、以下のように説明を省略しても理解は容易です。

describe '#published?' do
  subject { book.published? }
  let(:book) { build(:book, published: true) }

  it { expect(subject).to be true }
end

しかし、期待する値の根拠が自明でないテストを書く場合、説明を記述するとのちの理解の助けになります。

# Bad
describe '#price' do
  subject { book.price }
  let(:book) { build(:book, price: 2_000, bargain_price: 500)

  # なんのケースをテストしてるの?
  it { expect(subject).to eq 1_500 }
end

# Good
describe '#price' do
  subject { book.price }
  let(:book) { build(:book, price: 2_000) }

  it { expect(subject).to eq 2_000 }

  context 'bargain_price がある場合' do
     let(:book) { build(:book, price: 2_000, bargain_price: 500) }

     it 'price から bargain_price の値を引かれた価格となること' do
      expect(subject).to eq 1_500
    end
  end
end

テストに説明があることで、他の箇所を参照せずともどんなテストなのかがわかるようになります。 小さなことに感じますが、仕様がわからないときにはとても理解の役に立ちます。

Feature Spec や System Spec を書く場合は、シナリオが簡潔に記載されているとわかりやすいです。これらのテストは複数の操作から成り立つことが多いため、説明がない場合ひと目でテストの内容を理解することは難しいためです。 操作部分のコードを追ってなんのテストをしているかを調べることはできますが、説明があればすぐ直感的に理解することができます。

describe '本の購入について' do
  # Bad (最後まで読まないとどんなテストかわからない)
  scenario do
    # ...
    # いろいろな操作
    # ...
    expect(page).to have_content '購入しました'
  end

  # Good (最初に何をテストしているのかがわかる)
  scenario '新発売の本の中から本を選んで購入する' do
    # ...
    # いろいろな操作
    # ...
    expect(page).to have_content '購入しました'
  end
end

RSpec には --format オプション(--format option - Command line - RSpec Core - RSpec - Relish)があり、--format documentation と指定することでテスト結果を仕様書のような形で出力することができます。このとき説明が不足していると文章として意味をなさない形になってしまいます。 --format documentation を利用したときに意味が通るようにテストを書くことは、読みやすいテストにも繋がります。

# Bad
本の購入について
  is expected to have text "購入しました"

# Good
本の購入について
  新発売の本の中から本を選んで購入する

構造が適切か

同じレベルのテストが違う階層にまたがっていたりすると、テストの全体像が把握しづらかったり、テストを追加するときに困ることがあります。以下の例では編集が新規登録時の一部の機能のように読めてしまい混乱しますし、削除の機能を追加するときにテストをどこに追加しようか迷ってしまいます。

# Bad (編集は新規登録と何か関係があるのかな?)
describe '本を新規登録する' do
  # 新規登録に関するテスト
  describe '本を編集する' do
    # 編集に関するテスト
  end
end

# Good (独立した同じレベルの機能は、同じ階層に書こう)
describe '本を新規登録する' do
  # 新規登録に関するテスト
end
describe '本を編集する' do
  # 編集に関するテスト
end

この例は極端ですが、現実のプロジェクトでは機能が多くて、テストが本来あるべき階層から飛び出してしまっているものを見かけることがあります。 このようなときも、RSpec の --format documentation で結果を出力して文章として読みづらいものがあると、不適切な階層にテストを書いていることがわかります。

また、テストデータのセットアップも適切な箇所で行うべきです。 下の例では、一見データは 20 件しか作成していないはずなのに、テスト内では 21 件目のデータについて言及しています。よくよく上の方を見ていくと、このテスト内で作成しているデータとは別でデータが作成されているようでした。

# Bad
describe '本の一覧機能について' do
  before do
    create(:book)
  end
  
  describe 'xxx' do
    # 他のテスト
  end

  describe 'ページネーションについて' do
    before do
      create_list(:book, 20)
    end
    
    # 20 冊しかつくっていないはずなのに、21 冊目はどこから出てきたのだろう?
    scenerio '21 冊目の本は 2 ページ目に表示される' do
      # 2 ページ目の表示を確認するテスト
    end
  end
end

必要な箇所のみに絞ってテストデータを生成すれば、読みやすくなります。 また、あとでテストを追加するときに認識していないデータによって想定外の状況が引き起こされることを防げます。

# Good
describe '本の一覧機能について' do
  describe 'xxx' do
    before do
      create(:book)
    end
    # 他のテスト
  end

  describe 'ページネーションについて' do
    before do
      create_list(:book, 21)
    end

    scenerio '21件目の本は 2 ページ目に表示される' do
      # 2ページ目の表示を確認するテスト
    end
  end
end

おわりに

わたしが思う読みやすいテストの特徴の一部を紹介しました。あくまでわたしが感じたことなので、別の書き方が読みやすいという方もいらっしゃると思います。テストの書き方には唯一の正解があるわけではないので当然です。しかし、唯一の正解がなかったとしても、読みやすさを意識せずに書いたコードと意識して書いたコードでは後から読み返したときに大きな違いがあるとわたしは考えています。この記事がチームメンバーや未来の自分の開発のしやすさのためにテストコードの読みやすさについて考えるきっかけになれば嬉しいです。

asdf のススメ

こんにちわ。 仕事以外のすべての時間を原神に捧げている @kajisha です。

asdf とは

asdf の README から引用すると

asdf は単一のCLIツールで複数のランタイムバージョンを管理します。 
asdfは、プロジェクトごとに複数の言語のランタイムバージョンを管理できるCLIツールです。 
これは、gvm、nvm、rbenv、pyenv(およびその他)がすべて1つになっているようなものです。 
言語のプラグインをインストールするだけです!

とあるように anyenv に近いものですが、プラグインでサポートしている言語(言語に限りませんが)が圧倒的に多いのが特徴です。 Ruby, Python, Node.js, Golang, Rust などの主要な言語はもちろん starship, direnv などにも対応しており多彩なプラグインが利用可能です。

参考までに、わたしがインストールしているプラグインは以下のとおりです:

$ asdf current
awscli          2.1.31          /home/hiroshi/.tool-versions
bat             0.18.0          /home/hiroshi/.tool-versions
direnv          2.28.0          /home/hiroshi/.tool-versions
erlang          23.2.7          /home/hiroshi/.tool-versions
ghq             1.1.7           /home/hiroshi/.tool-versions
github-cli      1.7.0           /home/hiroshi/.tool-versions
golang          1.16.2          /home/hiroshi/.tool-versions
haskell         9.0.1           /home/hiroshi/.tool-versions
logtalk         3.00.0          /home/hiroshi/.tool-versions
mysql           8.0.23          /home/hiroshi/.tool-versions
neovim          nightly         /home/hiroshi/.tool-versions
nodejs          15.8.0          /home/hiroshi/.tool-versions
postgres        13.2            /home/hiroshi/.tool-versions
python          3.9.2           /home/hiroshi/.tool-versions
ruby            3.0.0           /home/hiroshi/.tool-versions
rust            1.50.0          /home/hiroshi/.tool-versions
sbcl            2.1.2           /home/hiroshi/.tool-versions
sqlite          3.35.2          /home/hiroshi/.tool-versions
starship        0.51.0          /home/hiroshi/.tool-versions
terraform       0.14.8          /home/hiroshi/.tool-versions

asdf のインストール

asdf のインストールは、各OSのパッケージマネージャからインストールすることも可能ですが、 ここでは、 Git を使ってインストールします。

$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.8.0

シェルに設定を追加します。わたしの普段使いのシェルは fish なので、fish を前提に記述します。

~/.config/fish/config.fish に以下の行を追加します。

source ~/.asdf/asdf.fish

fish の補完が効くようにします。

$ mkdir -p ~/.config/fish/completions; and ln -s ~/.asdf/completions/asdf.fish ~/.config/fish/completions

fish 以外のシェルのインストールについては、公式のドキュメントを参照してください。

asdf のプラグインを追加してみる

実際に Ruby を asdf で管理してみましょう。

$ asdf plugin add ruby

どのバージョンがあるのかリストしてみます。

$ asdf list all ruby
1.8.5-p52
1.8.5-p113
1.8.5-p114
1.8.5-p115
1.8.5-p231
1.8.6
1.8.6-p36
1.8.6-p110
1.8.6-p111
1.8.6-p114
...このあといっぱい続く

asdf の Ruby プラグインである asdf-ruby は内部で ruby-build を使っているので、rbenv を利用している方にはなじみのあるリストが表示されるはずです。 補足ですが asdf-ruby の ruby-build はリリース版を利用しているので master の ruby-build を利用する場合は、 環境変数 ASDF_RUBY_BUILD_VERSIONmaster を指定するとよいです。

それでは ASDF_RUBY_BUILD_VERSION=master を指定して 3.1.0-dev を入れてみましょう。

asdf でプラグインの特定のバージョンをインストールするのは asdf install [plugin] [version] という共通の構文になっています。 ちなみに [version] のところに latest と入れると最新版がインストールされるようになっています。常に最新版を追っかけたい人には便利な機能です。

$ env ASDF_RUBY_BUILD_VERSION=master asdf install ruby 3.1.0-dev
Downloading ruby-build...
Cloning into '/home/hiroshi/.asdf/plugins/ruby/ruby-build-source'...
remote: Enumerating objects: 120, done.
remote: Counting objects: 100% (120/120), done.
remote: Compressing objects: 100% (98/98), done.
remote: Total 11427 (delta 86), reused 39 (delta 16), pack-reused 11307
Receiving objects: 100% (11427/11427), 2.42 MiB | 4.25 MiB/s, done.
Resolving deltas: 100% (7553/7553), done.
Already on 'master'
Cloning https://github.com/ruby/ruby.git...
Installing ruby-master...
Installed ruby-master to /home/hiroshi/.asdf/installs/ruby/3.1.0-dev

ちゃんと入ったかどうか確認してみましょう。どのバージョンがインストールされているか確認するコマンドは asdf list [plugin] です。

$ asdf list ruby
  2.7.2
  3.0.0
  3.1.0-dev

インストールできたみたいですね。自分の環境全体で 3.1.0-dev を使うようにしてみます。 asdf のコマンドは asdf global [plugin] [version] です。

$ asdf global ruby 3.1.0-dev
$ ruby --version
ruby 3.1.0dev (2021-03-24T18:31:10Z master b25361f731) [x86_64-linux]

ただし、特定のディレクトリ(プロジェクト) では、別のバージョンを使いたいこともあると思います。その場合は、該当のディレクトリで指定のバージョンを使うようにします。 asdf のコマンドは asdf local [plugin] [version] です。

$ cd path/to/rails/project/
$ asdf local ruby 3.0.0
$ ruby --version
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]

これで、このディレクトリ以下では 3.0.0 が使われるようになります。

asdf のプラグインをつくるのはとても簡単

asdf がサポートしているプラグインが多い理由は、asdf プラグインのインタフェースが統一さているからです。 公式のドキュメントにあるように最低限必要なシェルスクリプトは以下の3つです:

Required Scripts
- bin/list-all - lists all installable versions
- bin/download - download source code or binary for the specified version
- bin/install - installs the specified version

わたしがつくった ghqプラグインを参照してみていただければわかると思いますが、 そこまで複雑なシェルスクリプトではありません。 (ちょっといいわけするとプラグインをつくった当時は bin/download はなかったような気がしてて作ってないです……)

プラグイン開発の詳細はドキュメントを参考にするか、既存のプラグインをベースに変更していくのでもよいと思います。 プラグインをつくったら公式に PR してとりこんでもらいましょう。 参考までにわたしが出したPRをはりつけておきます。(PR の Description になにも書いてなくてすみません……)

さいごに

asdf はサポートしているプラグインがいっぱいあってプラグインの開発も簡単です。 asdf を気にいってくれたら幸いです。

見積りのための「スパイク」

こんにちは。@junk0612です。今回は、普段の仕事の中でぶつかった疑問を解決した話をお送りします。

解決したかった問題

今関わっているプロジェクトでは、実際のエンドユーザーとは別にお客様の社内の方が使う画面で RailsAdmin を使っています。これまで RailsAdmin 上での画像のアップロードを取り扱ったことがありませんでしたが、新しく機能追加をするにあたって RailsAdmin から画像をアップロードできるようにするのが一番良さそうだという話になりました。

画像のアップロードに関しては CarrierWave を使っていて、RailsAdmin と CarrierWave の組み合わせを意外と誰も試したことがなかったため、見積りにあたって「できる方法があるのか、かかる手間はどれくらいか」を知りたいという要求が生まれ、その調査を僕が担当することになりました。

まずは調査結果

できます。それも簡単です。

たとえば作成画面なら、config/initializers/rails_admin.rb

config.model(Model) do
  create do
    field :image, :carrierwave
  end
end

と書くだけです (真ん中の行に :carrierwave というタイプを指定するのがミソ)。これで画像のアップロードフォーム付きの作成画面ができあがります。

ずいぶん簡単ですが、これは RailsAdmin 内に RailsAdmin::Config::Fields::Types::Carrierwave というクラスが存在しているためです (参考)。つまり RailsAdmin は CarrierWave に対応済みだった、ということになります。

「スパイク」とは

上記のような、「機能開発とは別の、技術的検証や調査のためのタスク」を「スパイク」と呼びます。スパイクは、チームがきちんとした根拠を持って見積りや技術的な決定をするために必要なタスクです。

『アジャイルな見積りと計画づくり』p.170 には以下のように書かれています。

スパイクとはイテレーション計画に含めるタスクの一種で、何らかの知見を得たり、疑問を解消することを目的に取り組む作業のことだ。(中略) チームは修正の影響範囲を十分に見極めることができなかったので、タスクを2つに分けたのである。1つはスパイクで、もう1つは大雑把な見積りを入れたプレースホルダー的なタスクである。スパイクを実施すれば2つ目のタスクへの見通しが得られるので、このタスクをより正確に見積もれるようになるのだ。

見積り時にあるタスクの実現方法がわからない場合、その方法の調査にかかる時間も予測できないということはままあります (実際に、今回の問題は調べれば簡単にわかることでしたが、少なくとも見積りのときは誰も知らなかったことです)。そういうとき、何もかもがあやふやなまま決め打ちで見積るしかないこともありますが、スパイクしてみるというのは一つの強力な選択肢です。実際にきちんとしたものを作ってみなくても、少し時間をかけて調査すれば、様々な情報が得られます。得られた情報を元にすれば、見積りの確度はぐっと上がりますし、なにより決定に対して自信が持てます1

スパイクは実装方法の道のりをすべて明らかにするのが目的ではありません。そうできれば見積りの確度は最も高まるでしょうが、実際に実装するわけではないし、もしかしたらその見積り結果を元に実装しないという結論に至るかもしれないからです。時間がかかりすぎては元も子もないので、ある程度の時間を区切って調査し、ここまではわかったというところで再度見積る、というのが効果的な利用方法だと思います。


  1. ちなみに、「調べても何もわからなかった」というのも一つの情報です。これは「簡単にはわからなかった == 実装でも調査から始めなければならず、時間がかかりそう」という推測ができるからです。

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

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

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

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

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

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

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントが必要になると思います。

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

ima1zumi: rurema/doctree

github.com

osyo-manga: Ruby

github.com

yucao24hours: seory

github.com

先日アナウンスされた RubyKaigi Takeout 2021 に向けた話題などあるかもしれません。

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

Reboot Rails/OSS meetup online · GitHub