【Web】徹底理解同源政策(Same Origin Policy)

對於網頁學習者來說,同源政策(Same Origin Policy)可能是一個很重要的困惑點。

當我們終於搞懂了client與server;理解了HTTP request/response;摸熟了AJAX;想出了一個絕佳的專案點子⋯⋯但不知為何,API的資料就是讀不出來。

於是我們開始研究關鍵字「同源政策」並持續Google,每次都好像又多懂了一點,但又不是那麼肯定——特別是在部署至production環境時,只要網域轉換,似乎就會發生不可預期的效果。

讓我們試著一次把它搞懂吧。

Meme of AJAX 圖片:Ajax迷因。Same Origin Policy是件麻煩的事(出處:reddit)

瀏覽器/使用者代理人/安全性

要理解同源政策,最核心的重點就是理解:

同源政策是由「誰」來執行?以及「為什麼」?

是HTTP(S)的嗎?還是JavaScript?是瀏覽器嗎?是網站框架嗎?還是TCP/IP?

如果能將以下重點銘記在心,我想在閱讀文件時就會感到豁然開朗:

同源政策,Same Origin Policy,是「瀏覽器」作為使用者代理人,為了「安全性考量」,所設定的規範。

什麼是使用者代理人?

請試著想像如下情境:我們拿著雙證件到銀行開了戶,也存了錢進去。行員告知:如果需要匯款或是其他操作,可以透過帳號+密碼登入銀行網站,在瀏覽器中進行——這樣就不用每次都跑銀行囉。

在現實世界裡,與我們互動的是銀行行員,我們可以透過證件與存摺存取帳戶;但在網路銀行中,與我們互動的對象成了Web server,而驗證身份的方式則變成帳號+密碼。

當我們請銀行行員協助時,使用的是人類的自然語言。但在網路銀行中,我們與web server是透過使用者介面/通訊協定/網路訊號來進行溝通的。

但作為一個人類,我們無法用大腦解析這一切。

於是我們需要一個代理人(user agent)——也就是瀏覽器,協助我們與web server進行溝通。這包含了渲染UI、設定Cookie、執行TCP三向交握、通訊加密、並將我們的表單解析成Http Request後送出⋯⋯等。

使用者的「安全性」是瀏覽器的職責

在操作的過程裡,瀏覽器有義務保護使用者的資料——尤其是涉及身份驗證的部分。

我們會透過瀏覽器登入各種帳號(例如網路銀行、臉書、Gmail、以及各式各樣來路不明的網站)。當成功使用帳號密碼登入時,這些服務的Web server會透過HTTP header, 提供一段auth token並要求瀏覽器存在cookie裡。在auth token的有效期間內,我們的http request都會自動帶上這段cookie,以此為憑,我們因此可以對服務進行各種操作,例如匯款、貼文、按讚、回覆email⋯⋯等。

但問題來了,如果有一個惡意的盜版漫畫網站evil.com,當我們在訪問時,透過頁面中JS以及AJAX,偷偷在瀏覽器中執行這樣的行為呢:

「請把銀行bank.com的cookie資料(包含auth token)讀取出來;透過AJAX+POST;回傳到https://evil.com/sniff

當然,作為一個使用者代理人,瀏覽器會拒絕如是請求。

拒絕的原則是什麼呢?那就是同源政策最重要的一環:「非同源的資料讀取是不被允許的」。既然evil.com不能讀取bank.com設定的cookie,那當然也無法將之回傳。

火狐姬

同源(Same-origin)的定義

如果能充分了解以上原則。剩下的,就只是技術細節的問題。

我們可以透過火狐家的MDN文件理解同源政策的規範。一言以敝之,我們可以把origin視做一個three-tuple(scheme, host, port),也就是(通訊協定, 網域, 埠號)的組合。兩個document的origin只要任一欄位不相同,就會被瀏覽器視作不同源[1]。

舉例如下:

此外,同源政策是以document為單位的。舉例來說:假設store.mystie.com的document透過<script>標籤取得了來自cdn.google.com的JS腳本,則該腳本的origin仍是由blog.mystie.com來定義。

在實務中,我們最常遇到的origin變數往往是網域。為了方便起見,以下討論會將網域等同於origin使用,但請注意origin包含的不只有網域而已。

SOP show domain 圖片:我們可以透過document.domain來取得文件所屬的網域。

跨源存取細節

討論到這裡,可能我們已經想到一堆例外了⋯⋯

  • 我的網站會透過CDN導入jQuery函式庫和Bootstrap的CSS,為什麼這不受限制?
  • 我的網站會使用<img>讀取外站圖片,為什麼這不受限制?

事實上,同源政策其實有許多細節,甚至各家瀏覽器的實作都會有些微不同。請注意,當我們在討論這個問題時,我們的角色已經轉變成服務的提供者與Web Server了。在研究同源政策時,釐清主詞(使用者/瀏覽器/網站後端)以及行為(request/response)是最重要的起手。

我們可以透過MDN的文件理解不同的跨源行為

跨源寫入(write)原則上允許

可以在A網域的document中提交請求給B網站;也可以透過超連結、轉址等方式連結至B網站。

跨源嵌入(embed)原則上允許

可以使用<img> <video> <iframe>標籤嵌入來自其他網域的資源。

可以使用<link rel="stylesheet" href="…">套用來自其他網域的CSS。

可以使用<scrip src="">標籤嵌入來自其他網域的腳本[3]

跨源讀取(read)原則上禁止

A網域的document不能讀取B網域的本地端儲存資料(例如Cookie)與回應。

值得注意的是:我們可以在A網域的document中,使用JS發送AJAX Request給B(跨源寫入允許);但就算B有回應;文件也無法讀取response(跨源讀取禁止)。例如這樣的jQuery操作$.get("https://www.google.com ")在所有非Google的document中,都會回報disallows reading的錯誤。

那第三方的Client端API是怎麼實作的?

如果跨源讀取是被禁止的,作為一個開發者(假設是交友網站),如果我們希望有一個第三方API服務(例如臉書),可以讓我們透過瀏覽器+AJAX取得資料,這其中的實作原理是什麼呢?

實情是:臉書作為第三方服務的提供者(務必注意主詞!),在回應請求時,是可以透過HTTP header中的Access-Control-Allow-Origin欄位,請瀏覽器特別通融。

