編程思想:我現(xiàn)在是這樣編程的
我在做什么
曾經(jīng),我試過接到一些需求。一眼帶過后,腦袋馬上隨著高昂的斗志沉溺在代碼的世界中 ,馬不停蹄地敲著鍵盤直到最后測試的完成。我從思緒中恢復(fù)過來,乍一看自己寫的功能,和需求差了十萬八千里,我TM都在干嘛?
除此之外,我還見過類似的很好笑的事情。有一個程序員,經(jīng)理提了需求,然后他在那里折騰了一天。結(jié)果不但沒做出來,而且和實際需求都是完全搭不上調(diào)。經(jīng)過詢問發(fā)現(xiàn),他不知道經(jīng)理說了什么,也不知道自己到底在做什么。
代碼的世界可能是昏天暗地的,但是我們的思維不能這樣隨之混亂,否則一切都會前功盡棄。所以我現(xiàn)在編寫程序的時候,經(jīng)常會想一下:我要做什么,我在做什么。更好的方法是把詳細(xì)需求落實到文檔,并時刻核對文檔。
大局為重
2-8法則告訴我們,一個項目核心的功能只有很少,其它大部分都是對核心功能輔助或增強(qiáng)的。但當(dāng)任務(wù)分發(fā)下來,我手頭總有一些自己很想開發(fā)的模塊,不過它們不屬于那20%。我以前經(jīng)常會在這些感興趣的模塊上花費(fèi)很多時間和精力。
結(jié)果項目快要到上線期限,主要的功能卻沒開發(fā)完成,其它一些不起眼的功能卻做得很好,但為此項目不得不延期了。如果反過來,只要對整體功能預(yù)期不會有太大偏差,可以將就的先上線。重要一點是:即使功能還有遺漏,但項目可以上線了,老板自然不會太追究,自己工作也能圖個安心。如果不知道那些功能模塊是最重要的,先問問經(jīng)理。
人總是喜歡做一些自己感興趣或者有挑戰(zhàn)的事。不過在這方面,為了項目和團(tuán)隊著想,應(yīng)該盡量壓制這種誘惑。
性能永遠(yuǎn)不是優(yōu)先考慮的問題
我從來不會一開始就考慮性能問題。如果項目成本很低,甚至到項目結(jié)束時,如果沒有感覺到明顯的性能問題,也不會去管。要知道現(xiàn)在已經(jīng)不是DOS的年代,CPU的計算能力很高,但成本很低了。重要一點是,如果只針對提升性能對代碼做改動,很容易破壞代碼的復(fù)用性和可維護(hù)性。而返過來,提高了代碼的復(fù)用性和可維護(hù)性,則很容易提高性能。
下面有一個PHP的代碼實例,功能是幫助用戶重置密碼(代碼為了簡單說明問題,請不要太在意一些無關(guān)的細(xì)節(jié))
requestResetPassword是接收用戶重置密碼的請求并且做了相應(yīng)的檢查。為了更好的復(fù)用性,我將重置密碼的操作單獨分配到一個新的resetPassword的函數(shù),更改完密碼的后再調(diào)用sendEmail向用戶發(fā)送一封通知郵件。
- /**
- * 用戶請求重置密碼的接收器
- */
- function requestResetPassword() {
- //檢查用戶是否存在
- if( !checkUserExists( $_GET['userid'] ) ) {
- exit('抱歉,用戶不存在,請確認(rèn)用戶帳號。');
- }
- resetPassword( $_GET['userid'] );
- //最后向用戶發(fā)送一封郵件
- sendEmail( $_GET['userid'], '重置密碼成功', '新的密碼是xxxx' );
- exit('新密碼已經(jīng)發(fā)送到你的郵箱。');
- }
- /**
- * 幫助用戶重置密碼
- */
- function resetPassword( $userid ) {
- //檢查用戶是否存在
- if( !checkUserExists( $userid ) ) {
- return false;
- }
- //進(jìn)行重置用戶密碼的操作
- //略...
- return true;
- }
- /**
- * 向用戶發(fā)送一封郵件
- */
- function sendEmail( $userid, $title, $content ) {
- //檢查用戶是否存在
- if( !checkUserExists( $userid ) ) {
- return false;
- }
- //發(fā)送郵件操作
- //略...
- return true;
- }
- /**
- * 檢查某個用戶是否存在
- */
- function checkUserExists( $userid ) {
- $user = getUserInfo( $userid );
- return !emptyempty( $user );
- }
- /**
- * 獲取某個用戶的數(shù)據(jù)
- */
- function getUserInfo( $userid ) {
- //假設(shè)我有一個query的函數(shù),它用來查詢數(shù)據(jù)庫并返回數(shù)據(jù)
- $user = query( "SELECT * FROM `user` WHERE `uid`=" . intval( $userid ) );
- return is_array( $user ) ? $user : array() ;
- }
現(xiàn)在問題是,這三個函數(shù)都同時使用checkUserExists這個函數(shù)來檢查用戶不存在,數(shù)據(jù)庫查詢了三次,這樣帶來了一些額外的開銷。
如果要去掉三者之間任意一個checkUserExists,看上去是可能的。但是如果之后有某些功能要調(diào)用resetPassword或者sendEmail,用戶不存在時,系統(tǒng)可能會發(fā)生錯誤。
還有一個解決方法是,將resetPassword的邏輯寫到requestResetPassword里,再過一點,把sendEmail的邏輯也寫進(jìn)去。這樣函數(shù)調(diào)用減少,數(shù)據(jù)庫查詢也變成一次了,性能得到了提高。但是重置密碼和發(fā)送郵件的功能將不能得到復(fù)用,并且違背了單一責(zé)任的原則,代碼復(fù)雜度也提高了。
不過,因為函數(shù)分離和復(fù)用性都很好,如果實際性能受到影響,可能考慮用緩存的方法減少數(shù)據(jù)庫查詢,我改動了它們共用的checkUserExists函數(shù):
- /**
- * 檢查某個用戶是否存在
- */
- function checkUserExists( $userid ) {
- //增加一個緩存,用以記錄檢查用戶的結(jié)果
- static $cache = array();
- //檢查當(dāng)前用戶是否已經(jīng)檢查過一次
- if( isset( $cache[ $userid ] ) ) {
- return $cache[ $userid ];
- }
- $user = getUserInfo( $userid );
- //把結(jié)果記錄到緩存中
- $cache[ $userid ] = !emptyempty( $user );
- return $cache[ $userid ];
- }
也可以用同樣的方法改動getUserInfo函數(shù)。
這里可以看到,當(dāng)代碼的復(fù)用性提高時,想提高性能是很簡單的,性能的瓶頸也很容易被發(fā)現(xiàn)和修改。
盡管這個例子對性能影響還不夠大,還有一些影響更大的,比如說遍歷,我可能為了復(fù)用而將遍歷封裝到一個函數(shù)中,并且多次使用它。這些開銷對我的項目根本沒有預(yù)想中那樣有太大的影響,或者說是微乎其微的。所以我更愿意把時間花在如何提高代碼的復(fù)用性和維護(hù)性方面,而不是糾結(jié)于浪費(fèi)多這一點性能。實際性能如果真的達(dá)不到要求,也可以權(quán)衡增加硬件配置。
#p#
名字長一點好
函數(shù)名和變量名等除了給機(jī)器看,也要給人看的,有時一個簡單直接的好名字實在是很難想,這時不妨用長一點的名字更好??勺x性更好:
- //好名字
- class ErasedTypeEquivalence {
- }
- //壞名字
- class ErdTypeEqe {
- }
- //好名字
- function checkUserExists () {
- }
- //壞名字
- function ckUserExt() {
- }
- //好名字
- $result;
- //壞名字
- $ret;
我見過一些代碼,由于簡單寫過多,整遍代碼很多都是4個字母或以下的,可讀性非常差,當(dāng)然不排除是為了偷懶。
但如果想有更多的時間騰出來偷懶,不應(yīng)該在這上面玩小聰明,否則這時我現(xiàn)在應(yīng)該在思考前幾天的代碼是在寫什么。
什么?短名字會讓代碼執(zhí)行得更快? 那證明給我看,如果真的快,快了多少?
自說明代碼很重要,但注釋同樣重要
代碼本身可以說明問題的確是很棒的,但并不是說注釋不重要,有時候我更喜歡先看注釋,因為它總比我看代碼更快的了解這程序是做什么的。
如果我把本文前面說性能的例子去掉注釋,哪個能讓你更快了解代碼的意圖?或者說,你更愿意看哪個?
- function requestResetPassword() {
- if( !checkUserExists( $_GET['userid'] ) ) {
- exit('抱歉,用戶不存在,請確認(rèn)用戶帳號。');
- }
- resetPassword( $_GET['userid'] );
- sendEmail( $_GET['userid'], '重置密碼成功', '新的密碼是xxxx' );
- exit('新密碼已經(jīng)發(fā)送到你的郵箱。');
- }
- function resetPassword( $userid ) {
- if( !checkUserExists( $userid ) ) {
- return false;
- }
- //進(jìn)行重置用戶密碼的操作
- //略...
- return true;
- }
- function sendEmail( $userid, $title, $content ) {
- if( !checkUserExists( $userid ) ) {
- return false;
- }
- //發(fā)送郵件操作
- //略...
- return true;
- }
- function checkUserExists( $userid ) {
- static $cache = array();
- if( isset( $cache[ $userid ] ) ) {
- return $cache[ $userid ];
- }
- $user = getUserInfo( $userid );
- $cache[ $userid ] = !emptyempty( $user );
- return $cache[ $userid ];
- }
- function getUserInfo( $userid ) {
- $user = query( "SELECT * FROM `user` WHERE `uid`=" . intval( $userid ) );
- return is_array( $user ) ? $user : array() ;
- }
所以,即使代碼本身很清晰,但是加上注釋的話,可讀性也能提高很多!
適當(dāng)抽象
編程就是為了解決實際中的問題,在思考如何編碼的時候,把問題抽象到一定的高度去思考,更容易把握問題所在。不過更多時候,我發(fā)現(xiàn)從代碼抽象到現(xiàn)實的例子是有一定難度的,同時我也相信,編程高手也是抽象高手,他們很容易把問題反映到真實生活中去。
不過如果經(jīng)常留意和思考生活中的細(xì)節(jié),會提升自己的抽象能力。
舉一個螺絲刀的例子,如果叫你造一個螺絲刀,你會做成什么樣子?我這里有三把不同的螺絲刀:
顯然第一種螺絲刀是最簡單的,比較中規(guī)中矩。
第二種螺絲刀中間可以旋轉(zhuǎn)刀柄,讓刀柄和刀頭成90度,這樣的設(shè)計讓擰螺絲更加輕松。
第三種螺絲刀則可以更換刀頭,如果以后有其它類型的螺絲,則只要造一個適合這種螺絲的刀頭就可以了。
那反映到編程中的問題,如果項目要增加一個工具類庫。
第一種方法,可以直接把類庫的所需功能寫出來就可以了。
第二種方法,不但把類庫寫出來,而且針對項目的一些情況做特殊改進(jìn),使得在這個項目中更好用。
第三種方法,根據(jù)類庫的特性,把公共部分的邏輯做成接口,特殊的部分分離出來單獨實現(xiàn),如果以后要增加相同類型的類庫,則實現(xiàn)特殊部分的邏輯,然后接入接口即可。
但是在抽象的時候,要避免不合理的抽象,有時也可能造成過渡設(shè)計,現(xiàn)在只需要一種螺絲刀,但你卻把更多類型的螺絲刀都做出來了(而且還是瑞士軍刀的樣子。。):
#p#
一致性
團(tuán)隊開發(fā)中,可能每個人的編程風(fēng)格都不一樣,拿花括號來說,有些人喜歡和代碼在同一行,而有些喜歡獨自一行
- //例一
- function func() {
- }
- //例二
- function func()
- {
- }
命名風(fēng)格也都不一樣,比如說聲明變量接收一個函數(shù)返回的數(shù)據(jù),有些喜歡用result,有些喜歡用data。
它們可能都很好,不過在團(tuán)隊開發(fā)中,盡量統(tǒng)一用同一種風(fēng)格能夠很好的減少交叉開發(fā)的成本。
將錯就錯
面對項目一些無關(guān)緊要的分歧或錯誤,應(yīng)該要接受和理解。承接上面的問題,如果團(tuán)隊中已經(jīng)有人大量用了data的變量命名,但你認(rèn)為result的更符合當(dāng)前狀況的描述。這種情況,我優(yōu)先選擇data命名,因為如果再使用result的話,會破壞項目的一致性,對開發(fā)沒有任何好處。
這只是很少的一方面,如果項目規(guī)范沒有很好的落實,實際工作中會有大量的一致性問題,必須靠團(tuán)隊每個人的決心和責(zé)任心去把它做好。通常,加入一個正在開發(fā)中的項目,編寫功能前,我都會首先看項目之前的類似的代碼,并盡量模仿他們的寫法。不過,如果有明顯的錯誤,應(yīng)該及時指出和修正。
只要堅持把一致性做好,很多方法會成為團(tuán)隊甚至業(yè)界的標(biāo)準(zhǔn),即使它們不是最好的,但是有什么關(guān)系呢?
適當(dāng)休息
編程的時候如果沒有思路或者感到混亂,到外邊休息10分鐘,或者看一下風(fēng)景,讓腦袋清醒一下是很好的。這招很管用,親測。
至少把代碼完整運(yùn)行一次
有時函數(shù)的邏輯過于簡單,以至于會認(rèn)為這個不可能發(fā)生錯誤,但事實上最容易發(fā)生錯誤的通常就是這些代碼,常見的單詞拼寫錯誤,參數(shù)錯誤,還有一些意料之外的問題。所以無論什么情況,我都會把代碼完整運(yùn)行一遍。
當(dāng)然更好的做法是用一些系統(tǒng)的測試方法,比如說單元測試。
編程不是藝術(shù)
從一開始,編程語言的出現(xiàn)和發(fā)展,都是為了解決現(xiàn)實生活中的問題,包括它自身產(chǎn)生的問題。
面向?qū)ο?、設(shè)計模式的出現(xiàn),是用來解決編程語言自身帶來的可讀性和維護(hù)性等問題,而不是為了讓編程語言上升到藝術(shù)的層面。盡管編程中有‘優(yōu)雅’一詞,但我更認(rèn)為它只是用來形容代碼更容易讓人讀懂和維護(hù)。
我拒絕一切看起來很‘優(yōu)雅’,卻不能為編程工作帶來一點好處的代碼。如果你喜歡玩弄語言,應(yīng)該去當(dāng)作家。
甘于平凡
程序員真的很高傲,在我接觸過的人中,包括我自己也是。我以前經(jīng)常對一些簡單的代碼感到不屑,而總想在項目中寫一些犀利的代碼,讓人看起來很NB,但結(jié)果總是和想象差太遠(yuǎn),代碼總是寫的很差,邏輯也不夠清晰。歸根到底,是我?guī)е@樣的思想去寫代碼,而忽略了編程的根本:解決問題?,F(xiàn)在我改掉了這個壞毛病,以解決問題為目的去編程,以簡單為主。出乎意料的是別人有時會對我說,這里的代碼寫得很棒。
踏實的做事,會有意想不到的收獲。
承認(rèn)錯誤
不要懷疑,當(dāng)別人用自己的程序或者代碼無法運(yùn)行時,首先考慮是否是自己的邏輯哪里有問題。一來別人會覺得我謙虛,二來實際大多數(shù)情況的確是自己的問題。
有原則,有決心
做任何事情都堅持原則,并有決心是最好的。有很多道理我們都明白,但經(jīng)常做不到,沒有任何人能幫到自己,未來也是自己爭取的。
所以,如果知道什么是好,就盡量去做,什么是不好,就盡量避免。
即使是在公司面對經(jīng)理和領(lǐng)導(dǎo),也要堅持自己的做法,一些不合理的需求應(yīng)該指出或拒絕。我還年輕,大不了換一家公司,而不愿意做一個受欺壓的碼農(nóng)。
我在做什么
文章寫完了,現(xiàn)在來回想一下,我是在分享自己現(xiàn)在編程的一些習(xí)慣,總算沒偏離開始的主題。本文的思想都是來自實際工作和一些書籍,想了解更多的話,推薦閱讀《整潔代碼之道》《代碼大全》《重構(gòu)》這幾本書。
如果你有一些認(rèn)為好的編程方法,不妨拿出來和大家分享一下。