以下の内容はhttps://tech.youtrust.co.jp/entry/ai-dart-fundamentalsより取得しました。


AI時代だからこそ基礎を叩き直す_Dart編

こんにちは👋 入社して1年になりました
アプリチームの葉 (YOUTRUST / X) です

AIのおかげで実装が早くなりましたが、速さの裏で「本当に理解してるのか?」と感じる場面も増えました。 今回は Flutter / Dart の基礎を AI と一緒に学び直した話を書きます。

はじめに

AI がコードを書いてくれる時代になりました。 Copilot、Cursor、Claude Code。もう「書けない」で困ることは減りました。

でも、こんな経験はないでしょうか?

  • AIが生成したコードの finalconst の使い分けが合ってるか、自信がない
  • case final String name をレビューで指摘されたけど、なぜそう書くべきか説明できない
  • dynamicObject の違いを聞かれて、言葉に詰まる

わかってるつもりだけど、手が止まる。

これは「知識としては知ってるけど、体得されてない」状態です。 AIが書いてくれるからこそ、基礎が曖昧なまま進んでしまいます。

だから今回、AIと対話しながら言語の基礎を1つずつ叩き直すことにしました。 私は Flutter エンジニアなので、Dart にしました。


なぜ AI と勉強するのか?

実際にやってみて感じたメリットが3つあります。

1. 遠慮なく、すぐに深掘りできる

「こんな基礎的なこと聞いていいかな…」がありません。 気になったらその場で聞けて、そのまま深掘りできます。 ずっと曖昧だった const の理解も、その場で確認して修正できました。

2. 自分の開発に合わせた説明をしてくれる

比喩や図解を理解度に合わせて出してくれるだけでなく、今書いているコードの文脈で教えてもらえます。 「あ、だからこう書くのか」と、実務と繋がった瞬間にスッと理解できます。

3. 公式ドキュメントを一緒に読める

公式は正確ですが、そのまま読むと難しいです。AIに噛み砕いてもらいながら読むと理解が速くなります。 さらに、ドキュメントの読み方自体を教えてもらえます。


今回AIと一緒にやったこと

Topic 1: final vs const vs var vs 型宣言

🧐 クイズ:この4つの違いを説明できますか?

final name = 'Middle';
const name = 'Middle';
var name = 'Middle';
String name = 'Middle';

💭 私の最初の理解

  • final → 一回だけ入る
  • const → 最初から fix、メモリ使わない ← ここが間違い
  • var → あまり使わないと思ってた ← 使う時がある
  • String name → 型を定義した var

✅ 正しい理解

再代入 いつ決まる
var できる 実行時
String name できる 実行時(型明示)
final できない 実行時でもOK
const できない コンパイル時のみ

const の本当の意味:コンパイル時に値が確定することです。

final now = DateTime.now(); // OK。実行時に決まる
const now = DateTime.now(); // エラー!コンパイル時に決まらない

const は「メモリを使わない」のではなく、同じ値なら使い回される(正規化される) のでメモリ効率が良い、が正しいです。

const a = 'hello';
const b = 'hello';
identical(a, b); // true。同じオブジェクトを指してる

var は普通に使うものです。「あまり使わない」と思っていましたが、for ループなどで普通に書いていました。動くからOK、で深く考えていませんでした。これがまさに、なんとなく書けてしまう → 理由がわからない、の原因だと思います。

Dart 公式の lint ルール(omit_local_variable_types)でも、ローカル変数で型が推論できる場合は省略を推奨しています。理由は、ローカル変数はスコープが小さいので、型よりも変数名と値に集中してもらう方が読みやすいからです。

// 型明示(型が長いと読みづらい)
List<List<Ingredient>> desserts = <List<Ingredient>>[];

// 型省略(変数名と値に集中できる)
var desserts = <List<Ingredient>>[];

ただし、後で別の型を代入する場合は型明示が推奨されます。

Widget result = Text('hello');
result = Padding(padding: EdgeInsets.all(8.0), child: result);

ドキュメントだけ読んでも、実際のコードとどう繋がるのかがわかりづらいです。でも AI なら「このルール、うちのコードだとどこに当てはまる?」とすぐ聞けます。ドキュメントと実装を素早く連携できるのが、学習の効率化に繋がっています。


Topic 2: Null Safety と3つの演算子

🧐 クイズ:この3つはそれぞれいつ使う?

name?.length     // 1
name!.length     // 2
name ?? 'default' // 3

💭 私の最初の理解

  1. ?. → null だったら ?? でデフォルトが必要 ← ?. 自体の説明としては不十分
  2. ! → 強制、絶対入ってるよ!でもなかったら crash ← 合ってた
  3. ?? → name が null ならデフォルトを決める ← 合ってた

✅ 正しい理解

演算子 意味 null のとき
?. 安全アクセス null を返す(crash しない)
! 強制アンラップ crash する
?? フォールバック 右側の値を使う
String? name = null;