舉例來說,當我們的交友網站向臉書申請API帳號時,臉書會提供一組token並詢問我方網域(假設是https://friend.com)。當我們使用這組token請求資料時,臉書就會在response中設定Access-Control-Allow-Origin=https://friend.com,翻成白話就是:

雖然 friend.com 與我不同源,但請准許他讀取這則HTTP response。

當收到這樣的回應時,瀏覽器就會允許friend.com之下的document讀取來自facebook API的回應囉[4]。

如果API server將Access-Control-Allow-Origin設定為*時,代表任何origin都可以讀取回覆,不受同源政策限制。

當然,回到一開始銀行的例子。基於安全性考量(只賦予必要的最小權限),當然就沒有必要設定這個欄位了。

client_app 圖片:使用client端API時,設定網域通常是必要的

那麼⋯⋯我們就這樣高枕無憂了嗎?

與同源政策常常一起討論,且觀念容易糾纏在一起的另一個主題,是所謂的跨域請求偽造攻擊(Cross Site Request Forgery)。這兩者彼此概念交錯,但其實同源政策並不能完全防止跨域請求偽造攻擊——不過這也是另一個主題了,我們就有空再說吧。

目前為止,希望我們已經清楚解釋了同源政策的大概念。


註解

[1] 在RFC-6454原文中的host對應的中文應該是「主機名稱」。但本篇文章主要聚焦於瀏覽器的一般用例(而非各種形式的user agent),因此使用「網域」一詞雖然不完全精準,但為了可讀性仍然選用。

[2] cookie的讀取權限可以下放給子網域。細節請見RFC-6256中的Domain Matching章節。

[3] 請注意這包含了「伺服器端後果自負」的前提。作為後端服務的開發者,在document中嵌入來自第三方(諸如Google CDN)的JavaScript意即「允許第三方腳本以我的origin身份進行操作」——這是另一個網頁安全性的大主題,有興趣的讀者可以搜尋Cross Site Scripting(XSS)。

[4] 請注意這只是允許讀取response,並不包含允許friend.com讀取facebook.com的cookie的意味。

[ref] IETF | RFC-6265 HTTP State Management Mechanism

[ref] IETF | RFC-6454 The Web Origin Concept

[ref] LiveOverflow | CSRF Introduction and what is the Same-Origin Policy? Concept

[ref] MDN | Same Origin Policy

[ref] stackoverflow | Does including all these 3rd party javascript files impose a security risk?

【舞獅】之五:發情的公狗

上色情網站似乎是許多男孩一輩子的回憶。

就從與W男孩的故事說起吧。那時我約莫國小五年級,家中剛裝上中華電信的撥接網路。某個假日的午後,我與W約好一起來家裡玩遊戲。在知道有網路後,W很興奮地為我示範了他從哥哥那學習到的,瀏覽色情網站的方法。一邊為我展示性愛圖片與故事的同時,自己也津津有味地瀏覽了起來。

晚餐飯後,父母親陰沉著臉,找機會支開姊姊並隔離了我。

「你在學校都交些什麼朋友?嗯?」我大概心裡有數,惶惑間,一時也不知如何回應。

「你知道W在家裡做了什麼事嗎?」父親似乎壓抑了極大的怒氣。

「他看了一堆色情的圖片!」父親的聲音無法遏止地激動起來:「他看了這樣的故事:一個學生,怎麼樣強暴了他的老師!一個姊姊,如何跟他的弟弟上床!」

「你知道嗎?你覺得這樣對嗎?嗯?」

其實在W熟稔地瀏覽這些素材時,我全程都在旁兩眼發直地望著。但懾於當時的氣氛,隱隱然感受到這其中似乎存在很大的邪惡,我滿頭冷汗,在情急中勉強擠出了一個謊:

「我不知道⋯⋯他用電腦的時候,我睡著了。」

不知道當時父親對我的謊言是信或不信,總之這件事就便在一陣嚴厲的斥責中被擱置了。盛怒的父母接著要求我交出了W家裡的電話(我蒼白無力地做了一些抗拒),並由母親致電W家長轉述此事。話筒中,母親要求W親自來接聽,對他說了這樣的話:

「阿姨知道你們對這些事情一定很好奇,但這真的對你們的身體不好,懂嗎?」

掛上電話,父親冷冷地接上一句:「以後不准W來我們家玩。」

隔天我懷著忐忑不安的心情回到學校,在那樣的男孩歲月裡,初嚐「朋友」這種情感聯繫的重量,其美妙感絲毫不下高中時第一次與心儀的女孩曖昧的情愫——在電影、卡通與三國小說的潛移默化下,每個男孩都彷彿與自己的夥伴立下了同生共死的義氣連結,而我無疑是一個沒有保守秘密的海盜,甚至在父母的詰問下,背棄了自己的同伴。

(我不知道,我睡著了⋯⋯)

下課時,W男孩從背後用力地踹了我的屁股,露出了一個大大的陽光笑容:「靠悲哦,林北木是三小啊?」

霎時感到瞬間的解脫,我回應以一個年幼時所能想像出最酷最叛逆的鬼臉,聳聳肩表達無奈,接著便與W嬉鬧了起來。

你的童年裡有沒有一個隱密禁忌的角落呢?比如說回家路上的某間空屋;或是校園陰暗的地下室裡,某個堆滿化學實驗品與標本教具的樓梯間。因為老師與父母的鄙棄與禁止,那樣的空間便因此發展出形形色色的傳說與故事,偶有機會前往那兒一訪的同學,往往也帶回了玄上加玄的親身體驗:

「我在返校打掃時,竟然發現那個假人晚上看的方向,和白天不大一樣」

「但是,那天只有我與某些人來學校,我們都沒有教室的鑰匙啊⋯⋯」

諸如此類,訴說這個故事的孩子,在成為同學眼中英雄的同時,也使得那個空間更添一份傳奇色彩。

性對幼時的男孩而言,就像是那個角落一樣。包裹禁忌,充滿誘惑,同時可以讓人成為英雄。

在W男孩事件後,上色情網站就變成了我念茲在茲的一件事。小學生的作息大部分是與父母是重疊的,因此沒有太多自由上網的時間。然而,一個月裡,總有幾個日子只需上半天的課(比如說月考的下午)。在那個日子裡,我總在放學鐘響後急匆匆地奔回家,打開電腦。由於母親規定我必須在兩點半前上床午睡,且不定時會自工作處返家進行午睡突襲檢查,基於這樣風險的不可承擔性,我通常習慣準時就寢,以至於必須在約莫兩小時內完成我的探索。

可別以為忙碌的小學生只想到色情網站,在這兩小時之內,我還選擇將一小時撥給平常沒有什麼機會玩的遊戲「仙劍奇俠傳」。如果某日心存僥倖,不小心偷玩了超過兩點半,同時母親又還沒有回來,在午睡前就必須用濕毛巾敷在那具以現代角度來看厚重得不可思議的CRT電腦螢幕上,以順利將之降溫,避免精明的母親察覺異狀。

從那個國小午後開始,一直到大學時北上住進學生宿舍為止,絕大多數這樣的時光都是最令人期待的:父母遠行而我獲得短暫片刻的安全獨處,在大部分這樣的時間裡,通常我不是在玩遊戲,便是在上色情網站。那個年代裡,接通撥接網路時數據機會發出一段奇異的電訊雜音。對於沒有經歷過那段歲月的讀者,很抱歉我委實無法將那段雜音與現存的任何經驗連結——硬要說的話,可能有點像是殺雞吧:雞先是在籠子裡咯咯作響,最後發出一段漫長的哀號聲,網路便這樣接通了。

我依循著那個下午的記憶,在當時最流行的Kimo搜尋引擎上輸入「色情」兩個字,隨意進入任何一個網站,就可以獲得海量的性愛圖片。來自日本的貼圖大多偏情境式的,圖中女角可能穿著好看的學生百褶裙、泳裝或是護士服,表情混雜著壓抑、羞恥與喜悅,男女的關鍵部位則通常都打有馬賽克;而歐美的圖片則是直來直往,大開大闔且肉慾橫流,講求乳豐臀肥屌大,器官所有的細節與凹凸皺摺,毫不掩飾地讓看客一覽無遺。

以那個年代的技術觀點而言,這樣即時且取之不盡的刺激,恐怕也足以使一個成人陷入慾望的狂亂狀態,更何況是一個未經世事的小學生。宛若巴夫洛夫之犬,數據機的撥接聲於我而言就是慾望之鈴,光是鈴聲響動就能讓我感到酣熱與膨脹。我在無法理解勃起的歲月裡便已勃起;無法理解射精時便已沈溺於射精。在那個歲月裡我偏愛歐美的無碼圖,那樣清楚直白的描繪總是讓人目不轉睛,卻又過目即忘,忘了又想,日復一日。

猶記得當時我在色情網站上看到口交的照片,感到的其實是疑惑與不解,甚至有些許的不安想要將之關閉。為什麼要那樣子做呢?為什麼要看來很舒服的樣子呢?因為好奇,我甚至曾在自己的房間裡以劈腿之姿,想要試著舔舐自己,以瞭解那樣快感的來源。由於筋骨不夠軟Q,這樣的嘗試沒有成功。

很後來的後來,我去當兵。當兵那年是我人生中最暴躁負面的一段日子。儘管對軍中的光怪陸離早有耳聞,但身歷其境仍然不免瞠目結舌。這是個讓人全無熱情與才華施展的所在,只有不成文的生存法則與雄性生物間無止盡的傾軋咆嘯,恃強凌弱。身陷其中就像是一個臨時演員,既無法抽離情緒,又委實難以入戲。

我服役的部隊是一個新兵訓練中心,在這裡受訓的新兵,在經過四十五天的訓練後,有部分人會被選中,留下來直接轉任教育班長。我不止一次目睹那些不久前還在隊伍裡瑟瑟發抖的新兵,是如何在極短的士官訓練後,流暢地對著下一梯新兵,怒吼著那些在老班長間流傳的名言:

「慢慢來沒關係嘿。你拖我時間,我就拖你時間啊」

「有人說可以向左轉嗎?自動導航嗎?」

又或是某日我搞砸了一件完全沒有被交接過的備課業務,使得某堂訓練課程因為缺乏教具而無法進行。那位鬢髮短而斑白,肩上別有兩朵梅花肩章,一身精悍肅殺的海軍陸戰隊教官因此大發雷霆,在百餘人面前逼視著我咆哮:

「媽的預官!又是預官!你哪個學校?台大?台大就出你這種廢物?」

「報告長官。是!我馬上處理」

「處理?去叫你們中隊長滾過來!媽的!混蛋!」

「報告長官。是」

放假回家的日子裡,我已經擁有自己的電腦與房間。深夜裡我總是那樣乾渴地瀏覽著色情網站,彷彿要一次將十幾天在營的鬱悶收集起來,再狠狠噴射出去。我感到自己充滿抑鬱與暴虐,在射出的前一刻,我總是幻想著自己壓住一個抗拒復順服的女孩,我壓住她的後腦勺,以A片上那樣強悍的力道往自己的下體按壓著,吞吐著,口交著。

全黑的房間裡螢幕發著淺淺的藍白光,照著我痴傻的臉龐與軍人短髮。發射完畢後,思緒在高潮的痙攣電波中緩慢甦醒,皺著眉清理完手上那些到處沾黏的、穿透層層衛生紙的黏液,才虛脫地上床睡去。

如果從小學五年級算起,約莫是三年後,國中的〈健康教育〉課本裡,性與性交才首次出現在學校的課堂中。在那個篇章裡,大部分的的男同學們都以一種「我早就全部都知道了」的那種詭異神色,幸災樂禍地期待女老師要如何講述此篇。大部分的結果都是令人感到無趣的,印象裡那位老師是個落落大方的中年淑女,她以一種不卑不亢的姿態,平靜地為我們解說完課文:「在性交時,男性將勃起的陰莖放入女性的陰道裡⋯⋯」

課文裡「陰莖」與「陰道」兩個字在某次小考中被挖空,成了填空題的答案。

陰莖要怎麼「放入」陰道裡呢?像是鉛筆放入鉛筆盒中那樣平靜嗎?對男孩如我來說,我們早已習得一種更魯莽、野性與奔放的抽插印象,並深信著那樣的魯莽不止將帶來自己的快感,同時也是女體的快感。

像是那些被提早送進「百世資優數學」,在小五時便已先修國中進度,二元一次方程式拆解法的小小資優生,每每在正式課堂裡以憊懶之姿發著呆;或是總在老師解說時狂寫習題不止,作為一種吸引關注的表演那樣。

(老師,我知道,我全都知道了。)

(親愛的男孩,你究竟知道了什麼呢?)

那樣對著色情圖片酒池肉林的午後並不總是順利的。事實上是,這其中包含了大量官兵捉強盜的情節。某個早晨,我一如往常換好了制服,在餐桌前因為晨起而恍惚著,母親冷不防地說:「你半天課,都在家裡做什麼?」

瞬間冷汗一炸,我隨即明白一切事蹟都已敗露。我想起父親當時痛斥W男孩;母親打電話訓誡W並告知家長時的情境,那種自知有罪的畏葸感再次襲來——唯一的差別是,如今我以不能再以「睡著」作為藉口。良久良久,我才勉強擠出一句:

「我只是好奇而已。」

(這是當時我所能想到的,最可能被原諒的說法。)

「好奇?」母親的尾音上揚,「只是好奇可以看一個多小時?」

接著我忘了是一段溫柔的說理;還是一段嚴峻的斥責。我記得的是,似乎是父親透過了某種上網紀錄,知悉了我下流的行徑,再將事證轉由母親處理——在我的記憶裡,自從W男孩事件後,類似這種嚴峻尷尬的場面,幾乎都是由母親出面。父親的角色則是幕後那個精明的檢察官,以及熟練的工程師 。

從此我便展開了與父母的大鬥法。先是知道IE瀏覽器裡面有瀏覽紀錄可以清除;這當然是不夠的,當父親發現瀏覽紀錄全部不見了,這謊就難以再圓(那天下午你又做了什麼?);接著,我學會只刪除片段的瀏覽紀錄,但卻在瀏覽器內建的暫存圖片裡留下了線索——那是一個叫做Internet Temporary File的資料夾:為了加快讀取速度與節省流量,瀏覽器會自動存下瀏覽過的圖片,當然也包含了那些顛鸞倒鳳的A圖(你來你來,這些是你最近看過的東西);儘管在多次失誤後我逐漸掌握了全部的機制,但也總有操作失誤而又導致東窗事發的時候。在那樣的日子裡,像是一個因為焦慮而頻頻洗手的潔癖症患者,我每每趁著晚飯後,父母親攜手去倒垃圾與散步的十五分鐘內,快速潛入父親房間,打開電腦重新檢查一番。

每當發生滅證失誤被抓包時,我總是羞愧得無地自容;但當又一人獨處,百無聊賴時,賀爾蒙的驟然湧升又讓我無可自止地接上網路。所幸,後來我發現了一個萬無一失的方法,那就是下載另一個較不常見的瀏覽器Netscap(大概只有Win95年代的玩家們會記得這個軟體了吧)。當我要上色情網站時,我就安裝上Netscape並以之瀏覽,事畢再把Netscap整個軟體直接移除。由於它和當時壟斷市場的IE是完全不同的程式核心,所以在軟體刪除後,所有的紀錄與圖片也將不復存在。

自此,父親熟悉的IE再也無法知道我的瀏覽行蹤。Netscap是我猥瑣人生的重大救贖,為了色情網站提心吊膽的日子,也大約隨著我進入高中後便結束了。我讀的高中是一間男校,那種你可以想像的男校:一群精力過剩的大男孩在早自習裡大剌剌地交換A片與A漫,甚或不時討論著某片裡角色的服裝或體位細節。那時的我逐漸矜於個人形象,不再願意與這樣的話題太靠近(我想好女孩可不喜歡這套),也因此並沒有積極參與那些男孩團體們的行動;但在心理上,卻完全感受到被接納與踏實的安全感。

像是我對著W男孩的回應一樣:聳聳肩,做個鬼臉,此刻的我已經真心地感到自在。

至今我仍然不知道,是隨著年紀漸長,父親對於我的慾望,終究採取了睜一隻眼閉一隻眼的態度,還是我真的再也沒有在電腦裡留下紀錄了呢?你記得小孩總有一段尚未社會化完全,會偷竊家裡錢的歲月嗎?在當時,父親總會在零錢筒上擺放一支筆,並默默觀察那支筆指的點鐘數方向數,藉此得知我有沒有偷錢的。我猜,如果他想知道,對於電腦這種工具來說,總是有辦法知道的吧。

儘管高中的我已經很少再為看色情網站被家人責難,但偶爾仍會發生一些零星的怪奇劇場。比如說:某日我回到家中,發現書桌上放了一個裝滿了我自慰後用過的,褐黃色衛生紙球的塑膠袋。袋子旁有一張字條,上面是母親一手端莊得不可思議的楷體(當年公教人員們人人都寫得一手好字),配合上她慣用的那隻淋漓飽滿的0.38豔藍原子筆,雖是硬筆字,但筆畫間的藏鋒撇捺,依舊到位:

「弟弟:媽不希望你像一個吸毒的人一樣,無法自拔⋯⋯」

字條的後續是什麼我忘記了,大致是一些擔憂我傷害身體或心靈沈淪一類的警語。那時的我早就生活於男孩團體裡多時,諸如「吸毒的人」等此類描述,已不再能為我帶來羞恥與自責,只剩下惹人發噱的讀感。在高三滿十八歲的那年,我與死黨C男孩在翹課後騎著摩托車,穿著校服來到高雄建國路上電腦商場的盜版A片店裡,煞氣十足地逛了兩圈,採購後並揚長離去(多麽令人失望,店員甚至沒有要求我們出示證件)。

那些衛生紙球是我高中時每天例行的睡前發洩後,隨手扔到床邊抽屜裡留下的。以衛生的觀點來看,那真是一個令人毛骨悚然的抽屜,盛滿了新舊雜陳、潮腐猩甜的慾望化石。每當我覺得多到很恐怖的時候,就用袋子把它們裝去學校的垃圾桶扔掉。

想想覺得很獵奇吧:一個高中生,書包裡裝了一大包自慰用過的衛生紙,在下課時假裝連同早餐奶茶與三明治的垃圾,一起扔到藍色的塑膠垃圾桶裡。偶爾忘記丟的時候,那包衛生紙就在我的書包中放上一整天,帶出門又帶回家。

如果我是一個女孩,高中時會有那樣的團體氛圍,也能讓女孩脫離家庭的壓抑,從而對自己的性慾感到自在嗎?曾經我與友人A女孩討論這個話題。

「我那時的女孩們似乎比較不談這些」。A在兒時某次自慰被家人發現後,家人間「小A喜歡摸屁股」的戲謔與譴責,就成了她心中一道深刻的陰影。

另一位友人T女孩在高中時,去歐洲參加了為期一個月的遊學團。在那裡她與一位微醺的英國男孩合影,T穿著緊俏的牛仔褲,小可愛背心半露出渾圓好看的少女臂膀,正側著臉輕吻那位俊美的金髮男孩臉頰。在回國與家人分享照片時,T的父親暴跳如雷:「花錢給妳出國,不開眼界,見到外國人就OOXX是不是!嗯?」

在我的想像裡,對於高中的T來說,那樣的語言彷彿一種專屬於雄性動物的鄉音。當時T的腦袋可能是一片空白,無從理解,更無以辯駁。

(不要問我為什麼認識T女孩)

為了上色情網站與世界對抗,只是我有記憶以來,性意識史中狹窄的一個片段。

你知道什麼是Dcard嗎?那是一個在2011年推出(約莫我25歲時),以大學生為主要目標用戶的校園交友軟體。使用者只要透過大學網域的學生信箱註冊,並設定好照片與個人資料,就可以在每天十二點時收到一則交友邀請——通常都是異性的配對。在此聚集了大量渴望戀愛的大學生,Dcard中的論壇也漸漸成了用戶們分享感情、討論感情的網路社群。

從當年我大學時流行的PTT論壇,到後來Dcard趁著智慧型手機的熱潮而興起,在校園的氛圍裡,對於那些終於被允許戀愛的大學生們來說,「性」這個神秘的角落已經不復被那樣嚴肅的禁忌封印,反而更像是一個充滿神秘財寶的海島,令所有勇敢的海盜們躍躍欲試,直想一探究竟。

大學的宿舍裡,四個赤裸著上身的異性戀男孩,在深夜裡漫長聊天;從PTT到Dcard,轉眼已經跨越兩個世代,網路上使用的語言亦早已大相逕庭,然而再怎麼聊,也離不開這樣的主題:

「交往三個月就做,會太快嗎?」 「是不是只愛我的身體?」 「閃光答應不碰我(愛心)」

做與不做,愛與不愛,永遠是一個困擾所有熱戀青年的問題。在這樣的競逐遊戲裡,普遍的遊戲規則也漸漸釐清:女孩成為了性寶藏的守護者;像是埃及神話裡說著謎語,要求過路者作答的妖物斯芬克斯一樣。這道謎語有關真愛,如同一個寶箱,只對唯一的鑰匙持有者開放;男孩的性需求則普遍相當強烈,大多關注於如何以更優雅且紳士的姿態,說明自己手上確有那把鑰匙。

那樣看待戀愛的框架像是一種約定俗成的語言與契約。儘管有助於快速的理解與協議,但過度高階與封裝的語言終究無法觸及那些最底層的可能性,更無從探索意義。誤解與傷害由是而生,遞出玫瑰的手,同時也交付荊棘與刺。刺生傷口,傷癒成繭,當一切過於艱難,有人從此改變走路與微笑的姿態。

還有那些雄性生物共有的鄉音,也仍在世代間毫無二致地傳遞著:T女孩在與初戀男友發生初夜後的一年分手;在與第二任男友熱烈的戀愛後,兩人陷入了關於處女情節的慢性憂鬱裡——那樣的憂鬱像是溽暑裡的雷陣雨,晴天的閃電與悶雷全無來由地憑空一響,世界又瞬間墜入了涕淚橫流的泥淖裡(男孩總說:對不起我只是突然想到,他曾經像我現在這樣抱著妳,就覺得痛苦)。

T女孩此時已經完全理解高中時暴跳如雷的父親;在最低潮的日子裡,她甚至願意相信那樣的姿態代表著無可取代的眷戀、在意,以及溫柔。

(為什麼失職?妳的謎語為什麼徒勞?為什麼?斯芬克斯)

身為一個較早熟的高中生,關於做與愛的困惑亦早已萌生:究竟是因為太想做而產生了虛假的愛;還是因為愛而更無比想做呢?又或者是,真如同那些解放先鋒們所言:不要被蒙蔽了,做與愛之間,從來不必要有任何瓜葛——即便已經年近三十,這些問題我仍然無法簡短,也無法不帶猶豫地說出自己的回答。

某些極端疑懼的日子裡,我甚至曾經,在與異女友人約會前,將手淫列為梳洗打扮的最後一步驟——我已足夠瞭解自己的身體與慾望,簡單地閉上眼並幻想影像與故事,只需約莫六十秒的操作,我便能輕易地痙攣射出,將性驅力於我身體中暫時關機。彷彿實驗室器皿中的蒸餾與透析,藉由分離某種物質,我得以用一種更簡練的姿態與他人會面。甚或是,我可以將自己分離成一個實驗組合:愛或不愛,是否因饑渴產生幻覺,彷彿就此可以真的得到實驗室的認證背書。

回到主題吧,在高二那年寒假裡,依照學校的傳統,我們總會與友校女中聯合舉行一場盛大的露營活動。那幾乎是整個高中生活裡最具話題性的一段時光了,男孩與女孩們靦腆地——甚至不無尷尬和勉強地見了面,在整個假期裡集體相約至校園的某個角落,或是附近的速食店,為了露營節目的籌辦共同討論著、歌唱著、舞蹈著。

當時我正為露營忙得不亦樂乎,就在某日出門時,鄰居那位多舌聒噪的中年大嬸攔住我,笑了開懷:「弟弟聽說你在準備露營啊,你媽說你整天去約會,好像一隻發情的公狗哈哈哈」。

我頓時無語,一股非常強烈的憤怒與羞恥漲滿全身;我想到一台被反覆暴踩油門與煞車的汽車,車身在柏油路上來回地甩尾打轉,發出狂躁壓抑的嘶吼。空氣裡瀰漫了汽油異常燃燒的味道,又或許是煞車來令在高速的摩擦下,粉碎成灰塵的氣息。柏油路上充滿了煞車印痕,以一個接著一個橢圓的形式徒勞交疊——像是高中時我的棉被上,靠近胯骨處,反覆被自瀆後遺下的體液沾染,潮濕復乾涸的印記。

夾雜著羞恥與狂熱,層層疊疊,熱切但絕望的印記。

【香檳】人何寥落花何多

2020 年春天,COVID-19 在美國大爆發。香檳校園也早早宣布了暫停實體授課與學術活動。

大學城一但停了課,那肯定是百般寂寥的。而此次尤甚,因為連留守的國際學生們也多慌張離開了。

因為即將畢業與返國需要好好收拾,我算是走得遲的。那幾天又看日出完成了一個大作業。稍事休息,便至稍遠的住宅區把二手車賣給了另一位留學生。雖說是買賣,不知為何,心情更像是託付——車子一賣,離別便已是篤定的了。

因為眷戀;因為春天;也因為隔離得慌,我和朋友決定步行一個多小時回到校園。春天裡滿滿開花,鄉間優雅的小房子因為瘟疫,不見人煙。

從未想到一切會如此結束。但轉念思及人生,能預料的事終究不多,遂有釋懷感。

香檳的冬天好長;春天又好美好美。春逢大疫,人何寥落花何多。

【程式】使用JavaScript在中文與英文間墊上空格

中文文字工作者應該都聽過這個排版準則:

中英文交界處必須增加一個空格,避免視覺上過於壅擠[1]。

在部分的軟體或系統中,會自動墊上這樣的空間(包含你現在正看的這篇文章,以及諸如蘋果與Google的作業系統[2][3]);然而,並不是所有的讀寫介面都內建有這樣的功能。根據筆者實測,在2020年3月,Facebook的iPhone應用會自動墊上這個空間;但是在Web中則沒有——前者亦不確定是來自iPhone的作業系統,抑或是APP本身的功能。

中英文交錯的排版圖片:上圖中中英文沒有空間,顯得擁擠;下圖中則添加了半行空格。你覺得有差嗎⋯⋯可以確定的是,有人覺得有差。

所以⋯⋯我們要手動補上空白嗎

儘管這個空白是很常見的需求。然而,就筆者的觀點來看。在文本原始檔中「手動」添加空格,並不是一個理想的做法。其原因如下:

  • 非常容易疏漏。
  • 不同字體可能有不同適合的空間。
  • 在文本的原始碼中加入非必要的空格會讓管理複雜化,增加修訂、編輯與校對的負擔。
  • 以上,如果文本需要長期維護與修改,則花費的精力與效果不成比例。

讓程式來做吧!

但如果我們還是希望達成最舒適的排版,要怎麼做呢?

個人覺得最好的作法就是在閱讀的輸出端,利用程式自動墊上這個空間。

以本篇部落格為例,當訪客在讀取頁面時,筆者啟動了一段JavaScript,會將頁面中部分區塊的內容進行自動化處理。也因此,部落格中所有的文章,中英文間都會自動墊上一個半形空格。

該功能的實作如下:

function getPaddedCnEnString(htmlStr, padding) {
  // ASCII 33~126
  const EN_ALPHA = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
  const EN_SET = new Set(EN_ALPHA.split(""));
  const CN_MARKS = "。,、;:「」『』()—-?!——⋯⋯〈〉・《》_~";
  const CN_MARKS_SET = new Set(CN_MARKS.split(""));
  const CN_RANGE_MIN = 0x3400;
  const CN_RANGE_MAX = 0x9FFC;
  // utility function
  const isCn = (char) => ((char.charCodeAt(0) >= CN_RANGE_MIN && char.charCodeAt(0) <= CN_RANGE_MAX) ||
    CN_MARKS_SET.has(char));
  const isEn = (char) => EN_SET.has(char);
  // process
  let result = "";
  let thisIdx = 0;
  while (thisIdx <= htmlStr.length) {
    const thisChar = htmlStr.charAt(thisIdx);
    let nextIdx = thisIdx + 1;
    let nextChar = htmlStr.charAt(nextIdx);
    // copy contents in html tags (do no processing)
    if (thisChar === "<") {
      while (nextIdx <= htmlStr.length && htmlStr[nextIdx - 1] !== ">") {
        nextIdx++;
      }
      result += htmlStr.substring(thisIdx, nextIdx);
      thisIdx = nextIdx;
      continue
    } else if (nextChar === "<") {
      while (nextIdx <= htmlStr.length && htmlStr[nextIdx - 1] !== ">") {
        nextIdx++;
      }
    }
    result += htmlStr.substring(thisIdx, nextIdx);
    nextChar = htmlStr.charAt(nextIdx);
    // compare with next char and pad space if necessary
    if (isCn(thisChar) && isEn(nextChar)) { // 中文_abc
      result += padding;
    } else if (isEn(thisChar) && isCn(nextChar)) { // abc_中文
      result += padding;
    }
    // continue
    thisIdx = nextIdx;
  }
  return result;
}
  • 函式的輸入為:
    • htmlStr:字串,可包含HTML標籤(HTML標籤中的屬性不會被處理)
    • padding:你可以使用半形空白" "或是例如<span>標籤,再透過CSS來調整)
  • 函式使用Unicode判斷字元屬於中文還是英文,並將台灣官方的標準標點符號視做中文[4]。以此為判斷,自動在中英文間加上空間。

