ソフトウェアの作成:はじめに

スモークアートキューブからスモーク— MattysFlicks —(CC BY 2.0)
注:これは、JavaScript ES6 +で関数型プログラミングと合成ソフトウェア技術を一から学ぶ「ソフトウェアの作成」シリーズ(現在は本です!)の一部です。乞うご期待。まだまだたくさんあります!
本を購入する|インデックス|次へ>
構成:「部分または要素を組み合わせて全体を形成する行為。」〜Dictionary.com

私の最初の高校のプログラミングクラスでは、ソフトウェア開発は「複雑な問題を小さな問題に分解し、簡単な解決策を作成して複雑な問題の完全な解決策を作る行為」であると言われました。

私の人生における最大の後悔の1つは、そのレッスンの重要性を早期に理解できなかったことです。私はソフトウェア設計の本質を人生のあまりに遅すぎることを学びました。

私は何百人もの開発者にインタビューしました。これらのセッションから学んだことは、私は一人ではないということです。ソフトウェア開発の本質を十分に把握している実務的なソフトウェア開発者はほとんどいません。彼らは、私たちが自由に使える最も重要なツールや、それらを有効に活用する方法を知りません。 100%は、ソフトウェア開発の分野で最も重要な質問の1つまたは両方に答えるのに苦労しています。

  • 関数構成とは何ですか?
  • オブジェクト構成とは何ですか?

問題は、気付いていないからといって構図を避けられないことです。あなたはまだそれをします—しかし、あなたはそれをひどくします。より多くのバグを含むコードを記述し、他の開発者が理解しにくくします。これは大きな問題です。効果は非常に高価です。私たちはソフトウェアをゼロから作成するよりも多くの時間を費やしており、バグは世界中の何十億もの人々に影響を与えています。

今日、全世界がソフトウェアで稼働しています。すべての新しい車は車輪付きのミニスーパーコンピューターであり、ソフトウェア設計の問題は実際の事故を引き起こし、実際の人命を犠牲にします。 2013年、accident審員は、事故調査で10,000個のグローバル変数を持つスパゲッティコードを発見した後、トヨタのソフトウェア開発チームが「無謀な無視」の罪を犯したことを発見しました。

ハッカーと政府は、人々をスパイし、クレジットカードを盗み、コンピューティングリソースを利用して分散型サービス拒否(DDoS)攻撃を開始し、パスワードをクラックし、選挙を操作するためにバグを備蓄します。

もっと良くしなければなりません。

毎日ソフトウェアを作成する

あなたがソフトウェア開発者であれば、知っているかどうかにかかわらず、関数とデータ構造を毎日作成します。ダクトテープとクレイジーグルーを使用して、意識的に(またはそれ以上に)行うことも、誤って行うこともできます。

ソフトウェア開発のプロセスは、大きな問題を小さな問題に分解し、それらの小さな問題を解決するコンポーネントを構築し、それらのコンポーネントを組み合わせて完全なアプリケーションを形成することです。

関数を作成する

関数合成は、関数を別の関数の出力に適用するプロセスです。代数では、2つの関数、fand g、(f∘g)(x)= f(g(x))が与えられます。円は合成演算子です。通常、「〜で構成される」または「〜後に」と発音されます。 「gで構成されるfはxのgのfに等しい」、または「gの後のfはxのgのfに等しい」と大声で言うことができます。 gが最初に評価され、その出力が引数としてfに渡されるため、gの後にfを指定します。

このようなコードを記述するたびに、関数を作成します。

const g = n => n + 1;
const f = n => n * 2;
const doStuff = x => {
  const afterG = g(x);
  const afterF = f(afterG);
  afterFを返します。
};
doStuff(20); // 42

promiseチェーンを作成するたびに、関数を作成します:

const g = n => n + 1;
const f = n => n * 2;
const wait = time => new Promise(
  (解決、拒否)=> setTimeout(
    解決する、
    時間
  )
);
待つ(300)
  .then(()=> 20)
  .then(g)
  .then(f)
  .then(value => console.log(value))// 42
;

同様に、配列メソッド呼び出し、lodashメソッド、Observable(RxJSなど)をチェーンするたびに、関数を作成しています。チェーンしている場合は、作曲中です。戻り値を他の関数に渡す場合は、作成中です。シーケンスで2つのメソッドを呼び出す場合、これを入力データとして使用して作成します。

チェーンしている場合は、作曲中です。

