代碼是否存在「美感」?

源文件頁面下的代碼是否存在美感?是否可能成為一種藝術形式?

這個問題想討論美的客體特徵:到底什麼是審美的基礎?什麼樣的事物才有被審美的可能?


結構的美就跟數學公式的美一樣。你看到一句代碼,你把不同的部分換成不同的東西,它的結構沒有變化,但是他的意義可以千差萬別。就像Haskell的Monad,做出來就很漂亮。當然Monad有他自己的缺點,我就不談了。相應的還有F#的computation expression。C#的yield return和async await算是應用的很巧妙的一個例子(也就是下面說的沒有噪音),但卻不存在結構的美,因為你不能替換裡面的零件。當然結構還有設計模式,但是設計模式看起來就像補丁,不美,遠遠不如這個:名著類 - ajoo

上面是第一種美。第二種美,就是代碼毫無噪音。噪音是什麼呢,我舉一個例子。下面兩段代碼描述的是同一類東西,你說哪個好?

https://gac.codeplex.com/sourceControl/latest#Common/Source/Parsing/Json/ParsingJson_Parser.parser.txt

https://gac.codeplex.com/sourceControl/latest#Common/Source/Parsing/ParsingDefinitions_CreateParserDefinition.cpp

打開讓屁股看一看都知道是第一個好了。這就是沒有噪音和有噪音的區別。當然我這段C++還是算好的,你要是把他的意義直接展開成代碼,也就是一個遞歸向下的語法分析器(不知道什麼是遞歸向下的看這篇教程: 如何手寫語法分析器),那就別想看懂了,只能靠作者好心寫注釋了。

如果你寫的一個程序,可以同時擁有結構優美和代碼沒有噪音這兩種優點,那就是一個漂亮的程序了,任何時候都應該拿出來炫耀。


舉三個例子說明代碼中可能存在的美,其中兩個來自《代碼之美》這本書的第一章和最後一章,第三個例子來自 C++ 之父的一次技術講座。

1. 正則表達式匹配程序

原文中的需求是寫一個精簡的正則表達式匹配程序,要求能夠匹配:

c 任意字幕

. 任意單個字元

^ 字元串開頭

$ 字元串結尾

* 零個或者多個前一個字元

/* match: search for regexp anywhere in text */
int match(char *regexp, char *text)
{
if (regexp[0] == ^)
return matchhere(regexp+1, text);
do { /* must look even if string is empty */
if (matchhere(regexp, text))
return 1;
} while (*text++ != );
return 0;
}

/* matchhere: search for regexp at beginning of text */
int matchhere(char *regexp, char *text)
{
if (regexp[0] == )
return 1;
if (regexp[1] == *)
return matchstar(regexp[0], regexp+2, text);
if (regexp[0] == $ regexp[1] == )
return *text == ;
if (*text!= (regexp[0]==. || regexp[0]==*text))
return matchhere(regexp+1, text+1);
return 0;
}

/* matchstar: search for c*regexp at beginning of text */
int matchstar(int c, char *regexp, char *text)
{
do { /* * matches zero or more instances */
if (matchhere(regexp, text))
return 1;
} while (*text != (*text++ == c || c == .));
return 0;
}

遞歸作為編程語言的基礎與核心,這段簡短精緻的代碼,清晰地證明如下事實:對正則表達式這種語言,如何用遞歸的語言表達出來。就像將你熟知的自動機轉換成為遞歸表達式。

這似乎是讓程序回歸到了其本源:任何程序都能以遞歸的方式表達出來。迷人的地方並不在於遞歸代碼的簡潔,而是你找到了問題的遞歸形式。這種美感是牛頓式的美感,當你使用函數式程序語言,當你使用動態規劃演算法,當給你去分而治之的時候,這種自相似的和諧美感能讓你興奮地(很可能誤)認為發現了宇宙萬物的真理。

如果你讀過《哥德爾,巴赫,費舍爾》就知道我在說什麼。

將問題分解的方法,從實踐的角度而言,並不需要一定轉化為遞歸式,將其分解為可靠的、結構良好的、已經解決的標準子問題,或許更有價值。Bjarne Stroustrup 的一次 C++ 技術講座 加強了我的這種看法。

2. 將代碼重構為演算法

Bjarne Stroupstrup 舉了這樣一個真實的例子,怎樣重構下面的代碼:

void drag_item_to(Vector v, Vector::iterator source, Coordinate p)
{
// find the insertion point
Vector::iterator dest = find_if(v.begin(), v.end(), contains(p));

if (source &< dest) rotate(source, source+1, dest); // from before insertion point else rotate(dest, source, source+1); // from after insertion point }

即使你很熟悉 rotate ,第一眼你不會想到這幾行代碼是作什麼用。它是 vector 內部的移動操作,將 source 插到 dest
之前。數據類型和判斷都是針對特定問題的。如果將它重構成泛型,這些特定的類型會被泛化,更重要的這個操作會將以基本、正交的操作表達出來,而不是各種條件判斷和循環。這好比提取特徵向量。

重構的代碼如下:

template &< typename Iter, typename Predicate&>
pair& gather(Iter first, Iter last, Iter p, Predicate pred)
// move elements for which pred() is true to the insertion point p
{
return make_pair(
stable_partition(first, p, !bind(pred, _1)), // from before insertion point
stable_partition(p, last, bind(pred, _1)) // from after insertion point
);
}

在線性隨機訪問空間 [i, j) 上將滿足條件 p 的元素插入到位置 k:insert(i,j,k,p),等價於將 [i, j) 分成 [i, k) 和 [k, j) 兩個空間,分別用條件 !p 和 p 進行 partition 操作:

insert(i, j, k, p) = partition(i, k, !p) ∪ partition(k, j, p)

很明顯,partition 更本質,也更通用,更重要的是它用(重複的)自己定義了 insert,insert
被分解成為兩個相同結構的子問題,這個分解像不像是發現問題的遞歸子結構?!將 insert 以 partition
的方式表達出來是不是顯得更接近問題的實質,而且還更漂亮,更少的代碼和 bug。C++ (STL) 將這種「子結構」平台搭建好,但需要
paradigm shift,才能發現其中之美。實踐意義上,泛化就像設計模式一樣,除了提供的技巧之外,更重要的是指明了重構的目標:將代碼用標準演算法組裝出來。

3. 判斷三點是否共線的程序

判斷三點是否共線,看起來是很簡單的問題 —— 你應該看看那本書,事情並沒有你看起來這麼簡單,比如使用斜率的話,你要考慮垂直的特殊情況;如果用三角形兩邊和大於第三邊,會涉及到開方運算,無理數精度有限會導致問題。最後,作者找到了完美的判斷方法:計算三角形的面積,如果為零,則三點共線。當作者給出最後完美的程序時,你只能嘆服:

(defun area-collinear (px py qx qy rx ry)
(= (* (- px rx) (- qy ry))
(* (- qx rx) (- py ry))))

它基於計算面積的矢量公式。

作者說存在傳說中的一本書,裡面記錄了最完美的數學證明,而他試圖尋找這種能記錄在類似書中的代碼。無獨與偶,頑固地認為存在這種書的並不只這篇文章的作者,大名鼎鼎的 Knuth 在他的 literate programming 中寫到:

我強烈地愛上了這套新方法,對過去寫的每一個程序,我都抑制不住地想將它們「文藝」化。我發現自己忍不住去編那些布置給學生助教們的作業程序,為何?因為對我而言,我最終寫出了那些程序,這就是它們本該被寫成的樣子。新方法促使我寫出比以往更可讀、也更好的程序。

對他們,對那些執著地尋找最完美的代碼的程序員們,在另一種語言,另一空間,有大師福樓拜遙相呼應:

不論一個作家所要描寫的東西是什麼,只有一個名詞可供他使用,只有一個動詞能使對象生動,只有一個形容詞能使對象的性質鮮明。因此就得用心去尋找,直至找到那一個名詞、那一個動詞和那一個形容詞。

他們深刻理解到符號系統最具價值的地方在其精確,這種極致追求劈開了普通人與真正的大師之間難以逾越的鴻溝,這是「差不多就可以」與「完全無法企及」之間的光年距離。這種美就像你初次遇見完全對的人:此生不再作它想。

總結

毫無疑問,上述例子證明了代碼的美感是存在的,但這種美,就像欣賞任何其他嚴肅的藝術作品一樣,需要你理智上的能力和付出,而不僅僅是直觀上的感受。更私人的看法就是,程序是最接近巴赫音樂的藝術。它們都是在形式上試圖去挑戰難以企及的內容,前者用符號接近人的理性,而後者用另一種符號向神祈禱傾訴。

簡言之,在我看來,代碼之美存在於簡潔優雅的數學真理和自我遞歸的完美重複中。而這一切,大自然早已知道,並以各種令人驚狂的方式藏在一滴水、或者一片樹葉當中。


只有看的懂的人才能了解其中的美,包括結構美。

你讓一個沒接觸過編程的人看代碼,TA只會說「我看不懂,真噁心,我無法理解你的藝術品」,親測,給女朋友看過,原話。


是有美感的。

當我第一次閱讀一個開源庫的源碼時,就情不自禁地感覺到好美好優雅,有點像藝術品。

而最近一直看公司內部的代碼,跟翔一樣……


代碼的美,我沒有太在意過。但是代碼的清晰易讀性是很顯然的。

有一本書叫《代碼整潔之道》。

少用全局變數,函數圈複雜度不要過高,函數體不要過長,等等,都能使代碼易於調試和維護。


微博上看到的,原po是@虐貓狂人薛定諤,你們隨意感受一下( ̄▽ ̄)ノ


對稱,比如send對recv,read對write,init對fini,甚至參數形式都是一致的。

一個典型的處理流程:

init();

i=recv(buf, len);

……

i=send(buf,len);

fini();

因此我覺得new和delete就不夠美……不夠對稱,如下:

foo=new Bar;

………

delete foo;

不能忍


UC雙11那天的歡迎界面 來自 @尹曉


感受下 我寫的

https://github.com/ccsdu2004/gaudio/blob/master/echo/gecho.cpp


雖然不是代碼出生,但是寫到一種境界的時候,代碼也變成一種圖形了,美不勝收。。


找公司里最亂的代碼(一定有的),然後根據《重構》,《代碼整潔之道》,可能的話加上設計模式,重構一遍,差不多就能感受到了


有的,當你遇到過翔一樣的代碼時,你就會覺得其他的代碼是多麼的美好優雅


適當的空白行,不要嵌套過多等等

代碼大全講了很多。


推薦閱讀:

不要浪費時間寫完美的代碼
劫持數字簽名與Powershell自動化過程
重訪黑客
未來屬於演算法,而不是代碼!

TAG:代碼 | 美學 | 藝術欣賞 | 藝術學 |