舉例來說,如果你的文章位於<div id="post">的標籤裡,那麼你可以這麼做[5]

$(document.onload).ready(function () {
  const element = $("#post");
  const newHTML = getPaddedCnEnString(element.html()," ");
  element.html(newHTML);
})

根據筆者簡單的測試,一份原始碼長5000字左右的markdown文章,以其HTML格式進行轉換,可以控制在5毫秒以內;且不妨礙原本內容的讀取;顯然不會造成太大的效能負擔。

以上,獻給排版的偏執狂們。


註解

[1] 儘管就筆者所知,這樣的準則並沒有被明文記載在各大軟體廠的設計文件裡。此外,筆者也反對將這樣的標準用於判斷寫作者是否專業;亦不建議用手動處理這個程序:"The space bar is not a design tool"。

[2] 出處:知乎|https://www.zhihu.com/question/19587406

[3] 非正規的指南例如:Tunghsiao Liu|中文文案排版指北|GitHub。這份文件在2020年3月時累計了7500個GitHub星星。

[4] 在Liu的指南中主張中文標點與英文間不需額外墊上空格。這其實很主觀,但我們的函示仍然提供了設定的參數。

[5] 文中預設使用jQuery在前端執行轉換。如果你可以控制後端的原始碼,也可以將轉換放在伺服器端進行。另外,如果放在前端且你有使用其他JS工具去處理HTML(比如說程式碼標註工具),那麼你可能需要注意執行的先後順序;或是採用適當的CSS Selector去跳過不需要處理的區塊。

