JavaScript:スタイル要素

1920年、William Stranka Jr.の 「スタイルの要素」。 英語に関するそれからの勧告は今日関連しています。 コードに同じ原則を適用すると、プログラムの品質を向上させることができます。



画像



厳密なルールについては話していないことに注意してください。 今日お話しすることは単なる推奨事項です。 あなたがそれらに従うことに決めたとしても、例えば、これがコードをより理解しやすくするのに役立つなら、それらから逸脱する正当な理由があるかもしれません。 ただし、その際には注意が必要であり、人々は認知的歪みの影響を受けることを忘れないでください。 たとえば、JavaScriptで普通の関数矢印関数を選択する場合、後者にあまり詳しくない人は、より理解しやすく、簡単で、便利であるため、習慣のために通常の関数を好むでしょう。



「スタイルの要素」の原則は、今まで偶然ではありません。 問題は、通常、それらを使用するとテキストが改善されることです。 通常、本の著者は正しいです。 気まぐれや個人的な好みのためではなく、それに十分な理由がある場合にのみ、それらから逸脱する価値があります。



「構成の基本原則」の章の推奨事項の多くは、プログラムコードに適用されます。



  1. 段落を構成の最小の部分にします。 1つの段落-1つのトピック。
  2. 不要な言葉は避けてください。
  3. 有効なデポジットを使用してください。
  4. 疎結合文のシーケンスを避けます。
  5. 意味が互いに関連している文の単語は、他の言語構成要素によって分離されるべきではありません。
  6. 肯定的な声明を使用します。
  7. 意味と目的が近い思考を、並列構成を使用して同様の形式で表現します。


コードスタイルについてもほぼ同じことが言えます。



  1. フィーチャーを構成の最小限の部分にします。 1つの機能-1つのタスク。
  2. 不要なコードを避けてください。
  3. 有効なデポジットを使用してください。
  4. 疎結合の言語構造のシーケンスを避けます。
  5. 1つの問題を解決することを目的としたコードと他のプログラム要素を1か所に保管します。
  6. 変数名と式を作成するときは、肯定形式を使用します。
  7. 同じテンプレートを使用して、同様の問題を解決します。


構成単位としての機能



ソフトウェア開発の本質は構成です。 モジュール、関数、データ構造を構成してプログラムを作成します。



関数を作成するプロセスと、それらを他の関数と一緒に使用する方法を理解することは、プログラマーの基本的なスキルの1つです。


モジュールは、関数またはデータ構造のコレクションです。 データ構造は、プログラムの状態を表す方法です。 ただし、関数を使用するようになるまで、興味深いことは何も起こりません。



JavaScriptには、3種類の関数があります。





I / O操作と特定のデータ処理アルゴリズムを実装するための関数はほぼどこでも必要ですが、使用する必要がある関数の大部分はマッピングに関与します。



▍1つのタスク-1つの機能



関数がI / O操作を実行するように設計されている場合、そのタスクでマッピングタスクを処理しないでください。 関数がマッピングを目的としている場合、その関数に対してI / O操作を実行しないでください。



手続き関数は、「1つの関数-1つのタスク」規則と疎結合言語構造に関する規則の両方に違反していると言わなければなりません。 しかし、そのような機能なしではできません。



理想的な関数は、次の基本的な特性を持つ単純で決定的な純粋な関数です。





冗長コード



鮮やかなテキストは簡潔です。 図面に不必要な線があってはならないという同じ理由で、文に不必要な単語がなく、段落に不必要な文があってはなりません。 これは、作家が短い文章のみを使用する、または詳細を避けて一般的な説明を使用する必要があることを意味するものではありません。 これは、すべての単語に意味があることを意味します。 [余分な単語は省略]。



ウィリアム・ストランク・ジュニア、「スタイルの要素」


ソフトウェア開発では、コードが多いほど、ミスを犯す可能性のある場所が増えるため、簡潔なコードは非常に重要です。 コードが少なくなると、エラーが隠れる場所が少なくなり、エラーの数が減ります。



簡潔なコードは、有用なデータと情報の「干渉」の比率が高いため、読みやすくなります。 読者は、プログラムの意味を理解するために、構文上の「ノイズ」を取り除く必要があります。 したがって、コードが少ないと、構文上の「ノイズ」が少なくなり、その結果、意味がより明確に伝達されます。



「スタイル要素」からの言葉で言えば、圧縮コードはエネルギッシュなコードです。 たとえば、次のような構造:



