僕は現在、TypeScript入門書『サバイバルTypeScript』を書いています。この本を書く過程で、わかりやすくて効果的な学習体験を提供するため、サンプルコードの制作には特別な注意を払っています。
サンプルコードは教育的な文書で重要な役割を果たします。しかし、よく考えられていないサンプルコードは学習者を混乱させ、思考の妨げになることさえあります。そのため、以下の6つのポイントに基づいてサンプルコードを制作するように心がけています。
- シンプルにする
- クリアにする
- コメントで解説する
- 未導入の機能を避ける
- 完全な動作を示す
- コードの長さに注意する
1. シンプルにする
コードは学習者が取り組んでいる特定の問題を解決する最短・最も簡単な方法を示すようにします。無関係な要素は省いて、特定のコンセプトや機能に焦点を当てます。
例: TypeScriptのforループを解説したいとき
✅Do: 最低限の機能のみを用いる
let sum = 0;
for (let i = 1; i <= 100; i++) {
sum += i;
}
let
とfor
だけの最低限の言語機能を用いていたシンプルな例になっています。
❌Don't: 不要な機能を持ち込んでいる
import * as fs from "fs";
let sum = 0;
for (let i = 1; i <= 100; i++) {
sum += i;
fs.writeFileSync(`log_${i}.txt`, `Current sum: ${sum}`);
}
ループを解説するにあたって、不必要なNode.jsのfs
モジュールを用いてしまっています。
2. クリアにする
具体的で意味の通じる変数名や関数名を用います。コードが何を達成するのかを完全に理解できるようにします。逆に、過剰にリアリティを持たせないようにもします。
例: 配列の長さを出力するコードを示す場合。
✅ Do: 具体的な変数名を用いる
const fruits = ["apple", "banana", "cherry"];
console.log(fruits.length);
このコードでは、変数名 fruits
が具体的で、それが何を意味するかが明確です。
❌ Don't: 変数名が抽象的するぎる
const x = ["apple", "banana", "cherry"];
console.log(x.length);
このコードでは、変数名 x
が抽象的で、その中身がはっきりしないため読み手は混乱するかもしれません。
例: 乗算演算子の例を示すとき
✅ Do: ちょうどいい抽象度の変数名
const x = 2 * 2;
console.log(x);
x
は抽象的な変数名ですが、乗算を説明するだけなら十分な抽象度です。
❌ Don't: リアリティーを求めすぎる
const price = 100;
const tax = 1.1;
const priceWithTax = price * tax;
このコードでは、リアリティを求めて実世界の用例を示していますが、読者に注目してほしい点は乗算演算子*
です。変数price
やtax
は乗算とは本質的に関係ないため省くほうが、読者が読み込む分量が減ります。
3. コメントで解説する
各関数や複雑なコードスニペットに説明を加えます。これにより学習者は、そのコードブロックが何をするのか、なぜそのコード行が存在するのかを理解できます。
例: 初学者に配列の扱いを説明するとき
✅ Do: 説明を含める
// 配列を初期化
const fruits = ["apple", "banana", "cherry"];
// 配列の長さを取得し、コンソールに出力
console.log(fruits.length);
読者のレベル感を意識して、それぞれのコード行が何を行っているかについての説明が明確に記述されています。TypeScriptの理解が十分でない初学者にとって、コードの意味を理解する手助けになります。
❌ Don't: 説明を含めない
const fruits = ["apple", "banana", "cherry"];
console.log(fruits.length);
console.log
や.length
の事前知識を必要となるので、コードの意味を理解するのが難しくなるかもしれません。
例: await文の構文について説明するとき
✅ Do: 注目を促す
async function fetchData(): Promise<void> {
const response = await fetch("https://api.example.com/data");
// ^^^^^ awaitは関数呼び出しの前に書く
console.log(response);
}
この例では、非同期処理が行われている具体的な行に注目を促すためにコメントが配置されています。読者の注目がその部分に向かうよう指導され、次に何が起きるべきかについて考えるきっかけを作ることができます。
❌ Don't: 注目を浴びる機会を失う
async function fetchData(): Promise<void> {
const response = await fetch("https://api.example.com/data");
console.log(response);
}
この例では、通信が成功するまで待つための非同期処理の行が他のコードの流れの中にあり、その重要性がはっきりせず伝わりにくいです。それが非同期処理であるという具体的な言及がなければ、読者はその特性を見落とす可能性があります。
例:配列の数値をすべて合計出力するコードを示す場合。
✅ Do: 出力結果のコメントを含める
const nums = [2, 3, 5, 7, 11, 13];
const sum = nums.reduce((a, v) => a + v, 0);
console.log(sum); //=> 41
このコードでは、console.log
の出力がどのようなものかがコメントで明示されています。これにより、学習者がコードを実行せずにその結果を理解できます。
❌Don't: 出力結果のコメントを含めない
const nums = [2, 3, 5, 7, 11, 13];
const sum = nums.reduce((a, v) => a + v, 0);
console.log(sum);
このコードでは、どのような出力がされるかは不明であり、学習者は自分でコードを実行しなければなりません。その結果、理解するのが難しくなる場合があります。
4. 未導入の機能を避ける
学習者がまだ理解していない機能はコードから除きます。これにより、学習者は新しいコンセプトに集中し、混乱を避けられます。
例: asyncをまだ紹介していない段階で、Promiseを使った非同期処理を紹介したいとき
✅ Do: Promiseを直接使用
const fetchData = () => {
return fetch("https://api.example.com/data");
};
fetchData()
.then((response) => response.json())
.then((data) => console.log(data))
.catch((err) => console.error(err));
このコードでは、Promiseと.then/.catchの構文のみを使用して非同期データの取得を行っています。async/awaitはまだ導入されていないため、使わないようにしています。
❌ Don't: 未導入のasyncを使用
const fetchData = async () => {
const response = await fetch("https://api.example.com/data");
return response.json();
};
fetchData()
.then((data) => console.log(data))
.catch((err) => console.error(err));
このコードでは、未導入のasync/await構文が使用されています。これにより、まだasync/awaitについて学習していない読者は混乱する可能性があります。
5. 完全な動作を示す
サンプルコードは、そのまま実行した場合に正常に動作する完全なコードであるべきです。
例: 文字列の配列をコンソールに出力するコードを紹介したいとき
✅ Do: 完全に動くコード
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
このコードをそのまま実行すると、すべてのフルーツがコンソールに出力されます。完全に動作するコードとして学習者に提供できます。
❌ Don't: コードの一部しかない
for (const fruit of fruits) {
console.log(fruit);
}
このコードはそのままではエラーになるため、学習者はこの断片の意図を理解するために補足的な情報を必要とします。
6. コードの長さに注意する
長すぎるコードは読者に圧倒感を与え、ドキュメントから離れる可能性があります。一方で、複雑な概念を説明するためには長いコードが必要な場合もありますが、その場合でも可読性を保つようにします。
例: クラスとインスタンスを作成する例を紹介する。
✅ Do: こじんまりしたサンプル
class Dog {
name: string;
constructor(name: string) {
this.name = name;
}
bark() {
console.log(`${this.name} says woof`);
}
}
❌ Don't: 長すぎて読者が興味を失う可能性があるサンプル
class Animal {
name: string;
species: string;
age: number;
breed: string;
color: string;
weight: number;
constructor(
name: string,
species: string,
age: number,
breed: string,
color: string,
weight: number
) {
this.name = name;
this.species = species;
this.age = age;
this.breed = breed;
this.color = color;
this.weight = weight;
}
eat() {
console.log(`${this.name} is eating`);
}
sleep() {
console.log(`${this.name} is sleeping`);
}
// ... さらに複数のメソッド
}
上記のコードは動作しますが、膨大な情報が含まれており、読者が混乱する可能性があります。さらに、シンプルな振る舞いの例を見せるのに多くの余計な情報(年齢、品種、体重など)が持ち込まれています。
おわり
サンプルコードは学習加速器であるべきです。それは、明快で理解しやすく、そして正確であるべきだと考えています。この記事で取り上げたポイントは常にすべてを満たすことは難しいものの、ポイントを基本に添えつつケースバイケースでより良いサンプルコードを書くことを意識することで、読者の知識獲得がスムーズになり、全体的な学習体験が良くなるであろうと思います。ご参考になったら嬉しいです。