name?.length;      // → null(length を呼ばない)
name!.length;      // → runtime crash 💥
name ?? 'default'; // → 'default'

よく使うコンボ:

final len = name?.length ?? 0;
// name が null → null → 0
// name が 'hello' → 5 → 5

Topic 3: Pattern Matching(case final

🧐 クイズ:この2つの書き方、何が違う?

// 書き方A
if (user.name != null) {
  print(user.name!);
}

// 書き方B
if (user.name case final String name) {
  print(name);
}

💭 私の理解

  • A → user.name が null じゃないとき
  • B → case でパターンマッチして、final String だったら name に変数に入れる

理解はできていましたが、なぜ B が良いのかは曖昧でした。

💡 なぜ B が良いのか

A の問題:! が必要になる

user.name が getter の場合、2回目の呼び出しで値が変わる可能性があります。 だから Dart コンパイラは「さっき null チェックしたよね」を信用してくれません。 結果、! を書かされます。

B の利点:! が不要

case final String name は「その瞬間の値」をローカル変数に変数に入れる。

  • ! 不要(crash リスクゼロ)
  • 型が StringString? じゃない)
  • 変数名を短くできる(user.namename
A (!= null) B (case final)
! 必要 不要
安全性 getter だと危険 常に安全
String? のまま String に確定

似ていますが、別のパターン構文もあります:

// typed variable pattern(型が String か確認して変数に入れる)
if (user.name case final String name) { ... }

// null-check pattern(null じゃないか確認して変数に入れる)
if (user.name case final name?) { ... }

結果は似てるけど、チェックしていることが違います。final String name は型を見て、final name? は null かどうかを見ています。 最初は省略形だと思っていましたが、社内レビューで「これは別のパターン構文だよ」と指摘してもらって気づきました。

実はこの記事を書く過程で、AI も「省略形」と説明していました。AI は学習を加速してくれますが、たまに間違えます。だからこそ公式ドキュメントでの裏取りや、人からのレビューが大事です。


Topic 4: dynamic vs Object

🧐 クイズ:この3つの違いは?

List<String> names = ['a', 'b'];
List<dynamic> items = ['a', 1, true];
List<Object> things = ['a', 1, true];

💭 私の最初の理解

  • List<String> → String のみ ← 合ってた
  • List<dynamic> → なんでもいい ← 合ってたけど不十分
  • List<Object> → Object とは? ← わかってなかった

✅ 正しい理解

dynamicObject も「なんでも入る」。でも安全性が全然違います。

// dynamic: コンパイラが何もチェックしない
dynamic x = 'hello';
x.foo();   // コンパイル通る → runtime crash 💥
x.length;  // コンパイル通る → たまたま動く

// Object: Dart の全ての型の親
Object x = 'hello';
x.foo();      // コンパイルエラー ← 守ってくれる
x.length;     // コンパイルエラー ← Object に length はない
x.toString(); // OK ← 全ての Object が持つメソッド
何が入る コンパイラ 危険度
dynamic なんでも チェックしない 危ない
Object なんでも チェックする 安全

🔍 深掘り:なぜ dynamic は JSON でだけ使うのか

JSON の中身は String, int, bool, List, null が混在してる。 Dart のコンパイラはコンパイル時に「このキーの値は何型?」を知りようがありません。

そして dart:convertjsonDecodedynamic を返す。 json_serializable / freezed もこれに合わせて Map<String, dynamic> を前提にしています。

dynamic = 「型の世界」と「型がない世界」の国境ゲート

国境の中(アプリ内)では使いません。国境(JSON パース)でだけ使います。

ずっとぼんやりしてた dynamic の使い所が、この一言でスッと理解できました。


まとめ

学び直したこと Before After
const メモリ使わない コンパイル時確定、同じ値は共有される
?.?? セットで考えてた ?. 単体で null を返す。?? はフォールバック。別の役割
case final 書き方は知ってた getter の型昇格問題を解決する手段
dynamic vs Object 違いがわからなかった コンパイラチェックの有無。dynamic は JSON 境界だけ

AI 時代だからこそ、基礎を固め直す価値がある。

AI が生成したコードを読んで「これで合ってる」と判断するのも、 レビューで「なぜこう書くべきか」を説明するのも、基礎がないとできません。

そして AI は、基礎を固め直す最高のパートナーでした。 遠慮なく聞けて、すぐ深掘りできて、自分に合った説明をしてくれます。

今回はこの4つをやった。でもこれはまだ Dart の基礎の一部でしかありません。 ここから Flutter の描画の仕組み、パッケージの設計など、さらにいろんなものをしっかり重ねていきたいです。

「わかってるつもり」を「本当にわかってる」に変える。 AIがあればいつでもできる。

基礎を固めることで実装の速度が一気に上がる。 速度と加速度こそ、AI時代に必要なもの。 まず言語の基礎から始めてみてはいかがでしょうか!

🚀 We are hiring!

youtrust.jp

herp.careers




以上の内容はhttps://tech.youtrust.co.jp/entry/ai-dart-fundamentalsより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14