メインコンテンツまでスキップ

変数の定義

変数を定義する際の重要なポイント

良い変数定義は、コードの可読性と保守性を大幅に向上させます。

変数定義の原則
  • マジックナンバーを避ける
  • 共通値は定数で保持する
  • 変数に複数の意味を持たせない
  • 変数のスコープを最小限にする
  • const を優先的に使用する
  • 適切な型を指定する

マジックナンバー・マジックキャラクタを避ける

マジックナンバーとは、コード中に直接書かれた数値で、その意味が不明確なものです。

❌ Bad: マジックナンバー

❌ Bad: マジックナンバー
// ❌ 7 や 24 が何を意味するのか不明
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);

例外: マジックナンバーが許容されるケース

✅ 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;
}

✅ 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 の意味

❌ Bad: 再代入は不可
// 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'; // 実行時エラー(strictモード)

// ✅ 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;
}
}

グローバルスコープは使用しない

グローバル変数は、プログラム全体から参照・変更できるため、以下の問題があります:

グローバル変数の問題
  • どこで変更されるか分からない
  • 名前の衝突が起きやすい
  • テストが困難
  • モジュール化が難しい

❌ Bad: グローバル変数

❌ Bad: グローバル変数
// ❌ グローバル変数
let currentUser: User;
let isLoggedIn: boolean;

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);

改善のポイント:

  1. マジックナンバーを定数化(TAX_RATE, SHIPPING_FEE など)
  2. ループ変数のスコープを最小化
  3. 各計算ステップに意味のある変数名を使用
  4. グローバル変数をクラスで管理
  5. 型を明示(Item インターフェース)

変数定義のまとめ

変数定義のベストプラクティス
  • マジックナンバーは定数化する
  • 共通値は一箇所で定義する
  • 一つの変数に一つの目的
  • const を優先的に使用する
  • スコープを最小限にする
  • グローバル変数を避ける
  • 分割代入で意味を明確にする
  • 型を明示する