十八年開發經驗分享(04)問題解決篇(下)
阿新 • • 發佈:2019-01-07
關於本系列文字的來源,初衷和內容定位可以參考第一篇的開頭部分,連結地址如下:
http://blog.csdn.net/binarytreeex/article/details/8174445
http://www.cnblogs.com/WideUnion/archive/2012/11/12/2766397.html
本文前一篇地址如下,感興趣的可以訪問下面的連線:
http://blog.csdn.net/binarytreeex/article/details/8625156
http://www.cnblogs.com/WideUnion/archive/2013/03/01/2938315.html
在實際工作中軟體工程師大部分情況下都是在攻城拔寨,解決一個個自己遇到的問題。這些問題往往是在自己的知識和經驗可以覆蓋的範圍內,所以基本上可以說是順利的。但是也會有另一些情況的存在,那就是遇到一個自己不會的問題。這些問題具有這樣一個外在的現象:問題涉及的內容是在職責或者當前開發任務範圍內的,但是對如何實現或者解決這個問題沒有思路和辦法,簡單的說就是第一反應之下是不知道該怎麼做來解決這個問題。為了行文方便在下文中,我把所謂不會的問題命名為“難題”,所以在本文中難題這個說法特指程式設計師不會解決的問題,而不是其它的含義。
一般來說,我們總是力求用簡單的方法來解決問題,所以一旦我們遇到一個自己不會解決的問題時,往往表示我們的處境已經不是很妙了,或者說我們的處境有點困難了。本文就是主要分享我個人在應對這些局面時的一些體會。希望幫助軟體工程師們能更好的來處理這樣的難局和困境。當然這裡以難題必須被解決為前提來討論問題,對於如何規避難題和是否需要解決難題就不討論了。在這裡還要對所謂的不會的問題做一個假設的前提。本文假設如果這個問題能被解決,那麼我們缺少的不是知識,也不是經驗。否則的話我們的話題會聚焦在學習和經驗的積累方面,而不是直接討論如何去解決一個自己不會的問題。當然我還是同樣的再次強調,本文介紹的內容來自個人的實踐,對於解決問題這樣一個巨集大的話題和全體開發者這樣一個寬泛的群體來說,侷限性和片面性是在所難免的。所以請同行們自行取捨,同時也要根據自己的經驗,實際應用場合做出適當的變化,這樣才能更好的應用本文介紹的內容。如果分享的內容可以為同行們解決實際工作中的問題起到積極作用的話,那麼我的目的就達到了。當然如果能夠達到庖丁解牛那樣遊刃有餘的境界那是最好的。
一.難題是什麼樣的問題
這主要是和一般問題相比而言的,先從來源上來說。一般來講我們不應該遇到難題,無論從風險還是進度來說,與專案有關的每一個人都應該在力求避免這種情況的出現。但是在某些情況下,難題還是會來的。其中原因是難題的客觀存在性。工程師嘗試解決實際的問題,而這些問題的難度並不以工程師的主觀意志決定的,而是問題本身決定的。另一個原因可能專案本身的要求,比如為了有競爭優勢等等,會在實現上提出一些有難度的要求。
從難度上講,這類問題對於工程師而言在第一反應之下是不會的,工程師所具有的知識經驗並不能直接告訴工程師能解決問題。這就是所謂的難。所以在嘗試解決問題時問題解決篇(中)裡面提到的行不行,在這裡就格外重要了。如果一個難題是不能解決的,那就沒有必要再去花時間了。
從方法上講,難題的解決會多出一個步驟,那就是分析。這個分析是對問題本身的認識和理解,這是能夠解決問題的先決條件。當然這個理解不僅僅只是審題層面的理解,而是問題本身結構上的認識。為了達到這樣的認識往往要求工程師在動手解決問題前需要有一定量的實踐活動,用於認識問題。
從策略上講,解決難題時需要明確自己的戰略,因為難題往往是一個產品或者專案的成敗關鍵。所以往往也是資源投入比較多的地方,如果沒有好的戰略來應對問題,同時從全域性把握的話,那麼難題引入的風險有可能會失控,這有可能會帶來很嚴重的後果。所以需要有一個恰當的戰略。
二.處理難題的戰略
在準備或者開始著手解決難題時,要求工程師對於解決問題的戰略有一個清楚的認識,並且在解決問題的過程中嚴格恪守這個戰略。這樣做的目的是為了減少失敗的損失。難題的解決往往需要時間,更有可能會增加對其它資源的消耗,那麼萬一解決難題失敗,我們改如何應對?不成功則成仁的作法是不可取的,也不是一個聰明的選擇。所以在遇到難題時,工程師就要確保自己處在進退都有餘地的狀態,這就是處理難題的戰略。
處理難題的戰略的第一條是儘量避免自己面對難題。這個意思當然不是說遇到難題時推卸掉或者踢皮球,而是說在構架,分析設計,演算法,思路等等每個步驟和細節上都要注意採取簡單化的策略,避免出現難題的情況。這個策略是有點矛盾的,但是和特警解決危機事件的策略一樣。一方面要求特警的槍法儘可能的好,另一方面在臨場處理事件時則採取儘量不開槍的策略。當然如果一定要開槍的話,那肯定要求一槍解決問題。不要為了顯示個人的才能或者為了出風頭而有事沒事的就去挑戰難題,這是非常不明智的作法,早晚會掛的很難看的。
第二個策略是時間管理。事實上解決任何問題都是需要花費時間的,只是簡單的問題所花費的時間完全在可接受的範圍之內,所以時間這個最重要最基本的限制條件往往不被我們意識到。但是解決一個難題所需要花費的時間是多少,往往是不確定的,當時間超出甚至遠遠超出我們預計時,就會產生很大的麻煩。所以團隊中的leader最好能夠預先確定可能出現的難題,並預留足夠的時間來應對這個情況。這當然是消極的一面,但是也有積極的一面。因為我們也可以假設預留時間以後,難題是可以解決的,很多時候這個假設也是合理的。那麼這樣再安排開發計劃時就可以方便一點了。
第三個策略是風險管理。這個意思就非常明確了,只需要考慮一點,那就是一旦解決難題失敗,那麼我們是否可以承受一切的後果。這是風險管理的底線,如果這點都做不到,那麼在決定解決難題時就要非常非常慎重了,我的看法是最好就放棄吧。另一個一般尺度的作法是,如果難題解決失敗,那麼應該有一個候選方案可以選擇。這樣我們就能進能退了,會比較主動一點,心裡也會踏實一點,這是一般分寸的作法。當然實際情況會更為複雜一些,往往是問題難度估計不對,或者有些難題沒有預估到,等等。所以最終被遇到難題的局面是有可能出現的,尤其是處在一個新產品的研發或者做原型階段時。這時候就要看內力和運氣了。強調一下,至少不要在戰術層面上肯定自己能夠解決難題,永遠不要這樣。
三.處理難題的方法以及解決難題的基礎條件
當面對一個會解決的問題時,我們一般就直接動手去解決了,即便是需要思考也是在“自己會的”這個前提下作思考。當然這是一種思考,但是面對一個難題時我們需要另一種思考,這種思考就是對問題本身的認識。我們需要通過一定量的反覆的實踐(或者說嘗試)和思考,才能逐步的認識清楚我們面對的是一個什麼樣的問題,問題究竟是什麼樣子的。從而讓我們對問題本質有一個清楚的認識,進而可以使用我們的知識來描述問題,或者看清楚問題的結構,知道一個難題是由什麼和多少簡單問題組成的。我把這個過程稱之為分析。然後直接使用我們的知識或者依次解決每一個簡單問題來解決這個難題。與問題解決篇(中)裡面討論的內容相比,這是一個重要的新的步驟。一旦分析的步驟完成,那麼後面的工作就可以按照處理普通問題那樣來做了。
上一篇中提到了兩個解決問題的基礎條件,這裡需要再說一個,那就是智商或者說你有多聰明。我覺得智商是這個三個基礎條件中是最重要的。當然這聽上去不是一個什麼好訊息,因為智商貌似是老天給的。但是事實上不是這樣的,後天的努力可以在很大程度上改善先天的不足。
上一篇中提到的知識在解決難題時當然還是毫無疑問的有用。於上一篇中使用的場景相比,在解決一個難題時如果只是簡單重複使用自己的知識恐怕是不充分的。工程師們需要用更具有創造性的思維方式來使用自己的知識。在實踐活動中還發現一些簡單的知識,甚至是很基礎的知識在解決問題時往往能夠發揮更好的作用,扮演著主力的角色。
四.思考方法
介紹幾個思考問題的方法,這些內容在我看來是非常重要的,也是非常有實用價值的。
1.數學家的思考方法
先說一個數學家思考問題的方法。這個例子是很多年以前我在電視中看到的,當時沒覺得怎麼樣,但是隨著年齡的增加和處理的問題難度的加大,越來越開始體會到它的作用了。電視中在介紹數學家如何思考問題時舉了下面這個燒水的例子。
假設我們能做一件事情,這件事情是將一個裝滿水的水壺放到爐子上,然後把這壺水燒開。所以當有人給你一個裝滿水的水壺,並要求你燒開水時,你是直接能夠完成。好,現在假設有人把一個空的水壺拿給你,並且還是要求你燒開一壺水,那麼你怎麼解決這個問題?生活中處理這個問題是不用想的,直接把水灌到水壺中,然後放到爐子上燒就是了。但是我們用數學家的思維方式來解決這個問題時卻需要費一點周折。數學家是按照下面步驟來思考的:
第一步,首先是意識到問題的差異,我們會處理的是裝滿水的壺,現在是空的水壺。放到實際工作中,這一步就是要求我們有足夠的觀察力,辨識能力和一種敏感的職業嗅覺來發現問題的關鍵點和解決問題的正確方向。這個和之前博文中提到的對比法是雷同的,只是使用的場合不同,當然難度也不同。實際上這也是一個分析問題的步驟,但是這個步驟只是一個簡單的比較。通過這個比較讓我們發現了差異,這個差異告訴我們難題和我們會解決的問題之間的聯絡。
第二步,然後考慮有沒有辦法把空的水壺處理成裝滿水的水壺。如果可以那麼這個問題就解決了。數學家的思路的亮點是在這一步,這是一個開啟解決問題之門的思考問題的技巧,讓我們看到了從不會到會的希望。很多情況下這個時候,我們的想法是換方法,或者是簡單就做出判斷我不會,而不是想辦法去創造或者說嘗試創造我們解決問題所缺少的條件。這點上數學家的思維方式提供了一個重要的啟示。
第三步,發現將空水壺處理成裝滿水的水壺方法之一(你也可以花錢讓人替你做)是將水灌到空水壺中。注意這是一個發現,也是向解決問題邁出了一步,就這個例子而言還是關鍵的一步。所以這個發現非常重要。在實際工作的表現就是看你的知識,經驗和獲取的資訊是否足夠讓我們來找到這個做法,同時對你如何使用你的知識也提出了要求。上面兩個步驟是遇到問題和分析問題,這一步則是在分析的基礎上發現解決問題的方法。
第四步,現在需要考察將水灌到空水壺中這件事情我們會不會做。在實際工作中的表現是在對一個問題思考分析後,對其中子問題(或者子步驟)的又一次思考。顯然實際的情況是我們會做的,至少絕大部分能用手敲程式碼的程式設計師是會做的。好了,思考到這一步我們就可以知道這個問題是可以解決的。這裡第二步是關鍵,第三,四步是主體。
這個例子或者說思考問題的方法,非常精彩的演示瞭如何使用已有的知識來解決一個自己已有知識沒有覆蓋的問題。當然這樣的例子有很多,限於筆墨,就不在說了。
2.牛頓求解曲邊梯形面積的方法
牛頓在他的傳世名著數學原理中使用我們中學裡的知識(現在的中學已經講極限和導數了,這個不算,我是說我那個時候)定義並證明極限和導數,進而給出了曲邊梯形面積的計算方法。這是我目前為止看到的用自己會的簡單知識,解決難題的最終極的例子了。各位工科科班出來的都是學過高數的,建議看一下數學原理中那部分的推演和證明,就知道牛頓為什麼是牛頓了。當然我想強調的是,這個例子告訴我們還不止是這些。
將數學原理中的證明過程,當然還包括遣詞造句的論述部分,和我們學過的高等數學教科書中的證明做一個比較,我們會發現數學原理中牛頓的證明很簡單,如果可以的話我想說是簡陋。如果我用這樣的描述來證明一個題目的話,那麼我的高數老師是不會允許我過關的。教科書中極限的概念是用ε-δ語言來描述的,而牛頓基本上使用的是自然語言,甚至連符號都沒有(這裡申明一下翻譯的水平問題不考慮在內),相比較而言牛頓的證明不算什麼。這個事實給我們的啟示在於,當我們在解決難題時,不一定就可以很快發現一個滿意的解,而往往只是一個看上去有可能是解的解。這個時候我們還是要堅持下去的,因為有一個解相對於沒有解你已經是一個很大的進步了,畢竟離目標近了一步。如果當年牛頓一定要等到用ε-δ語言描述極限概念時才來完成微積分的建立,那麼工業革命有可能就會推遲一百年了。
3.大爆炸理論的線索
天文學家觀察到一個現象,那就是所有的星體相互之間的距離越來越遠。基於這個事實我們可以得出什麼結論?科學們就此提出了宇宙大爆炸理論,就是說宇宙從一個非常小的所謂奇點開始爆炸,從而產生了現在的宇宙。那麼科學家是如何從這個簡單的事實推出大爆炸理論的呢?其推演過程簡單的令人吃驚。如果現在此時此刻所有的星體正在彼此遠離,那麼過去的某一個時刻,所有的星體的位置就比現在的位置要近,這個結論很顯然是對的。那麼過去的過去的某一個時刻,所有星體的位置就比過去的某一個時刻的距離會更近一些,這個顯然也正確。好了,照這個思路推理下去,星體的距離就會越來越近,最後只能在一起。於是大爆炸理論(當然剛開始應該只是一個假說)就這麼誕生了。從這個例子可以看出優秀或者巧妙的思維方式對解決問題會產生不可估量的作用。在大爆炸這個例子中根本沒有用到天文相關的任何專業知識,只是通過正確的推理,就得出了這個假說。希望開發者能夠體會一下並從中吸取營養。
上面的三個例子是給我留下印象並對我產生指導作用的例子,我相信類似的例子會有很多,大家可以找出適合自己的。數學家例子是最基礎也是最重要的,我覺的所有的方法都可以從這個例子中推演出來。牛頓的例子說明的如何創造性的使用知識,最後的例子是考驗我們的智商了,或者說觀察力了。
五.破解難題的方法
破解難題之難有兩個方法可供參考,第一個就是所謂的分析。這個分析通俗來講就是認識問題,當然認識的內容除了審題和正確理解外,我們更關心的是能不能用一個嚴格描述來表達難題的結構。如果一旦可以做到這一點,那麼解決問題的可能性就極大提高了。從分析的手段來說,大概只有一個那就是實踐,通過和事務的接觸和互動來考察問題的結構和性質,進而達到認識問題的目的。我有一次去面試,考官出了一個題,說是有三種圖形:矩形,圓和橢圓,要求如何設計類來表示這三種圖形。我當時沒有仔細考慮,第一反應就是按照幾何上的定義來設計類。對於矩形就是矩形類,屬性是一個左上角的座標,另外兩個是寬和高;圓類的屬性是兩個:圓心座標點和半徑;橢圓的定義當時我忘了,所以向考官說明想法就不寫了。如果這時讓我用UML或者OOD來解釋一下的,有可能我會說上一段。但是,實際使用的資料結構只有一種:矩形。這一點在以後接觸GDI+程式設計時得到了證明。這個例子告訴我們看出三種本質在幾何上完全不同的圖形的外在差異,而問題的本質只是一個矩形,這才是表述問題的正確結構。
在分析問題或者說在採取某些活動嘗試瞭解問題時,觀察力是一個重要的輔助能力。因為很多活動是通過人機互動完成的,那麼觀察計算機的輸出裝置(一般是顯示器)顯示的資訊是認識問題的極為重要的手段。任何細微的變化或者結果都有可能是破解的難題的重要線索。我曾經有過這樣一個例子,要讓控制元件的背景顯示指定的顏色,當時我還在用VC,於是我就用紅色做了一下嘗試。結果是控制元件的背景色沒有變化,當時就很被動了,糾結在繪製了紅色為什麼紅色不顯示,於是不知道該如何解決。結果在一次偶然的操作中,好像是視窗的最小化以後再最大化,結果發現控制元件的背景有一次閃動,重複操作一次再觀察發現是紅色閃了一下。這個現象說明,紅色的背景被畫上去了,但是又被控制元件當前的背景色覆蓋了。於是破解問題的線索是如何不讓控制元件的背景色覆蓋我繪製的顏色。從這個線索出發最終還是解決了問題。
另外還需要強調一下,在採取的認識問題的活動,我認為應該是多樣的並富有變化的,可以儘可能多的讓自己瞭解問題。有些時候我們採取了一些行動,但是往往收穫不大,這個時候就需要思考了。不能簡單的重複活動,而是要採取不同的活動來認識問題,關鍵點是變化。舉一個簡單例子,如果我們實現一個檔案複製的功能,結果複製失敗。這裡先忽略複製函式的返回值或者丟擲異常的資訊這些內容,直接問自己如何採取活動來了解不成功的原因進而幫助解決問題?如果是我的話,我採取的活動可能會是這些:
1.不寫程式,新建一個文字檔案,然後直接複製該檔案
2.不寫程式,直接複製程式中需要複製的檔案
3.在程式中以直接執行命令的方式複製檔案
4.不讀取檔案內容,而是將一個固定的字串寫入到目標檔案
5.在目標位置新建檔案是否可以
6.直接在目標位置新建一個同名檔案,並且不讀取檔案直接向新檔案寫入需要複製的內容
7.讀取檔案內容,寫入到一個已經複製成功過的檔案中
8.讀取檔案內容,但是隻寫入成功複製檔案的那部分內容
我估計這些活動基本上可以找出問題的原因了。發現不同的嘗試途徑和內容,有時候不難,有時候卻相當有難,我建議大家要學會思考。當遇到這種場景可以考慮將雙手離開鍵盤,人站起來離開電腦,到外面散散步,在散步中仔細考慮應該採取的嘗試方法。
分析問題的另一個有力工具是排列組合,簡單來說就是中學裡學的加法定理和乘法定理,更完整的說法就是組合數學中的那些計數方法,定理及其性質。排列組合實際上只有兩個核心內容:計數和列舉。如果能夠對問題做到計數,那麼我們就完成了對問題在結構上的認識;如果對計數結果能夠完成列舉,那麼列舉的方法和過程也就是解決問題的方法和過程。所以再次強調一下,書本上的知識是有用的,並且是最有用的。
難題破解的第二個方法是簡化。假設有一個問題A我們解決不了那麼就可以考慮先忽略一些問題中的要求,使得問題的難度降低,然後再嘗試去解決這個簡化後的問題。如果還是不能解決,那麼可以考慮再次簡化,或者可以考慮不斷的簡化。這個做法和迭代開發很類似。這裡舉一個WideUnion團隊實際遇到的問題。Entity Model Studio支援圖形化的UML建模,那麼當初設計了一個功能,在生成程式碼前對使用者設計的UML模型做語法檢查,其中的一項就是不允許出現關係的環。比如繼承關係的環:A類繼承自B類,B類繼承自C類,而C類又繼承自A類。顯然這個語言檢查需要查出模型中所有的這樣的關係環。更為規範的描述是:找出一個有向圖中的所有的環。
在我學過的知識中,教課書只告訴我如何判斷一個圖中是否存在環而不是找出所有的環,所以這是超出我的知識範圍的,第一反應當然是不會,真心不會。採取的第一個步驟是構造若干個構成環的例項,看著圖讓自己有一個感性的認識。然後簡化問題,這裡有兩個策略可以走,第一個是假設是有一個環;第二個是有環並且是直接構成環,就是兩個節點直接存在指向對方的邊。我採取的是第一個策略。然後完善演算法,使得演算法可以完成這個功能:只要圖中有一個環,那麼一定能找出來。這樣就進入到解決問題的第二個步驟,查詢多個環,這一步實際上是分為兩個小步驟來完成的。第一個小步驟是,限制構成不同的環只能出現不同的邊,不能出現不同的節點。換句話說,新的環是有相同的節點之間的不同有向邊構成的。然後再次完善演算法,保證圖中只要有這樣的多個環一定都能找出來。完成這幾步後,就可以邁向最後一步了。任何一個環都是由節點和邊構成的,上述的步驟實現了相同節點中不同邊構成的所有的環,這個演算法簡稱為A的話,那麼下面需要做的就是改變節點再次執行演算法A就可以了。於是一個原來不會解的問題通過逐步簡化就找到一個解了。最近在看書時偶然發現尋找哈密爾頓環的演算法應該也是可以參考的。
有時候解決難題往往表現為缺少條件或者有些條件不明確。這個時候,有兩個簡單而實用的辦法可以使用。第一個是假設,這方法在做邏輯判斷題時經常使用,我們可以假設條件A成立,或者假設A的內容是XX,從而構造出一個明確的已知條件來幫助解決問題。另一個方法是非常熟悉的反證法。反證法的本質也是假設,只不過假設的是結論成立。一旦假設結論成立,那麼結論成立所以依賴的條件也必須成立,那麼成功構造出這些條件的方向往往就是解決問題的入口和起點。
六.例項分析
下面通過兩個有代表性或者能說明問題的例項來討論一下,如何使用上面提到的內容來解決難題。
我想先介紹一個偶然看到的電視節目。大概是3月中旬,央視的世界地理頻道播出了一期節目,其中一個橋段是說一個大夫如何給一個小男孩致傷的事情。事件的來龍去脈簡單的描述如下:
症狀:小男孩的背部和大腿上大概有好幾百根類似仙人球一樣的刺。
問題:如何把這些刺拔出來?
方法:1.直接用器械一根一根的拔。大夫的實踐證明效率極低,無法接受。
2.用膠帶紙粘掉刺。結果也不行,膠帶紙的粘性不夠,刺還是拔不出來。
3.使用女性用的脫毛用品和膠帶紙。成功,效果非常好。
這個故事和我們軟體工程師如何解決問題的相關性是什麼呢?簡單來說有以下幾點:
1.醫生遇到的問題嚴格上來說不是治病,而是一種對人體的修理。同樣的軟體工程師遇到的問題也不一定是嚴格意義上的技術問題,但是隻要你遇上了還是要你來解決的。不管會不會至少你要盡力去解決。
2.膠帶紙的使用。我相信那個大夫在學校裡讀書的時候,他的教材上絕不會把膠帶紙作為一個工具告訴學生去給病人治病的,但是在實際工作中他卻這麼做了。對軟體工程師來說,問題的攻克往往也不是用正統的書本知識來解決的。所謂的“旁門左道”也是需要的。
3.脫毛用品的使用。這顯然是一個亮點。如果說使用膠帶紙是脫離了書本知識,那麼脫毛用品的加入則是一個創造性思維的質變。這點對於程式設計師的借鑑意義在於,要求程式設計師們要有靈活和富有創造性的思維方式和能力。對於自己掌握的知識是否100%的用盡了,是不是創造性的使用了自己掌握的知識,而不是僅僅簡單的重複。
還可以再提出幾個問題,考察其中的若干細節,我們能發現這個過程中還有更多的內容可以給與我們啟示與借鑑。
1.第一個方法是最正統的方法,但是效率低,那麼什麼理由決定放棄?
從節目看效率低是直接原因。但是一般來說同一個問題應該有不同的方法可以解決。我在看到這裡時,第一反映是是否可以改進拔刺的動作,或者找另外的醫生一起來做。那麼與那個醫生的選擇就會不同,於是就有了不同的方法。工程師在解決實際問題時,就要根據自己所處的環境來決定方法的使用。
另外醫生嘗試後覺得效率慢,那麼是不是就一定很慢呢?我相信不同的醫生來做效率上是會有差異的。這個說明,解決問題是要發揮自己的技術特長,用長處來解決問題。
2.膠帶紙的使用
刺插入身體,刺與身體會形成一個夾角,這個夾角導致刺和身體表面不是平行的。那麼在用膠帶紙時就會有一定的麻煩。另外在撕下膠帶紙時,粘力和刺拔出的方向也不是平行的,顯然這個動作對男孩來說是會帶來痛苦的。這些細節告訴我們在方法的具體實施時,工程師自身的操作過程也很重要。操作技巧往往可以彌補方法的不足,也有可能會影響方法的效果。當我們決定是否放棄或者使用一個方法時,需要根據實際情況綜合考慮的。所以可以看出膠帶紙的使用表明那個醫生確實遇到了麻煩。
3.脫毛用品的使用
大夫是在和護士不經意的聊天中獲得了靈感,嘗試使用脫毛用品的。這告訴我們解決問題的思路往往不是來自主戰場,有可能來自生活中的點點滴滴的細節中。另外一個需要注意的是,一個創造性的方法的使用,需要考慮其負面的影響,這點非常重要,務必注意。比如,脫毛用品的使用不但沒有解決問題,反而帶來更大的麻煩,那麼這個醫生的處境會有多麼的被動和狼狽?!
第二個例子來自Entity Model Studio產品,這個問題是我們在開發時序圖時遇到的,先簡單交代一下問題的背景。在繪製時序圖時,使用者可以通過滑鼠拖動訊息,從而改變訊息和訊息生命線在時序圖中的位置,這是所有時序圖都應該支援的功能。以Visual Studio中的時序圖為例,當用戶拖動訊息後,直接相關的以及依次相鄰的圖形都會根據拖動的訊息的新位置做出調整,從而保證拖動後各個圖形還是在一個合理的位置,這個稱之為自動調整。這就是我們想實現的功能。下面依次討論幾個要點。
1.問題的提出
這個功能如果能得到實現的話,使用者操作會非常方便,否則使用者就必須自己手工調整每一個相關的圖形,很麻煩的,體驗非常差。所以出於這個目的就提出了實現該功能的要求
2.實現的風險和難度
在我們考察的幾款產品中,沒有實現該功能的產品也是有的。所以從戰略層面上來說,這個自動調整功能不是必須的。這樣我們的風險就小很多了,因為就算實現失敗,產品也是可行的,當然能實現是最好的。實現該功能的難度在於,這是我們第一次嘗試解決圖形位置自動調整功能,對相關的演算法和知識我們一無所知,也沒有相關的任何經驗,所以在最初我們不知道該如何去解決這個問題。但是最終決定去的原因,除了風險不大之外,我確實想或者說非常非常想實現一個微軟做到的功能,因為在歷史上我曾兩次挑戰失敗,所以這次想雪恥。
3.問題的分析
對這個問題的分析,是從使用Visual Studio的時序圖開始的,反覆的構造不同的圖,然後拖動訊息,考察其行為,從而找出規律,對這個問題有一個最初的感性認識。通過這個過程,認識到資料結構的設計是需要改動的。通過觀察這些行為表現,我們還意識到有可能需要使用到某些我們不瞭解的定理和演算法,這才是最可怕的。為了確認這個疑問,於是又做了進一步的分析。實踐表明可以有變通方法解決,這個情況和牛頓用中學知識定義極限是有點類似的。都是在用已知的簡單知識去描述一個未知的新的內容。這個過程中,觀察力和思考力起著非常重要的作用。
4.問題的簡化
問題簡化分兩步走,第一步嘗試最簡單的情況,然後考察實現的過程和結果,從反饋中獲得資訊,判斷我們的認識是否正確,難度是否可以接受。第二步是分析可能的存在的各種不同的結構,對每一種不同的結構依次處理,從而把問題肢解掉。這裡需要用到一些基礎的知識,比如計數方法之類的。
通過上面幾步,基本上就可以確定該問題從一個不會做的問題,變成了一個可以解決的問題。當然方法不是唯一的,我相信同行們會有更好的作法。
七.最後想說的話
總體來說解決難題是一項很具有難度和挑戰的事情。從使用的方法和思路上來說,我發現扮演主力角色的非常意外的是一些簡單的方法和知識,其難點是在於如何應用。醫生拔刺,數學家,牛頓和天文學家的思考方法,給我們的啟示是:解決問題的方法和思維方式是互通的,沒有行業和領域的限制,正所謂它山之石可以攻玉。我們要學會去點滴積累這樣的經驗,吸收營養,豐富知識,擴充套件思路從而提升我們分析問題並解決問題的能力。這種現象可以用這樣一句話來概括:功夫在題外。
解決難題的過程是一個痛並快樂著的過程,工程師們要有勇氣和膽識去面對這樣的挑戰。你會經受折磨但是也會有快樂的體驗。如果你愛一個就讓他去解決難題吧,因為那是天堂;如果你恨一個人那也讓他去解決難題吧,因為那是地獄。鑑於一些非常令人不愉快的行為,我這裡申明一下如果達內需要轉載或者修改本人寫的任何博文,那麼請先獲得本人的許可。
好了,這次就寫到這裡,如何解決問題的話題到這裡就算是告一個段落了,通過這次回顧我自己也有相當的體會,確實也收穫了一些東西。同行們有興趣進一步交流的可以加我的群:244054966,這個群定位是創業,新手就不要去了。另一個是:231233168,這個群沒什麼限制。入群時請加上訊息:CSDN部落格。下一篇談談能力相關的話題,名字暫定為:能力養成篇。
http://blog.csdn.net/binarytreeex/article/details/8174445
http://www.cnblogs.com/WideUnion/archive/2012/11/12/2766397.html
本文前一篇地址如下,感興趣的可以訪問下面的連線:
http://blog.csdn.net/binarytreeex/article/details/8625156
http://www.cnblogs.com/WideUnion/archive/2013/03/01/2938315.html
在實際工作中軟體工程師大部分情況下都是在攻城拔寨,解決一個個自己遇到的問題。這些問題往往是在自己的知識和經驗可以覆蓋的範圍內,所以基本上可以說是順利的。但是也會有另一些情況的存在,那就是遇到一個自己不會的問題。這些問題具有這樣一個外在的現象:問題涉及的內容是在職責或者當前開發任務範圍內的,但是對如何實現或者解決這個問題沒有思路和辦法,簡單的說就是第一反應之下是不知道該怎麼做來解決這個問題。為了行文方便在下文中,我把所謂不會的問題命名為“難題”,所以在本文中難題這個說法特指程式設計師不會解決的問題,而不是其它的含義。
一般來說,我們總是力求用簡單的方法來解決問題,所以一旦我們遇到一個自己不會解決的問題時,往往表示我們的處境已經不是很妙了,或者說我們的處境有點困難了。本文就是主要分享我個人在應對這些局面時的一些體會。希望幫助軟體工程師們能更好的來處理這樣的難局和困境。當然這裡以難題必須被解決為前提來討論問題,對於如何規避難題和是否需要解決難題就不討論了。在這裡還要對所謂的不會的問題做一個假設的前提。本文假設如果這個問題能被解決,那麼我們缺少的不是知識,也不是經驗。否則的話我們的話題會聚焦在學習和經驗的積累方面,而不是直接討論如何去解決一個自己不會的問題。當然我還是同樣的再次強調,本文介紹的內容來自個人的實踐,對於解決問題這樣一個巨集大的話題和全體開發者這樣一個寬泛的群體來說,侷限性和片面性是在所難免的。所以請同行們自行取捨,同時也要根據自己的經驗,實際應用場合做出適當的變化,這樣才能更好的應用本文介紹的內容。如果分享的內容可以為同行們解決實際工作中的問題起到積極作用的話,那麼我的目的就達到了。當然如果能夠達到庖丁解牛那樣遊刃有餘的境界那是最好的。
一.難題是什麼樣的問題
這主要是和一般問題相比而言的,先從來源上來說。一般來講我們不應該遇到難題,無論從風險還是進度來說,與專案有關的每一個人都應該在力求避免這種情況的出現。但是在某些情況下,難題還是會來的。其中原因是難題的客觀存在性。工程師嘗試解決實際的問題,而這些問題的難度並不以工程師的主觀意志決定的,而是問題本身決定的。另一個原因可能專案本身的要求,比如為了有競爭優勢等等,會在實現上提出一些有難度的要求。
從難度上講,這類問題對於工程師而言在第一反應之下是不會的,工程師所具有的知識經驗並不能直接告訴工程師能解決問題。這就是所謂的難。所以在嘗試解決問題時問題解決篇(中)裡面提到的行不行,在這裡就格外重要了。如果一個難題是不能解決的,那就沒有必要再去花時間了。
從方法上講,難題的解決會多出一個步驟,那就是分析。這個分析是對問題本身的認識和理解,這是能夠解決問題的先決條件。當然這個理解不僅僅只是審題層面的理解,而是問題本身結構上的認識。為了達到這樣的認識往往要求工程師在動手解決問題前需要有一定量的實踐活動,用於認識問題。
從策略上講,解決難題時需要明確自己的戰略,因為難題往往是一個產品或者專案的成敗關鍵。所以往往也是資源投入比較多的地方,如果沒有好的戰略來應對問題,同時從全域性把握的話,那麼難題引入的風險有可能會失控,這有可能會帶來很嚴重的後果。所以需要有一個恰當的戰略。
二.處理難題的戰略
在準備或者開始著手解決難題時,要求工程師對於解決問題的戰略有一個清楚的認識,並且在解決問題的過程中嚴格恪守這個戰略。這樣做的目的是為了減少失敗的損失。難題的解決往往需要時間,更有可能會增加對其它資源的消耗,那麼萬一解決難題失敗,我們改如何應對?不成功則成仁的作法是不可取的,也不是一個聰明的選擇。所以在遇到難題時,工程師就要確保自己處在進退都有餘地的狀態,這就是處理難題的戰略。
處理難題的戰略的第一條是儘量避免自己面對難題。這個意思當然不是說遇到難題時推卸掉或者踢皮球,而是說在構架,分析設計,演算法,思路等等每個步驟和細節上都要注意採取簡單化的策略,避免出現難題的情況。這個策略是有點矛盾的,但是和特警解決危機事件的策略一樣。一方面要求特警的槍法儘可能的好,另一方面在臨場處理事件時則採取儘量不開槍的策略。當然如果一定要開槍的話,那肯定要求一槍解決問題。不要為了顯示個人的才能或者為了出風頭而有事沒事的就去挑戰難題,這是非常不明智的作法,早晚會掛的很難看的。
第二個策略是時間管理。事實上解決任何問題都是需要花費時間的,只是簡單的問題所花費的時間完全在可接受的範圍之內,所以時間這個最重要最基本的限制條件往往不被我們意識到。但是解決一個難題所需要花費的時間是多少,往往是不確定的,當時間超出甚至遠遠超出我們預計時,就會產生很大的麻煩。所以團隊中的leader最好能夠預先確定可能出現的難題,並預留足夠的時間來應對這個情況。這當然是消極的一面,但是也有積極的一面。因為我們也可以假設預留時間以後,難題是可以解決的,很多時候這個假設也是合理的。那麼這樣再安排開發計劃時就可以方便一點了。
第三個策略是風險管理。這個意思就非常明確了,只需要考慮一點,那就是一旦解決難題失敗,那麼我們是否可以承受一切的後果。這是風險管理的底線,如果這點都做不到,那麼在決定解決難題時就要非常非常慎重了,我的看法是最好就放棄吧。另一個一般尺度的作法是,如果難題解決失敗,那麼應該有一個候選方案可以選擇。這樣我們就能進能退了,會比較主動一點,心裡也會踏實一點,這是一般分寸的作法。當然實際情況會更為複雜一些,往往是問題難度估計不對,或者有些難題沒有預估到,等等。所以最終被遇到難題的局面是有可能出現的,尤其是處在一個新產品的研發或者做原型階段時。這時候就要看內力和運氣了。強調一下,至少不要在戰術層面上肯定自己能夠解決難題,永遠不要這樣。
三.處理難題的方法以及解決難題的基礎條件
當面對一個會解決的問題時,我們一般就直接動手去解決了,即便是需要思考也是在“自己會的”這個前提下作思考。當然這是一種思考,但是面對一個難題時我們需要另一種思考,這種思考就是對問題本身的認識。我們需要通過一定量的反覆的實踐(或者說嘗試)和思考,才能逐步的認識清楚我們面對的是一個什麼樣的問題,問題究竟是什麼樣子的。從而讓我們對問題本質有一個清楚的認識,進而可以使用我們的知識來描述問題,或者看清楚問題的結構,知道一個難題是由什麼和多少簡單問題組成的。我把這個過程稱之為分析。然後直接使用我們的知識或者依次解決每一個簡單問題來解決這個難題。與問題解決篇(中)裡面討論的內容相比,這是一個重要的新的步驟。一旦分析的步驟完成,那麼後面的工作就可以按照處理普通問題那樣來做了。
上一篇中提到了兩個解決問題的基礎條件,這裡需要再說一個,那就是智商或者說你有多聰明。我覺得智商是這個三個基礎條件中是最重要的。當然這聽上去不是一個什麼好訊息,因為智商貌似是老天給的。但是事實上不是這樣的,後天的努力可以在很大程度上改善先天的不足。
上一篇中提到的知識在解決難題時當然還是毫無疑問的有用。於上一篇中使用的場景相比,在解決一個難題時如果只是簡單重複使用自己的知識恐怕是不充分的。工程師們需要用更具有創造性的思維方式來使用自己的知識。在實踐活動中還發現一些簡單的知識,甚至是很基礎的知識在解決問題時往往能夠發揮更好的作用,扮演著主力的角色。
四.思考方法
介紹幾個思考問題的方法,這些內容在我看來是非常重要的,也是非常有實用價值的。
1.數學家的思考方法
先說一個數學家思考問題的方法。這個例子是很多年以前我在電視中看到的,當時沒覺得怎麼樣,但是隨著年齡的增加和處理的問題難度的加大,越來越開始體會到它的作用了。電視中在介紹數學家如何思考問題時舉了下面這個燒水的例子。
假設我們能做一件事情,這件事情是將一個裝滿水的水壺放到爐子上,然後把這壺水燒開。所以當有人給你一個裝滿水的水壺,並要求你燒開水時,你是直接能夠完成。好,現在假設有人把一個空的水壺拿給你,並且還是要求你燒開一壺水,那麼你怎麼解決這個問題?生活中處理這個問題是不用想的,直接把水灌到水壺中,然後放到爐子上燒就是了。但是我們用數學家的思維方式來解決這個問題時卻需要費一點周折。數學家是按照下面步驟來思考的:
第一步,首先是意識到問題的差異,我們會處理的是裝滿水的壺,現在是空的水壺。放到實際工作中,這一步就是要求我們有足夠的觀察力,辨識能力和一種敏感的職業嗅覺來發現問題的關鍵點和解決問題的正確方向。這個和之前博文中提到的對比法是雷同的,只是使用的場合不同,當然難度也不同。實際上這也是一個分析問題的步驟,但是這個步驟只是一個簡單的比較。通過這個比較讓我們發現了差異,這個差異告訴我們難題和我們會解決的問題之間的聯絡。
第二步,然後考慮有沒有辦法把空的水壺處理成裝滿水的水壺。如果可以那麼這個問題就解決了。數學家的思路的亮點是在這一步,這是一個開啟解決問題之門的思考問題的技巧,讓我們看到了從不會到會的希望。很多情況下這個時候,我們的想法是換方法,或者是簡單就做出判斷我不會,而不是想辦法去創造或者說嘗試創造我們解決問題所缺少的條件。這點上數學家的思維方式提供了一個重要的啟示。
第三步,發現將空水壺處理成裝滿水的水壺方法之一(你也可以花錢讓人替你做)是將水灌到空水壺中。注意這是一個發現,也是向解決問題邁出了一步,就這個例子而言還是關鍵的一步。所以這個發現非常重要。在實際工作的表現就是看你的知識,經驗和獲取的資訊是否足夠讓我們來找到這個做法,同時對你如何使用你的知識也提出了要求。上面兩個步驟是遇到問題和分析問題,這一步則是在分析的基礎上發現解決問題的方法。
第四步,現在需要考察將水灌到空水壺中這件事情我們會不會做。在實際工作中的表現是在對一個問題思考分析後,對其中子問題(或者子步驟)的又一次思考。顯然實際的情況是我們會做的,至少絕大部分能用手敲程式碼的程式設計師是會做的。好了,思考到這一步我們就可以知道這個問題是可以解決的。這裡第二步是關鍵,第三,四步是主體。
這個例子或者說思考問題的方法,非常精彩的演示瞭如何使用已有的知識來解決一個自己已有知識沒有覆蓋的問題。當然這樣的例子有很多,限於筆墨,就不在說了。
2.牛頓求解曲邊梯形面積的方法
牛頓在他的傳世名著數學原理中使用我們中學裡的知識(現在的中學已經講極限和導數了,這個不算,我是說我那個時候)定義並證明極限和導數,進而給出了曲邊梯形面積的計算方法。這是我目前為止看到的用自己會的簡單知識,解決難題的最終極的例子了。各位工科科班出來的都是學過高數的,建議看一下數學原理中那部分的推演和證明,就知道牛頓為什麼是牛頓了。當然我想強調的是,這個例子告訴我們還不止是這些。
將數學原理中的證明過程,當然還包括遣詞造句的論述部分,和我們學過的高等數學教科書中的證明做一個比較,我們會發現數學原理中牛頓的證明很簡單,如果可以的話我想說是簡陋。如果我用這樣的描述來證明一個題目的話,那麼我的高數老師是不會允許我過關的。教科書中極限的概念是用ε-δ語言來描述的,而牛頓基本上使用的是自然語言,甚至連符號都沒有(這裡申明一下翻譯的水平問題不考慮在內),相比較而言牛頓的證明不算什麼。這個事實給我們的啟示在於,當我們在解決難題時,不一定就可以很快發現一個滿意的解,而往往只是一個看上去有可能是解的解。這個時候我們還是要堅持下去的,因為有一個解相對於沒有解你已經是一個很大的進步了,畢竟離目標近了一步。如果當年牛頓一定要等到用ε-δ語言描述極限概念時才來完成微積分的建立,那麼工業革命有可能就會推遲一百年了。
3.大爆炸理論的線索
天文學家觀察到一個現象,那就是所有的星體相互之間的距離越來越遠。基於這個事實我們可以得出什麼結論?科學們就此提出了宇宙大爆炸理論,就是說宇宙從一個非常小的所謂奇點開始爆炸,從而產生了現在的宇宙。那麼科學家是如何從這個簡單的事實推出大爆炸理論的呢?其推演過程簡單的令人吃驚。如果現在此時此刻所有的星體正在彼此遠離,那麼過去的某一個時刻,所有的星體的位置就比現在的位置要近,這個結論很顯然是對的。那麼過去的過去的某一個時刻,所有星體的位置就比過去的某一個時刻的距離會更近一些,這個顯然也正確。好了,照這個思路推理下去,星體的距離就會越來越近,最後只能在一起。於是大爆炸理論(當然剛開始應該只是一個假說)就這麼誕生了。從這個例子可以看出優秀或者巧妙的思維方式對解決問題會產生不可估量的作用。在大爆炸這個例子中根本沒有用到天文相關的任何專業知識,只是通過正確的推理,就得出了這個假說。希望開發者能夠體會一下並從中吸取營養。
上面的三個例子是給我留下印象並對我產生指導作用的例子,我相信類似的例子會有很多,大家可以找出適合自己的。數學家例子是最基礎也是最重要的,我覺的所有的方法都可以從這個例子中推演出來。牛頓的例子說明的如何創造性的使用知識,最後的例子是考驗我們的智商了,或者說觀察力了。
五.破解難題的方法
破解難題之難有兩個方法可供參考,第一個就是所謂的分析。這個分析通俗來講就是認識問題,當然認識的內容除了審題和正確理解外,我們更關心的是能不能用一個嚴格描述來表達難題的結構。如果一旦可以做到這一點,那麼解決問題的可能性就極大提高了。從分析的手段來說,大概只有一個那就是實踐,通過和事務的接觸和互動來考察問題的結構和性質,進而達到認識問題的目的。我有一次去面試,考官出了一個題,說是有三種圖形:矩形,圓和橢圓,要求如何設計類來表示這三種圖形。我當時沒有仔細考慮,第一反應就是按照幾何上的定義來設計類。對於矩形就是矩形類,屬性是一個左上角的座標,另外兩個是寬和高;圓類的屬性是兩個:圓心座標點和半徑;橢圓的定義當時我忘了,所以向考官說明想法就不寫了。如果這時讓我用UML或者OOD來解釋一下的,有可能我會說上一段。但是,實際使用的資料結構只有一種:矩形。這一點在以後接觸GDI+程式設計時得到了證明。這個例子告訴我們看出三種本質在幾何上完全不同的圖形的外在差異,而問題的本質只是一個矩形,這才是表述問題的正確結構。
在分析問題或者說在採取某些活動嘗試瞭解問題時,觀察力是一個重要的輔助能力。因為很多活動是通過人機互動完成的,那麼觀察計算機的輸出裝置(一般是顯示器)顯示的資訊是認識問題的極為重要的手段。任何細微的變化或者結果都有可能是破解的難題的重要線索。我曾經有過這樣一個例子,要讓控制元件的背景顯示指定的顏色,當時我還在用VC,於是我就用紅色做了一下嘗試。結果是控制元件的背景色沒有變化,當時就很被動了,糾結在繪製了紅色為什麼紅色不顯示,於是不知道該如何解決。結果在一次偶然的操作中,好像是視窗的最小化以後再最大化,結果發現控制元件的背景有一次閃動,重複操作一次再觀察發現是紅色閃了一下。這個現象說明,紅色的背景被畫上去了,但是又被控制元件當前的背景色覆蓋了。於是破解問題的線索是如何不讓控制元件的背景色覆蓋我繪製的顏色。從這個線索出發最終還是解決了問題。
另外還需要強調一下,在採取的認識問題的活動,我認為應該是多樣的並富有變化的,可以儘可能多的讓自己瞭解問題。有些時候我們採取了一些行動,但是往往收穫不大,這個時候就需要思考了。不能簡單的重複活動,而是要採取不同的活動來認識問題,關鍵點是變化。舉一個簡單例子,如果我們實現一個檔案複製的功能,結果複製失敗。這裡先忽略複製函式的返回值或者丟擲異常的資訊這些內容,直接問自己如何採取活動來了解不成功的原因進而幫助解決問題?如果是我的話,我採取的活動可能會是這些:
1.不寫程式,新建一個文字檔案,然後直接複製該檔案
2.不寫程式,直接複製程式中需要複製的檔案
3.在程式中以直接執行命令的方式複製檔案
4.不讀取檔案內容,而是將一個固定的字串寫入到目標檔案
5.在目標位置新建檔案是否可以
6.直接在目標位置新建一個同名檔案,並且不讀取檔案直接向新檔案寫入需要複製的內容
7.讀取檔案內容,寫入到一個已經複製成功過的檔案中
8.讀取檔案內容,但是隻寫入成功複製檔案的那部分內容
我估計這些活動基本上可以找出問題的原因了。發現不同的嘗試途徑和內容,有時候不難,有時候卻相當有難,我建議大家要學會思考。當遇到這種場景可以考慮將雙手離開鍵盤,人站起來離開電腦,到外面散散步,在散步中仔細考慮應該採取的嘗試方法。
分析問題的另一個有力工具是排列組合,簡單來說就是中學裡學的加法定理和乘法定理,更完整的說法就是組合數學中的那些計數方法,定理及其性質。排列組合實際上只有兩個核心內容:計數和列舉。如果能夠對問題做到計數,那麼我們就完成了對問題在結構上的認識;如果對計數結果能夠完成列舉,那麼列舉的方法和過程也就是解決問題的方法和過程。所以再次強調一下,書本上的知識是有用的,並且是最有用的。
難題破解的第二個方法是簡化。假設有一個問題A我們解決不了那麼就可以考慮先忽略一些問題中的要求,使得問題的難度降低,然後再嘗試去解決這個簡化後的問題。如果還是不能解決,那麼可以考慮再次簡化,或者可以考慮不斷的簡化。這個做法和迭代開發很類似。這裡舉一個WideUnion團隊實際遇到的問題。Entity Model Studio支援圖形化的UML建模,那麼當初設計了一個功能,在生成程式碼前對使用者設計的UML模型做語法檢查,其中的一項就是不允許出現關係的環。比如繼承關係的環:A類繼承自B類,B類繼承自C類,而C類又繼承自A類。顯然這個語言檢查需要查出模型中所有的這樣的關係環。更為規範的描述是:找出一個有向圖中的所有的環。
在我學過的知識中,教課書只告訴我如何判斷一個圖中是否存在環而不是找出所有的環,所以這是超出我的知識範圍的,第一反應當然是不會,真心不會。採取的第一個步驟是構造若干個構成環的例項,看著圖讓自己有一個感性的認識。然後簡化問題,這裡有兩個策略可以走,第一個是假設是有一個環;第二個是有環並且是直接構成環,就是兩個節點直接存在指向對方的邊。我採取的是第一個策略。然後完善演算法,使得演算法可以完成這個功能:只要圖中有一個環,那麼一定能找出來。這樣就進入到解決問題的第二個步驟,查詢多個環,這一步實際上是分為兩個小步驟來完成的。第一個小步驟是,限制構成不同的環只能出現不同的邊,不能出現不同的節點。換句話說,新的環是有相同的節點之間的不同有向邊構成的。然後再次完善演算法,保證圖中只要有這樣的多個環一定都能找出來。完成這幾步後,就可以邁向最後一步了。任何一個環都是由節點和邊構成的,上述的步驟實現了相同節點中不同邊構成的所有的環,這個演算法簡稱為A的話,那麼下面需要做的就是改變節點再次執行演算法A就可以了。於是一個原來不會解的問題通過逐步簡化就找到一個解了。最近在看書時偶然發現尋找哈密爾頓環的演算法應該也是可以參考的。
有時候解決難題往往表現為缺少條件或者有些條件不明確。這個時候,有兩個簡單而實用的辦法可以使用。第一個是假設,這方法在做邏輯判斷題時經常使用,我們可以假設條件A成立,或者假設A的內容是XX,從而構造出一個明確的已知條件來幫助解決問題。另一個方法是非常熟悉的反證法。反證法的本質也是假設,只不過假設的是結論成立。一旦假設結論成立,那麼結論成立所以依賴的條件也必須成立,那麼成功構造出這些條件的方向往往就是解決問題的入口和起點。
六.例項分析
下面通過兩個有代表性或者能說明問題的例項來討論一下,如何使用上面提到的內容來解決難題。
我想先介紹一個偶然看到的電視節目。大概是3月中旬,央視的世界地理頻道播出了一期節目,其中一個橋段是說一個大夫如何給一個小男孩致傷的事情。事件的來龍去脈簡單的描述如下:
症狀:小男孩的背部和大腿上大概有好幾百根類似仙人球一樣的刺。
問題:如何把這些刺拔出來?
方法:1.直接用器械一根一根的拔。大夫的實踐證明效率極低,無法接受。
2.用膠帶紙粘掉刺。結果也不行,膠帶紙的粘性不夠,刺還是拔不出來。
3.使用女性用的脫毛用品和膠帶紙。成功,效果非常好。
這個故事和我們軟體工程師如何解決問題的相關性是什麼呢?簡單來說有以下幾點:
1.醫生遇到的問題嚴格上來說不是治病,而是一種對人體的修理。同樣的軟體工程師遇到的問題也不一定是嚴格意義上的技術問題,但是隻要你遇上了還是要你來解決的。不管會不會至少你要盡力去解決。
2.膠帶紙的使用。我相信那個大夫在學校裡讀書的時候,他的教材上絕不會把膠帶紙作為一個工具告訴學生去給病人治病的,但是在實際工作中他卻這麼做了。對軟體工程師來說,問題的攻克往往也不是用正統的書本知識來解決的。所謂的“旁門左道”也是需要的。
3.脫毛用品的使用。這顯然是一個亮點。如果說使用膠帶紙是脫離了書本知識,那麼脫毛用品的加入則是一個創造性思維的質變。這點對於程式設計師的借鑑意義在於,要求程式設計師們要有靈活和富有創造性的思維方式和能力。對於自己掌握的知識是否100%的用盡了,是不是創造性的使用了自己掌握的知識,而不是僅僅簡單的重複。
還可以再提出幾個問題,考察其中的若干細節,我們能發現這個過程中還有更多的內容可以給與我們啟示與借鑑。
1.第一個方法是最正統的方法,但是效率低,那麼什麼理由決定放棄?
從節目看效率低是直接原因。但是一般來說同一個問題應該有不同的方法可以解決。我在看到這裡時,第一反映是是否可以改進拔刺的動作,或者找另外的醫生一起來做。那麼與那個醫生的選擇就會不同,於是就有了不同的方法。工程師在解決實際問題時,就要根據自己所處的環境來決定方法的使用。
另外醫生嘗試後覺得效率慢,那麼是不是就一定很慢呢?我相信不同的醫生來做效率上是會有差異的。這個說明,解決問題是要發揮自己的技術特長,用長處來解決問題。
2.膠帶紙的使用
刺插入身體,刺與身體會形成一個夾角,這個夾角導致刺和身體表面不是平行的。那麼在用膠帶紙時就會有一定的麻煩。另外在撕下膠帶紙時,粘力和刺拔出的方向也不是平行的,顯然這個動作對男孩來說是會帶來痛苦的。這些細節告訴我們在方法的具體實施時,工程師自身的操作過程也很重要。操作技巧往往可以彌補方法的不足,也有可能會影響方法的效果。當我們決定是否放棄或者使用一個方法時,需要根據實際情況綜合考慮的。所以可以看出膠帶紙的使用表明那個醫生確實遇到了麻煩。
3.脫毛用品的使用
大夫是在和護士不經意的聊天中獲得了靈感,嘗試使用脫毛用品的。這告訴我們解決問題的思路往往不是來自主戰場,有可能來自生活中的點點滴滴的細節中。另外一個需要注意的是,一個創造性的方法的使用,需要考慮其負面的影響,這點非常重要,務必注意。比如,脫毛用品的使用不但沒有解決問題,反而帶來更大的麻煩,那麼這個醫生的處境會有多麼的被動和狼狽?!
第二個例子來自Entity Model Studio產品,這個問題是我們在開發時序圖時遇到的,先簡單交代一下問題的背景。在繪製時序圖時,使用者可以通過滑鼠拖動訊息,從而改變訊息和訊息生命線在時序圖中的位置,這是所有時序圖都應該支援的功能。以Visual Studio中的時序圖為例,當用戶拖動訊息後,直接相關的以及依次相鄰的圖形都會根據拖動的訊息的新位置做出調整,從而保證拖動後各個圖形還是在一個合理的位置,這個稱之為自動調整。這就是我們想實現的功能。下面依次討論幾個要點。
1.問題的提出
這個功能如果能得到實現的話,使用者操作會非常方便,否則使用者就必須自己手工調整每一個相關的圖形,很麻煩的,體驗非常差。所以出於這個目的就提出了實現該功能的要求
2.實現的風險和難度
在我們考察的幾款產品中,沒有實現該功能的產品也是有的。所以從戰略層面上來說,這個自動調整功能不是必須的。這樣我們的風險就小很多了,因為就算實現失敗,產品也是可行的,當然能實現是最好的。實現該功能的難度在於,這是我們第一次嘗試解決圖形位置自動調整功能,對相關的演算法和知識我們一無所知,也沒有相關的任何經驗,所以在最初我們不知道該如何去解決這個問題。但是最終決定去的原因,除了風險不大之外,我確實想或者說非常非常想實現一個微軟做到的功能,因為在歷史上我曾兩次挑戰失敗,所以這次想雪恥。
3.問題的分析
對這個問題的分析,是從使用Visual Studio的時序圖開始的,反覆的構造不同的圖,然後拖動訊息,考察其行為,從而找出規律,對這個問題有一個最初的感性認識。通過這個過程,認識到資料結構的設計是需要改動的。通過觀察這些行為表現,我們還意識到有可能需要使用到某些我們不瞭解的定理和演算法,這才是最可怕的。為了確認這個疑問,於是又做了進一步的分析。實踐表明可以有變通方法解決,這個情況和牛頓用中學知識定義極限是有點類似的。都是在用已知的簡單知識去描述一個未知的新的內容。這個過程中,觀察力和思考力起著非常重要的作用。
4.問題的簡化
問題簡化分兩步走,第一步嘗試最簡單的情況,然後考察實現的過程和結果,從反饋中獲得資訊,判斷我們的認識是否正確,難度是否可以接受。第二步是分析可能的存在的各種不同的結構,對每一種不同的結構依次處理,從而把問題肢解掉。這裡需要用到一些基礎的知識,比如計數方法之類的。
通過上面幾步,基本上就可以確定該問題從一個不會做的問題,變成了一個可以解決的問題。當然方法不是唯一的,我相信同行們會有更好的作法。
七.最後想說的話
總體來說解決難題是一項很具有難度和挑戰的事情。從使用的方法和思路上來說,我發現扮演主力角色的非常意外的是一些簡單的方法和知識,其難點是在於如何應用。醫生拔刺,數學家,牛頓和天文學家的思考方法,給我們的啟示是:解決問題的方法和思維方式是互通的,沒有行業和領域的限制,正所謂它山之石可以攻玉。我們要學會去點滴積累這樣的經驗,吸收營養,豐富知識,擴充套件思路從而提升我們分析問題並解決問題的能力。這種現象可以用這樣一句話來概括:功夫在題外。
解決難題的過程是一個痛並快樂著的過程,工程師們要有勇氣和膽識去面對這樣的挑戰。你會經受折磨但是也會有快樂的體驗。如果你愛一個就讓他去解決難題吧,因為那是天堂;如果你恨一個人那也讓他去解決難題吧,因為那是地獄。鑑於一些非常令人不愉快的行為,我這裡申明一下如果達內需要轉載或者修改本人寫的任何博文,那麼請先獲得本人的許可。
好了,這次就寫到這裡,如何解決問題的話題到這裡就算是告一個段落了,通過這次回顧我自己也有相當的體會,確實也收穫了一些東西。同行們有興趣進一步交流的可以加我的群:244054966,這個群定位是創業,新手就不要去了。另一個是:231233168,這個群沒什麼限制。入群時請加上訊息:CSDN部落格。下一篇談談能力相關的話題,名字暫定為:能力養成篇。