【遊戲】線上遊戲的轉蛋機制設計與倫理議題

轉蛋機制是免費制線上遊戲(free to play,以下簡稱F2P)中常見的遊戲機制。一言以敝之,這樣的機制消耗了一定數量的遊戲貨幣,並以機率的形式釋放遊戲獎勵。這些遊戲獎勵可能是價值不菲的,例如一張珍貴的角色卡片、關鍵的轉職材料或是罕見的裝備;但更多的是一些不怎麼樣的物品,例如一些普通的角色、雜物或貨幣。

在筆者的工作與遊戲經驗裡,對於F2P線上遊戲而言,轉蛋無疑是最有效能產生營收(monetization)的遊戲機制。儘管我們無法得到任何一款公司提供的確切產品統計,筆者也無法透露自己參與過的專案數據;我們仍然可以透過觀看玩家的社群討論或已公開的抽卡影片與直播,來得到這樣的結論。我想,對於任何遊戲從業人員而言,我想也沒有人會訝異:有不少玩家願意花三五十萬台幣在一款遊戲的轉蛋機上。

在這篇文章中,筆者將討論一些簡單的轉蛋設計方法,以及其所衍發相關的商業倫理議題

簡單純機率

在遊戲製作的實務裡,轉蛋機制往往遠較想像中複雜。從外觀上來看,轉蛋機通常有著一個符合遊戲背景故事的外表(可能是一台轉蛋機、一個召喚怪物的魔法陣、或是打開一個神秘的寶箱)。不論這個機制被包裝得如何,它們通常讓玩家聯想到生活中常見的籤筒抽籤:一個桶子裡有各種的籤,少數代表中獎,但大部分都代表銘謝惠顧或一些雜物。在抽籤的過程裡,每一次抽籤都是獨立事件