function secret (message) {  return function () {    return message;  } };
      
      





これはこれに減らすことができます:



 const secret = msg => () => msg;
      
      





矢印関数に典型的な最小限の構文(2015年にES6に登場)に精通している人は、最初の例のコードではなく、このレコードを読む可能性が高くなります。 括弧、 function



キーワード、 return



などの不要な要素はここでは省略されます。



最初のバージョンには、多くのユーティリティ構文構文があります。 これらは、括弧、キーワードfunction



、およびreturn



です。 それらは、矢印関数に精通している人にとっては、構文上の「ノイズ」に過ぎません。 そして、現代のJavaScriptでは、そのような構造は、ES6でまだ十分に自信がない人がコードを読むことができるようにするためにのみ存在します。 しかし、ES6は2015年に言語標準になったため、ES6をよりよく理解するときが来ました。



不要な変数



時々私たちは何かに名前を付けますが、それはあまり必要ではありません。 なんらかの中間変数があるとしましょう。 なぜこれが有害なのですか? ここでの問題は、人間の脳の短期記憶リソース限られていることです 。 プログラムのテキストで変数に出会ったので、それを覚えておく必要があります。 名前がたくさんある場合、私たちの記憶は一杯です。定期的に読むとき、私たちは戻って行かなければなりません...



それが、経験豊富な開発者が不必要な変数を排除することに慣れている理由です。



たとえば、ほとんどの場合、関数の戻り値を保存するためだけに作成された変数は使用しないでください。 関数の名前は、関数が正確に返すものに関する適切な情報を提供する必要があります。 例を考えてみましょう:



 const getFullName = ({firstName, lastName}) => { const fullName = firstName + ' ' + lastName; return fullName; };
      
      





不要なものを取り除いた後、コードを次のように書き換えることができます。



 const getFullName = ({firstName, lastName}) => ( firstName + ' ' + lastName );
      
      





変数の数を減らす別の一般的なアプローチは、関数の構成といわゆる「無意味表記」を使用することです。



パッチレス表記法は、これらの関数が動作する引数に言及せずに関数を宣言する方法です。 このアプローチの一般的な用途は、カリー化と機能の合成です。



カリー化の例を次に示します。



 const add2 = a => b => a + b; //       inc(), //    1   . const inc = add2(1); inc(3); // 4
      
      





inc()



関数の宣言を見てください。 function



キーワードも、矢印関数の宣言に固有の構文要素もないことに注意してください。 ここでは、関数がパラメーターを使用しないため、関数のパラメーターの説明はありません。 代わりに、渡された引数をどうするかを知っている別の関数を返します。



関数合成を使用する例を見てください。 関数構成とは、別の関数によって返された結果に関数を適用することです。 これを知っているかどうかに関係なく、機能の構成は常に適用されます。



たとえば、 .map()



promise.then()



などのメソッド呼び出しのチェーンを使用する場合。 関数の構成を記述する最も一般的な形式に目を向けると、次の構造が得られます: f(g(x))



。 数学では、これは通常f ∘ g



として記述され、「関数g



結果に関数f



を適用する」と読みます。



2つの関数の構成を呼び出すことにより、関数呼び出し間の中間値を保持する変数を作成する必要がなくなります。



この手法を使用して、よりクリーンなコードを作成する方法を見てみましょう。



 const g = n => n + 1; const f = n => n * 2; //    : const incThenDoublePoints = n => { const incremented = g(n); return f(incremented); }; incThenDoublePoints(20); // 42 // compose2 —        const compose2 = (f, g) => x => f(g(x)); //   : const incThenDoublePointFree = compose2(f, g); incThenDoublePointFree(20); // 42
      
      





同じことはどの関数でも実行できます。



ファンクターは、マッピング関数を実装するオブジェクトです。 たとえば、JSでは、これらは配列( Array.map()



)またはpromise.then()



)です。 関数を構成する目的でマッピング関数への呼び出しのチェーンを使用して、 compose2



関数の別のバージョンを作成します。



 const compose2 = (f, g) => x => [x].map(g).map(f).pop(); const incThenDoublePointFree = compose2(f, g); incThenDoublePointFree(20); // 42
      
      





Promiseでコールチェーンを使用するたびに、実質的に同じことを行います。



実際、各関数型プログラミングライブラリは、関数を構成する少なくとも2つの方法を実装しています。 これは、右から左に関数を適用compose()



関数と、左から右に関数を適用するpipe()



です。



