閱讀Mike Bostock文章:What Makes Software Good?

註:此篇為翻譯Mike Bostock的文章:What Makes Software Good?

身為一個開源軟體開發者,我花很多時間在思考如何讓軟體很好。

身為開發者,無可避免地會有永無止盡的求救來自於Stack Overflow, GitHub issues和Slack mentions、email等。幸運地是你也可以看到大家是如何成功應用你所開發的軟體做出超乎你想像的事情,知道自己幫得上忙,也是一個很強大的自我鼓勵,讓你堅持者去開發。

所以你會開始思考一件事?什麼樣的軟體特質,可以讓使用者成功或是失敗?如何改善我的軟體讓更多人可以成功?我可以清楚地說明任何開發原則,或者其實我只是見招拆招?如同知名德國工業設計師Dieter Ram’s的設計十誡

Good design is innovative.
Good design makes a product useful.
Good design is  aesthetic.
Good design makes a product understandable.
Good design is unobtrutive.
Good design is honest.
Good design is long-lasting.
Good design thorough down to the last details.
Good design is environmentally-friendly.
Good design is as little design as possible.

我曾經嘗試談論過所謂的Big Picture Stuff,像是從有趣的小問題著手、辨認和減少工具的偏誤或是利用相關技術以及標準。

雖然Big picture是重要的,可能重要於今天我要談的事情,但實際上我自己感覺Big picture的建議往往不實用,甚至很差。比如說,“盡量簡單,但別太簡單(Make it as simple as possible, but no simpler)”,我們也許都想要事情越簡單越好,但往往我們不知道如何取捨來達到目標。

即使你有一個正確的大目標,往往不能保證你的設計能成功。實踐一個想法和想法本身同樣重要。魔鬼就在細節之中。

假如我沒辦法提供實務性或是可以立即實踐的意見,那麼給少點意見反而更好。一個來自Green&Petre的Cognitive dimensions defines a set of “discussion tools" to “raise the level of discourse" about the usability of “information artifacts" such as code.

Abstraction gradient

Closeness of mapping

Consistency

Diffuseness

Error-proness

Hard mental operations

Hidden dependencies

Premature commitment

Progressive evaluation

Role-expressiveness

Secondary notation

Viscosity

Visibility

上面這十誡聽起來不是很完美,也不是框架。聽起來只是單純對於視覺編成(Visual Programming),且只限於特定情境。我發現很難單純地把一個領域中單一象限的問題直接映射到另一個領域,但還是個很好的起點來思考軟體設計的“Cognitive consequences”。

這邊我並不會去定義一個泛用型的框架,但我有一些觀點可以分享,恰好是一個時機對於D3 4.0做事後的分析(Post-hoc rationalization),除了對於Data joing, scales, layout解離於視覺呈現很滿意外,這邊我將D3分解成一個又一個模組,讓其他人更容易來延展應用,且修改了編程上的API,很擔心這樣的改變對於使用者來說很微妙,但實際上很重要。多數開發者過度專注於functionality, performance, correctness這些較好量化的主觀特性。

的確上面的特質很重要,但是使用不易(poor usability)造成的代價也不小。問問那些在奮力理解令人疑惑代碼模塊的人,以及如找髮絲般除蟲的人。我們必須盡快找到一個評估易用性的方法,且讓程式更好用(We need to get better at evaluating usability sooner, and better at making software usable in the first place.)。

我們無法把代碼放在手上把玩感受它的重量或是質地。代碼的本質是資訊的"人工產物"(information artifact)而非物理性或是圖形。你可以藉由編輯文本或是指令行來跟這些API互動。

這些互動依據標準定義來說,受到人為因子複雜性所影響。所以我們應該評估代碼,就如同評估任何工具一樣,並非只依據他能否達成任務,還要看這些工具是否好用,有效率和令人享受。同時我們還需要考慮到其Affordance(直觀特質)和Aesthetics(美觀)。

編程介面即是所謂的使用者介面。簡單說,編程者也是人。在關於低估設計中人的成分,又可以聽到Ram’s的說法:

Indifference towards people and the reality in which they live is actually the one and only cardinal sin in design.

這同樣的暗示好的文檔說明不代表是壞設計的藉口。也許你可以叫使用者去RTFM,但這是相當蠢(folly)的假設所有使用者能記得文檔中的任何小細節。範例的清晰明白(clarity)、軟體的可拆性(Decipherability)和除蟲性(Debuggability)在真實生活中很重要。

這裡提供七個案例來分享D3 4.0所做的改進和想法,這邊先挑一個來翻譯:

案例一:關於enter.append語法的改進

D3最重要的觀念,便是資料驅動(Data-Driven),這邊的Data代表的是你要視覺化的資料,而document則是你視覺化的呈現,更精確地說就是網頁的Document Object Model(DOM)。


<!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>

上面是一個簡單的html文件,裡面包含一個svg模塊,其中相對應的資料就如下:


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

在這序列資料中,要有相對應的文檔模塊(即為),這時就是d3中很重要的關鍵概念:Data-join

螢幕快照 2018-02-09 下午6.14.38

D3中Data-join的設計是給予其資料的序列和相對應要的文件模塊,其會返回三個selection:

  • enter selection: 代表還未添加上去的要用來呈現資料的部件(element)。
  • update selection: 代表本來在文檔中的部件,但需要調整的部分。
  • exit selection:代表在更動後需要被移除的部件。

但data-join並分直接去調整DOM本身,而是去計算所需要的enter, update, exit樣貌。在下面這個簡單的範例中,如何讓文檔隨者資料改動而改動,是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; });

上面這塊代碼,是最初data-join設計的邏輯,先選擇文檔模塊,導入資料(join),去掉更新後被移除的模塊(exit),在更新還存在的部分(update),接者把新加入的資料代表要件更新(enter)。觀察上面的代碼模塊會發現畫有辣椒部分的代碼是重複的:分別是更新x attribute在enter和update的部分。

在D3 2.0的時候,為了解決這個重複的問題,將對於enter selection執行append時,自動複製entering elements到update selection的部分,如此撰寫邏輯可以變成如下面這樣:


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; });

雖然這樣的設計減少了代碼重複,但是實際上似乎是造成了使用性降低,第一點,因為其實踐的機制,會讓人不知道原來enter selection,會將append的要件,複製到update selection的部分,因此而產生了前面設計所謂的poort role-expressiveness或是hidden dependency。
第二點,代碼的順序因此變得會影響到執行,最後在D3 4.0版本,取消了這樣的設計,提出了新的selection.merge,來整合enter和update的selection。


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; });

由上面的過程,可以體會到Rams原則: good design makes a product undersandable,而在cognitive dimension詞彙上,它造成了poor consistency、poor role-expressiveness和hidden dependency.

發表留言