轉蛋示意圖 圖片:〈怪物彈珠〉抽取卡片的畫面。轉蛋機制會被包裝成符合遊戲故事背景的樣子。

這樣單純的機制筆者稱之為簡單純機率,以下的機率表即可作為示範。它非常地直觀且好懂:每次抽取都是獨立且公平的;玩家每一抽有5%的機率抽中S卡;若每一抽的價格是100台幣,則抽中一張S卡的平均價格(期望值)會是2000台幣。

簡單純機率表

這樣的簡單純機率看似正常,但其實大有問題。舉例來說,這意味著玩家付出4000台幣(也就是兩倍於期望值的價格),卻仍抽不到S卡的機率其實是13%——如果這發生在遊戲初期,這代表著你有13%具有高度價值的玩家,將承受幾乎毀滅性的遊戲體驗:不只花了大錢卻空手而歸,還必須眼睜睜看著為數眾多的免費試玩玩家在第一抽就抽中S卡(這樣的機率是5%,且第一抽常常是免費的)。

以統計學來說,這是一個典型的二項分布(Binomial Distribution)。下圖顯示了一個p=5%n=40的圖例,當n=40p(x=0)=12.9%,亦即玩家即便願意花4000元,仍然有很高的機率抽不到任何一張S卡(但是有9%的人可以抽到四張)。

二項分布

也許你會這樣想:呃⋯⋯至少這很公平,而且這不過是個遊戲而已——直到你的客服信箱被憤怒的玩家轟炸;臉書專頁與應用程式頁面都被心碎的付費玩家塞滿一星評價與崩潰留言為止。玩家出於對遊戲的愛而付費,當他們承受了毀滅性的遊戲體驗,這樣的愛很容易會轉成熾烈的怨恨。

此外,在台灣與中國,許多遊戲公司往往與最願意花錢的大戶保持私下的、程度不一聯繫,並提供諸如到府收款、專案客服等特殊服務;對於一般在社群網站上活躍的、具有影響力的、願意花個幾千元的玩家,亦有一定程度的交流。當這樣令人不滿的遊戲情境發生時,遊戲公司往往會為了產品形象著想,而願意提供私下的補償。然而,補償的標準要如何設定?寶物的發放要如何控管?又如何避免玩家口耳相傳變成會吵的孩子有糖吃?這對於遊戲經營團隊都會是嚴重的困擾。

更重要的是,有些玩家心碎了,往往也就無聲地離開了。

簡單純機率有什麼問題?

在討論簡單純機率所衍生的的問題前,我們必須先討論何謂遊戲的強度/獎勵。為了方便閱讀過程中的想像,讀者可以將這篇文章提到的「遊戲」設定為幾款熱門的F2P作品,諸如〈神魔之塔〉、〈刀塔傳奇〉或〈皇室戰爭〉。

在一款F2P的的遊戲產品裡,遊戲設計師往往為玩家配置了各種獎勵——又或著說是所謂的強度,來鼓勵玩家的各種遊戲行為。這些行為包含了:

活躍強度/獎勵

意指玩家透過持續地玩遊戲,所能得到的強度與獎勵。你可以把它想像成遊戲時間強度/獎勵的函數。在大部分的F2P遊戲裡,這樣的函數常常是(也必須是)遞減的,但其遞減幅度、以及何時開始遞減,端看產品鎖定的族群而定。舉例來說,學生族群可能可以投注較長的總時數在遊戲裡(放學後有較長的空閒);但對於遊戲的頻率,卻往往不如上班族(上學不能常常用手機)。當產品鎖定的族群不同,活躍強度的函數設計亦會不同。

當時間函數遞減太快,玩家會容易感到無聊(想玩遊戲卻無法得到獎勵);當時間強度遞減太慢時,玩家則會感到強烈的作業感(獎勵捨不得不拿,但又沒那麼多時間一直玩)。

付費強度/獎勵

意指玩家透過付費,所能得到的強度與獎勵,也就是金錢強度/獎勵的函數。舉個極端的例子而言,〈英雄聯盟〉作為一款純電子競技類型的遊戲,其付費幾乎是完全與強度脫鉤的[1],也就是幾乎不存在pay to win的現象;而相反的,〈皇室戰爭〉則透過卡牌的搜集與升級機制,將強度與付費做了一定程度的掛鉤。

社交強度(獎勵)

意指玩家透過與其他玩家互動,所能得到的強度與獎勵。標準的例子例如農場遊戲常見的拜訪獎勵;〈Candy Crush〉系列贈送愛心的機制以及〈神魔之塔〉的公會任務。在長線的遊戲經營裡,社交機制往往是最後能否留下玩家、延長產品壽命的關鍵。

技術強度(獎勵):

意指玩家透過本身的遊戲技術,所能得到的強度與獎勵。這個概念也不難理解:〈英雄聯盟〉就是一個幾乎100%技術強度的遊戲,付費者並不會因為付費而變得更強;而〈皇室戰爭〉大概是付費與技術各佔50%;至於〈刀塔傳奇〉中的競技場裡,付費玩家幾乎可以獲得絕對的優勢。

掌握不同行為獎勵之間的均衡,是遊戲設計至關重要的議題。對於F2P遊戲而言,付費強度的設計又可以說是重中之重。

然而,簡單純機率以及其所連帶的期望值計算,卻無法負擔這樣精細的操控。舉例來說,當一個設計師期待一張S卡所帶來的強度約莫等值於2500~3500台幣之間時,簡單純機率的轉蛋機卻從來無法保證這樣精準的控制。因為,玩家可能會抽到很多張,也可能一張都抽不到。

總得有個辦法吧⋯⋯

儘管簡單純機率有這麼多的問題,轉蛋機制與機率仍然是F2P產品最有效的盈利機制之一。諷刺地是,如果我們直接出售一張S級卡片,並將之標價為三千台幣,玩家很有可能直接嘲笑製作團隊想錢想瘋了:這不過是個遊戲道具,以及一條卑微的資料庫記錄而已。人應該回到現實、多吃大餐、多看電影、多去旅行、買買喜歡的衣服、甚至是拿去吃O喝X,而不該沈溺在虛擬的世界裡⋯⋯

然而,當我們把一張卡片放進轉蛋機裡,並且將期望值設定為三千台幣時,這一切又瞬間變得饒富趣味且值得討論了起來。或許我們可以說:觀察、議論進而承擔風險,並取得風險帶來的期待與獎勵,本身就是遊戲最有趣(甚至是所謂令人成癮)的一部分。

對於轉蛋,必須要有比簡單純機率更好的設計才行。

事實上,你可以放入任何演算法在轉蛋機裡⋯⋯

是的,轉蛋機的背後不過是幾行程式碼[2]。任何演算法,只要能產生貌似隨機的結果,且具備不易被逆推的特色,都可以用來驅動轉蛋機。你討厭遊戲ID是5結尾的玩家,或是email裡有william一詞的人嗎?開間遊戲公司,做出一款好遊戲,你可以請數值企劃和工程師讓這些玩家抽卡抽到懷疑人生[3]⋯⋯

接下來筆者會舉出些常見但基本的轉蛋設計技巧,以及其所可能對應到的遊戲體驗,作為範例。

範例1:取出不放回

取出不放回是一個基礎統計常討論的模型。當應用在遊戲設計裡,它可以確保玩家至少在n抽裡獲得一個或數個以上的大獎,卻仍然保持了機率所帶來的有趣性。

以下圖為例,這台簡化的轉蛋機以20為一個循環,每次20抽裡會確保玩家得到一張S卡。相較於簡單純機率,超級倒霉與超級好運的極端狀況就消失了,對於付費與強度的關聯控制也變得更加精準,但仍然保有一些機率的樂趣。

取出不放回

當然,這樣的設計也並非全無缺點,例如某些具備經驗的玩家可以精準地抓出其中的規律,並公告在網路社群上,這可能使得玩家對於轉蛋失去期待感。不過,當母池容量變得巨大,且混合了更多隨機機制時,其實出獎規律仍然不是那麼容易抓到的。

在實務裡,這樣的機率配置可能是隱匿的。但設計師亦可採用部分揭露或是完全揭露的機制:前者會讓玩家意識到自己中特獎的機率逐漸提高[4];而後者則會直接在遊戲的使用者介面上,讓玩家理解到母池的概念。

範例二:條件對應

這如之前所提:在過分簡單的外表下,轉蛋機制的背後可以是任何型式的演算法。對於遊戲工程師而言,只要數值企劃能提供明確的演算過程,將之透過程式實踐通常不會是太困難的事。