関数を意図的に構成するときは、より適切に行います。

関数を意図的に構成する場合、doStuff()関数を単純なワンライナーに改善できます。

const g = n => n + 1;
const f = n => n * 2;
const doStuffBetter = x => f(g(x));
doStuffBetter(20); // 42

このフォームに対する一般的な反対意見は、デバッグが難しいことです。たとえば、関数合成を使用してこれをどのように記述しますか?

const doStuff = x => {
  const afterG = g(x);
  console.log( `after g:$ {afterG}`);
  const afterF = f(afterG);
  console.log( `after f:$ {afterF}`);
  afterFを返します。
};
doStuff(20); // =>
/ *
「gの後:21」
「fの後:42」
* /

まず、「fの後」、「gの後」、trace()と呼ばれる小さなユーティリティにログインすることを抽象化します。

const trace = label => value => {
  console.log( `$ {label}:$ {value}`);
  戻り値;
};

これで次のように使用できます。

const doStuff = x => {
  const afterG = g(x);
  trace( 'after g')(afterG);
  const afterF = f(afterG);
  trace( 'after f')(afterF);
  afterFを返します。
};
doStuff(20); // =>
/ *
「gの後:21」
「fの後:42」
* /

LodashやRamdaなどの一般的な関数型プログラミングライブラリには、関数の構成を簡単にするユーティリティが含まれています。上記の関数は次のように書き換えることができます。

