TypeScriptのベストプラクティスを見てみる #3

github.com TypeScriptのベストプラクティスを見てみようの第3弾です。

11. グローバル変数を避ける

グローバル変数や関数はコードのどこからでも参照できるため便利ですが、呼び出し側で再代入することができるため、上書きされてしまい意図せぬバグが引き起こされる可能性があります。

そのため変数を定義する場合にはconstキーワードを使い、スコープを最低限にして定義することが推奨されます。 グローバルスコープで定義するにしても定数定義を行います。

例:

// 悪い例
var globalVar = "Hello, World!";

function globalFunction() {
    console.log(globalVar);
}

// 良い例
(() => {
    const localVar = "Hello, World!";

    function localFunction() {
        console.log(localVar);
    }

    localFunction();
})();

12. 他の技術との混在を避ける

JavaScriptとDOMを使ってドキュメント内に必要なすべてを作成することは可能ですが、必ずしも最も効果的な方法ではないようです。CSS-in-JSのような技術が非常に人気です。しかし、JavaScript、HTML、CSSを同じファイル内で混在させることは危険とのこと。適切に関心の分離を行うことを心がけたいです。

例:

// 悪い例
const button = document.createElement('button');
button.style.backgroundColor = 'blue';
button.innerText = 'Click me';
document.body.appendChild(button);

// 良い例
// CSSファイル (styles.css)
button {
    background-color: blue;
}

// TypeScriptファイル (app.ts)
const button = document.createElement('button');
button.className = 'button';
button.innerText = 'Click me';
document.body.appendChild(button);

13. 深いネスティングを避ける

ネスティングはコードのロジックを説明しやすくし、読みやすくします。しかし、ネスティングが深すぎると、何をしようとしているのかを追うのが難しくなります。コードの読者が水平にスクロールする必要があったり、コードエディタが長い行を折り返すと混乱する可能性があります。もう一つの問題は、変数名とループです。通常、最初のループをiというイテレータ変数で始め、次にj、k、lと続けますが、これもすぐに混乱を招く可能性があります。

例:

// 悪い例
function processItems(items: any[]) {
    for (let i = 0; i < items.length; i++) {
        for (let j = 0; j < items[i].subItems.length; j++) {
            for (let k = 0; k < items[i].subItems[j].details.length; k++) {
                console.log(items[i].subItems[j].details[k]);
            }
        }
    }
}

// 良い例
function processItems(items: any[]) {
    items.forEach(item => {
        item.subItems.forEach(subItem => {
            subItem.details.forEach(detail => {
                console.log(detail);
            });
        });
    });
}

こんな感じでforEachを使いましょう。 あとはこの例に手を加えるなら、繰り返し文で処理したい最下層のオブジェクトを別の箇所で定義するのもありですね。

14. 長い関数を避ける

長い関数は、通常たくさんのことをしていることを意味します。小さな関数は読みやすく、目的を理解しやすいです。関数が10行以上の場合、より小さな関数に分けるべきかどうかを検討する必要があります。最良の関数やメソッドは5〜10行のコードです。メソッド自体は一つのことを行っているかもしれませんが、その内部では他のいくつかの操作が行われていることがあります。これらの操作を独自のメソッドに抽出し、それぞれ一つのことを行わせることができます。

例:

// 悪い例
function processData(data: any[]) {
    let result = [];
    for (let i = 0; i < data.length; i++) {
        if (data[i].isActive) {
            let item = data[i];
            item.processed = true;
            result.push(item);
        }
    }
    return result;
}

// 良い例
function filterActiveData(data: any[]) {
    return data.filter(item => item.isActive);
}

function processItem(item: any) {
    item.processed = true;
    return item;
}

function processData(data: any[]) {
    return filterActiveData(data).map(processItem);
}

15. 関数パラメータを減らす

関数パラメータの数を制限することは非常に重要です。なぜなら、これにより関数のテストが容易になるからです。3つ以上のパラメータを持つと、テストシナリオの組み合わせが爆発的に増加します。理想的なケースは1〜2つの引数であり、3つは可能であれば避けるべきです。それ以上の場合は、通常、高レベルのオブジェクトを引数として渡すことで十分です。

例:

// 悪い例
function createUser(name: string, age: number, address: string, isAdmin: boolean) {
    // ユーザー作成ロジック
}

// 良い例
interface UserOptions {
    name: string;
    age: number;
    address: string;
    isAdmin: boolean;
}

function createUser(options: UserOptions) {
    // ユーザー作成ロジック
}

const userOptions: UserOptions = {
    name: "John Doe",
    age: 30,
    address: "123 Main St",
    isAdmin: true
};

createUser(userOptions);