條件對應亦是一個可使用的設計手法。其原理在於:在實踐機率的運算之前,先以其他指摽進行非機率的邏輯切換。

舉個簡單的例子,在下圖中,轉蛋機制會記錄玩家的總抽取次數,再以抽取次數進行除以三的餘數運算(%3),再依照餘數對應至不同的簡單純機率表格。在這個例子裡,玩家會不斷地依序抽取到卡牌、武器、貨幣。

次數條件對應

當然,這樣過分簡化的機制是很容易被玩家察覺的。然而,遊戲設計師可以將條件對應機制與其他的模式結合,例如前段所提到的取出不放回

下圖中我們示範了一個混合了條件對應+取出不放回+簡單純機率的轉蛋設計。在一個有十顆彩球的母池裡;每種顏色的彩球對應到了不同等級的獎勵。在這樣的抽獎循環裡,玩家每十次抽獎都會得到一張S卡/兩張普通卡/兩件裝備/五個遊戲貨幣包。至於玩家會拿到哪一張S卡,哪兩張普通卡呢,以及以什麼順序抽到各種獎勵呢——這部分或許就是設計師願意讓機率決定並作為樂趣的範圍了。

綜合應用

範例三:付費與強度之外的考量

轉蛋機制的機率設計往往也不只是為了坑錢這麼單純的考量。舉例來說,在卡牌遊戲裡,玩家手牌的組合往往是前期體驗展開社交與合作的重要關鍵——作為一個設計師,可能不會希望玩家在前期裡滿手都是坦克而沒有攻擊手;又或是過早取得了完全均衡的團隊,以至於失去和其他玩家互動、租借卡牌的需求。

因此,牌組的多元性也可以是設計師操作機率的考量之一。對於數值企劃而言,這其實是一種透過數學與統計創造體驗的技術。舉例來說,當設計師規劃了數組可能的策略牌組時,他可以要求工程師以條件對應的方式,將玩家依照用戶ID的尾數(玩家甚至可能不知道這個數字的存在),將轉蛋機對應至不同的抽卡路徑。這樣的做法,可以確保玩家獲得某種(鬆散但不是絕對的)牌組雛形,也能確保多元的遊戲話題與玩家之間的合作。

轉蛋機制的倫理爭議與規範的困難

行文至此,讀者或許已經可發現其爭議之處——轉蛋機制以一個過分簡單的外表、絕少的資訊揭露,來掩蓋了十分複雜的機率黑盒子。當它被用作主要的營收機制時,其實是可能存在類似賭博與詐欺的疑慮的。舉個例子來說:當玩家看到一個特殊的轉蛋活動,宣告「S卡機率加倍時」,其背後的機率設定「可以」是完全沒變的。除此之外,這樣的機率也可以透過程式,進行動態調整

這的確是具有爭議的。在玩家的社群討論板裡,我們也常常看到因為消費聽驗不佳的用戶們,在討論著如何成功地與Google與Apple的客服申請退款。

這很有可能早已造成了兩大平台的困擾。儘管它們都曾試圖採取規範行動——主要著重在公開內容一項,但截至2019年中,這樣的規範似乎尚未產生具體的影響力。其關鍵在於,不論是公開內容甚至是機率,都仍然侷限於簡單純機率的想像裡,對於更多複雜的設計方法,諸如取出不放回條件對應以及各種機率設計的交互應用,還是無以規範。更重要的是:兩大平台既不可能要求廠商公開轉蛋機制的原始碼[5];亦無以透過外在的實驗去進行逆向檢核,以至於即便廠商公布了資訊,玩家也無從得知其是否為真。

公布機率 圖片:〈皇室戰爭〉中揭露寶箱內容的介面。筆者認為,複雜的機率很難用介面顯示;顯示了也不一定包含完整的細節;完整了也不一定是真的;就算是真的,還是可以用程式進行動態調整。

總結地說,遊戲設計的複雜性更加深了規範的困難。首先,正如文章中所言:轉蛋機制擁有多元的外皮,要執行有效的規範,更不容易。其次;部分的轉蛋亦包含了付費以外,諸如遊戲體驗與平衡類的考量;最後,在當代的遊戲設計裡,轉蛋使用的貨幣亦不全然來自於付費——以經典F2P遊戲〈神魔之塔〉為例,其抽卡用的貨幣可能是免費發送(諸如活躍獎勵與過關獎勵),亦可能是來自於付費購買的。如果以消費透明為由,要求廠商通盤公布可能屬於商業知識的機率配置,亦未必公允。

最後的最後,當遊戲營運商透過Google、Steam、Apple進行全球發行已成常態,區域政府的法規是否能有效規範並公平地規範每一個產品,亦是一大疑慮。舉例來說,如果台灣自行通過了較嚴格消費者保護法律,執法者卻只能針對本地的遊戲研發者執法,卻無力規範主流的跨國遊戲。這無疑讓競爭變得更不公平。

2019年的美國立法嘗試:兒童遊戲成癮防制法

2019年上半年,美國參議員Josh Hawley提出了名為The Protecting Children from Abusive Games Act的法案草案(暫譯:兒童遊戲成癮保護法),亦引起了遊戲開發者一定程度的重視。參議員Hawley主張:「以兒童為目標客群的遊戲,不該被允許從成癮行為裡盈利。當兒童玩著以成人為目標客群設計的遊戲時,他們亦應該被隔絕於成癮性的小額付費體驗之外。那些刻意從兒童身上剝削營收的遊戲開發者,必須面臨法律的懲處」

在具體的案例上,他標示出了兩大最被濫用的設計模式,其一是Pay to win[6];其二是Loot Box(也就是我們所謂的轉蛋機制)。在申論中,參議員Hawley主張:「Loot Box機制廣泛被採用於免費與付費的遊戲裡。這樣的機制提供了隨機的付費獎品,並結合了pay to win令人黏著的特性;以及沿襲自賭博的成癮行為」[7]

這是一個很有趣的提案,也是F2P的商業模式再次承受了來自法律規範的挑戰。礙於篇幅與精力,本篇文章在此點到為止,有興趣的讀者可自行追蹤相關報導與後續。

倫理議題

更廣遠地看,這世界上有著販賣有害健康食物的餐廳、政府做莊家的彩券與運動彩券、特許的民營賭場、配備高級音響但安全設備不足的汽車、文案曖昧但幾乎沒有實質效用的減肥藥與保健食品⋯⋯遊戲轉蛋究竟應該被做到何種程度的規範?我想並不容易回答。而遊戲作為常常被主流價值污名化與缺乏瞭解的標的,當又再次變成法律規範的焦點時,亦不難想像開發者心中是會有所不平的(你看過彩卷或刮刮樂公告中獎率嗎?什麼該被歸類為所謂「面向兒童」設計的遊戲?薯條和冰淇淋算是面向兒童設計的垃圾食物嗎?那迪士尼樂園裡到處陳列的超可愛Shellie May熊熊呢?)。

這題並不好回答,筆者也沒有打算在本篇文章中回答[8]。可以確定的是,作為一個開發者,亦不該一味迷信夠坑=賺錢=成功的教條。正如文章中所言,數值企劃是一門透過數學與統計創造體驗的技術,在過去的五年十年內,更好、副作用更低、更有回饋感與創意的盈利模式持續被優秀的開發者創造出來。團隊除了適時地討論、確認自己內部的倫理自律標準外,亦應持續重視、精進設計的技術,才能做出好的作品。


註解

[1] 英雄聯盟的強度仍然與付費有著些許微妙的掛鉤。例如付費玩家可以擁有更深的角色池以及更多的天賦頁面,可以針對對手做出更好的反制配置。

[2] 嚴格來說,應該是好幾十行百行和一堆資料庫配置。

[3] 但通常是你自己在遊戲研發的過程中會比較懷疑人生⋯⋯

[4] 例如,提供一個諸如「運氣指數」的指標,然後隨著母池的清空,逐漸顯示為普通/略高/極高。當玩家知道這些機率是由自己過去的消費所堆疊起來時,也常常更難以放棄。

[5] 就算真的公開原始碼,也不能確保伺服器上運作的版本與數值是公開的版本。

[6] 熟悉遊戲產業發展歷史的玩家與開發者應該都可以想像,完全禁止pay to win,也就是所謂付費與強度的連結,是一件多麽荒謬的事。

[7] 本段引言取材自網路。美國參議院網站,(May 8, 2019), Senator Hawley to Introduce Legislation Banning Manipulative Video Game Features Aimed at Children. 擷取自 https://www.hawley.senate.gov/senator-hawley-introduce-legislation-banning-manipulative-video-game-features-aimed-children

[8] 事實上是,社會裡的立法與規範形成的過程,往往也不只是透過倫理與應然的辯論而產生的。對於實力尚未充足的開發者而言,花太多時間討論倫理不如靜觀其變,打好研發技術才是正途。

【程式】我需要學Python資料科學嗎?

近年來自學coding蔚為風潮,很常被朋友問到:「我需要學Python資料科學嗎?」。具體地說,被提出的課程通常包含了資料處理、視覺化,有的還包含了諸如網路爬蟲或是API串接[1]。

詢問者的動機通常是想充實專業,應用於學術或是工作領域。另一方面,也因為鋪天蓋地的媒體新聞與課程廣告,感到焦慮。

先講結論:個人認為,對於非資訊背景的學生,速成課程的學習效果相當有限;而大部分來詢問的人,未必真的需要;最後,儘管Python是個很棒的工具,但「會寫程式/Python」這件事,被包裝成了一個太過美好的想像。

