変数の定義
変数を定義する際の重要なポイント
良い変数定義は、コードの可読性と保守性を向上させます。
変数定義の原則
- マジックナンバーを避ける
- 共通値は定数で保持する
- 変数に複数の意味を持たせない
- 変数のスコープを最小限にする
- const を優先的に使用する
- 適切な型を指定する
マジックナンバー・マジックキャラクタを避ける
マジックナンバーとは、コード中に直接書かれた数値で、その意味が不明確なものです。
❌ Bad: マジックナンバー
❌ Bad: マジックナンバー
// ❌ 7 が何を意味するのか不明
function getWeekdayName(index: number): string {
const days = ['日', '月', '火', '水', '木', '金', '土'];
return days[index % 7];
}
// ❌ 86400 が何を意味するのか不明
function convertDaysToSeconds(days: number): number {
return days * 86400;
}
// ❌ 0, 1, 2 が何を表すのか不明
if (user.status === 0) {
console.log('アクティブ');
} else if (user.status === 1) {
console.log('一時停止中');
} else if (user.status === 2) {
console.log('削除済み');
}
// ❌ 1.1 が何を意味するのか不明
const totalPrice = price * 1.1;
✅ Good: 名前付き定数を使用
✅ Good: 名前付き定数を使用
// ✅ 定数で意味を明確にする
const DAYS_IN_WEEK = 7;
function getWeekdayName(index: number): string {
const days = ['日', '月', '火', '水', '木', '金', '土'];
return days[index % DAYS_IN_WEEK];
}
// ✅ 計算式が理解しやすい
const HOURS_PER_DAY = 24;
const MINUTES_PER_HOUR = 60;
const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;
function convertDaysToSeconds(days: number): number {
return days * SECONDS_PER_DAY;
}
// ✅ Enumで状態を明確にする
enum UserStatus {
Active = 0,
Suspended = 1,
Deleted = 2,
}
if (user.status === UserStatus.Active) {
console.log('アクティブ');
} else if (user.status === UserStatus.Suspended) {
console.log('一時停止中');
} else if (user.status === UserStatus.Deleted) {
console.log('削除済み');
}
// ✅ 税率を定数化
const TAX_RATE = 0.1;
const totalPrice = price * (1 + TAX_RATE);
enum について
enum でも状態の意味は明確にできますが、新規コードでは as const オブジェクト + Union 型リテラルの利用が推奨されます。詳細は Union型・リテラル型・enum を参照してください。
例外: マジックナンバーが許容されるケース
✅ Good: 以下のような自明な値は定数化不要
// ✅ 以下のような自明な値は定数化不要
// 配列の初期化
const emptyArray: number[] = [];
const firstItem = items[0];
// 比較演算
if (count === 0) { // ✅ 0 は自明
console.log('空です');
}
// 数学的な定数
const double = value * 2; // ✅ 2倍は自明
const half = value / 2; // ✅ 半分は自明
// ループ
for (let i = 0; i < 10; i++) { // ✅ 小さいループ範囲は許容
// ...
}
// ただし、意味がある場合は定数化する
const MAX_RETRY_COUNT = 10;
for (let i = 0; i < MAX_RETRY_COUNT; i++) {
// ...
}
共通して使用する値は定数で保持する
プロジェクト全体で使う値は、1箇所で定義して再利用します。
❌ Bad: 値が散らばっている
❌ Bad: 値が散らばっている
// ❌ 同じ値が複数箇所に
// file1.ts
if (age >= 20) {
// ...
}
// file2.ts
function canVote(age: number): boolean {
return age >= 20;
}
// file3.ts
const isAdult = user.age >= 20;
問題点:
- 値を変更する際、全箇所を修正する必要がある
- 修正漏れが発生しやすい
✅ Good: 定数で一元管理
✅ Good: 定数で一元管理
// constants.ts
export const ADULT_AGE = 20;
export const TAX_RATE = 0.1;
export const MAX_FILE_SIZE_MB = 10;
export const API_BASE_URL = 'https://api.example.com';
// file1.ts
import { ADULT_AGE } from './constants';
if (age >= ADULT_AGE) {
// ...
}
// file2.ts
import { ADULT_AGE } from './constants';
function canVote(age: number): boolean {
return age >= ADULT_AGE;
}
// file3.ts
import { ADULT_AGE } from './constants';
const isAdult = user.age >= ADULT_AGE;
メリット:
- 値を変更する際は1箇所のみ修正
- 値の意味が明確
- タイポを防げる
変数に本来の目的以外の情報を持たせない
❌ Bad: 複数の意味を持つ変数
❌ Bad: 複数の意味を持つ変数
// ❌ Bad: result が複数の意味を持つ
// -1: エラー, 0以上: 成功時のインデックス
function findUser(name: string): number {
const index = users.findIndex(u => u.name === name);
if (index === -1) {
return -1; // エラー
}
return index; // 成功
}
const result = findUser('Taro');
if (result === -1) {
console.log('ユーザーが見つかりません');
} else {
console.log(`インデックス: ${result}`);
}
問題点:
-1という特別な値でエラーを表現- 型だけでは成功/失敗が分からない
✅ Good: 明示的な型で表現
✅ Good: 明示的な型で表現
// ✅ Good: 成功と失敗を型で区別
type FindResult =
| { success: true; index: number }
| { success: false; error: string };
function findUser(name: string): FindResult {
const index = users.findIndex(u => u.name === name);
if (index === -1) {
return { success: false, error: 'User not found' };
}
return { success: true, index };
}
const result = findUser('Taro');
if (result.success) {
console.log(`インデックス: ${result.index}`);
} else {
console.log(result.error);
}
// ✅ または undefined を使用
function findUserIndex(name: string): number | undefined {
const index = users.findIndex(u => u.name === name);
return index !== -1 ? index : undefined;
}
const index = findUserIndex('Taro');
if (index !== undefined) {
console.log(`インデックス: ${index}`);
} else {
console.log('ユーザーが見つかりません');
}
分割代入で値を明示する
配列の添え字アクセスよりも、分割代入で意味を明確にします。
❌ Bad: 添え字アクセス
❌ Bad: 添え字アクセス
// ❌ [0], [1] が何を意味するのか不明
const data = getUserData();
const name = data[0];
const age = data[1];
const email = data[2];
// ❌ split の結果が分かりにくい
const parts = '2024-01-15'.split('-');
const year = parts[0];
const month = parts[1];
const day = parts[2];
✅ Good: 分割代入
✅ Good: 分割代入
// ✅ 分割代入で意味が明確
const [name, age, email] = getUserData();
// ✅ さらに明確
const [year, month, day] = '2024-01-15'.split('-');
// ✅ オブジェクトの場合
const { name, age, email } = user;
// ✅ 配列の一部のみ取得
const [first, second, ...rest] = items;
const [, , third] = items; // 3番目のみ
// ✅ デフォルト値
const [name = 'Unknown', age = 0] = getUserData();
一つの目的に一つの変数を定義する
❌ Bad: 変数を使い回す
❌ Bad: 変数を使い回す
// ❌ temp を複数の用途で使い回す
let temp = user.firstName + ' ' + user.lastName;
console.log(temp);
temp = temp.toUpperCase();
console.log(temp);
temp = calculateAge(user.birthDate);
console.log(temp); // tempが年齢を表すのか分かりにくい
問題点:
tempが何を表すのか変わる- デバッグが困難
- 意図が不明確
✅ Good: 各目的に専用の変数
✅ Good: 各目的に専用の変数
// ✅ 各変数が明確な意味を持つ
const fullName = user.firstName + ' ' + user.lastName;
console.log(fullName);
const fullNameUpper = fullName.toUpperCase();
console.log(fullNameUpper);
const age = calculateAge(user.birthDate);
console.log(age);
❌ Bad: 変数の再利用
❌ Bad: 変数の再利用
// ❌ result を使い回す
function processData(input: string): number {
let result = input.trim();
result = result.toLowerCase();
result = result.replace(/\s+/g, '');
result = result.length; // 型が変わる!
return result;
}
なお、このコードは TypeScript では TS2322 のコンパイルエラーになります(string と推論された result に number を代入できないため)。型が変わる再利用は TypeScript が構造的に防いでくれますが、同じ型での使い回し(前述の temp の例)は検出されないため、原則として 1 変数 1 目的を守りましょう。
✅ Good: 各ステップで新しい変数
✅ Good: 各ステップで新しい変数
// ✅ 各ステップが明確
function processData(input: string): number {
const trimmed = input.trim();
const lowercase = trimmed.toLowerCase();
const withoutSpaces = lowercase.replace(/\s+/g, '');
const length = withoutSpaces.length;
return length;
}
// ✅ またはメソッドチェーン
function processData(input: string): number {
return input
.trim()
.toLowerCase()
.replace(/\s+/g, '')
.length;
}
オブジェクトや配列は const で定義する
TypeScriptの const の意味
const の意味(再代入不可 ≠ 不変)
// const は「再代入不可」を意味する(不変ではない)
const user = { name: 'Taro' };
user.name = 'Hanako'; // ✅ プロパティの変更は可能
// user = { name: 'Jiro' }; // ❌ 再代入は不可
const items = [1, 2, 3];
items.push(4); // ✅ 要素の追加は可能
// items = [5, 6]; // ❌ 再代入は不可
❌ Bad: let を使用
❌ Bad: let を使用
// ❌ 再代入しないのに let を使用
let user = { name: 'Taro' };
let items = [1, 2, 3];
// 後で再代入されるのか、プロパティが変更されるのか分からない
✅ Good: const を使用
✅ Good: const を使用
// ✅ 再代入されないことが明確
const user = { name: 'Taro' };
const items = [1, 2, 3];
user.name = 'Hanako'; // プロパティ変更は可能
items.push(4); // 要素追加は可能
完全に不変にする場合
✅ Good: as const で型レベルで不変(コンパイル時チェック)
// ✅ as const で型レベルで不変(コンパイル時チェック)
const user = { name: 'Taro' } as const;
// user.name = 'Hanako'; // ❌ エラー
const items = [1, 2, 3] as const;
// items.push(4); // ❌ エラー
// ✅ Object.freeze で実行時に不変
const user2 = Object.freeze({ name: 'Taro' });
// user2.name = 'Hanako'; // ❌ コンパイルエラー (TS2540)。型を回避しても strict モードで実行時 TypeError
// ✅ ReadonlyArray
const items2: readonly number[] = [1, 2, 3];
// items2.push(4); // ❌ コンパイルエラー
変数の寿命(スコープ)を短くする
変数のスコープが広いと、以下の問題が発生します:
スコープが広い場合の問題
- どこで値が変更されるか追いにくい
- 意図しない再代入が発生しやすい
- 名前の衝突が起きやすい
- メモリが解放されにくい
❌ Bad: スコープが広い
❌ Bad: スコープが広い
// ❌ 関数全体で使われる変数
function processUsers(): void {
let user: User; // スコープが広すぎる
let result: string;
for (let i = 0; i < users.length; i++) {
user = users[i];
if (user.isActive) {
result = `${user.name} is active`;
console.log(result);
}
}
// user と result が意図せず使える
console.log(user); // 最後のユーザー
}
✅ Good: スコープを最小限にする
✅ Good: スコープを最小限にする
// ✅ 必要な場所で定義
function processUsers(): void {
for (let i = 0; i < users.length; i++) {
const user = users[i]; // ループ内でのみ有効
if (user.isActive) {
const result = `${user.name} is active`; // if ブロック内でのみ有効
console.log(result);
}
}
}
// ✅ より良い: forEach を使用
function processUsers(): void {
users.forEach(user => {
if (user.isActive) {
const result = `${user.name} is active`;
console.log(result);
}
});
}
for ループでのスコープ
❌ Bad: ループ前に変数を定義
// ❌ Bad: ループ前に変数を定義
let i: number;
for (i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // ループ外でも使える(意図しない)
// ✅ Good: ループ内でのみ有効
for (let i = 0; i < 10; i++) {
console.log(i);
}
// console.log(i); // ❌ エラー(スコープ外)
レキシカルスコープは極力使わない
レキシカルスコープとは、変数のスコープがソースコード上の宣言位置で決まる仕組みです。これにより、内側の関数(クロージャ)は外側の関数の変数を参照・変更できます。ここで避けたいのは、レキシカルスコープを利用して外側の可変変数に暗黙に依存するパターンです。
❌ Bad: 外側の変数に依存
❌ Bad: 外側の変数に依存
// ❌ 外側の変数に依存している
function createCounter() {
let count = 0; // 外側の変数
function increment() {
count++; // 外側の count を変更
console.log(count);
}
function decrement() {
count--; // 外側の count を変更
console.log(count);
}
function reset() {
count = 0; // 外側の count を変更
}
return { increment, decrement, reset };
}
問題点:
countがどこで変更されるか追いにくい- テストが困難
✅ Good: クラスで状態管理を明示する
✅ Good: クラスで状態管理を明示する
// ✅ クラスで状態を管理
class Counter {
private count = 0;
increment(): void {
this.count++;
console.log(this.count);
}
decrement(): void {
this.count--;
console.log(this.count);
}
reset(): void {
this.count = 0;
}
getCount(): number {
return this.count;
}
}
クラスにすると、this.count の変更箇所がクラス定義内に閉じ、状態とそれを変更するメソッドが 1 箇所に集まります。状態の置き場所と変更経路が明示されるため、変更を追いやすくテストもしやすくなります。
グローバルスコープは使用しない
グローバル変数は、プログラム全体から参照・変更できるため、以下の問題があります:
グローバル変数の問題
- どこで変更されるか分からない
- 名前の衝突が起きやすい
- テストが困難
- モジュール化が難しい
❌ Bad: グローバル変数
❌ Bad: グローバル変数
// ❌ グローバル変数
let currentUser: User | null = null;
let isLoggedIn = false;
function login(user: User) {
currentUser = user;
isLoggedIn = true;
}
function logout() {
currentUser = null;
isLoggedIn = false;
}
✅ Good: モジュールスコープまたはクラス
✅ Good: モジュールスコープまたはクラス
// ✅ モジュールスコープ(ファイル内でのみアクセス可能)
// auth.ts
let currentUser: User | null = null;
let isLoggedIn = false;
export function login(user: User): void {
currentUser = user;
isLoggedIn = true;
}
export function logout(): void {
currentUser = null;
isLoggedIn = false;
}
export function getCurrentUser(): User | null {
return currentUser;
}
export function checkIsLoggedIn(): boolean {
return isLoggedIn;
}
// ✅ さらに良い: クラスで状態を管理
export class AuthService {
private currentUser: User | null = null;
login(user: User): void {
this.currentUser = user;
}
logout(): void {
this.currentUser = null;
}
getCurrentUser(): User | null {
return this.currentUser;
}
isLoggedIn(): boolean {
return this.currentUser !== null;
}
}
異なるスコープで同じ変数名を使用しない
❌ Bad: シャドーイング(変数の隠蔽)
❌ Bad: シャドーイング(変数の隠蔽)
// ❌ 同じ名前の変数がネストしている
const name = 'Global';
function greet() {
const name = 'Function'; // 外側の name を隠蔽
console.log(name); // "Function"
if (true) {
const name = 'Block'; // さらに隠蔽
console.log(name); // "Block"
}
console.log(name); // "Function"
}
console.log(name); // "Global"
問題点:
- どの
nameを参照しているのか分かりにくい - リファクタリング時にバグを生みやすい
✅ Good: 異なる名前を使用
✅ Good: 異なる名前を使用
// ✅ 異なる名前で明確に
const globalName = 'Global';
function greet() {
const functionName = 'Function';
console.log(functionName);
if (true) {
const blockName = 'Block';
console.log(blockName);
}
console.log(functionName);
}
console.log(globalName);
練習問題 7: 変数の定義
以下のコードを改善してください。
❌ Bad: 修正前
// ❌ 修正前
function calculatePrice(items: any[]) {
let total = 0;
let i;
for (i = 0; i < items.length; i++) {
total = total + items[i].price;
}
// 消費税を加算
total = total * 1.1;
// 配送料を加算
if (total < 5000) {
total = total + 500;
}
return total;
}
let userName = 'Taro';
let userAge = 25;
function updateUser() {
userName = 'Hanako';
userAge = 30;
}
📝 解答を見る
✅ Good: 修正後
// ✅ 修正後
// 定数を定義
const TAX_RATE = 0.1;
const FREE_SHIPPING_THRESHOLD = 5000;
const SHIPPING_FEE = 500;
interface Item {
price: number;
}
function calculatePrice(items: Item[]): number {
// 小計を計算
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
// 消費税込み価格
const priceWithTax = subtotal * (1 + TAX_RATE);
// 配送料
const shippingFee = priceWithTax < FREE_SHIPPING_THRESHOLD ? SHIPPING_FEE : 0;
// 合計金額
const totalPrice = priceWithTax + shippingFee;
return totalPrice;
}
// ✅ グローバル変数を避け、クラスで管理
class User {
constructor(
private name: string,
private age: number
) {}
updateUser(name: string, age: number): void {
this.name = name;
this.age = age;
}
getName(): string {
return this.name;
}
getAge(): number {
return this.age;
}
}
const user = new User('Taro', 25);
user.updateUser('Hanako', 30);
改善のポイント:
- マジックナンバーを定数化(TAX_RATE, SHIPPING_FEE など)
- ループ変数のスコープを最小化
- 各計算ステップに意味のある変数名を使用
- グローバル変数をクラスで管理
- 型を明示(Item インターフェース)
変数定義のまとめ
変数定義のベストプラクティス
- マジックナンバーは定数化する
- 共通値は一箇所で定義する
- 1つの変数に1つの目的
- const を優先的に使用する
- スコープを最小限にする
- グローバル変数を避ける
- 分割代入で意味を明確にする
- 型を明示する