「lodash / fp / flow」からパイプをインポートします。
const doStuffBetter = pipe(
  g、
  trace( 'after g')、
  f、
  trace( 'fの後)
);
doStuffBetter(20); // =>
/ *
「gの後:21」
「fの後:42」
* /

何かをインポートせずにこのコードを試したい場合は、次のようにパイプを定義できます。

// pipe(... fns:[... Function])=> x => y
const pipe =(... fns)=> x => fns.reduce((y、f)=> f(y)、x);

まだその仕組みに従っていなくても心配しないでください。後ほど、関数の構成についてさらに詳しく説明します。実際、これは非常に重要であり、このテキスト全体で何度も定義および実証されていることがわかります。重要なのは、その定義と使用法が自動的になるようにあなたがそれに慣れるのを助けることです。作曲と一体になる。

pipe()は、関数のパイプラインを作成し、ある関数の出力を別の関数の入力に渡します。 pipe()(およびそのツイン、compose())を使用する場合、中間変数は必要ありません。引数に言及せずに関数を記述することは、ポイントフリースタイルと呼ばれます。そのためには、関数を明示的に宣言するのではなく、新しい関数を返す関数を呼び出します。つまり、functionキーワードまたは矢印構文(=>)は必要ありません。

ポイントフリーのスタイルはあまりにも遠すぎるかもしれませんが、それらの中間変数が関数に不必要な複雑さを追加するので、ここで少しは素晴らしいです。

複雑さを軽減することにはいくつかの利点があります。

ワーキングメモリー

平均的な人間の脳には、ワーキングメモリ内の離散的な量子の共有リソースがわずかしかなく、各変数はそれらの量子の1つを潜在的に消費します。さらに変数を追加すると、各変数の意味を正確に思い出すことができなくなります。ワーキングメモリモデルには、通常4〜7個の離散クォンタムが含まれます。これらの数値を超えると、エラー率が劇的に増加します。

パイプ形式を使用して、3つの変数を削除し、利用可能な作業メモリのほぼ半分を他のことのために解放しました。これにより、認知負荷が大幅に軽減されます。ソフトウェア開発者は、一般の人よりも作業メモリにデータを分割するのが得意ですが、保存の重要性を弱めるほどではありません。

信号対雑音比

簡潔なコードは、コードの信号対雑音比も改善します。ラジオを聴くようなものです。ラジオがステーションに適切にチューニングされていない場合、干渉ノイズが多くなり、音楽を聞くのが難しくなります。正しいステーションにチューニングすると、ノイズがなくなり、より強力な音楽信号が得られます。

コードは同じ方法です。より簡潔なコード表現は、理解度の向上につながります。一部のコードは有用な情報を提供し、一部のコードはスペースを占有します。送信される意味を減らすことなく、使用するコードの量を減らすことができる場合、コードを解析し、それを読む必要がある他の人が理解しやすくなります。

バグの表面積

beforeおよびafter関数をご覧ください。機能がダイエットを始め、体重が大幅に減ったようです。余分なコードはバグを隠すための余分な表面積を意味し、より多くのバグがそこに隠れることを意味するため、これは重要です。

少ないコード=バグの表面積が小さい=バグが少ない。

オブジェクトを構成する

「クラスの継承よりもオブジェクトの構成を優先する」、ギャングオブフォー、「デザインパターン:再利用可能なオブジェクト指向ソフトウェアの要素」
「コンピューターサイエンスでは、複合データ型または複合データ型は、プログラミング言語のプリミティブデータ型およびその他の複合型を使用してプログラムで構築できる任意のデータ型です。 […]複合型を構築する行為は、構成として知られています。」〜ウィキペディア

これらはプリミティブです:

const firstName = 'Claude';
const lastName = 'Debussy';

そして、これは複合です:

const fullName = {
  ファーストネーム、
  苗字
};

同様に、すべての配列、セット、マップ、WeakMap、TypedArrayなどは複合データ型です。非プリミティブデータ構造を構築するときはいつでも、何らかのオブジェクト構成を実行しています。

Gang of Fourは、複合パターンと呼ばれるパターンを定義します。これは、特定のタイプの再帰的オブジェクト構成であり、個々のコンポーネントと集約された複合物を同じように処理できます。一部の開発者は、複合パターンがオブジェクト合成の唯一の形式であると考えて混乱します。混乱しないでください。オブジェクトの構成にはさまざまな種類があります。

Gang of Fourは、「オブジェクトの構成がデザインパターンで何度も適用されることを確認します」と続き、委任(状態、戦略、訪問者パターンで使用される)、知人など、3種類のオブジェクト構成関係をカタログします。 (オブジェクトが参照によって別のオブジェクトについて知っている場合、通常はパラメーターとして渡されます。たとえば、ネットワークリクエストハンドラーなどの使用関係は、ロガーへの参照を渡されてリクエストを記録します—リクエストハンドラーはロガーを使用します)および集約(子オブジェクトが親オブジェクトの一部を形成する場合:has-a関係。たとえば、DOM子はDOMノードのコンポーネント要素— DOMノードには子があります)。

クラス継承を使用して複合オブジェクトを構築できますが、これは制限的で脆弱な方法です。 Gang of Fourが「クラス継承よりもオブジェクト構成を優先する」と言うとき、クラス継承の堅固で密結合したアプローチではなく、柔軟なアプローチで複合オブジェクトを構築することを勧めています。

「コンピューターサイエンスのカテゴリメソッド:トポロジの側面」(1989)のオブジェクト構成のより一般的な定義を使用します。

「複合オブジェクトは、後者のそれぞれが前者の「一部」になるようにオブジェクトをまとめることによって形成されます。」

もう1つの参考資料は、「複合設計による信頼性の高いソフトウェア」、グレンフォードJマイヤーズ、1975年です。どちらの本も絶版ですが、オブジェクト構成のテーマをさらに詳しく知りたい場合は、AmazonまたはeBayで売り手を見つけることができます。技術的な深さ。

クラスの継承は、複合オブジェクトの構築の一種です。すべてのクラスが複合オブジェクトを生成しますが、すべての複合オブジェクトがクラスまたはクラス継承によって生成されるわけではありません。 「クラスの継承よりもオブジェクトの構成を優先する」とは、クラス階層の祖先からすべてのプロパティを継承するのではなく、小さなコンポーネントパーツから複合オブジェクトを形成することを意味します。後者は、オブジェクト指向設計でよく知られているさまざまな問題を引き起こします。

  • 密結合の問題:子クラスは親クラスの実装に依存しているため、クラス継承はオブジェクト指向設計で利用可能な最も密な結合です。
  • 壊れやすい基本クラスの問題:密結合のため、基本クラスへの変更により、多数の子孫クラスが破壊される可能性があります(サードパーティが管理するコードの可能性があります)。作成者は、知らないコードを破ることができます。
  • 柔軟性に欠ける階層の問題:十分な時間と進化を考えると、単一の祖先分類法では、すべてのクラス分類法は最終的に新しいユースケースに対して間違っています。
  • 必要性による重複の問題:階層が柔軟性に欠けるため、新しいユースケースは拡張ではなく複製によって実装されることが多く、同様のクラスが予期せず分岐します。複製が始まると、新しいクラスがどのクラスから派生するのか、またはその理由が明らかではありません。
  • ゴリラ/バナナの問題:「...オブジェクト指向言語の問題は、彼らが持ち歩く暗黙の環境をすべて持っていることです。バナナが欲しかったが、得たのはバナナとジャングル全体を保持するゴリラだった。」〜Joe Armstrong、「Coders at Work」

JavaScriptのオブジェクト構成の最も一般的な形式は、オブジェクトの連結(別名ミックスイン構成)として知られています。アイスクリームのように機能します。オブジェクト(バニラアイスクリームなど)から始めて、必要な機能を混ぜ合わせます。ナッツ、キャラメル、チョコレートスワールを追加すると、ナッツのキャラメルチョコレートスワールアイスクリームになります。

クラス継承を使用したコンポジットの構築:

クラスFoo {
  コンストラクター(){
    this.a = 'a'
  }
}
クラスBarはFooを拡張します{
  コンストラクター(オプション){
    super(オプション);
    this.b = 'b'
  }
}
const myBar = new Bar(); // {a: 'a'、b: 'b'}

ミックスイン構成を使用した複合材の構築:

const a = {
  a:「a」
};
const b = {
  b: 'b'
};
const c = {... a、... b}; // {a: 'a'、b: 'b'}

他のスタイルのオブジェクト構成については、後ほど詳しく説明します。現時点では、次のことを理解する必要があります。

  1. それを行う方法は複数あります。
  2. いくつかの方法は他の方法よりも優れています。
  3. 手元のタスクに対して最もシンプルで柔軟性の高いソリューションを選択します。

結論

これは、関数型プログラミング(FP)対オブジェクト指向プログラミング(OOP)、またはある言語と別の言語に関するものではありません。コンポーネントは、関数、データ構造、クラスなどの形をとることができます…プログラミング言語が異なれば、コンポーネントの原子要素も異なります。 Javaにはクラスがあり、Haskellには関数などがあります。しかし、どの言語やパラダイムを好んでも、関数やデータ構造の作成から逃れることはできません。結局のところ、それがすべてを要約したものです。

関数はJavaScriptで構成する最も簡単なものであるため、関数型プログラミングについて多くのことを話します。関数型プログラミングコミュニティは、関数構成手法を形式化するために多くの時間と労力を費やしました。

関数型プログラミングはオブジェクト指向プログラミングよりも優れているとか、どちらかを選択する必要があるということです。 OOP対FPは誤った二分法です。私が最近見たすべての実際のJavascriptアプリケーションは、FPとOOPを広範囲に組み合わせています。

オブジェクト構成を使用して、関数型プログラミングのデータ型を生成し、関数型プログラミングを使用してOOPのオブジェクトを生成します。

ソフトウェアの作成方法に関係なく、適切に作成する必要があります。

ソフトウェア開発の本質は構成です。

構成を理解していないソフトウェア開発者は、ボルトや釘を知らないホームビルダーのようなものです。構成を意識せずにソフトウェアを構築することは、ダクトテープとクレイジーグルーで壁を組み立てるホームビルダーのようなものです。

単純化する時が来ました。単純化する最良の方法は、本質に到達することです。問題は、業界のほとんどの人が必要なものをうまく扱えないことです。私たちは業界として、ソフトウェア開発者であるあなたに失敗しました。開発者をよりよく訓練することは、業界としての私たちの責任です。改善しなければなりません。責任を取る必要があります。現在、経済から医療機器まで、すべてがソフトウェアで実行されています。私たちのソフトウェアの品質の影響を受けない、この惑星には文字通り人間の生活の隅はありません。何をしているかを知る必要があります。

ソフトウェアを作成する方法を学びましょう。

本を購入する|インデックス|次へ>

EricElliottJS.comで詳細を見る

EricElliottJS.comのメンバーは、インタラクティブなコードチャレンジのビデオレッスンを利用できます。メンバーでない場合は、今すぐサインアップしてください。

Eric Elliottは、分散システムの専門家であり、「Composing Software」および「Programming JavaScript Applications」という本の著者です。 DevAnywhere.ioの共同創設者として、彼は開発者にリモートで作業し、ワーク/ライフバランスを受け入れるために必要なスキルを教えています。彼は暗号化プロジェクトの開発チームを構築して助言し、Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN、BBC、およびUsher、Frank Ocean、Metallicaなどのトップレコーディングアーティストのソフトウェアエクスペリエンスに貢献しています。

彼は世界で最も美しい女性との遠隔生活を楽しんでいます。