而我的習慣是這樣的:如果一個大哉問被問了許多次;而又算是我有經驗的領域;我就會把它寫成文章。 所以我們就開始吧。

Python資料科學是什麼?

首先,請見附圖[2]。在這個結構裡,資料科學——或是一場有意義的數據專案(諸如行銷、選舉、乃至最近很常見的肺炎數據呈現),個人認為需要三個領域的完備:

  • 專項領域知識
  • 數學與統計學
  • 電腦應用 <= Python在這裡

Python Data Science

請特別注意前兩項的存在!首先,Python有許多套件,可以協助我們處理數據,實作出「專項領域知識」+「數學與統計學」的交集——然而在這個場景裡,Python本身並無法自外於兩者而產生意義。其次,Python的操作並不屬於太高深的計算機科學[3],你也不會因為只熟悉Python(或任何一種程式語言)而獲得一位(認真學習多年的)資訊本科生的同等能力;或是可以因此去Google開發無人車。比較好的比喻是:你可以把Python想像成威力更強大但也更不容易操作的Excel:它們都有很棒的資料處理能力,但要從資料中提取出價值,還是要倚賴領域本身的理論基礎才行。

再舉個幾個例子吧:電鍋很好用,但電鍋不會讓你成為大廚;一組完備的工具機,也不會讓你成為一個優秀的水電工;Python可以幫你處理股票資料,但不保證你能做出有意義的分析甚至盈利——而用Excel就做得風生水起的投資戶大有人在。

這段用詞比較負面,但我想透過這樣的措辭,來平衡那些課程廣告中過分美好的(他們也知道不是事實但仍然留下的)暗示。

所以⋯⋯我需要學Python資料科學嗎?

來詢問我的大多是商管學院出身的工作者。坦白說,我覺得最泛用的答案是「NO」,以及接著的「unless……」

僅管Python很棒,但根據個人經驗,對於非資訊本科生而言,一場成功的入門學習通常必須滿足以下(任一)條件:

  • 你是一個專業人士,你已經相當熟悉Excel或其他試算表軟體,且已經遭遇到Excel無法滿足的使用情境。
  • 你是一個專業人士,你現在的工作團隊已經在使用Python(或其他資料庫語言)。在有實務用例與演練的情況下,學習效率會有十倍百倍的提升。
  • 你是一個高中生或大學生,你擁有充足的學習時間,也正在處於廣泛探索知識的階段。在這個情境下,程式語言會是一個很棒的通識主題——同樣的道理我也會推薦你學習經濟學、社會學、數學、法律⋯⋯而一但開始學programming,別忘了務必多寫code以及實作對你有意義的專案,唯有如此才能真正內化這門技藝。
  • 你是一個研究生,你想要在研究領域裡實作量化資料分析,並將過程與方法有系統地與其他研究者分享。
  • 如果以上皆非;但你有充足的時間與意願學習這項技術;也有想要實作的應用(例如股票分析)。
  • 再次以上皆非;但你想要在履歷表放上這項專業;且你是個善於包裝的人;同時你相信你現在或未來的雇主崇拜/敬畏會寫code的人(即便沒有實務上的價值依然如此?)

Why?

對於學習Python說NO,這樣的建議其實是相當違反潮流的。我想原因如下:

  • 我沒有要賣你課程(超級100%正解)。
  • 儘管Python已經是相當高階的(面向人類而非機器的)程式語言,但對於一般沒有資訊背景的人而言,仍然是易學卻難通。在大部分強調速成的網路課程裡,為了提升學生的購買意願以及成就感,往往快速進入資料處理與視覺化的部分,而略過了基本的安裝、除錯、command line操作、版本控制、程式語言的基本概念;以及Python的資料結構、基本語法,與套件管理。這樣的課程雖然速成,但學生並不會獲得解決問題、有效率地寫code,以及舉一反三的能力。常見的結局是:學生只能拿著教材提供的代碼修修補補,但不具備真正實作專案的能力。如果又沒有實務上的用例,這樣零碎的知識很快就被會遺忘。
  • 如果你是一個已累積一定專業的工作者,我假定你的時間是寶貴的,且你的投入是期待回饋的。因此,除非已有明確的需求,散亂地學習的投資報酬率並不高。另外,不深入地學習也無法讓Python或資料分析成為你的第二專業——那些速成課程的內容對於資訊本科生而言,往往只是翻翻文件就(應該要)做得出來的東西。但無可取代的永遠是你的專項領域知識,以及對於數學及統計學的掌握。而這都是一般Python課程無法給你的。
  • 承上,Excel有什麼不足?如果你已經親身遭遇並能回答出來,那我相信你會獲得遠超過平均的學習成果。
  • 更不用說「Python+AI+大數據+爬蟲」的大雜燴課程了。這些課程就像健身房會籍一樣,絕大部分的人付費後並沒有妥善使用,而只是被販售恐懼以及購買美好的想像而已。並不是說課程本身有什麼過不去的惡點,但不論是投入任何專業領域,請問問自己:你真的準備好放下其他事、充足的補給、十足的熱情,以及不怕卡關的韌性了嗎?
  • 當然,如果你覺得自己只是想探索,也不介意花一點時間,那就出發吧。

怎麼學?

這個段落偏向主觀,有很多有經驗者見解也和我不同。如果你心意已決,請斟酌自已的個性與過去的學習經驗,作為參考即可。

首先,在「時間寶貴」與「實作優先」的兩項前提下,我推薦線上課程勝於實體課程。另外,個人是獨處與根性的信徒,如果你總是相信「有實體的活動或同學比較不會怠惰」,那我(偏狹地)猜測你可能不適合寫程式(笑)。

其次,如果語言與經濟能力許可,我建議選購英文的付費課程。長遠來說,習慣閱讀英文文件以及論壇,能達到最好的學習效果;另外,付費課程往往有更好的架構,可以避免初學者無法有效過濾學習素材的問題。個人用過的線上服務包含teamtreehouse.com與datacamp,都稱不上完美,但也絕對不至於帶來關鍵性的失敗。

另外,許多課程會提供網路介面與沙盒讓你寫code。儘管這都是不錯的初學工具,但你應該要很快地離開這些介面,快速掌握自己本機上的開發環境才行——如果你要寫實戰的code,總不可能寫在教學網站的編輯器裡對吧?

最後,別忘了Python是一個泛功能(general purpose)的程式語言。資料分析套件只是它眾多功能其中的一項而已。儘管是一個很棒的入門點,但個人建議你必須先懂Python,然後才能好好操作這些套件並讀懂文件——這包含了Python的基本語法、資料結構、函式應用、物件導向編程、套件管理等。另外,你可能也需要花時間熟悉command line以及版本控制等進階的電腦應用。

結語

對於非資訊背景者而言,想要有效學習Python資料分析,最好已經具備具體的需求,以及投入充足的時間(個人體感估計是:平均每週十小時,持續半年),才能累積出明顯的成效——別忘了,這意味著排擠其他生活中的事務,包含已有的專業、人際關係、休息與娛樂等⋯⋯

總而言之。寫程式對某種類型的人類會是很快樂的事。商人自有話術,商品未必邪惡,但coding絕非每個人都必須具備的技術。萬事皆有成本;而玩票近似於不玩——剩下的,就請自行斟酌了。


註解

[1] 嚴謹地說,這樣的課程其實是以應用為主的。稱之為科學太沈重了。

[2] 改編自:Jake VanderPlas著|2017|Python Data Science Handbook|第一章|O’REILLY發行

[3] 有一派主張「計算機工程」更精準於「計算機科學」。本文不討論此議題且並列兩者。

【翻譯】我們將在海灘上戰鬥(邱吉爾)

原文取自二戰時英國首相Winston Churcill在1940年發表的演說〈We Shall Fight on the Beaches〉。時值納粹德國席捲歐陸,希特勒試圖勸降英國,而邱吉爾斷然回絕。

我們不會頹喪或敗退。

…We shall not flag or fail.

我們將從一而終。我們將在法國戰鬥;我們將在近海與遠洋戰鬥;我們將在天空裡越戰越堅定、越戰越強;我們將捍衛我們的島嶼;不計代價。

We shall go on to the end. We shall fight in France, we shall fight on the seas and oceans, we shall fight with growing confidence and growing strength in the air, we shall defend our island, whatever the cost may be.

我們將在海灘上戰鬥;
我們將在登陸點上戰鬥;
我們將在田野與街頭中戰鬥;
我們將在丘陵間戰鬥;
我們絕不投降。

We shall fight on the beaches,
we shall fight on the landing grounds,
we shall fight in the fields and in the streets,
we shall fight in the hills;
we shall never surrender,

儘管我不信,但哪怕是這座島嶼或其大部分都淪陷且彈盡糧絕。那麼,我們在海外的帝國,被不列顛艦隊守護且武裝的帝國,亦將奮戰到底——直到上帝眷顧的時光來臨。在那個新世界裡,祂的力量與全能將拯救舊世界,並帶來自由。

and if, which I do not for a moment believe, this island or a large part of it were subjugated and starving, then our Empire beyond the seas, armed and guarded by the British Fleet, would carry on the struggle, until, in God’s good time, the New World, with all its power and might, steps forth to the rescue and the liberation of the old.

【香檳】菁英中心不收垃圾

常常覺得知識是很有趣的,但有時教育者本身並不關心人。即便是我這樣對程式有愛的學習者,到了CS學院上課,也常常覺得折磨⋯⋯

以下圖為例,取材自最近上課的講義。上方是投影片截圖,下方是我整理出來的筆記。這張投影片的主題是透過一個簡單的遞迴函式,示範OCaml語言的遞迴語法。

pic

