Go言語をTDDで学んでみた話(続き)

新しいプログラミング言語をTDDを使って学ぶ試みとして、今回はGo言語を学んでいます。言語の作法も開発環境もフィードバックサイクルを意図的に回しながら学びます。

Eiji Ienaga
時を超えたプログラミングの道

--

前回からの続き

前回に引き続き、Go言語をテスト駆動開発(TDD)を使いながら学びを進めてみます。前回の記事はこちらをお読みください。

さて、まずはここまでのTODOリストを見てみましょう。#TODOの下はこれからやるべきタスクで、#DONEの下はすでに完了したタスクになります。

#TODO
0の場合、4000の場合はエラー
4の場合はIV
9の場合はIX
5の場合はV
40の場合
50の場合
11の場合はXI
(1-3999で抜け漏れをあとで...考える)
gorename を試す
VSCodeで保存したら即テスト実行の方法を探す
# DONE
10,20の場合
1 => Iの変換ができること。
APIの見直し(オブジェクト指向風のメソッドに置き換え)
Goでよく見かけるデータ駆動型のテストに置き換え
2 => IIの変換ができること。

今回は、気になっていたエラーケース(0の場合、4000の場合)を進めていきます。

と、その前に、VSCode上で保存したら即テストのやり方がどうしても気になって調べてみました。コードとドキュメントを追ってみると、前回入れた機能拡張が使え、workspace settings で、go.testOnSave: true を追加すればOKです。

これで保存したらテスト実行ができるようになりました。実に気分が良い!!

素早いフィードバックサイクルを作れ

TDDのコツ: フィードバックサイクルを作ること。素早く小さいフィードバック機構ができるように改善を続けること

フィードバックサイクルを短くすることで、単位時間当たりの学びの数を増やすことができます。保存したら即実行の道具の改善のほか、手動と目視による繰り返し確認を機械に任せる自動テストへ置き換えもフィードバックサイクルを短くする典型的な例ですね。

手間のかかりすぎるデプロイ&エンドツーエンドテストを、純粋なドメインロジック部分のユニットテストに注力することに置き換えるやり方も、フィードバックサイクルを短くする方法の一つです。純粋なビジネスロジックと環境の境界を見極めて分離することがコツです。いつかモックについても解説したいと思います。

https://speakerdeck.com/twada/testable-lambda-working-effectively-with-legacy-lambda

余談ですが、TDDを含め多重のフィードバックサイクル機構の仕組みづくりはソフトウェア開発をうまくやるコツです。アジャイルもTDDもDevOps もリーンスタートアップもオープンソースのエコシステムも根っこにあるのはフォードバック機構です。つまりは、構え撃て狙えです。

Go言語でのやり方を学ぶ

xxx, err := <関数やメソッド呼び出し>
if err != nil {
<何か例外処理>
return xxx
}
<通常の処理>

Go言語の学び: 複数の値を返す、エラーを返す。

Goでは関数やメソッドでは複数の値を返すことができます。複数の値を返しif文でエラーチェックします。Goではよく出てくるイディオムなので覚えておきましょう。他のプログラミング言語でよくある例外を投げてtry-catch-finallyで処理する構文はGo言語の場合は提供していません

Go言語のエラーハンドリングについては、次の記事を参考にしました。

エラーの場合に複数の値にエラーを含めて返す以外に、panicを使う方法があります。ただし、回復不能なエラー場合のみにpanicを使い、基本は複数の値でエラーを伝え、利用者に提供したAPIの境界でpanicで伝播させることは基本行いません。気になっているところが少しスッキリしました。

panicの使い方については、次の記事を参考にしました。

本家のドキュメントを読め

プログラミング言語の学習のコツ:本家のドキュメントを読む。

開発者として、個人のブログの2次的な情報より本家のドキュメントを読む癖は付けておきましょう。ドキュメントが英語の場合も、ブラウザの機能拡張やOSについている英語辞書があれば直ぐに調べることが出来ます。有志によって本家のドキュメントの日本語訳が行われている場合もあります。補助の情報として、本家が提供しているコードがGitHub等にあれば直ぐに試して学ぶことができるでしょう。StackoverflowやQiitaの情報も活用していきましょう。情報が古くなってしまったブログの情報には注意が必要です。

まずは、0で変換できないことの失敗することを確認して、

テストがパスすること確認します。

Roman(“”)の代わりに nilを返すバージョンならどうなるか気になったので試してみました。

おっと。nilを返すにはAPIを変えて、メソッド側では&outと&を付けて返し、呼び出し側で*gotと*を付けて値を取り出す必要がでてきました。(ポインタや参照を使ったのは大学時代にC言語を勉強した以来💦)。

Go言語の学び: ポインタ(参照)を返すには &を付ける。アドレスではなく値を取り出すには*を付ける。

呼び出し元で*を付けて値を取り出すは意図したAPIではないのでこれは捨てて、来た道を戻ります。前のバージョンに戻して、テストコード、プロダククションコードともに試行錯誤し、テストを追加して次になりました。

最後にテストパターンを増やした結果が次になります。

#TODO
gorename を試す
# DONE
10,20の場合
1 => Iの変換ができること。
APIの見直し(オブジェクト指向風のメソッドに置き換え)
Goでよく見かけるデータ駆動型のテストに置き換え
2 => IIの変換ができること。
0の場合、4000の場合はエラー
4の場合はIV
9の場合はIX
5の場合はV
40の場合
50の場合
11の場合はXI
。。。
VSCodeで保存したら即テスト実行の方法を探す

おっと、gorenameを試そうとして忘れてました。別の機会に調べてみようと思います。

TDDのカタを使って言語を学ぶ理由

2回に渡って、Go言語 & TDDについて簡単なお題を使って学習過程を記述してみました。実際には、もっと多くの調べ物をしながら進めていましたが省略しています。(Goのテストフレームワークのコードを軽く覗く、Stackoverflowで調べる、Qiitaの記事を徘徊する。Webフレームワークなどのライブラリのテストを覗く、ここに記載しなかった別の実装案も試す。。。)

プログラミングは、知らない項目が多数あるなか1つ1つ確実にクリアにして実際に動くものをつくっていくトライ&エラーが必要な作業です。このトライ&エラーの過程を暗中模索で道に迷わないように工夫する方法として、TDDはいくつかのカタを示しています。

  • 問題の明確化:テストが書けないということは、問題定義(≒仕様)になにか不明瞭なことがあることのサインです。
  • ゴールの明確化:テストがあれば”何ができればOKか”を明確にすることができます。
  • 異常の即時検知:後からの追加修正で壊れていないことを直ぐに確認できます。
  • 実現手段の探索:リファクタリングによって、”どう実装すればOKか”の実現案を模索します。
  • 選択肢の収束:リファクタリングも1直線に、理想のきれいなコードに向かえず、試行錯誤することもあるでしょう。幾つかもっともらしい解決案を試し、1つに収束させます。ペアプロしていれば対話しながら、もっともらしい案に収束させることができるでしょう。

それにしても、JavaやRubyばかり触ってきた僕にとっては、Go言語は不思議なプログラミング言語に感じました。他のプログラミング言語にはある言語仕様が色々削られていて、逆に清々しいです。

コードの統一感を与えてくれる自動整形は便利ですし、IDEなくても軽いエディタ書けるのは嬉しいところです。Ruby好きなら型は要らないのかもしれませんが、僕にとっては、コードジャンプなどがやりやすく他人のコードが読みやすいです。Goを使ってもう少し、DDDやマイクロサービスなどを調べて行きたいです。

--

--