Chris Grainger:我們如何才能更好地編程?
本文作者 Chris Grainger 是開源 IDE Light Table 的開發者,之前在微軟 Visual Studio 開發團隊效力。本文是 Chris 根據他自己的演講《Finding a way out》改寫而成。
當開始設計 Light Table 的原型時,我并沒有任何宏偉目標,只是一直在思考如何能更好地編程,還想看看做到這點有多難。直到最近,我才幡然醒悟:過去的十年努力:從Web框架,到Visual Studio,到 Light Table 的這段經歷讓我意識到:從一開始我就錯了。事實上,我犯了一個典型的新手錯誤:想回答一個自己都不明白的問題。
如何更好地編程?
我只是不停地問自己:“我們怎樣才能更好地編程?”但從來沒有退一步,具體去想編程有什么地方不對。一直以來,通過對自己和他人的工作的觀察,我得到了最完整的答案。以“Light Table”為例,如果縮短反饋循環會提升性能,雖然它的確可以,但卻掩蓋了一個事實:只是相同的老版本迭代。人們在痛苦的深淵中掙扎,我也是。“縮短反饋循環”并沒有將我們從編寫和調試代碼的“深淵”中解救出來。所以,如果問題不只是反饋循環,那又是什么呢?編程的問題到底在哪?
要回答這個問題,我需要更多的數據,不僅僅從我的“后視鏡”或自己的經驗,而是從“現實世界”。于是我開始問大家兩個問題:“什么是編程?它的問題到底在哪?”我問過從未編程的普通人,正在上班的白領,同行業中最好的工程師,等等。他們的答案讓我吃驚不已。
什么是編程?
這個問題的答案真是令人沮喪。問了這么多人,居然沒有一個人說“編程是用來解決問題的。”相反,大家更多的覺得它只是用半干的膠水把東西粘起來盛水,并祈禱在被新東西取代之前還可以繼續盛水。我聽別人說過無數遍,說我們只是水管工和膠水工廠。還有人說”編程主要解決自身不足“,這讓我特別難過,因為它是眾多答案中唯一接近正解的答案:我們把東西放到一起來解決某個問題,而“這個問題”是讓程序員沮喪的源頭之一。
編程本來是用來解決問題的,卻變成了解決問題的問題(一個工程師朋友喜歡稱之為“自舔冰淇淋”)。“膠水工廠”肯定不是我想要的答案,所以我一直在想一些可以行得通、說得過去的答案。到目前為止,我最喜歡的一個是:programming is our way of encoding thought such that the computer can help us with it. 編程是把我們思想轉成代碼,并讓計算機可以幫助我們實現它的方式。當一天結束的時候,我們試圖做的是構建一個新東西,并能夠實現它——編程恰好是實現它的最好方式而已。
編程的問題在哪呢?
這個問題的答案也是我不想再問人(約400人)的原因。他們往往分為兩類:他們要么隨大流,并沒有提供任何有用的信息,要么非常有個性的回答(當得到這些“死鎖”時,我相當無語)。但總體而言,對于“編程的問題在哪?”這個問題的數百條答案,我提煉出了三類答案:不可察,非直達 和不簡單。(unobservable, indirect, and incidentally complex)
編程是不可察
最近由于Bret和“Light Table”的開發工作,這點似乎是我們最為關注的。我們不能看到我們的程序是如何執行的。我們不能看到對程序的改變是如何影響項目。我們不能看到我們的程序是如何連接在一起。這基本上意味著我們觀察不到任何東西。可觀測性可以看做是一個一步一步查看狀態的調試器,它強迫時間停止,并觀察在某個時間瞬間的狀態。但事實是,程序在某個時間瞬間基本上不會出錯;大多數錯誤都是在一段時間內發生。就因為這一點,我們最好寫一個print語句。但這十分荒謬。我們需要的是觀察整個計劃執行的過程,后進,后退,甚至未來——而不僅僅只是當我們的斷點命中時。比這更可悲的是,我們似乎已經接受了不可觀察性為一個既定的事實。例如下面這行代碼:
person.walk();
它有什么作用?面向對象的封裝的定義就是不可觀察性。我不知道person.walk()有什么用。它可能做一些事情,像設置isWalking為true,但它也可以設置ateCarrots為true,它也可以決定我是否因疲憊而昏厥——我不知道,也沒有辦法知道。就好像,我們在一片漆黑中無所顧忌地扔飛鏢并祈禱我們至少能射中靶心。不管你是剛剛起步或已經寫了數以百萬計的漂亮的代碼行的程序員,根本無法看到我們的程序怎么運行,這是一個嚴重的問題。
編程是非直達的
寫一個程序是一個容易出錯的翻譯工作。即使是數學——編程語言因其而生,都被翻譯成這樣了:
#include <algorithm> #include <iostream> #include <iterator> #include <cmath> #include <vector> #include <iterator> #include <numeric> template <typename Iterator> double standard_dev( Iterator begin , Iterator end ) { double mean = std::accumulate( begin , end , 0 ) / std::distance( begin , end ) ; std::vector<double> squares ; for( Iterator vdi = begin ; vdi != end ; vdi++ ) squares.push_back( std::pow( *vdi - mean , 2 ) ) ; return std::sqrt( std::accumulate( squares.begin( ) , squares.end( ) , 0 ) / squares.size( ) ) ; } int main( ) { double demoset[] = { 2 , 4 , 4 , 4 , 5 , 5 , 7 , 9 } ; int demosize = sizeof demoset / sizeof *demoset ; std::cout << "The standard deviation of\n" ; std::copy( demoset , demoset + demosize , std::ostream_iterator<double>( std::cout, " " ) ) ; std::cout << "\nis " << standard_dev( demoset , demoset + demosize ) << " !\n" ; return 0 ; }
這個例子,還算是翻譯的比較清晰了。那要是代碼不是這么清晰,又會發生什么呢?我們能得到就只有符號了。我們從來沒有看到或與真實的東西交互,他們只是象征性的表示交互。而在某些情況下,符號固然重要和強大,它們不必是不透明:
cards[0][12];
是啊,打牌的時候,當我拿到卡[0] [12],我會很開心。我們正在編寫一個紙牌游戲,紙牌有自己的名稱(黑桃A),為什么我們不能直接存儲這個呢?
翻譯很難,而且使用的符號又容易出錯,尤其是當與其他元件的上層操作耦合時。這種間接性,導致其不能代表有意義的東西,如果直接使用它們,又會讓我們抓狂。大量編程的錯誤僅僅是翻譯問題。雖然我們的腦子里有解決方案,但在試圖把它翻譯成代碼的時候,我們要不是忘了什么東西就是犯些小錯誤。所以,我們必須擺脫翻譯。當我們設計UI,我們應該做一個可視化編輯器。當我們研究數學,我們手邊應該有類似的Mathematica的工具。我們應該用最自然的方式展示他們,而不是混淆不清的方式。
編程是復雜的
在編程中通常帶有許多的復雜性事件,你得是要做一堆并不與你要解決的問題直接相關的工作。試著計算即使得到最簡單的運行結果所需的時間,至少要花一個星期的時間來搭建一個開發環境。這些例子都是一個系統中不必要的復雜性,但事實是這種偶然的擔心普遍存在的整個編寫軟件的全過程。最糟糕的是,我們現在是在邏輯層用代碼做時間管理。大多數人可能會反駁,我們只要立即跳轉到并發和并行就行了啊,但實際問題不是這么簡單的。每當我們添加一個事件處理程序或創建一個回調,我們就是在做時間管理。
考慮到交互模式越來越復雜,我們程序面臨多內核增加的變化(隨之而來的并發問題),很快我們得知回調是解決該問題的糟糕方案。每次我們想要自己手動管理事物的時候,我們都可以想出一個實現自動管理的方案。我們曾經手動處理二進制,后來我們創建了Fortran語言。我們曾經手動管理內存,后來我們創建了垃圾收集器。我們曾經管理數據的限制,后來我們有了類型系統。我認為,下一步優化代碼的復雜性的重大舉措將來自于實現自動時間管理,這將對我們清晰地表達意圖的能力有重大的影響。
試圖列舉編程中所有復雜性的例子將會令人感到無望,但現在是時候要解決這些問題了。我們應該專注于解決問題——而不是解決問題的問題。這樣我們就可以在一天之內從一無所有到生成解決方案,而不是需要幾周或幾個月的時間。
追求局部最優(Chasing local maxima)
如果你回顧在過去50年主流技術上的進步,他們雖然很大程度上提高了我們的效率,卻沒有真正改變程序的行為。之所以我在文章的開始暗示:一切都是被動的,因為我們往往只在戰術層面上的修正。事實上,幾乎每一個我們取得的進步已經完美的解決了以上三類問題。我們讓事情變得更好,我們不斷接近局部最優,因為我們認為這些東西能以某種獨立的方式解決。我聽過最好的比喻是茶杯堆疊效應,每次我們修復某件事情,就是在茶杯堆上繼續放茶杯,但最終我們只是在抽象多層次的工作,而該塔也開始傾斜并快要倒塌。我們必須停止單獨思考這些問題,而是要想怎樣才能同時解決他們。
有一天,我突然開竅了:編程其實是一切的對立的統一。晦澀的語言,神秘的錯誤,缺乏(或大多是零散的)文檔——就像坐在某個真人秀的現場(幕后還會傳來陣陣笑聲)故意為難你。在一定程度上,這是自虐,但我們這樣做,因為它給了我們提供了一個難得的機會塑造世界。通過認真思考編程的定義和它的問題所在,我們可以找到消除多余內容的框架的機會。但為了做到這一點,我們不能只考慮我們現在擁有的。另一個抽象層是不夠的。相反,我們必須從根本的層面解決問題。不要更多的茶杯,不要更多的妥協。
編程之人
跟非程序員的聊天,他們會提供非常不同的看法。事實是,在我看來,他們大多也是程序員。他們只是不寫“代碼”而已。如果您使用Excel,你就是在編程——你要電腦根據你寫的一個模塊為你工作。 Excel是一個特別有趣的例子,這讓它取得為人們能夠解決問題的巨大成功。這也恰好滿足我們的三個基本條件,并一些證據表明,它具有強大的解決問題的能力和“平易近人”的開發環境。
Excel是可觀察的,因為它沒有任何隱藏的狀態,所有的值你的看到并操作。它也是直接的。你可以在表格中直接更改數值,拖放東西,選擇性地計算等等。它設法回避了很多復雜性問題;電子表格是永久有效的,它沒有安裝程序,甚至沒有的正在運行的概念。Excel通過權衡權限實現了這些。有很多事情是無法表達,因為放置在編程模型的約束條件非常好(或全部)。一個有趣的問題是,是否以類似的方式就可以解決我們的問題,但緩解了一些限制和保留更多的權力。
我們越多探討過這個問題,就越會意識到,通過修復了這些問題我們在編程更加普遍的路上已經走了很遠了。因此,如果這個有趣的問題的答案是肯定的,那大約有十億人可以擁有現代超能力。試想一下,如果每個人都用電腦做到80%如今的程序員的工作。這樣對什么的影響會最大?我不知道,但我認為它這將是我們人類作為一個集體的根本性轉變,而且肯定是一件非常棒的事情。從長遠來看,我相信操縱電腦將成為一項基本技能,但不像大多數的“編程是識字”(programming is literacy!)運動,我認為這將與能否寫出’ if ‘語句無關。未來編程將會是大多數人都可以做到的事。任何以我們現在這樣做事的方式,是注定要失敗的。事實上“受虐狂”一點也不受歡迎。
文化差異
我的問題還沒有解決的另外一個原因是:編程帶來的社會問題,最近也得到了越來越多的關注。有許多方面對這個問題從編程已有的固有印象,到我們社區互動的方式,已經上升到了偏見和成見。我覺得編程最有趣的地方之一是它有可能引起的文化的重新定位。如果編程本身并不對立,那會變成什么樣呢?如果只需要粗略地計劃就能編程,又會是什么樣?如果編程從一開始就是思路清晰的?這樣的編程,與我們如今的編程沒有一點共同之處。因為解決編程帶來的文化問題才是真正地改變世界。
好,現在該怎么辦?
我們找到一個解決問題的基礎!沒問題吧?在“Strange Loop”的演講中,我展示了我們一直都在做的Aurora非常早期的原型。這里,我帶來了新的進展。雖然我們的策略已經有了顯著改變,其背后的概念仍然是相同——我們想要找更好的東西,不只是針對程序員或非程序員,而是完全消除這種區別。我們最近在這個方向做了些實實在在的進展,當穩定之后,我們將分享更多。但有一點我想說的是,大家一定要眼見為實。
原文鏈接: Chris Granger 翻譯: 伯樂在線 - Stellar
譯文鏈接: http://blog.jobbole.com/65896/