きれいなコード: 関数 (Javascript で)

Mar 26 2023
コードベース内の複雑で理解しにくい機能に苦労していませんか? より保守しやすく、再利用可能で、エラーが発生しにくい関数を作成しますか? もしそうなら、「Clean Code: Functions」について学ぶ必要があります。Robert C.

コードベース内の複雑で理解しにくい機能に苦労していませんか? より保守しやすく、再利用可能で、エラーが発生しにくい関数を作成しますか? もしそうなら、「Clean Code: Functions」について学ぶ必要があります。Robert C. Martin 著『Clean Code』で概説されているベスト プラクティスに従うことで、読みやすく、テストしやすく、維持しやすい関数を作成する方法を学ぶことができます。このブログ投稿では、クリーン コード関数の主要な概念を探り、これらの概念を独自のコードに実装するのに役立つ実用的なヒントと例を提供します。私たちの助けを借りて、コーディング スキルを次のレベルに引き上げ、楽しく作業できる関数を作成できます。では、クリーン コード関数について学習する準備はできていますか? 飛び込みましょう!

目次

·小さい関数は大きい関数よりも優れている
·関数は 1 つのことを行う必要がある
·関数にはわかりやすい名前を付ける必要がある · 関数は
引数の数を制限する必要がある
·関数には副作用があってはならない
·関数はグローバル変数に依存してはならない
·関数は過度であってはならない深くネスト
·エラーコードではなく例外を使用する
·まとめ

小さな関数は大きな関数よりも優れています

小さな関数は、通常 1 つのことを行い、明確な目的を持っているため、読みやすく理解しやすいものです。これにより、確認または変更するコードが少なくなるため、テストと保守が容易になります。一方、大規模な関数はより複雑で理解しにくく、バグやエラーにつながる可能性があります。

// Large function
function processOrders(orders) {
  const total = orders.reduce((acc, order) => acc + order.price, 0);
  const orderIds = orders.map(order => order.id);
  const orderCount = orders.length;
  const orderSummary = `You have ${orderCount} orders with IDs ${orderIds.join(', ')} for a total of $${total}.`;
  console.log(orderSummary);
  return orderSummary;
}

// --------------------------------------------------------------

// Refactored into smaller functions
function getTotalPrice(orders) {
  return orders.reduce((acc, order) => acc + order.price, 0);
}
function getOrderIds(orders) {
  return orders.map(order => order.id);
}
function getOrderCount(orders) {
  return orders.length;
}
function generateOrderSummary(orders) {
  const total = getTotalPrice(orders);
  const orderIds = getOrderIds(orders);
  const orderCount = getOrderCount(orders);
  return `You have ${orderCount} orders with IDs ${orderIds.join(', ')} for a total of $${total}.`;
}
function logOrderSummary(orders) {
  const orderSummary = generateOrderSummary(orders);
  console.log(orderSummary);
  return orderSummary;
}

このgetTotalPrice()関数は注文の配列を受け取り、reduce()関数を使用して各注文の価格を合計することにより、合計価格を返します。関数getOrderIds()は注文の配列を受け取り、関数を使用して ID の配列を返しますmap()。このgetOrderCount()関数は注文の配列を受け取り、lengthプロパティを使用してその数を返します。最後に、このgenerateOrderSummary()関数は注文の配列を受け取り、3 つの小さな関数を使用して要約文字列を生成します。このlogOrderSummary()関数は注文の配列を受け取り、要約文字列をコンソールに記録します。

関数は 1 つのことを行う必要があります

各機能には、明確で明確な目的が必要です。関数が複数のことを行う場合、理解、テスト、および保守が難しくなる可能性があります。大きなタスクを小さな関数に分割することで、より読みやすく保守しやすいコードを作成できます。

