ソフトウェアの品質とは何ですか?

画像

KDPV



誰かがオープンソースソフトウェアを作成していますが、私はソフトウェアをより良くする方法について多くの時間を費やしています。 スタックオーバーフロー、GitHub、Slack、電子メール、プライベートメッセージフォーラムに関するヘルプのリクエストの無限の流れは避けられません。 幸いなことに、最終的には、ある程度の成功を収めて素晴らしいことを成し遂げた多くの人々を知っています。あなたとあなたの助けのおかげでこれに参加したという知識は、新しい業績の良い動機になります。



問題は、ソフトウェアのどの品質が開発者を成功または失敗に導くのかということです。 ソフトウェアを改善し、より多くの人々が成功するようにするにはどうすればよいですか? いくつかの基本原則を明確にしたり、場合によっては直観に頼ったりできますか? (1つの思考の誕生と具体化は、2つのまったく異なるアクションです)。



おそらくこれはDieter Ramsの原則のようなもので、高品質のソフトウェア設計に貢献しているのでしょうか?





全体像は、今日私が書いていることよりもおそらく重要ですが、彼女の「アドバイス」は時々非実用的であるか、まったく適用できないか、さらに悪いことに、 真実です。 これは、「できるだけ簡単に実行しますが、単純すぎないこと」です。 (「できるだけシンプルにするが、シンプルではない」。元のメモ。) 私たち全員が物事をもっとシンプルにしたいのは明らかです。 しかし、この目標を達成するために何をする必要があるか正確にわからない場合もあります。



そして、あなたがやっていることの正しい「一般的な考え」を持っているとしても、あなたのプロジェクトが成功するという保証はありません。 アイデアを実践する方法は、アイデア自体と同じくらい重要です。 悪魔は詳細にあります。



効果的な普遍的なアドバイスをすることはできませんが、おそらく別の方法があるでしょう。 私はトーマス・グリーンとマリアン・ペトレに触発されました。それらの「認知的次元」は、コードなどの「情報アーティファクト」の適合性について「議論のレベルを上げる」ための「討論可能なツール」のセットを定義します。





完璧なプラットフォームはありません。 それ(ある種の抽象的なプラットフォーム)は、ビジュアルプログラミング環境を研究するために、場合によっては特定のアプリケーション向けに作成されました。 (一度にすべてのコードの観測をシミュレートする状況を考えてください。今日のソフトウェアは、1つの画面で完全に表示できるほど小さいですか?おそらく、モジュール方式の方が良いでしょうか?)。 ある次元または別の次元の使いやすさの問題を適切に処理するのは難しいと思います。 (隠された依存関係とロール表現力の両方は、以前に行われたものとは異なる何かがこのコードで行われることを規定しています) ただし、これはソフトウェア設計の「認知的含意」について考えるには良い食べ物です。



「共通フレームワーク」を定義しません。 しかし、私は本当に共有したい所見がいくつかあります。これは昨年の事後合理化を完了するのに適した時期です。私はD3 4.0に多くを費やしました。



「大規模な」 D3プロジェクトには戻りません。 データ接続、スケール、視覚表現とは別のレイアウトなどの概念に非常に満足しています。 ここには興味深い研究もあり、これは私の最後の焦点では​​ありません。 私はD3をモジュールに分割して、より多くのアプリケーションでの使用に適したものにし、他のアプリケーションへの拡張を容易にし、開発を容易にしますが、作業中です。 また、APIの多数の欠陥と欠点を特定して修正します。 これらは簡単に見落とされがちですが、同時に、人々の行動を大きく制限します。



特に、個別に検討する場合、これらの変更が些細なことであると心配することがあります。 そうでなければあなたを納得させることができると思います。 私たち(つまり、ソフトウェアを書く人)は、プログラミングインターフェイスの使いやすさを過小評価する傾向があり、代わりに測定しやすい客観的な品質(機能性、パフォーマンス、正確さ)を考慮する傾向があると思うので心配です。



そのような品質は重要ですが、ユーザビリティの低さには真の価値があります。 デバッガーとの戦闘中に、コードのもつれたブロックを見つけたり、髪を引き裂くのに苦労した人に聞いてください。 むしろ、ユーザビリティをよりよく評価し、最初に使用する高品質のソフトウェアを作成する必要があります。