たとえば、Lodashでは、このような関数はそれぞれcompose()



およびflow()



と呼ばれます。 このライブラリを使用するとき、次のようにflow()



関数を使用します。



 import pipe from 'lodash/fp/flow'; pipe(g, f)(20); // 42
      
      





ただし、そのような機能は、ライブラリなしで個別に実装できます。



 const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x); pipe(g, f)(20); // 42
      
      





上記が非常に難解なように思われ、このすべてをどのように利用するかわからない場合は、次のことを考慮してください。



ソフトウェア開発の本質は構成です。 小さなモジュール、関数、データ構造を構成してプログラムを作成します。



関数とオブジェクトを作成するためのツールを理解することは、ビルダーにとってもドリルにとってもアセンブリガンを扱う能力にとっても、プログラマにとっても重要です。 また、命令コードを使用して関数を組み合わせ、変数を不当に使用して中間結果を保存することは、家具を粘着テープで組み立てるのに似ています。



その結果、次のことを覚えておくことをお勧めします。





有効な入金



通常、有効な音声とは、受動的な音声よりも明確で生き生きとした思考の表現を意味します。



ウィリアム・ストランク・ジュニア、「スタイルの要素」


ソフトウェア構成要素には、できるだけ明確で正確な名前を付けます。





述語関数とブール値を、はいまたはいいえと答えた質問であるかのように呼び出します。





関数名に動詞形式を使用します。





▍イベントハンドラー



イベントハンドラーとライフサイクルメソッドの命名は、修飾子として使用されるため、関数名に動詞を使用する規則の例外です。 彼らは「何を」するのではなく、「いつ」を示しています。 これらのパターンは、「<when when action to action>、<verb>」というパターンに従って名前を付ける必要があります。





失敗したと見なされるリストのイベントハンドラーの名前は、応答するのではなく、イベントをトリガーするように見えます。



▍ライフサイクルメソッド



このコンポーネントを更新する前にハンドラー関数を呼び出すために作成される仮想コンポーネントのライフサイクルメソッドの次のオプションを見てください。





最初の例では、受動的な音声(「更新」ではなく「更新」)を使用します。 この名前は冗長であり、他のオプションよりも明確ではありません。



2番目の例は見栄えが良いですが、このライフサイクルメソッドの意味はハンドラを呼び出すことです。 componentWillUpdate(handler)



という名前は、コンポーネントがハンドラーで動作するかのように読み取り、それを更新します。これは、このソフトウェア構成の真の値を表現しません。 つまり、「コンポーネントが更新される前に、ハンドラーを呼び出します。」 beforeComponentUpdate()



という名前は、意図を最も明確に表しています。



単純化の道をさらに進むことができます。 オブジェクトのメソッドについて話しているので、呼び出されると、オブジェクト自体が言及されます。 つまり、オブジェクト名をメソッド名に追加することは冗長です。 コンポーネントにアクセスしているときにメソッドを呼び出すと、次の構成がどのようになるかを考えてください: component.componentWillUpdate()



。 「Vasya Vasyaは昼食にカツレツを食べる」と同じように読みます。 オブジェクトの名前への二重参照は冗長です。 その結果、次のようになります: component.beforeUpdate(doSomething)



omponent.beforeComponentUpdate(doSomething)



component.beforeUpdate(doSomething)



c omponent.beforeComponentUpdate(doSomething)



component.beforeUpdate(doSomething)



よりも優れています。



機能的不純物は、オブジェクトにプロパティとメソッドを追加する機能です。 このような機能は、工場の組立ラインに似たコンベアベルトで次々に呼び出されます。 各関数は、入力でinstance



であるオブジェクトを受け取り、パイプラインの次の関数に渡す前に何かを追加します。



このような関数には形容詞を使用して名前を付けることを好みます。 適切な単語を見つけるために、接尾辞「ing」および「able」を使用できます。 以下に例を示します。





疎結合言語構造のシーケンス



...一連のステートメントはすぐに単調で退屈になります。



ウィリアム・ストランク・ジュニア、スタイルの要素。



開発者は、関数を言語構成体のシーケンスで満たします。 これらの構造は、実際に一連の疎結合ステートメントの例として、次々に実行されるように設計されています。 特定のプログラムブロックでこのような呼び出しがあまりにも多く収集される場合、同様のアプローチにより、いわゆる「スパゲッティコード」が表示されます。



