如何使自己編寫的程序更靠譜(Robust)?

如何更好地在編寫代碼時理清思路,及時注意到邊界條件,異常處理等,並使得代碼的時間和空間效率得到最優解,最終實現程序(尤其是大型項目程序)的整體魯棒性獲得質的飛躍。

____________以下是題主原文____________

大二,三流本科院校,cs相關專業,自學前端,學了html,css,js,jq,bootstrap,一般的功能可以實現,但是讓別人看了代碼就會被打死的那種,js高級編程的各類名詞雲里霧裡的聽了無數次,但是不明白什麼意思,也不知道如何下手去提高,想去學vue,ng之類的高級框架,但是感覺自己基礎薄弱,就不去好高騖遠了,總之很迷茫,求指點


想讓自己寫的程序靠譜,最笨、最實用的方法是:不斷地驗證。

這裡的驗證包括:

  • 程序設計評審(對需求和程序設計的驗證);
  • Code Review(對代碼質量和程序設計的驗證);
  • 自動化測試(比如Android的Monkey和集成測試,對程序穩定性的驗證);
  • 開發自測(對基本功能的驗證);
  • 持續集成(對項目健康狀態的驗證);
  • 單元測試(對單個方法代碼質量的驗證,由於在實際項目中這方面做得少,沒多少話語權。。。);
  • SQA(專業測試人員,主要是黑盒測試,保證程序的質量);
  • 持續地迭代(每個版本只交付有限的功能,不斷經過用戶驗證改善)。

這些驗證環節基本包含了軟體開發的各個流程,只要是比較正規的公司都會有明確軟體開發流程,但僅僅依靠開發人員自身把這些環節做到位是很不現實的(從github上主流的開源項目就可以看出,很少有項目寫單元測試、Demo中也沒有對庫的性能測試,在實際項目中使用時問題一大堆,真正優秀的項目都是經過實際項目和較長時間沉澱進化而來的),必須要依靠流程和規範。很多大公司的軟體做得很靠譜就是因為這個原因。

所以,你想要進步、提升自己的能力、讓自己的程序更靠譜,一個不錯的開發環境很重要。


  1. 必須要寫測試,包括單元測試(mocha+chai),UI測試(selenium, nightwath)與持續集成測試(通過GITHUB的hook與IC平台結合)
  2. 持續優化(這必須位於擁有龐大的測試套裝的基礎)。一個龐大的東西寫著寫著,必然需要模塊化。模塊化是基於某些抽象的概念之上。許多相同的操作,經過多次修改,就需要聚合到一塊,它們就可以形成一個模塊。而模塊可以由一個核心的類及若干什麼私有方法與數據構成。
  3. 數據結構優於演算法。框架與業務不單是由於某些任務進行劃分。好的代碼必須是可以遞歸。遞歸可以讓我們的代碼大大減少。為了形成這種遞歸結構,我們需要設計一些數據結構。因此基礎很重要。
  4. 為了保證效率,需要引入緩存機制。越大的東西,必須有所損耗。緩存是一個很好的解決方案。
  5. 處處布置各種異常捕捉代碼,凡是可能出錯的地方都會有機會出錯。因此要構築錯誤棧,及合置錯誤棧輸入,方便日後的調試。
  6. 先完成後完美,每完成一個階段必須提交到版本管理系統。給自己留一條後路。


當你在問程序靠譜的時候,我的第一反應是寫測試啊。

剛畢業不久的時候,在寫前端框架 Lettuce (GitHub:phodal/lettuce)的時候,測試覆蓋率是 91%,並且用 Code Climate 來控制代碼質量:

在寫物聯網框架 Lan(phodal/lan)的時候,測試覆蓋率是 92%:

在寫 EchoesWorks(GitHub:phodal/echoesworks) 的時候,測試覆蓋率是 95%:

測試代碼基本上是功能代碼的兩倍左右。測試分成三種層次:

單元測試