コードをつかんで、その重さや質感を手で感じることはできません。 コードは「情報オブジェクト」ですが、物理的またはグラフィックではありません。 エディターまたはコマンドラインでテキストを操作して、APIを操作します。



しかし、これは、人的要因の存在との所定の基準に従った相互作用です。 したがって、目的のタスクの実行可能性の基準によって、他のツールと同様にコードを評価できますが、経験豊富なプログラマーになって効果的に使用するのは本当に簡単ですか? コードの可能性と美学を考慮しなければなりません。 すべてが本当に明確ですか? または、逆に悲しいですか? または多分美しい?



プログラミングインターフェイス-これらはユーザーインターフェイスです。 または、別の言い方をすれば、プログラマーも人間です。 デザインの開発における人間の側面を過小評価することをテーマに、ラムズ氏は再び耳にします。



「人々への無関心と彼らが生きる現実は、実際にはデザインにおける唯一無二の罪です。」


たとえば、優れたドキュメントは貧弱なデザインを正当化するものではありません。 「スモークマナに行く」ようにアドバイスすることはできますが、すべてを読んですべての詳細を覚えていると考えるのは愚かなことです。 例の明瞭さ、現実の世界でソフトウェアを解読および編集する能力は、おそらくはるかに重要です。 フォームは関数を渡す必要があります。



ここでは、使いやすさを考慮して、D3で目で見た変更の一部を示します。 しかし、まず、D3のデータ処理に関する集中コースです。



ケース1.魔法のenter.appendを削除します。



「D3」はデータ駆動型ドキュメントを表します。 データは視覚化するものを指し、ドキュメントはその視覚的表現を指します。 D3はWebページの標準モデルであるドキュメントオブジェクトモデルに基づいているため、これは「ドキュメント」と呼ばれます



シンプルなページは次のようになります。



<!DOCTYPE html> <svg width="960" height="540"> <g transform="translate(32,270)"> <text x="0">b</text> <text x="32">c</text> <text x="64">d</text> <text x="96">k</text> <text x="128">n</text> <text x="160">r</text> <text x="192">t</text> </g> </svg>
      
      





これはSVG要素を含むHTMLドキュメントになりますが、概念を理解するために各要素と属性のセマンティクスを知る必要はありません。 たとえば、特定のテキストの各要素は、個別のグラフィック文字であることに注意してください。 要素スタイルのグループを配置できるように、要素は階層順にグループ化されます(さらに含む、含むなど)。



適切なサンプルデータセットは次のようになります。



 var data = [ "b", "c", "d", "k", "n", "r", "t" ];
      
      





このデータセットは文字列の配列です。 (文字列は文字のシーケンスで、文字列ごとに別々の文字があります。)ただし、JavaScriptで表現できる場合、データは任意の構造を持つことができます。



データ配列内の各レコード(各行)について、ドキュメント内の対応する要素が必要です。 これがデータ結合の目的です。ドキュメントを変換する簡単な方法は、データに一致するように要素を追加、削除、または変更することです。



画像

ソースとしてのデータ結合は、データの配列とドキュメント要素の配列を受け取り、選択する3つのオプションを返します。

入力の選択は、ドキュメントを作成してドキュメントに追加する必要のある「欠落」要素(入力データ)を表します。



更新の選択は、変更(再配置など)に必要な既存の要素(データストレージ)を表します。 出力タイプの選択は、ドキュメントから削除する必要がある「残りの」要素(送信データ)を表します。



データを結合しても、ドキュメント自体は変更されません。 結果を計算、入力、更新、表示してから、必要なすべての操作を実行します。 これにより、可視性が提供されます。たとえば、要素の入力と出力をアニメーション化します。



画像

ご想像のとおり、データのマージは、最初の視覚化とすべての変更の両方でよく使用するものです。 この機能の使いやすさは、D3の全体的な「有用性」にとって非常に重要です。 次のようになります。



 var text = g .selectAll("text") .data(data, key); // JOIN text.exit() // EXIT .remove(); text // UPDATE .attr("x", function(d, i) { return i * 32; }); text.enter() // ENTER .append("text") .attr("x", function(d, i) { return i * 32; }) // .text(function(d) { return d; });
      
      