さらに、多くの場合、コールセットは多くの同様の形式で繰り返されます。 同時に、繰り返しブロックのそれぞれは、他のブロックとわずかに異なる場合があり、多くの場合、このような違いは完全に予期せずに発生します。 たとえば、ユーザーインターフェイスコンポーネントの基本的なニーズは、そのようなコンポーネントのほぼすべてのニーズに対応しています。 ライフサイクルのさまざまな段階に基づいて、これらすべてのコンポーネントに必要なものを実装し、実装をいくつかの機能に分割できます。



次の一連の呼び出しを検討してください。



 const drawUserProfile = ({ userId }) => { const userData = loadUserData(userId); const dataToDisplay = calculateDisplayData(userData); renderProfileData(dataToDisplay); };
      
      





この関数は、データのロード、ロードされた内容に基づいた構築、インターフェイス要素のデータモデル、ページ上の要素の表示という3つの異なる処理を実行します。



インターフェイスを開発するための最新のライブラリでは、上記の各タスクは、たとえば専用の機能を使用して、他のタスクとは別に解決されます。 これらのタスクを分離することで、特別な問題なく機能を組み合わせて、さまざまな状況で望ましい結果を得ることができます。



このアプローチにより、たとえばコンポーネントを出力する機能を完全に置き換えることができ、これはプログラムの他の部分には影響しません。 たとえば、Reactには、さまざまなプラットフォームとさまざまなライブラリ使用シナリオ向けに設計された多くのレンダリングサブシステムがあります。 完全なリストはここにはありません。ネイティブiOSおよびAndroidアプリケーション用のReactNative、WebVR用のAFrame、サーバー側でコンポーネントをレンダリングするためのReactDOM /サーバー。



上記の関数の別の問題は、最初にソースデータをロードしないと、インターフェイス要素のモデルを準備してページに表示できないことです。 このデータが既にロードされている場合はどうなりますか? 最終的に、複数の操作を組み合わせた同様の関数が複数回呼び出された場合、これは不要なアクションにつながります。



さらに、運用の分離は、独立したテストへの道を開きます。 コードを作成する過程で、コードに加えられた変更のアプリケーションへの影響を即座に評価するために、常に単体テストを実行しています。 ただし、この例のように、コントロールのレンダリングコードとソースデータを読み込むためのコードを組み合わせる場合、テスト目的で要素出力関数に条件付きデータを渡すことはできません。 ここでは、すべてをテストする必要があります-ロード、準備、およびデータ出力。 これは、1つだけを確認する必要がある場合、不当な時間の無駄につながります。たとえば、データをネットワーク経由でダウンロードし、処理し、ブラウザに表示する必要があります...テスト結果を取得するには、個々のコンポーネントを確認する場合よりも長く待つ必要があります。 関数を分離すると、アプリケーションの他の部分とは別にテストできます。



この例では、コンポーネントライフサイクルのさまざまなメソッドに呼び出しを配置できる3つの個別の関数が既にあります。 たとえば、コンポーネントが接続されたときにソースデータをロードし、インターフェイス要素の状態の更新に関連するイベントに応じて、このデータを処理し、画面にコンポーネントを表示することができます。



上記の原則を適用すると、個々のコンポーネントの責任範囲がより明確に定義されたソフトウェアが出現します。 各コンポーネントは同じデータ構造とライフサイクルイベントハンドラを再利用できるため、1回だけで十分なアクションを何度も実行しません。



1つの問題の解決を目的としたコードおよびその他のプログラム要素の保存



多くのフレームワークとテンプレートは、タイプごとにプログラムファイルを整理するために用意されています。 小さな電卓やToDoアプリケーションなどの単純なプロジェクトについて話している場合、このアプローチは問題を引き起こしませんが、大規模な開発では、実装するアプリケーションの機能に応じてファイルをグループ化する方が適切です。



たとえば、ToDoアプリケーションのファイル階層には2つのオプションがあります。 最初のオプションは、ファイルをタイプ別にグループ化することです。



 . ├── components │   ├── todos │   └── user ├── reducers │   ├── todos │   └── user └── tests   ├── todos   └── user
      
      





2番目は論理的なグループ化です。



 . ├── todos │   ├── component │   ├── reducer │   └── test └── user   ├── component   ├── reducer   └── test
      
      





実装する機能の原則に従ってファイルをグループ化すると、アプリケーションの一部に変更を加える必要がある場合、必要なファイルを検索するためにフォルダー間を常に移動する必要がなくなります。



