純粋なJavaScript関数

Ryan McDermott Book Translation clean-code-javascript



目次:









関数の引数(理想的には2以下)



機能パラメーターの数を制限することは、機能テストを簡素化するため、非常に重要です。 3つ以上の引数が存在すると、個々の引数ごとに多くの異なるケースをソートする必要がある場合、組み合わせの爆発につながります。



理想的な状況は、議論の欠如です。 1つまたは2つの引数は適切ですが、3つは既に回避する必要があります。



より多くの引数を統合する必要があります。 通常、3つ以上の引数が渡されると、関数は多くのことを試みます。 それでもそうでない場合は、オブジェクトを引数として使用することをお勧めします。 JavaScriptを使用すると、クラスを特別に記述しなくても、その場でオブジェクトを作成できるため、多くの引数を渡す必要がある場合に使用できます。



悪い:



function createMenu(title, body, buttonText, cancellable) { // ... }
      
      





良い:



 const menuConfig = { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }; function createMenu(config) { // ... } createMenu(menuConfig);
      
      





関数は1つの問題を解決する必要があります。



これは、ソフトウェア開発において断然最も重要なルールです。 関数が複数の問題を解決する場合、それらを組み合わせたり、テストしたり、理解することはより困難です。 各関数を実行して1つのアクションのみを実行できるようになると、すぐにそれらのリファクタリングがはるかに簡単になり、コードがはるかに読みやすくなります。 上記のルールだけがこのガイドから取ったものであるとしても、多くの開発者よりもクールです。



悪い:



 function emailClients(clients) { clients.forEach((client) => { const clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } }); }
      
      





良い:



 function emailClients(clients) { clients .filter(isClientActive) .forEach(email); } function isClientActive(client) { const clientRecord = database.lookup(client); return clientRecord.isActive(); }
      
      





関数名はその目的を説明する必要があります



悪い:



 function addToDate(date, month) { // ... } const date = new Date(); //     ,    addToDate(date, 1);
      
      





良い:



 function addMonthToDate(month, date) { // ... } const date = new Date(); addMonthToDate(1, date);
      
      





関数は抽象化の1つのレベルのみを表す必要があります。



関数に複数の抽象化レベルがある場合、通常は機能が多すぎます。 そのような機能を分離すると、再利用性とテストの容易さが得られます。



悪い:



 function parseBetterJSAlternative(code) { const REGEXES = [ // ... ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { // ... }); }); const ast = []; tokens.forEach((token) => { // ... }); ast.forEach((node) => { // ... }); }
      
      





良い:



 function tokenize(code) { const REGEXES = [ // ... ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { tokens.push( /* ... */ ); }); }); return tokens; } function lexer(tokens) { const ast = []; tokens.forEach((token) => { ast.push( /* ... */ ); }); return ast; } function parseBetterJSAlternative(code) { const tokens = tokenize(code); const ast = lexer(tokens); ast.forEach((node) => { // ... }); }
      
      





重複するコードを取り除く



コードの重複を避けるために最善を尽くしてください。 コードの複製は、アクションのロジックが変更された場合に変更を行う必要がある場所が複数存在することを意味するという点で有害です。



レストランを経営し、トマト、玉ねぎ、ニンニク、スパイスなど、すべての製品の記録を保管していると想像してください。 記録が異なるリストに保存されている場合、トマトを含む料理を提供するには、各リストを変更する必要があります。 リストが1つしかない場合、編集は1つだけになります。



多くの場合、2つ以上のわずかに異なるアクションを実装する必要がある場合に複製コードが発生します。これらのアクションは一般に非常に似ていますが、それらの違いにより、ほぼ同じことを行う2つ以上の関数が必要になります。 この場合、重複したコードを取り除くということは、単一の関数、クラス、またはモジュールの形ですべての違いを表すことができる抽象化を作成することを意味します。



適切な抽象化を作成することは非常に重要であり、SOLIDの原則に従う必要があるのはこのためです。 悪い抽象化は、コードの複製よりも悪い場合があるため、注意してください!



要約すると、コードを適切な抽象化でラップできる場合-それを行う! コードを複製しないでください。複製しないと、小さな変更ごとに多くの変更を行う必要があります。



悪い:



 function showDeveloperList(developers) { developers.forEach((developer) => { const expectedSalary = developer.calculateExpectedSalary(); const experience = developer.getExperience(); const githubLink = developer.getGithubLink(); const data = { expectedSalary, experience, githubLink }; render(data); }); } function showManagerList(managers) { managers.forEach((manager) => { const expectedSalary = manager.calculateExpectedSalary(); const experience = manager.getExperience(); const portfolio = manager.getMBAProjects(); const data = { expectedSalary, experience, portfolio }; render(data); }); }
      
      





良い:



 function showList(employees) { employees.forEach((employee) => { const expectedSalary = employee.calculateExpectedSalary(); const experience = employee.getExperience(); let portfolio = employee.getGithubLink(); if (employee.type === 'manager') { portfolio = employee.getMBAProjects(); } const data = { expectedSalary, experience, portfolio }; render(data); }); }
      
      





Object.assignを使用してデフォルトのオブジェクトを設定します



悪い:



 const menuConfig = { title: null, body: 'Bar', buttonText: null, cancellable: true }; function createMenu(config) { config.title = config.title || 'Foo'; config.body = config.body || 'Bar'; config.buttonText = config.buttonText || 'Baz'; config.cancellable = config.cancellable === undefined ? config.cancellable : true; } createMenu(menuConfig);
      
      





良い:



 const menuConfig = { title: 'Order', //     'body' buttonText: 'Send', cancellable: true }; function createMenu(config) { config = Object.assign({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }, config); //  config = {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} // ... } createMenu(menuConfig);
      
      





関数パラメーターとしてフラグを使用しないでください



フラグは、関数が複数のアクションを実行することをユーザーに伝えます。 関数は1つの問題を解決する必要があります。 ブール値に基づいて異なるコードバリアントを実行する場合は、関数を分離します。



悪い:



 function createFile(name, temp) { if (temp) { fs.create(`./temp/${name}`); } else { fs.create(name); } }
      
      





良い:



 function createFile(name) { fs.create(name); } function createTempFile(name) { createFile(`./temp/${name}`); }
      
      





副作用を避ける(パート1)



関数は、値を受け取って別の値を返す以外のアクションを実行すると副作用を引き起こします。 副作用として、ファイルへの書き込み、グローバル変数の変更、またはすべてのお金の見知らぬ人へのランダムな転送があります。



ただし、プログラムには副作用が必要です。 前の例のように、ファイルに書き込む必要があります。 やりたいことを、厳密に1か所で説明します。



特定のファイルに何かを書き込む複数の関数とクラスを作成しないでください。 これをすべて行う1つのサービスを作成します。 唯一無二。



一番下の行は、たとえば、構造を持たないオブジェクト間で状態を転送する、誰でも上書きできる可変データを使用する、副作用が適用される中心的な場所をバイパスするなどの一般的な間違いを避けることです。



これを学べば、他のプログラマーの大部分よりも幸せになります。



悪い:



 //  ,     . //        ,      name   , //   ,     let name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott'];
      
      





良い:



 function splitIntoFirstAndLastName(name) { return name.split(' '); } const name = 'Ryan McDermott'; const newName = splitIntoFirstAndLastName(name); console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott'];
      
      







副作用を避ける(パート2)

JavaScriptでは、プリミティブは値によって渡され、オブジェクトと配列はによって渡されます

リンク。 オブジェクトと配列の場合、関数が変更を加える場合

バスケット(配列)に、たとえば、配列に要素を追加して、

このバスケット(配列)を使用する他の関数はこれに依存します

追加。 さまざまなケースで良いことも悪いこともあります。 悪い状況を想像してみましょう:



ユーザーが「購入」ボタンをクリックすると、「購入」機能が呼び出され、バスケット(配列)からサーバーにデータが送信されます。 ネットワークへの接続が不十分な場合、購入機能は2番目のリクエストを送信する必要があります。 では、ユーザーが誤って[カートに追加]ボタンをクリックしたが、まだ製品を購入したくない場合はどうでしょうか。

これが発生してネットワーク要求が開始されると、「購入」機能

addItemToCart関数によって変更された前のバスケット(配列)へのリンクがあるため、ランダムに追加されたアイテムを送信します。 優れたソリューションは、「addItemToCart」が常にカートを複製し、編集して複製を返すことです。 これにより、他のバスケット依存機能が変更によって影響を受けないことが保証されます。



このアプローチに関する2つの警告:

  1. 参照によってオブジェクトを本当に変更したい場合もありますが、そのようなケースは非常にまれです。 ほとんどの機能は副作用なしで宣言できます!
  2. 大きなオブジェクトのクローン作成は非常にストレスがかかり、パフォーマンスに影響を与える可能性があります。 幸いなことに、これは実際には大きな問題ではありません。手動クローンよりも少ないメモリ負荷でオブジェクトをクローンできる優れたライブラリがあるからです。




悪い:



 const addItemToCart = (cart, item) => { cart.push({ item, date: Date.now() }); };
      
      





良い:



 const addItemToCart = (cart, item) => { return [...cart, { item, date : Date.now() }]; };
      
      







グローバル関数をオーバーライドしないでください



JavaScriptでは、グローバル変数の汚染は別のライブラリとの競合を引き起こす可能性があり、APIのユーザーには本番環境で例外を受け取るまでエラーが表示されないため、悪い習慣です。 例を見てみましょう。2つの配列間の差を計算するdiffメソッドを追加して、JavaScriptから標準の配列機能を拡張する場合はどうでしょうか。 Array.prototypeで新しい関数を作成する必要がありますが、同じことをしようとした別のライブラリと競合する可能性があります。 そして、別のライブラリがdiffメソッドを使用して、配列の最初の要素と最後の要素の違いを見つけた場合はどうなりますか? そのため、ES2015 / ES6クラスを使用し、Arrayクラスから実装を継承する方がはるかに優れています。



悪い:



 Array.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); };
      
      





良い:



 class SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); } }
      
      





命令型よりも関数型プログラミングを好む



JavaScriptはHaskellほど機能的な言語ではありませんが、ある程度の機能がないわけではありません。 関数型言語はよりクリーンでテストしやすいです。 可能な限り、関数型プログラミングスタイルを使用してください。



悪い:



 const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 } ]; let totalOutput = 0; for (let i = 0; i < programmerOutput.length; i++) { totalOutput += programmerOutput[i].linesOfCode; }
      
      





良い:



 const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 } ]; const totalOutput = programmerOutput .map((programmer) => programmer.linesOfCode) .reduce((acc, linesOfCode) => acc + linesOfCode, 0);
      
      