這張投影片在課堂中大概只會使用30秒。但是否理解這30秒的內容,卻關乎接下來的學習效果。

計算原理就只是國中數學的乘法公式——乍看之下卻像是一種艱深定理的證明。但只要經過簡單的排版與上色,就可以大幅降低學生消耗的認知能量。我認為,不論你是不是程式熟手,都不該浪費能量在混亂的資訊中。

以下是我覺得可以做出的改變:

  • OCaml有許多宣告函式的語法,我換了一個最靠近C家族的(因為學生一定學過)。並透過排版與縮排讓他結構上更容易與已有的知識連結。

  • 使用等寬字來區分(可交給機器的)程式碼與(不可交給機器的)自然語言。

  • 使用等寬字後,我們可以輕易地做出上下整齊的排版空間。並將將後兩句switch概念的語法進行同質性的排比。第一個 | 符可加可不加,但個人偏好會加。

  • 使用桃紅色標明這則案例的關鍵字rec。

  • 使用橘色區分參數n。而n一但被標示出來,match與with便具備了關鍵字的隱喻。

  • 使用灰色標示註解——學生甚至不需要知道OCaml是使用(**)作為註解符這件事。

  • 使用黃色螢光筆標示出函式的遞迴點。

  • 井字號(#)為REPL工具的prompt字符,但其實在教學的上下文中,根本不需要使用——使用的結果就是讓學生copy代碼時如果沒有略過#,會得到錯誤。

最後還是想讚嘆Python當初把縮排納入語法的設計——這堪稱是科技對人類的無上關懷。考量到「程式更常被閱讀,而不是撰寫」這件事實,Python讓你無論怎麼惡搞,都得處理好排版;而一但你處理好了排版,其實也就沒那麼惡搞了⋯⋯

而世界的現實似乎就是,大學往往是教天才的,不是教笨蛋(如敝人)的。更廣一點來看,這其實也反映了大學體系強迫頂尖研究者同時也必須教學的弔詭之處。

【香檳】 A General Purpose Programmer

最近比較認真地思考著未來的求職,苦思一句新版的About Me用於履歷,想來想去,竟沒有比「A general purpose programmer」更好的——總之是個削瘦寡言的華人男性工程師。無臉孔,近似物質。要發射火箭那是不行;翻翻文件寫點不出格的代碼,持續累積知識,倒還算有點信心。

在美國也一年多了,漸漸地體驗到所謂「虛偽的美國人」的說法:一方面,他們強調多元文化與個人特色;但另一方面,其實每個人又忙碌得不得了,以至於只能勉力維繫價值的表象——作為一個潛在的海外移工,在此生活,人的屬性其實相當稀薄。

舉例來說,敝系iSchool是個國際學生非常多的學院——你要說是國際學生學店我也不介意[2]。其對於分組扮多元文化家家酒之熱衷,已經到了連教資料結構都要分組的境界。但是,其實不止教職員忙,連來美國拿身分與賺錢的台灣人中國人印度人,以及來敝系偷學Python的美國人其實也都很忙。不只課業負擔其重;還要抽空投履歷與面試,以至於分組的化學效應真的奇差無比。所謂的多元文化融合,也就是一件「我們極度重視,儘管它得到的資源排不進top 10」這般,如此重要的事。

再兼之美國的企業規模之大,我常笑稱,美國人總說:We are an agile startup with ONLY 500 employees。其人才需求之專業化程度,本質上就近似於軍隊。我感受到自己在台灣做過的工作,那些吾少也賤現在依然很賤的歲月裡所學會的鄙事,在這裡真的一點也派不上用場(噢你還做過遊戲設計和PM啊,那你會不會功夫或包餅乾呢)。

(岔個題,在台灣工作的朋友真的要慎重評估「待在小公司當小叮噹」這件事——儘管我本人不後悔,但還是建議大家慎思這件事對職涯可能的負面影響。除非你很堅定,否則想辦法擠進大公司擔任專職;又或是至少是小公司的專職;都還是相對安全很多的選擇)。

我對於美國人的所謂虛偽有一種私人的解讀暨體諒。記得曾看過一篇報導:「(在美國)除非你是比爾蓋茲,否則你離破產永遠只有一場大病的距離」。作為資本主義社會的模範,美國的專業人士一方面賺得真的很多;另一方面環境卻也內建了可能是世界上最強烈的消費主義與最進步的商業技術;同時,個人主義的盛行更讓社會無從建構出完善的福利制度,讓人得以在不幸時免於失去人之為人的基本尊嚴。

這樣生活的具現就是:人們賺得很多;但是想買的東西更多。幸福的背後就是萬丈深淵,因此無事不需保險——同時必須祈禱保險條款裡沒有致命性的漏洞。

正如《寄生上流》裡說的「如果我有錢,我也會是個好人」。所謂倫理道德暨多元文化包容,估計也都是吃飽撐著沒事的人兒發明出來維繫社會安定的東西。大家都有生存壓力,相同人種結合在一起,中文組刷著一畝三分地;美國組與印度組自己進行著神秘的計畫,其實才是再合理不過的事。題外話是,在我私人的理解裡,東亞的主流貌美女性,儘管仍然一定程度地受制父權價值的壓迫,但當在融入多元文化圈時,仍然是具有相當優勢的(東亞女生真的好美啊)。

而至於要不要真的用「A general purpose programmer」呢,我還是有點猶豫。就像敝業師曾苦心規勸本寶寶「A great programmers don’t result from people who only study programming」。所謂國王沒穿衣服是一件事,公然宣稱國王沒穿衣服,那又是另一件事——但我覺得最有可能的是,根本沒有人讀得出本寶寶心中的百轉千迴。看到這句子,起先覺得疑惑,但在30毫秒內便斷定了這人either英文不太好or腦子有問題,總之總之,真的不是很重要⋯⋯

以上,其實也沒什麼。新學期修了一門叫作Programming Language & Compiler的課,學習了一個讓人三觀盡毀的語言叫OCaml。感受到自己對於程式理論的鑽研即將要暫告一段落了——再往深處,就是所謂學術的巍巍大門了。而自己不論是求知慾與心智負擔,似乎也都將到了一個臨界。

連同申請算入,算是任性地低頭念了兩年半的書。儘管不大情願,還是要抬起頭關照社會、關照人。勉勵自己要慢慢開始恢復社交,多面試、多講英文與多交朋友。故以此為記。以上所描述的看法,說不定未來也會改變。


註解

[1] General purpose programming language是一個常見的對於程式語言的描述,意指該語言的目的不限於專門領域(例如Python與Java)。以Python為例,其被泛用於資料分析、自動化腳本、網站後端⋯⋯等。

[2] 但要澄清的是,這學店賣的不主要是學歷,主力商品反而是OPT。另外以美國的教育成本來看,敝系收費尚屬公道。

【香檳】時事有感

想想民主其實是一種很微妙的制度,它的精妙性似乎不全是當初制度與理論推行者,所刻意為之的。

舉例來說吧,太陽花事件裡馬政府最後出動了鎮暴警察與水炮車,為什麼最後沒有上綱到如香港般更大規模的全島癱瘓運動,以及隨之而來的催淚彈雨和恐怖政治呢?

因為光是選舉,就直接讓國民黨輸到脫褲。

這其中固然有內耗,有不效率,甚至看來愚蠢可笑。但很明確地,這成功避免了更多慘無人道的殺伐。而台灣一定程度上獨立的司法,更讓國家機器要形成一黨專政的利益集團更加困難——當馬英九無法透過司法,將王金平或是其他派系抄家時,我們就會看到一堆顧慮著選舉的跳船仔,以及一個至今還在國民黨內呵呵笑,不戰不降不談不和不走不死,亦絕不出力的魯洨王系。

民主制度裡固然有撈仔,有政客,也有距離所謂「理想公民」有極大差距的選民群體。但能讓人類在生物內建「人人為己」的驅力下,還能某種程度地避免因權勢競逐而陷入原始狀態,導致人類在戰爭與恐怖政治裡無人道地死去的,大概也只有民主政治了。

而什麼是人性?我說人性的本質就是「嚮往生但終需死」,其間有老病餓苦的過程。而得以免於老病餓苦與其憂慮的片段,謂之快樂。就像一台飛在空中但失去起落架的飛機,人人都在苦思著一個免於粉身碎骨的結尾。

就像聖經裡說的,那些最偉大的、得以讓義人前往天堂的善行,也不過就是「我餓了你們給我吃,渴了給我喝;赤身露體你們給我穿;我作客旅,你們留我住;我病了、我在監裡,你們來看顧我」

而所有器物與制度的發明,其實都是中立的。在我的看法裡,其中真正有價值的所謂「文明」,便是那些能讓更多人免於老病餓苦,抑或是至少肯認老病餓苦之為議題的創造——以這個觀點來看,文學大多是文明的,而計算機則為不可知。

我直覺地相信中國共產黨並不是傻子,近年來歷任的國家主席大致還能維持一個彈性內的均勢與平衡。只可惜傻子只需要一個,就足以毀了一整代人。習近平政權可能已經用強太過,導致他只能繼續用強。而前所未見的計算機科學所帶來來的AI與大數據監控系統,是他維持統治的希望所在。

獨裁之患,在於當政者聰明但又不夠聰明,在體系裡缺乏深思卻便恣意重構,再搞不定就hard coding硬上,還自以為效率與洋洋得意。但我說,不管蓋了什麼高樓鐵路,沒有什麼比迫害人民,並讓人民在恐懼、饑饉與疾病中死亡更可憎的罪惡了。

只願一切還有轉圜餘地,台港中這一代眾生,不要因為習政權的愚蠢而一併「攬炒」,玉石俱焚去了。