いくつかの詳細(たとえば、要素にデータを割り当てる重要な機能)については触れませんでしたが、トピックの本質が明らかになることを願っています。 上記のコードは、データと結合した後、既存の要素を削除し、更新要素を再配置して、着信要素を追加します。



上記のコードには、誰もが退屈しているユーザビリティの問題があります(line .attr("x", function(d, i) { return i * 32; }) //



)。 これは重複したコードです。入力および更新する属性「x」を設定します。



通常、これらの操作は、入力と要素の更新の両方に使用されます。 アイテムが更新された場合(つまり、最初から作成していない場合)、変更する必要があります。 このような変更は、新しいデータを反映する必要があるため、多くの場合、要素の入力に使用されます。



D3 2.0は、重複の問題を解決するために特定の変更を加えました。選択に入力を追加すると、要素の入力が更新選択にコピーされるようになりました。 したがって、選択に入力を追加した後に更新のリストに適用される操作は、入力と要素の変更の両方に適用されるため、重複するコードは削除されます。



 var text = g .selectAll("text") .data(data, key); // JOIN text.exit() // EXIT .remove(); text.enter() // ENTER .append("text") // .text(function(d) { return d; }); text // ENTER + UPDATE .attr("x", function(d, i) { return i * 32; });
      
      





悲しいかな、それはユーザビリティを悪化させました。



まず、内部で何が起こっているかを示す指標がありません(ロールの表現力が乏しい、またはおそらく隠された依存関係)。 ほとんどの場合、 selection.append



は新しい要素を作成、追加、選択します。 デフォルトでは、更新の選択が変更されます。 サプライズ!



次に、コードは操作の順序に依存するようになりました。更新を選択する操作がenter.append



よりも前に適用される場合、ノードの更新のみに影響します。 それらが後で適用される場合、この場合、入力と更新の両方に影響します。 データを結合する目的は、このような複雑なロジックを排除し、複雑な分岐や反復を行わずに、文書変換のより宣言的な仕様を適用することです。 コードはもっとシンプルに見えるかもしれません。



D3 4.0はenter.append



の不確実性をenter.append



ます。 (実際、D3 4.0は入力と選択の区別を完全に排除しています。選択できるクラスは1つだけです)。 その代わりに、新しいselection.merge



メソッドがあります。これは、入力選択と更新を組み合わせることができます。



 var text = g .selectAll("text") .data(data, key); // JOIN text.exit() // EXIT .remove(); text.enter() // ENTER .append("text") .text(function(d) { return d; }) .merge(text) // ENTER + UPDATE .attr("x", function(d, i) { return i * 32; });
      
      





このメソッドは、一般に受け入れられている手法(selection.append)の動作を歪ませることなく、順序依存性を導入することなく、重複コードを排除します。 さらに、selection.mergeメソッドは読者へのなじみのないポインタであり、ドキュメントで見つけることができます。



原則1.過負荷を避ける



この失敗からどのような結論を導き出すことができますか? D3 3.xはラムズの原則に違反しました。優れたデザインは製品を理解しやすくします。 ディメンションに関しては、 selection.append



の入力選択に対する動作が異なるため、一貫性がありませんでした。 この方法は、過去の動作が明らかではなかったため、役割の表現力が乏しかった。 また、非表示の依存関係もあります。入力を追加した後にテキスト選択操作を開始する必要がありますが、コードにはこの要件が明示されていません。



D3 4.0は、値のオーバーロードを回避します。 enter.append



にデフォルトで機能を追加する代わりに、一般的な場合に役立つ場合でも、 selection.append



常に要素のみを追加します。 サンプルを結合する場合は、新しいメソッドが必要です! したがって、これはselection.merge



です。



ケース2. transition.eachマジックの削除



遷移は、ドキュメントへの変更をアニメーション化するインターフェイスを選択することに似ています。 ドキュメントを即座に変更する代わりに、遷移はドキュメントを現在の状態から特定の時間の間、目的のターゲット状態にスムーズに補間します。



トランジションは異種混合にすることができます。複数の選択を介してトランジションを同期する必要がある場合があります。 たとえば、軸に沿って移動するには、線とラベルの位置を同時にチェックする必要があります。



画像

または、オプションの1つとして:



 bl.ocks.org/1166403 One way of specifying such a transition: d3.selectAll("line").transition() .duration(750) .attr("x1", x) .attr("x2", x); d3.selectAll("text").transition() // .duration(750) // .attr("x", x);
      
      