單元測試是針對程序模塊(軟體設計的最小單位)來進行正確性檢驗的測試工作。它是應用的最小可測試部件。舉個例子來說,下面是一個JavaScript 的函數,用於判斷一個變數是否是一個對象:

var isObject = function (obj) {
var type = typeof obj;
return type === "function" || type === "object" !!obj;
};

這是一個很簡單的功能,對應的我們會有一個簡單的 Jasmine 測試來保證這個函數是正常工作的:

it("should be a object", function () {
expect(l.isObject([])).toEqual(true);
expect(l.isObject([{}])).toEqual(true);
});

雖然這個測試看上去很簡單,但是大量的基本的單元測試可以保證我們調用的函數都是可以正常工作的。這也相當於是我們在建設金字塔時用的石塊——如果我們的石塊都是經常測試的,那麼我們就不怕金字塔因為石塊的損壞而坍塌。

當單元測試達到一定的覆蓋率,我們的代碼就會變得更健壯。因為我們都需要保證我們的代碼都是可測的,也意味著我們代碼間的耦合度會降低。我們需要去考慮代碼的長度,越長的代碼在測試的時間會變得越困難。這也就是為什麼 TDD 會促使我們寫出短的代碼。如果我們的代碼都是有測試的,單元測試可以幫助我們在未來重構我們的代碼。

並且在很多沒有文檔或者文檔不完整的開源項目中,了解這個項目某個函數的用法就是查看他的測試用例。測試用例(Test Case)是為某個特殊目標而編製的一組測試輸入、執行條件以及預期結果,以便測試某個程序路徑或核實是否滿足某個特定需求。這些測試用例可以讓我們直觀地理解程序程序的 API。

服務測試

服務測試顧名思義便是對服務進行測試,而服務可以是有不同的類型,不同層次的測試。如第三方的 API 服務、我們程序提供的服務,雖然他們他應該在這一個層級上進行測試,但是對他們的測試會稍有不同。

對於第三方的提供的 API 服務或者其他類似的服務,在這一個層級的測試,我們都不會真實地去測試他們能不能工作——這些依賴性的服務只會在功能測試上進行測試。在這裡的測試,我們只會保證我們的功能代碼是可以正常工作的,所以我們會使用一些虛假的 API 測試數據來進行測試。這一類提供 API 的 Mock Server 可以模擬被測系統外部依賴模塊行為的通用服務。我們只要保證我們的功能代碼是正常工作的,那麼依賴他的服務也會是正常工作的。

http://growth.phodal.com/assets/article/chapter3/mock-server.pngMock Server

而對於我們提供的服務來說,這一類的服務不一定是 API 的服務,還有可能是多個函數組成的功能性服務。當我們在測試這些服務的時候,實際上是在測試這個函數結合在一起是不是正常的。

一個服務可能依賴於多個函數,因而我們會發現服務測試的數量是少於單元測試的。

UI 測試

在傳統的軟體開發中,UI 測試多數是由人手動來完成的。而在稍後的章節里,你將會看到這些工作是可以由機器自己來完成的——當然,前提是我們要編寫這些自動化測試的代碼。需要注意的是 UI 測試並不能完全替代手工的工作,一些測試還是應該由人來進行測試——如對 UI 的布局,在現階段機器還沒有審美意識呢。

自動化 UI 測試是一個緩慢的過程,在這個過程里我們需要做這麼幾件事:

  1. 運行起我們的網站——這可能需要幾分鐘。
  2. 添加一些 Mock 的數據,以使網站看上去正常——這也需要幾分鐘到幾十分鐘的時間。
  3. 開始運行測試——在一些依賴於網路的測試中,運行完一個測試可能會需要幾分鐘。儘管可以並行運行測試,但是一個測試幾分鐘算到最後就會累積成很長的時間。

所以,你會發現這是一個很長的測試過程。儘可能地將這個層級的測試往下層級移,就會儘可能的節省時間。一個 UI 測試需要幾分鐘,但是一個單元測試可能不到1秒。這就意味著,這樣的測試下移可以節省上百個數量級的時間。

