Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions docs/development/onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SeeFT に新しく入った人向けの資料です。Go も Flutter も Apps Sc

## この資料でいちばん伝えたいこと

SeeFT には、書き間違えてもエラーにならず、動かしても黙って間違った結果になる場所がいくつもあります。コンパイラ(コードを実行できる形に変換し、そのときに誤りを検査するプログラム)もテストも CI も、そこでは守ってくれません。たとえば次の場所です。
SeeFT には、書き間違えてもエラーが出ず、動かしても間違った結果のまま進んでしまう場所がいくつもあります。コンパイラ(コードを実行できる形に変換し、そのときに誤りを検査するプログラム)もテストも CI も、そこでは誤りを見つけてくれません。たとえば次の場所です。

- api と mobile・GAS の間でやり取りする JSON のキー名(0節)
- migration のファイルの番号と、適用済みのファイルの書き換え(4節)
Expand All @@ -16,7 +16,13 @@ SeeFT には、書き間違えてもエラーにならず、動かしても黙
- リポジトリの `gas/` と、実際に動いている GAS のずれ(8節)
- 偽の DB を相手にしたテストでは分からない誤り(10節)

各節では、こうした場所を「**黙って壊れるところ。**」という書き出しで示します。13節の「完了」の問いも、多くはここから出します。技術を1つずつ学ぶのと並行して、「この変更で、何が黙って壊れうるか」を考えながら読んでください。
これをいちばん伝えたい理由は3つあります。

- 45th の事故の多くが、この形で起きたからです。事故の記録([incidents-45th.md](../operations/incidents-45th.md) の「共通する原因」)は、共通点を2つ挙げています。データがスプレッドシートから api に渡る境目で起きていることと、失敗が画面に出ないことです。
- エラーが出る誤りは、エラーの文を読めば直し始められます。エラーが出ない誤りは、どこで起きうるかを先に知っていなければ、探すこともできません。
- 言語やフレームワークの使い方は、公式のチュートリアルで学べます。SeeFT のどこが危ないかは、SeeFT の中でしか学べません。

各節では、こうした場所を「**エラーが出ないまま壊れるところ。**」という書き出しで示します。13節の「完了」の問いも、多くはここから出します。技術を1つずつ学ぶのと並行して、「この変更で、どこがエラーの出ないまま壊れうるか」を考えながら読んでください。

## この資料の読み方

Expand Down Expand Up @@ -59,7 +65,7 @@ SeeFT には、書き間違えてもエラーにならず、動かしても黙
| mobile | Dart | 参加者のブラウザ(サーバーはビルド済みのファイルを配るだけ) | 本体のコード |
| GAS | JavaScript | Google のクラウド | ある時点の写し(8節) |

**黙って壊れるところ。** 言語が場所ごとに違うので、api と mobile / GAS の間で型を共有する仕組みはありません。たとえば Go の struct に項目を足しても、Dart のクラスは自動では変わりません。両者の間の約束は、どの URL にどのメソッド(GET や POST。5節)で、どんな形の JSON を送り、何が返ってくるかだけです。JSON の形とは、キー名、値の型(文字列か数値か)、値が `null` になりうるか、配列かどうかです。エラーのときに `{"error": "..."}` を返すことや、空の一覧を `[]` で返すことも、この約束に含まれます(5節)。
**エラーが出ないまま壊れるところ。** 言語が場所ごとに違うので、api と mobile / GAS の間で型を共有する仕組みはありません。たとえば Go の struct に項目を足しても、Dart のクラスは自動では変わりません。両者の間の約束は、どの URL にどのメソッド(GET や POST。5節)で、どんな形の JSON を送り、何が返ってくるかだけです。JSON の形とは、キー名、値の型(文字列か数値か)、値が `null` になりうるか、配列かどうかです。エラーのときに `{"error": "..."}` を返すことや、空の一覧を `[]` で返すことも、この約束に含まれます(5節)。

この約束は、コンパイラには検査できません。食い違ったときの起き方は、食い違いの種類で2通りあります。

Expand Down Expand Up @@ -214,7 +220,7 @@ Python を書いたことがある人がつまずくのは、次の3つです。
- schema は、ファイルごとに、適用済みかどうかと中身のハッシュ(中身から計算した値。中身が変わると値も変わる)を DB に記録します。
- golang-migrate は、適用済みの一番新しい番号だけを記録し、それより新しい番号のファイルだけを適用します。