// Function that performs multiple tasks
function processUserInput(input) {
  const inputLines = input.split('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
  const trimmedLines = inputLines.map(line => line.trim());
  const nonEmptyLines = trimmedLines.filter(line => line !== '');
  const formattedLines = nonEmptyLines.map(line => `> ${line}`);
  return formattedLines.join('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
}
// --------------------------------------------------------------
// Refactored into smaller functions
function splitInput(input) {
  return input.split('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
}
function trimLines(lines) {
  return lines.map(line => line.trim());
}
function removeEmptyLines(lines) {
  return lines.filter(line => line !== '');
}
function formatLines(lines) {
  return lines.map(line => `> ${line}`);
}
function joinLines(lines) {
  return lines.join('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
}
function processUserInput(input) {
  const lines = splitInput(input);
  const trimmedLines = trimLines(lines);
  const nonEmptyLines = removeEmptyLines(trimmedLines);
  const formattedLines = formatLines(nonEmptyLines);
  return joinLines(formattedLines);
}

このsplitInput()関数は文字列を受け取り、split()メソッドを使用してそれを行の配列に分割します。このtrimLines()関数は行の配列を受け取り、map()メソッドを使用して各行の先頭と末尾から空白を削除します。このremoveEmptyLines()関数は行の配列を受け取り、filter()メソッドを使用して空の行を削除します。このformatLines()関数は行の配列を受け取り、メソッドを使用して各行の先頭に文字をmap()追加します。>このjoinLines()関数は行の配列を取り、join()メソッドを使用してそれらを改行で区切られた単一の文字列に結合します。最後に、processUserInput()関数は小さい関数を使用して入力文字列を処理します。

関数にはわかりやすい名前を付ける必要があります

関数名は、関数の機能を正確に説明し、関数の実装を読まなくても理解しやすいものにする必要があります。わかりやすい関数名は、コードをより自明なものにし、コメントの必要性を減らすのに役立ちます。

// Function with a vague name
function processData(data) {
  // ...
}
// --------------------------------------------------------------
// Refactored with a descriptive name
function processUserData(userData) {
  // ...
}

関数は引数の数を制限する必要があります

多数の引数を取る関数は、使用、理解、およびテストが難しくなる可能性があります。可能であれば、引数の数を 3 つまたは 4 つ未満に抑えることを目指してください。引数の数を減らす 1 つの方法は、それらを単一のオブジェクトまたはデータ構造として渡すことです。

// Function with too many arguments
function createPerson(name, age, address, phone, email) {
  // ...
}

// --------------------------------------------------------------
// Refactored to use an object instead
function createPerson(personData) {
  // ...
}
// Now, we can pass an object with properties for name, age, address, phone, and email:

const personData = {
  name: 'John Doe',
  age: 30,
  address: '123 Main St',
  phone: '555-555-1234',
  email: '[email protected]'
};
const person = createPerson(personData);

関数に副作用があってはならない

関数が自身のスコープ外の状態を変更すると、副作用が発生します。副作用により、関数の動作のテストと推論が難しくなり、予期しない動作につながる可能性もあります。関数は、独自のパラメーターとローカル変数でのみ動作する必要があり、スコープ外の状態を変更してはなりません。

// Example with side effects
let arr = [1, 2, 3];
function popLastElement() {
  arr.pop(); // modifies the external state of `arr`
}
popLastElement();
console.log(arr); // Output: [1, 2]

// --------------------------------------------------------------
// Example without side effects
function getLastElement(arr) {
  return arr[arr.length - 1]; // returns a new value without modifying the input
}
let arr = [1, 2, 3];
let lastElement = getLastElement(arr);
console.log(lastElement); // Output: 3

関数はグローバル変数に依存すべきではない

グローバル変数を使用すると、関数の理解とテストが難しくなり、予期しない動作が発生する可能性があります。関数は、独自のパラメーターとローカル変数でのみ動作する必要があり、グローバル状態に依存するべきではありません。これにより、さまざまなコンテキストで関数を再利用することも容易になります。

// Example relying on global state
let x = 0;
function increment() {
  x++;
}
increment();
console.log(x); // Output: 1

// --------------------------------------------------------------
// Example without relying on global state
function increment(x) {
  return x + 1;
}
let y = 0;
y = increment(y);
console.log(y); // Output: 1

関数の入れ子が深すぎてはならない

深くネストされた関数は、読み取りや理解が難しくなるだけでなく、制御構造が複雑になる可能性があります。関数は、ネストされた関数の複数のレベルを頭の中で追跡する必要がなく、制御の流れを理解しやすい方法で構造化する必要があります。

// Example with deeply nested functions
function add(a, b) {
  function increment(x) {
    return x + 1;
  }
  let c = increment(a);
  let d = increment(b);
  return c + d;
}
console.log(add(1, 2)); // Output: 5

// --------------------------------------------------------------
// Example with flatter function structure
function increment(x) {
  return x + 1;
}
function add(a, b) {
  let c = increment(a);
  let d = increment(b);
  return c + d;
}
console.log(add(1, 2)); // Output: 5

エラーコードではなく例外を使用する

エラー コードがあると、コードについての推論が難しくなり、制御構造が複雑になる可能性があります。例外により、より自然な制御フローと簡単なエラー処理が可能になります。関数は、エラー コードや値を返すのではなく、エラーや予期しない動作を示すために例外をスローする必要があります。

// Example with error code
function divide(a, b) {
  if (b === 0) {
    return -1; // error code for divide by zero
  }
  return a / b;
}
let result = divide(4, 0);
if (result === -1) {
  console.log("Error: Divide by zero"); // error handling with error code
}

// --------------------------------------------------------------
// Example with exception
function divide(a, b) {
  if (b === 0) {
    throw new Error("Divide by zero"); // throws an exception
  }
  return a / b;
}
try {
  let result = divide(4, 0);
} catch (e) {
  console.log("Error: " + e.message); // error handling with exception
}

結論

結論として、コードベースの全体的な品質を向上させるためには、関数を記述するときにベスト プラクティスに従うことが重要です。そうすることで、関数が読み取り可能で再利用可能であるだけでなく、長期的に保守可能であることを保証できます。これにより、不適切に記述されたコードから発生する可能性のあるバグやエラーの数を減らすことができ、長期的には時間とリソースを節約できます。

クリーンなコードとは、機能するコードを書くことだけではないことに注意してください。また、簡単に理解、テスト、変更できるコードを書くことも重要です。したがって、コードの品質と保守性を最適化するために、関数を作成する際には、上記のベスト プラクティスを慎重に検討することをお勧めします。