對於框架類型的項目來說,有單元測試和服務測試就差不多。如我最近在寫的基於 React Native 的下一代 Growth(phodal/growth-ng),它一共有三種測試:

  • 基於 jest 的單元測試
  • 基於 react-test-render 的 UI 測試(snapshot )
  • 基於 appium 的集成測試

不過因為剛開始,測試覆蓋率目前還有點低:

當你熟練掌握測試編寫的時候,你可以採用 TDD 來開始。這個時候你的代碼就開始變得靠譜了:

測試驅動開發的主要過程是: 紅 —&> 綠 -&> 重構。

  1. 先寫一個失敗的單元測試。即我們並沒有實現這個方法,但是已經有了這個方法的測試。
  2. 讓測試通過。實現簡單的代碼來保證測試通過,就算我們用一些作弊的方法也是可以的。我們寫的是功能代碼,那麼我們應該提交代碼,因為我們已經實現了這個功能。
  3. 重構,並改進功能代碼,讓它變得更加合理。

TDD 有助於我們將問題分解成更小的部分,再一點點的添加我們所需要的業務代碼。隨著這個過程的不斷進行,我們會發現我們已經接近完成我們的功能代碼了。並且到了最後,我們會發現我們的代碼都會被測試到。

雖然說起來很簡單,但是真正實現起來並不是那麼容易。於我而言我只會在我自己造的一些輪子中使用 TDD。因為這個花費大量的時間,通常來說測試代碼和功能代碼的比例可能是1:1,或者是2:1等等。在自己創建的一些個人應用,如博客中,我不需要與其他人 Share 我的 Content。由於我使用的是第三方框架,框架本身的測試已經足夠多,並且沒有複雜的邏輯,我就沒有對我的博客寫測試。而在我寫的一些框架里,我就會盡量保證足夠高的測試覆蓋率,並且在適當的時候會去 TDD。

通常來說對於單元測試我會採用 TDD 的方式來進行,但是功能測試仍會選擇在最後添加進去。主要的緣由是:在寫 UI 的過程中,元素會發生變化。這一點和我們在寫 Unit 的時候,有很大的區別。div + class 會使得我們思考問題的方式發生變化,我們需要去點擊某個元素,並觀察某個元素髮生的變化。而多數時候,我們很難把握好一個頁面最後的樣子。

不得不說明的一點是,TDD 需要你對測試比較了解後,才容易使用它。從個人的感受來說,TDD 在一開始是一件很難的事。


有些人知道,這幾天我在寫一個知乎格鬥程序。去看看github的歷史,就知道我是如何讓自己的程序逐漸演化的。

首先我不寫代碼,寫文檔。我大致想好我的程序所有的對象,大概的屬性和方法。這點上,之後可能有更改,不過這是一個基礎,避免我天馬行空。

然後我開始寫code,我是草稿派,也就是說,我首先不考慮任何優化,直接快速地實現功能,代碼要多醜有多醜,循環套循環,代碼塊重複率很高。然而寫的很快,幾個小時就能完全實現功能。然後在寫的過程當中,自然就有感覺,比如這段代碼複製的很多次,就立馬抽一個方法,這段代碼套了三個循環,就換一個演算法邏輯,如此多次迭代重複以後,我的代碼基本上會縮小60%並保持功能不變。

再之後開始關心健壯性的問題,在代碼各處增加例外處理。

再之後增加關鍵點log,作為生產環境defect的差錯依據。

再之後增加擴展點,為將來可能出現的需求做準備。

趁我在陪老婆看電視的時候,再來更新一下

用一個例子來說話,熟悉我的人都知道,我最喜歡的就是fact,別扯空的。

比如我這個知乎格鬥遊戲,會有AB兩組人,對砍,目前還沒做地圖,那麼就是站樁對抗。在另外一個我提的問題裡面,我已經設計了數據結構,這就不重複了。