**黙って壊れるところ。** この違いのせいで、失敗したときの起き方も違います。
**エラーが出ないまま壊れるところ。** この違いのせいで、失敗したときの起き方も違います。

- 適用済みの schema のファイルを書き換えると、`make migrate` がエラーで止まります。すぐ気づけます。
- 適用済みの migration のファイルを書き換えても、その番号はもう適用済みなので、変更は黙って無視されます。
Expand Down Expand Up @@ -279,7 +285,7 @@ for rows.Next() { // 行を1つずつ読む

例外は、GORM を使う `shift_card_repository.go` です。ここだけは repository が struct に詰めて返します。

**黙って壊れるところ。** repository の多くは `SELECT *` で全列を取っていて、`Scan` に渡す変数はテーブルの列の順番と一致していなければなりません。テーブルに列を1つ足すと、そのテーブルを `SELECT *` して `Scan` している箇所をすべて直す必要があります。直し漏れは、コンパイルでは見つかりません。実行時に、列の数が合わなければ `Scan` がエラーを返します。列の数が合っていて順番だけずれた場合は、型が変換できる限りエラーにならず、違う列の値が黙って入ります。
**エラーが出ないまま壊れるところ。** repository の多くは `SELECT *` で全列を取っていて、`Scan` に渡す変数はテーブルの列の順番と一致していなければなりません。テーブルに列を1つ足すと、そのテーブルを `SELECT *` して `Scan` している箇所をすべて直す必要があります。直し漏れは、コンパイルでは見つかりません。実行時に、列の数が合わなければ `Scan` がエラーを返します。列の数が合っていて順番だけずれた場合は、型が変換できる限りエラーにならず、違う列の値が黙って入ります。

**層をまたいで使っている仕組み:**

Expand Down Expand Up @@ -328,7 +334,7 @@ for rows.Next() { // 行を1つずつ読む
setState(() => _isLoading = false);
```

**黙って壊れるところ。** 設定値はビルド時に埋め込まれます。api の URL などは `mobile/env/.env` に書き、`--dart-define-from-file=env/.env` で渡しています。コードからは `String.fromEnvironment('API_BASE_URL')`(`lib/configs/constant.dart`)で読みますが、これはビルドした時点で値がコードに埋め込まれる仕組みです。
**エラーが出ないまま壊れるところ。** 設定値はビルド時に埋め込まれます。api の URL などは `mobile/env/.env` に書き、`--dart-define-from-file=env/.env` で渡しています。コードからは `String.fromEnvironment('API_BASE_URL')`(`lib/configs/constant.dart`)で読みますが、これはビルドした時点で値がコードに埋め込まれる仕組みです。

- 値を変えても、ビルドし直すまで反映されません。
- `--dart-define-from-file` を渡し忘れても、エラーにはなりません。既定の `http://localhost:1234` に向きます。
Expand Down Expand Up @@ -383,7 +389,7 @@ setState(() => _allManuals = manuals);

Web なので、どちらも実体はブラウザのストレージです。ブラウザがデータを消すこともあるので、消えても取り戻せるもの(api から取り直せるデータや、ログインし直せば入る ID)だけを置きます。

**黙って壊れるところ。** 保存したデータは、消えることより、古いまま残ることのほうが危険です。シフトが変わったのに前回の内容を表示し続けると、参加者は古いシフトのとおりに動いてしまいます。検証中に表示が古いままのときは、ブラウザのサイトデータを消すと直ることがあります。
**エラーが出ないまま壊れるところ。** 保存したデータは、消えることより、古いまま残ることのほうが危険です。シフトが変わったのに前回の内容を表示し続けると、参加者は古いシフトのとおりに動いてしまいます。検証中に表示が古いままのときは、ブラウザのサイトデータを消すと直ることがあります。

**見た目とログの規約。** 色と文字サイズは `AppColors.main` や `AppFontSizes.md`(`lib/theme/tokens.dart`)を使い、`Color(0xFF...)` を直接書きません。ログは `print` ではなく `logger.i(...)` / `logger.e(...)` で出します。

Expand All @@ -402,7 +408,7 @@ Web なので、どちらも実体はブラウザのストレージです。ブ

**何か。** Google のスプレッドシートに紐づけて動かせる JavaScript です。SeeFT では、スプレッドシートに作ったメニューから、名簿・タスク・シフトを api に送ります。また、api から届いたレスキューをスプレッドシートに書き込みます。

**黙って壊れるところ。** `gas/` はクラウド上のコードの写しです。GAS のコードは、各スプレッドシートに紐づいた Google のクラウド上のプロジェクト(スプレッドシートごとに1つ)にあり、動いているのはそちらです。リポジトリの `gas/` にあるのは、[clasp](https://github.com/google/clasp)(GAS のコードをコマンドラインで取得・反映するツール)で取ってきた、ある時点の写しです。誰かがスプレッドシートのエディタで直接編集すると、クラウド上のプロジェクトだけが更新され、`gas/` は古いままになります。`gas/` の中には、45th では使っていないディレクトリもあります(`task/` など)。
**エラーが出ないまま壊れるところ。** `gas/` はクラウド上のコードの写しです。GAS のコードは、各スプレッドシートに紐づいた Google のクラウド上のプロジェクト(スプレッドシートごとに1つ)にあり、動いているのはそちらです。リポジトリの `gas/` にあるのは、[clasp](https://github.com/google/clasp)(GAS のコードをコマンドラインで取得・反映するツール)で取ってきた、ある時点の写しです。誰かがスプレッドシートのエディタで直接編集すると、クラウド上のプロジェクトだけが更新され、`gas/` は古いままになります。`gas/` の中には、45th では使っていないディレクトリもあります(`task/` など)。

**GAS を触る作業は、`gas/` を読むことではなく、`clasp` でクラウド上の最新のコードを取ってくることから始めます。** 手順は [gas/README.md](../../gas/README.md) にあり、流れは次のとおりです。

Expand Down Expand Up @@ -475,7 +481,7 @@ mock.ExpectExec(`INSERT INTO reviews \(user_id, task_id, staffing_rating, manual

引数の `"12"` や `"5"` が文字列なのは、review の repository が値を文字列で受け取り、SQL の中で `$1::int` のように数値に変換しているからです。

**黙って壊れるところ。** go-sqlmock が確かめられるのは「送った SQL の文字列と引数」だけです。SQL が実際のテーブルで正しく動くか、`Scan` の列の順番が合っているかは確かめられません。テストが通っても、5節の `SELECT *` の問題は見つかりません。[test-roadmap.md](test-roadmap.md) では、repository を実際の DB で確かめるテスト(「フェーズ2」)も計画していますが、まだありません。
**エラーが出ないまま壊れるところ。** go-sqlmock が確かめられるのは「送った SQL の文字列と引数」だけです。SQL が実際のテーブルで正しく動くか、`Scan` の列の順番が合っているかは確かめられません。テストが通っても、5節の `SELECT *` の問題は見つかりません。[test-roadmap.md](test-roadmap.md) では、repository を実際の DB で確かめるテスト(「フェーズ2」)も計画していますが、まだありません。

repository のテストは、`sqlmock_helper_test.go` の `newDBMock` を使うと短く書けます(repository のパッケージの中でだけ使えます)。どの層からどう書き進めるか、書く前にテストのケースの表を作ってレビューを受ける進め方は、test-roadmap.md にあります。テストが書かれていない関数の issue(題名が `[test]` で始まるもの)が練習に向いています。

Expand Down Expand Up @@ -539,7 +545,7 @@ cd mobile && fvm flutter test

## 13. 何か作る(全員)

読むだけでは足りません。同じ技術を使った**自分の小さなシステムを、新しいリポジトリで**作ってください。SeeFT とは関係ないもの(本棚、習慣の記録、サークルの備品の貸し出し表など)で構いません。大事なのは、つなぐ部分(画面と api、api と DB)を自分で書くことです。冒頭に挙げた「黙って壊れるところ」の多くも、このつなぐ部分にあります。
読むだけでは足りません。同じ技術を使った**自分の小さなシステムを、新しいリポジトリで**作ってください。SeeFT とは関係ないもの(本棚、習慣の記録、サークルの備品の貸し出し表など)で構いません。大事なのは、つなぐ部分(画面と api、api と DB)を自分で書くことです。冒頭に挙げた「エラーが出ないまま壊れるところ」の多くも、このつなぐ部分にあります。

担当が片方だけなら、作る範囲を次のように減らして構いません。

Expand Down Expand Up @@ -581,7 +587,7 @@ cd mobile && fvm flutter test

### 「完了」の意味

次を、調べずに説明できることです。多くは、この資料の「黙って壊れるところ」から出しています。括弧の中は、答えが書いてある節です。
次を、調べずに説明できることです。多くは、この資料の「エラーが出ないまま壊れるところ」から出しています。括弧の中は、答えが書いてある節です。

全員:

Expand Down
Loading