その結果、実装するアプリケーション機能の種類に基づいてファイルをグループ化することをお勧めします。



変数名に肯定形式を使用し、式を構築する



明確な声明を作成します。 気怠い、無色、優柔不断、回避的な舌を避けてください。 否定の手段として、アンチテーゼで、またはトピックを回避する方法として単語を使用しないでください。



ウィリアム・ストランク・ジュニア、「スタイルの要素」


変数名の例に移りましょう:





▍条件演算子



この設計:



 if (err) return reject(err); //  -...
      
      





...これより良い:



 if (!err) { // ...  - } else { return reject(err); }
      
      





▍三項演算子



だから:



 { [Symbol.iterator]: iterator ? iterator : defaultIterator }
      
      





...それよりも優れています:



 { [Symbol.iterator]: (!iterator) ? defaultIterator : iterator }
      
      





▍ネガティブステートメントについて



論理変数は、その値が偽である場合にのみ関心を引く場合があります。 そのような変数の名前を肯定形式で使用すると、チェックするときに論理否定演算子を使用する必要があるという事実につながります! 。 このような場合は、変数に明確な負の名前を付ける方が適切です。 変数名と演算子に含まれる単語「not」 比較操作では、あいまいな定式化が表示されます。 いくつかの例を見てみましょう。



if (missingValue)



if (!hasValue)

if (anonymous)




よりも優れています if (!hasValue)

if (anonymous)




if (!hasValue)

if (anonymous)




if (!user)

if (isEmpty(thing))




よりも優れているif (!user)

if (isEmpty(thing))




if (!user)

if (isEmpty(thing))




if (!user)

if (isEmpty(thing))




よりも優れています。



null nullおよび未定義の関数の引数



オプションのパラメータの代わりに、 undefined



またはnull



を呼び出す関数を作成しないでください。 このような状況では、名前付きパラメーターを持つオブジェクトを使用するのが最適です。



 const createEvent = ({ title = 'Untitled', timeStamp = Date.now(), description = '' }) => ({ title, description, timeStamp }); // ... const birthdayParty = createEvent({ title: 'Birthday Party', description: 'Best party ever!' });
      
      





...より良い:



 const createEvent = ( title = 'Untitled', timeStamp = Date.now(), description = '' ) => ({ title, description, timeStamp }); // ... const birthdayParty = createEvent( 'Birthday Party', undefined, //     'Best party ever!' );
      
      





テンプレートと同様の問題の解決



...並列構築では、類似したコンテンツと目的を持つテキストフラグメントの外部類似性が必要です。 フォームの類似性により、読者はコンテンツの類似性をより簡単に認識できます。



ウィリアム・ストランク・ジュニア、「スタイルの要素」


アプリケーションを作成するとき、プログラマは非常によく似たタスクを解決する必要があります。 繰り返すことができるコードは通常、完全に一意ではありません。 その結果、作業中は常に同じことをしなければなりません。 ここでの良いところは、同様のコードを一般化し、抽象化を作成できることです。 これを行うには、コードの同じ部分を識別し、それらを選択して、必要な場所で使用するだけで十分です。 また、開発の過程で、アプリケーションの特定のフラグメントに固有の設計のみに注意を払ってください。 実際、さまざまなライブラリとフレームワークが機能するのはまさにこの目的です。



たとえば、ここにユーザーインターフェイスのコンポーネントがありますが、jQuery、アプリケーションロジック、および外部とのやり取りの組織を使用してインターフェイスの更新を積み重ねることが一般的になってから10年が経過していません。 その後、プログラマーはMVCをクライアントWebアプリケーションで使用できることに気付き始め、モデルをインターフェイス更新ロジックから分離し始めました。



その結果、コンポーネントアプローチを使用してWebアプリケーションの構築を開始しました。これにより、HTMLまたはJSXを使用して作成されたテンプレートなどを使用して、コンポーネントを宣言的にモデル化できました。



これにより、すべてのコンポーネントに同じUI更新ロジックが使用されるようになり、独自の命令型コードよりもはるかに優れています。



コンポーネントに精通している人は、なじみのないアプリケーションについて話している場合でも、その動作を簡単に理解できます。 , , , , , , , , .



, , , , .



: ,



ES6 2015-, , , . , , , , , . —



, , . , — . , , ES6 , ES5. .



, , . , :





, , , :





, :





, , , , . . , , , , , , .



, , .



, . , . , ES6, , , , , « » . , , , -, JS, .



親愛なる読者! ES6 ?



All Articles