剛開始我補考慮太多,就純功能,那麼我就寫兩個list,一個包含a組,一個包含b組(可惜這個階段我忘記提交去github了,所以就被覆蓋了),並且由於我設計的數據結構裡面,每個entity有一個componentl list,所以天然就有一個嵌套的list。然後先決定哪個組攻擊,這時候我怎麼做?我直接循環listA,對於每個entity en:listA&,我隨機選取entity的一個部件,再隨機選取listB的某個entity的某個部件,扣減hp算是攻擊。這樣的話,因為AB會互相攻擊,就會有兩個類似的大循環,而且,因為list的關係,get出來,就要set回去,這樣的話,大段代碼就花在操作這些東西上了,然而功能的確是實現的,所以我就純寫。並且還有反擊,所以就會有多重循環嵌套,加上list的元素取出來,set回去,非常醜陋。有效行數大概在200行左右。

寫完了以後,我尋思尋思,太丑,要優化。我就考慮如何簡化代碼。剛才的思想,你能很明確看出來,我是OP時代過來的人,基本上不是OO,所以我採用OO重構,給每個entity增加了一個攻擊方法,並使用一個工具方法將重複的代碼抽取出來,這樣就把代碼行數壓縮到了50%以內,如下


虛的就不說了,對於掌握基本演算法和軟體工程的入門者,個人建議兩點

1. 使用代碼靜態檢查工具。Java上findbug, JS上JsLint。保證無警告。

2. 在1的基礎上,學習單元測試的基礎理論。然後無腦保證單元測試行覆蓋率95%以上,分支覆蓋率85%以上。你會發現如果代碼太不靠譜,編寫和維護單元測試會把你煩死。


關注核心概念,否則很容易停留在淺層次的技術應用上,好像也會寫代碼實現個什麼東西,但是不懂程序設計,沒有質量意識,沒有架構意識。

給自己時間,慢慢地學習高質量程序設計的標準,看書,看文章視頻都行,從簡單的格式,到什麼是合理的抽象,主動去找資料,而不要期望別人給你標準答案。

觀察改進已經寫過的代碼,把高質量設計的標準應用進去,在重構中實踐更好的設計。


先寫測試用例,再寫測試代碼,最後寫功能代碼。(參考TDD)。

並且,做功能實現時,多用框架,別老自己造輪子。以你舉的例子,可以看ng2或者vue,它們對某些常見操作的封裝,經過了錘鍊,比自己實現更靠譜。


1. 高內聚低耦合,模塊分離,業務無關的代碼抽象出來。

2. 不斷重構,簡化邏輯。

3. 善於使用各種靜態檢查工具,例如各種IDE,各種 LINT,對於工具誤判的,盡量 suppress 掉。

4. 學會編寫測試用例。


題主你這是個綜合症,寫不好代碼的原因一是理論不足,二是實踐不夠。這兩個方面需要惡補一下

理論方面補充:前端相關原理,js模塊化相關文章。軟體工程和面向對象也大致了解一下

實踐方面:多看一些優秀開源工程,看看同樣功能人家是怎麼實現的。

另外,稍微深入一些的資料還是google英文搜索比較靠譜。

最後,即使像我這樣編了很多年程序的老傢伙,也不敢保證頭一次寫的代碼就是OK的。一般都是寫測試用例,全通過後,重構代碼直到自己滿意。

…………

參考我的回答:有哪些IT初學者(新人)成長為技術大牛的真實經歷? … https://www.zhihu.com/question/40662462/answer/153695315


說實話,出去呼吸一下新鮮空氣,間隔一小時站起來轉轉,時刻保持大腦的清醒比什麼都重要。

不過你的問題是說你現在基礎差,想把程序寫靠譜,光靠頭腦清醒肯定是不夠的。你的基本功和代碼邏輯是很重要的,想把程序寫靠譜,首先你得學會學習。

js高級編程的各類名詞雲里霧裡的聽了無數次,但是不明白什麼意思

你這很明顯就是眼不高手低,聽了無數次不如自己練三次,你想的多的同時需要自己去做,如果自身的能力撐不起自己的野心,就做一個早起的鳥吧。