条件をカプセル化する



悪い:



 if (fsm.state === 'fetching' && isEmpty(listNode)) { // ... }
      
      





良い:



 function shouldShowSpinner(fsm, listNode) { return fsm.state === 'fetching' && isEmpty(listNode); } if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ... }
      
      





負の条件を避ける



悪い:



 function isDOMNodeNotPresent(node) { // ... } if (!isDOMNodeNotPresent(node)) { // ... }
      
      





良い:



 function isDOMNodePresent(node) { // ... } if (isDOMNodePresent(node)) { // ... }
      
      





条件付きコンストラクトを避ける



そのようなタスクは不可能のようです。 これを聞いて、ほとんどの人は言う:「if文なしで何をしたらいいの?」 答えは、多くの場合、ポリモーフィズムを使用して同じ目標を達成できるということです。 原則として、2番目の質問は次のように聞こえます。「良い、素晴らしい、しかしなぜ避けるべきですか?」 答えは、私たちが学んだクリーンコードの以前の概念です。関数は1つのタスクのみを実行する必要があります。 'if'構造を含むクラスと関数がある場合、関数が複数のタスクを実行していることをユーザーに伝えるようになります。 要確認:1つの機能-1つのタスク。



悪い:



 class Airplane { // ... getCruisingAltitude() { switch (this.type) { case '777': return this.getMaxAltitude() - this.getPassengerCount(); case 'Air Force One': return this.getMaxAltitude(); case 'Cessna': return this.getMaxAltitude() - this.getFuelExpenditure(); } } }
      
      





良い:



 class Airplane { // ... } class Boeing777 extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getPassengerCount(); } } class AirForceOne extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude(); } } class Cessna extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getFuelExpenditure(); } }
      
      





型チェックを避ける(パート1)



JavaScriptは型なし言語です。つまり、関数は任意の型の引数を受け入れることができます。 この自由に火傷を受け、関数の型チェックを実行するように促されることがありました。 それを避ける方法はたくさんあります。 最初に考慮すべきことは、一貫したAPIです。



悪い:



 function travelToTexas(vehicle) { if (vehicle instanceof Bicycle) { vehicle.peddle(this.currentLocation, new Location('texas')); } else if (vehicle instanceof Car) { vehicle.drive(this.currentLocation, new Location('texas')); } }
      
      





良い:



 function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location('texas')); }
      
      





型チェックを避ける(パート2)



文字列、整数、配列などの基本的なプリミティブを使用し、ポリモーフィズムを使用できない場合、タイプチェックの必要性を感じているが、TypeScriptの使用を検討する必要があります。 これは、標準のJavaScript構文に加えて静的な型指定を可能にする、通常のJavaScriptの優れた代替手段です。 通常のJavaScriptでの手動型チェックの問題は、それが作成するセキュリティの錯覚が、コードの冗長性による読みやすさの損失によって補償されないことです。 コードをクリーンに保ち、適切なテストを作成し、効果的なコード修正を行います。 または、同じことを行いますが、TypeScriptを使用します(先ほど述べたように、これは素晴らしい選択肢です!)。



悪い:



 function combine(val1, val2) { if (typeof val1 === 'number' && typeof val2 === 'number' || typeof val1 === 'string' && typeof val2 === 'string') { return val1 + val2; } throw new Error('Must be of type String or Number'); }
      
      





良い:



 function combine(val1, val2) { return val1 + val2; }
      
      





最適化しないでください



最新のブラウザは、コード実行中に内部で多くの最適化を行います。 コードを手動で最適化することにより、多くの場合、時間を無駄にします。 最適化が本当に不十分な状況を説明する優れたリソースがあります 。 もちろん、これらの問題が修正されるまで、自由時間にそれらを見てください。



悪い:



 //        `list.length`   // -  `list.length`.     . for (let i = 0, len = list.length; i < len; i++) { // ... }
      
      





良い:



 for (let i = 0; i < list.length; i++) { // ... }
      
      





デッドコードを削除する



デッドコードは重複コードと同じくらい悪いです。 リポジトリに保持する理由はありません。 コードが呼び出されない場合は、取り除いてください!



いつか必要な場合は、バージョン管理システムに残ります。



悪い:



 function oldRequestModule(url) { // ... } function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io');
      
      





良い:



 function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io');
      
      






All Articles