(xは線形スケールと同じ関数で、対応するデータ値から各目盛りの水平位置を計算するのに役立ちます)。



これらの2行( d3.selectAll("text").transition() //



および.duration(750) //



)は注意する必要があります。 重複したコードが再び表示されます。行要素とテキスト要素の遷移は個別に作成されるため、遅延や期間などの同期パラメーターを複製する必要があります。



問題は、これらの2つの遷移が同期されるという保証がないことです。 2番目の遷移は最初の遷移の後に作成されるため、少し遅れて開始されます。 ここでは、他のアプリケーションとは異なり、1〜2ミリ秒の差は感知できない場合があります。



D3 2.8は、この種の異種遷移を同期するための新しい関数を導入しました。これにより、 transition.each



選択された各要素を反復処理する方法が追加されました。 次のように言えます:



 var t = d3.transition() .duration(750); t.each(function() { d3.selectAll("line").transition() // .attr("x1", x) .attr("x2", x); d3.selectAll("text").transition() // .attr("x", x); });
      
      





enter.append



、使い勝手が悪い:既存のメソッド(selection.eachとselection.transition)の動作を通知せずに変更します。 特定の選択で2番目のトランジションを作成する場合、古いトランジションは置き換えられません。 古いトランジションを再選択するだけです。 おっと!



この例は、残念ながら、教育学のために考案されました。 transition.selectとtransition.selectAllを使用してフェッチすることで遷移を同期する別のより明確な方法があります(D3 3.xでも):



 var t = d3.transition() .duration(750); t.selectAll("line") .attr("x1", x) .attr("x2", x); t.selectAll("text") .attr("x", x);
      
      





この場合、ドキュメントルートディレクトリへの遷移tは、行要素とテキスト要素を選択することで適用されます。 この移行は制限されており、この問題の解決策は次のとおりです。移行は新しい選択肢にのみ適用できます。 再選択は常に可能ですが、余分な作業と余分なコードです(特に、データプーリングによって返される一時的な入力、更新、および終了の選択)。



D3 4.0では、transition.eachの移行ロジックのあいまいさがなくなりました。 これでselection.eachコマンドの実装が提供されます。 代わりに、selection.transitionコマンドを変換手段に渡すことができ、その結果、新しい遷移は指定された遷移から時間を継承します。 これで、新しい選択肢を作成するときに目的の同期を実現できます。



 var t = d3.transition() .duration(750); d3.selectAll("line").transition(t) .attr("x1", x) .attr("x2", x); d3.selectAll("text").transition(t) .attr("x", x); Or when using existing selections: var t = d3.transition() .duration(750); line.transition(t) .attr("x1", x) .attr("x2", x); text.transition(t) .attr("x", x);
      
      





新しいデザインはselection.transitionの動作を歪めます。 しかし、新しいシグネチャメソッド(名前は同じでパラメーターが異なるメソッド)は、かなり一般的なデザインパターンであり、動作の違いは1回の呼び出しで特定されます。



原則2.行動のパターンを避けます。



この原則は、より重大な違反に対する値の過負荷を避けるために、前の原則の継続です。 ここでD3 2.8はselection.transitionとの矛盾を導入しましたが、動作トリガーは別のクラスではありませんでした。 それはちょうどtransition.each呼び出しの中にありました。 この設計の大きな結果は、transition.eachでラップすることにより、記述しなかったコードの動作を変更できることです。

グローバル変数を設定して動作をグローバルに変更するコードを見つけた場合、これはおそらく悪い考えです。



振り返ってみると、今回は特に印象的だと思います。 私はちょうど何を考えていましたか? たぶん私は失敗したデザイナーですか? 悪いアイデアが魅力的である理由を理解することには、ある程度の慰めがあります。それは、将来認識し、回避するのが簡単です。 ここで、新しい方法の使用を避けて、想像上の複雑さを最小限に抑えようとしたことを思い出します。 ただし、これは、既存のメソッドをオーバーロードするよりも新しいメソッド(または署名)を導入する方が簡単な好例です。



ケース3.マジックd3.transitionの削除(選択)



最新のプログラミング言語の強力な概念は、再利用可能なコードブロックを関数として定義できることです。 コードを関数に変換すると、コピーして貼り付けることなく、好きな場所で呼び出すことができます。 一部のソフトウェアライブラリはコードの再利用のために独自の抽象化を定義しますが(たとえば、ダイアグラムのタイプを拡張します)、D3はコードをカプセル化する方法のインジケーターです。これは関数でのみ行うことをお勧めします。



選択クエリと遷移は、selection.styleやtransition.styleなどの多くのメソッドを共有してスタイルプロパティを設定するため、選択クエリと遷移の両方に影響する関数を作成できます。 例:



 function makeitred(context) { context.style("color", "red"); }
      
      





makeitred選択を設定して、テキストを赤で即座に再描画できます。

d3.select ("body") .call (makeitred);





ただし、makeitredにトランジションを渡すこともできます。その場合、テキストの色は短期間赤に変わります。

d3.select("body").transition().call(makeitred);







このアプローチは、軸やブラシなどの組み込みD3コンポーネント、およびズームなどのモードで使用されます。

このアプローチの欠点は、遷移と選択に同一のAPIがないため、すべてのコードが不可知論的であるとは限らないことです。 軸のチェックボックスを更新するためのデータ集計のカウントなどの操作には、フェッチクエリが必要です。



D3 2.8は、このユースケースの別の誤った機能を提供しました。d3.transitionをオーバーロードし、通常はドキュメントルートディレクトリへの新しい遷移を返します。 d3.transitionコマンドを入力し、transition.eachコールバックの内部にいた場合、d3.transitionコマンドは指定された選択肢に新しい遷移を返します。 それ以外の場合は、示された選択肢を返すだけです。 (この関数は、上記のtransition.each欠陥と同じコミットに追加されました。問題は単独では発生しません!)



私の面倒な説明から、この関数を作成することは悪い考えであると結論付ける必要があります。 しかし、科学のためにそれをより詳細に見てみましょう。 上記のmakeitred



関数を記述する同様の方法は、(sを使用して)コードを(tを使用して)別のコードを持つAPIの選択に制限し、代わりにコンテキストが遷移である場合、API遷移に適用します。



 unction makeitred(context) { context.each(function() { // var s = d3.select(this), t = d3.transition(s); // t.style("color", "red"); }); }
      
      





transition.each



関数の混乱は次のとおりですd3.transition



selection.transition



を呼び出し、 transition.each



コールバック内にあるため、新しい遷移は周囲の遷移から同期を継承します。 問題は、d3.transitionが本来すべきことをしていないことです。 また、コンテキストとtの両方が不明なタイプ(選択または遷移)であるという混乱もありますが、選択と遷移の両方に対してmakeitred関数を呼び出す便利さによって正当化される可能性があります。



D3 4.0はd3.transition



(選択)を削除します。 d3.transition



は、 d3.selection



と同様に、ドキュメントのルートディレクトリへのトランジションの作成にのみ使用できるようにd3.selection



。 選択と遷移を分離するには、通常のJavaScriptコマンドを使用して、好みに応じてタイプを確認します: instanceofまたはduckタイピング



 function makeitred(context) { var s = context.selection ? context.selection() : context, t = context; t.style("color", "red"); }
      
      





transition.each



およびd3.transition



関数の「マジック」を削除することに加えて、新しいmakeitred



関数はtransition.each



完全に回避しながら、D3 4.0の選択および新しいtransition.selectionの使用に固有のコードを記述することができることに注意してください。メソッド。 これは、sの選択が使用されておらず、tがコンテキストと同じ意味を持っている場合の非常にフェッチされた例であり、したがって、元の定義に簡単に縮小できます。



 function makeitred(context) { context.style("color", "red"); }
      
      





しかし、これは私の個人的な意見です。 特定のコードを選択する必要は、 transition.each



の使用の完全な対応を必要とするべきではありません! グリーンとペトラは、それを時期尚早な事業と呼びました。



原則3.正確な使用



d3.transition



メソッドは、2つの操作を組み合わせようとしました。 最初は、 transition.each callback



コマンド内にいるかどうかを確認します。 まだそこにいる場合、2番目の操作は選択から新しいトランジションを抽出します。 ただし、後者はselection.transitionを使用できるため、 d3.transition



は多くのことを実行しようとし、その結果、非表示になりすぎます。



ケース4. d3.activeを使用してトランジションを繰り返す



D3遷移は有限シーケンスです。 ほとんどの場合、移行はドキュメントの現在の状態から目的の状態への移行です。 ただし、いくつかの段階を経てより思慮深いシーケンスが必要になる場合があります。



画像

(アニメーションを準備するときは注意してください!Heer&RobertsonによるStatistics Data Graphicsのトランジションのアニメーション化に関する情報をお読みください。)

この例で説明されているシーケンスを繰り返すことができます。



画像

D3には無限のトランジションシーケンス用の特別なメソッドはありませんが、古いトランジションが既に終了しているときに新しいトランジションを作成できます。 これは、私がこれまでに書いた中で最も紛らわしいコード例を導きました。



 svg.selectAll("circle") .transition() .duration(2500) .delay(function(d) { return d * 40; }) .each(slide); // function slide() { var circle = d3.select(this); (function repeat() { circle = circle.transition() // .attr("cx", width) .transition() .attr("cx", 0) .each("end", repeat); })(); // }
      
      





以前は控えていた「認知的結果」の後に何かを説明しようとすることをためらうこともあります。 しかし、あなたはこれから非常に遠いので、私は最善を尽くします。 最初に、 transition.each



はループを反復するcallback



を起動しcallback



Callback



、循環変数をキャプチャする繰り返し呼び出しの終了を決定します。 最初は、サイクルはサイクルの1つの要素の選択です。 したがって、トランジションの最初のステージはselection.transition



を使用して作成され、周囲のトランジションから時間を継承します! 2番目のステージはtransition.transition



を使用して作成されるため、最初のステージが終了した後にのみ開始されます。 2番目のステージはサイクルに割り当てられます。 そして最後に、2段階の移行シーケンスが終了するたびに、移行サイクルを繰り返して修正する繰り返しコマンドが実行されます。



さらに、おそらく、1つのパラメーターを持つtransition.each



が2つのパラメーターを持つtransition.each



とはまったく異なることに気づいたでしょうか?

うわー



では、D3 4.0と比較しましょう。



 svg.selectAll("circle") .transition() .duration(2500) .delay(function(d) { return d * 40; }) .on("start", slide); function slide() { d3.active(this) .attr("cx", width) .transition() .attr("cx", 0) .transition() .on("start", slide); }
      
      





D3 4.0はd3.active



提供し、指定された要素のアクティブな遷移を返します。 これにより、各サイクルのローカル変数(循環変数)を取得する必要がなくなり、呼び出しを閉じる必要性(繰り返し関数)が増加するとともに、わかりにくいtransition.each



コマンドが必要になります!



原則4.理解できない決定は決定ではない



これは、問題を解決する方法がある場合です。しかし、それは非常に複雑で不安定なので、あなたはそれを見つける可能性が低く、ほとんど覚えていません。 私は図書館を書きましたが、それでも私は常にGoogleに頼っています。



また、いです。



ケース5.バックグラウンドでハングアップする



D3 3.x , . , "" . :



, , , , . , , , "" !



, : , . , .



D3 4.0 . ; . D3 4.0 , , . , .



5.



. , , .



6. selection.interrupt



, . — . , , () .



, . , . , , :



画像

D3 . , selection.interrupt



, .



selection.interrupt



D3 3.x , , . — D3 3.x's, , . ( ).

D3 3.x , :



 selection .interrupt() // interrupt the active transition .transition(); // pre-empt any scheduled transitions That mostly works. But you can cheat it by scheduling another transition: selection .transition() .each("start", alert); // selection .interrupt() .transition();
      
      





, . , , .



D3 4.0, selection.interrupt



, , . : , .



6.



, , , . , API-, , "". " ."



7.



. D3 4.0 , -. , D3 3.x:



 selection.transition() .duration(750) .ease("elastic-out", 1, 0.3);
      
      





, :





D3 4.0:



 selection.transition() .duration(750) .ease(d3.easeElasticOut .amplitude(1) .period(0.3));
      
      





1 0,3 , , , API , :



画像

, , transition.ease



; . D3 4.0 .



7.



, , . . ( , context.arc



2D canvas.)



, D3 , . . , .



?



. .



, . . , . , , .



. , . , , D3 , . , , , , !



, D3 D3. , , .



. . , -.



. , . . .



. . .



. , .



. , . , . .




All Articles