不要猶猶豫豫的,挑一本自己喜歡的書(沒有就抓鬮吧),犀牛書、head first,拿起一本書從第一行代碼開始吧!

如果你認真做了,你會發現自己每天都會很充實,迷茫就會越來越淺


使用更嚴格的語言比如Haskell Idiris,也可以試試相對工業化的Rust Swift Kotlin (主要針對null,Rust還有內存控制),或者寫大量的單元/模塊測試,上持續集成跑測試,過不了還會發郵件,安全感max


三大利器

畫流程圖 注釋 測試


代碼的質量要求:正確性、健壯性、可靠性、效率、易用性、可讀性(可理解性)、可擴展性、可復 用性、兼容性、可移植性。

想要提高代碼的質量,可以學習以下兩方面的內容:

1.數據結構與演算法

2.OO設計原則與設計模式

你是 CS 相關專業,應該學過數據結構與演算法,Java應該也學過,可以學習下設計模式。

設計模式的定義是:在面向對象軟體設計過程中針對特定問題的簡潔而優雅的解決方案。
通俗一點說,設計模式是在某種場合下對某個問題的一種解決方案。如果再通俗一點說,設計模式就是給面向對象軟體開發中的一些好的設計取個名字。

推薦以下書籍(大部分需要OO語言基礎):

《JavaScript設計模式與開發實踐》

《代碼大全(第2版)》

《設計模式: 可復用面向對象軟體的基礎》

《Head First 設計模式(中文版)》

《重構: 改善既有代碼的設計》

《敏捷軟體開發:原則、模式與實踐 》


大部分cs專業的學生接受的都是面向對象的思想。本人一年前也是按此思路寫代碼,但其實我覺得面向對象並沒有一個提供一些有用的思路解決編程問題,只是引入多態,繼承,實例這些概念。我覺得函數式編程可以給這部分人很多啟發。多態,繼承,實例等概念都可以用別的寫法替代,本質上來說這些概念都是實現抽象具象,這些交給函數即可而且更加安全。


謝邀:樓上有很多很棒的建議,寫文檔,測試驅動,畫流程圖都很好,只是樓主你才大二,也不希望你失去學習的興趣,所以我的建議就是你想到什麼就寫什麼。完全隨心所欲,功能實現就行,愛怎麼寫怎麼寫。然後呢,你試著去優化你的代碼,再然後你選最好理解的讓你同學看一下,他要是看明白了,你就成功了。多年代碼經驗深知多數時候技巧,不如簡單明了來的好。代碼規範強烈推薦 阿里出的Java編程手冊


1.一眼看懂自己寫的代碼

2.一眼看懂別人寫的代碼

3.別人能看懂你的代碼


其實和寫文章一樣,你第一遍寫肯定漏洞百出,改了幾十稿之後,你就會覺得這是藝術品了。

再然後就是讀書破萬卷,下筆如有神的階段了。


每當做電梯的時候就告誡自己,一旦自己遇到這種能出人命的代碼,就一定要多上點心


車上碼字,簡單回答

第一,程序可讀性好。是其他程序猿容易讀懂而且維護你的代碼,即使沒有注釋都能讀懂最好,符合編程規範

第二,模塊化盡量實現單一功能,越簡單,越封閉的功能模塊最好。所謂低依賴,高耦合。

第三,避免重複編寫相似功能,學習面向對象編程,模塊組織,編程模式

第四,讀一些經典演算法代碼,學習一些經典程序的編碼風格和約定俗成,應用到自己的編程中

第五,團隊有條件就提供人員寫測試代碼


能證明的代碼最靠譜


推薦閱讀:

js原型鏈與lua元表的異同?
產品經理如何在設計產品時避免給開發挖坑?
前端開發js函數式編程真實用途體現在哪裡?
怎麼評價Vue.js2.5以後要大力加強對TypeScript和VSCode的支持?
font-weight和fontWeight的區別?

TAG:Web開發 | 前端開發 | 程序員 | 軟體開發 | Android開發 |