帆影小隊這週又發生一次熟悉的插曲,資深工程師盧卡斯把一份新故事的規格文件貼進頻道,寫得相當詳盡,連驗收條件都列了五六條。工程師奧莉維亞照著規格開始動工,做到一半,卻發現規格裡有一條驗收條件,跟另一條隱含的假設互相矛盾:文件說「金額欄位需支援到小數點後兩位」,卻又說「顯示時一律四捨五入取整數」,兩條放在一起看沒問題,但實際串起資料流程後,會導致某些邊界金額,顯示結果跟後端記錄的實際金額對不上。
奧莉維亞把問題回報給盧卡斯,盧卡斯愣了一下:「這……我當時沒想到這兩條會衝突,你先照後面那條改,我把規格更新一下。」他打開規格文件,在原本的驗收條件旁邊,加了一句「金額顯示以四捨五入為準,涵蓋所有情境」,存檔,訊息回了一句「已修正,麻煩繼續」,這件事就這樣被結案了。
蘇菲把這一幕記下來,隔天卻聽丹尼爾提起,類似的事情過去半年已經發生過好幾次,都是同一種模式:規格寫得很細,卻總有某個角落的假設沒被涵蓋到,等工程師動手做才發現矛盾,然後盧卡斯修規格,事情翻頁繼續。「我們每次都說要把規格寫得更詳細,」丹尼爾說,「可是規格已經很詳細了,問題還是一直冒出來。」蘇菲翻了翻過去幾個月的修訂紀錄,光是這個季度,類似的「事後補條款」就出現了七八次,沒有一次是在動工之前就先被發現的。
奧莉維亞在下一次回顧會議上,提出了一個不太一樣的想法:「我們是不是應該在動工之前,找盧卡斯、負責開發的人、還有麥克斯(負責下游資料驗證),三個人一起坐下來,把這個故事真正討論一遍,而不是盧卡斯寫完丟過來,我們照著做?」
盧卡斯有點遲疑,手指在桌上輕輕敲了兩下:「這樣是不是比較花時間?我現在寫規格,已經算是把大部分細節都先想過一輪了。」
「我猜問題可能就在這裡,」奧莉維亞說,「你一個人先想過一輪,想到的是你自己看得到的角度;金額顯示跟後端驗證怎麼兜起來,你可能本來就想不到,因為那是我們動手做的時候才會撞見的問題。與其你先寫、我們後改,不如一開始三個角度一起看——四十分鐘,換掉的是後面好幾輪來回修訂的時間。」
這一次,大家決定試一次「三方會談」,盧卡斯、奧莉維亞、麥克斯,三個人在動工前,花了四十分鐘,一起把下一個故事從頭討論一遍。麥克斯一開口就問了一句盧卡斯完全沒想過的問題:「這個金額如果是負數,退款那種情況,顯示規則會不會不一樣?」盧卡斯愣住,這正是他寫規格時完全沒考慮到的情境。奧莉維亞緊接著補了一句:「如果是負數,我們資料庫那邊儲存的精度,跟顯示要不要一致,也得先講清楚,不然又會是下一個修訂紀錄。」三人你一言我一語,挖出好幾個原本要等開發到一半才會撞見的坑,四十分鐘結束時,盧卡斯忍不住笑說:「這些問題,我自己坐在位子上想三天,大概都想不到。」
蘇菲把這件事帶回去給艾佛勒。「這感覺跟以前那種『規格寫更細』的做法不太一樣。」
「差別很大,」艾佛勒說,「盧卡斯每次修規格,是在解決『這一條規則寫錯了』;奧莉維亞這次改的,是『規格該怎麼被產生出來』這件事本身——前者是症狀層次修修補補,後者是回頭去動那個支配整件事的假設。這正是雙環學習的實務版本,就像羅盤小隊當年那次投票風波裡已經做到的事——把『要多反省』這句口號,變成行事曆上會固定出現的四十分鐘。」
蘇菲把這句話寫進筆記本,想著帆影小隊這幾週走過的路,從那面貼滿便利貼的牆,到麥克斯與丹尼爾帶艾瑪的兩種方式,再到今天這場會談,她第一次覺得,這個「全公司最會反省」的團隊,好像真的找到了一條不再只是反覆解決同一批問題的路:不是某一個人變得更厲害,而是這個團隊,一次又一次,願意把焦點從「這次錯在哪」,往後退一步,去問「是什麼樣的做法,一直在製造這些錯」。
接下來一個月,三方會談成了帆影小隊固定的節奏,每個新故事動工前,盧卡斯、奧莉維亞、麥克斯,三人先花四十分鐘一起討論過一輪,才真正開始寫程式。蘇菲把這個改變寫進給薇薇安的月報裡,用的形容詞是「顯著」。
直到某一次,三人在討論一個新故事,把客戶批次匯入的資料,從「全部成功才算完成」,改成「允許部分成功、部分失敗」。盧卡斯先開了場:「我覺得邏輯不難,失敗的那幾筆,我們原樣退回去給客戶,讓他們自己修正重傳就好。」麥克斯附和:「對,這樣最簡單,我們也不用額外處理狀態機。」兩人一來一往,不到十分鐘就敲定了方向。
奧莉維亞其實有一個沒說出口的念頭——如果客戶的匯入檔案裡,同一筆資料被拆成好幾個批次分次送出,「部分成功、部分失敗」疊加起來,可能會出現同一筆資料,一部分欄位已經被更新、另一部分還停留在舊值的情況,那不是單純的「退回去重傳」能解決的。她張了張嘴,看盧卡斯跟麥克斯兩人聊得很有共識,那個念頭終究沒有說出口——一部分是因為她自己還沒把這個邊界情況想清楚,怕答不上細節,一部分是因為,過去這幾週的三方會談,一直都是盧卡斯跟麥克斯先對出方向,她很少在意見不同時主動打斷重新討論。四十分鐘的會談,在一片「這次應該沒問題」的氣氛裡結束。
三週後,這個功能上線,真的有一位客戶分批匯入同一批資料,觸發了奧莉維亞當初隱約擔心的那個邊界情況,所幸只是造成了幾筆訂單金額顯示不一致,客服在客戶抱怨前就先發現、手動修正,丹尼爾查log時忍不住問奧莉維亞:「這個情況,妳當初討論的時候,有想到嗎?」奧莉維亞遲疑了一下,老實說:「有一點點,但沒有想得很清楚,而且盧卡斯跟麥克斯那時候聊得很順,我就沒說。」
蘇菲聽到這段對話,第一個反應是有點自責——這幾週,她盯著三方會談「有沒有真的發生」,卻沒有回頭問過,奧莉維亞在裡面,真正說話的份量,跟盧卡斯、麥克斯,是不是一樣的。她想起自己這幾週,跟盧卡斯、丹尼爾、麥克斯都個別聊過三方會談的感受,卻獨獨沒有找奧莉維亞好好坐下來談一次——大概是因為,奧莉維亞看起來一直很稱職、很少抱怨,讓她下意識覺得,這個人不需要被特別關心。
她把這件事帶去給艾佛勒,語氣裡帶著一點難得的懊惱:「我們當初推三方會談,覺得這是雙環學習的機會——這個判斷沒有錯,事後補條款確實少了。可是我一直沒有回頭確認,這個空間,是不是每個人都真的敢講話。」
艾佛勒沉默了幾秒,難得沒有立刻給出解釋,反而先說:「這件事,我也有責任——妳當時回報『問題變少了』,我就直接判斷這個介入成功了,沒有再追問一句『每個人的發言份量是不是都差不多』。我們兩個,都太快把『機制有沒有被建立起來』,當成了『機制有沒有真正發揮作用』的證明。」
蘇菲愣了一下。這是她第一次,聽到艾佛勒這樣坦白承認自己判斷有漏洞。「所以我們現在錯過的,不是三方會談這個做法本身錯了?」
「不是它錯了,」艾佛勒說,「是我們把一個複雜的問題,當成了一個簡單問題來解——三個人湊進同一個房間,解決的是『有沒有地方可以講』,沒有解決『敢不敢講』。後面這個問題,牽涉到奧莉維亞跟盧卡斯、麥克斯之間的資歷與聲量,不是靠一套固定流程套用一次就能解決的。」
「那我們現在該怎麼補救?」蘇菲問。
「有兩種方式可以試,」艾佛勒說,「一種是我先跟盧卡斯、麥克斯談,讓他們知道要刻意留白;另一種是妳先單獨找奧莉維亞聊,搞清楚她真正的顧慮。妳覺得呢?」
蘇菲想了想:「我想先去找奧莉維亞。如果我連她真正的顧慮都還沒聽清楚,就急著去改盧卡斯跟麥克斯的行為,感覺又會重蹈一次同樣的覆轍,覺得自己已經看懂問題,其實只看懂了一半。」
那次談話,蘇菲第一次跟奧莉維亞真正坐下來,聊了將近一小時。她沒有把三方會談改成什麼新規則,只是在下一次會談前,先私下問了奧莉維亞一句:「這次故事裡,妳有沒有什麼想法,想先跟我說,我可以幫妳想一下,怎麼在會談上提出來?」奧莉維亞愣了一下,才慢慢說出這次故事裡,她一直沒把握該不該提的一個疑慮——如果同一位客戶同時觸發退款跟重新匯入,兩個流程前後順序不同,最後系統該以哪一筆記錄為準。這一次,她的顧慮,被提前準備好,帶進了會議室。
至於盧卡斯跟麥克斯,會不會在沒有蘇菲事先鋪墊的情況下,也能自然地把發言空間留給奧莉維亞——蘇菲還沒有把握。她把這個問題,原封不動地記在筆記本上,沒有劃掉,也沒有寫下答案。