怎麼寫規範、風格良好的代碼?
工作了幾個月,感覺自己代碼很不規範,有很多冗餘,比較亂,請問怎麼針對性的改善代碼規範,有沒有什麼比較好的書可以推薦,謝謝大家..
拿一段代碼來,俺改給你看
——修改——
樓主放棄了機會,@劉繼龍 最先發,所以俺就以@劉繼龍 的代碼為例。
其他同學自行按照我所說的原則修改吧。。其實演算法代碼是很不好改的,因為看起來特別費勁,浪費時間不說,且修改餘地不大,加之處理的東西比較單一,所以也沒有太多內容。演算法題不是編程,只是「編程實現」而已。
俺知道,很多人瞧不起業務,認為數學、演算法才是根本,但不可否認的是windows操作系統,iphone手機,知乎網站這些產品的絕大部分代碼,都只是業務代碼而已,俺們搞代碼的,能把代碼寫得好,也就足矣
原代碼如下:vector&
vector&
vector&
if (root == 0) return ans;
vector&
unordered_map&
vec.push_back(root);
add.push_back(root-&>val);
map[root] = add;
while (!vec.empty()){
auto temp = *vec.begin();
if ((temp-&>left == 0) (temp-&>right == 0))
if (temp-&>val == sum)
ans.push_back(*map[temp]);
if (temp-&>left) {
auto tempadd = new vector &< int &>(*map[temp]) ;
(*tempadd).push_back(temp-&>left-&>val);
map[temp-&>left] = tempadd;
temp-&>left-&>val += temp-&>val ;
vec.push_back(temp-&>left);
}
if (temp-&>right) {
auto tempadd = new vector &< int &>(*map[temp]);
(*tempadd).push_back(temp-&>right-&>val);
map[temp-&>right] = tempadd;
temp-&>right-&>val += temp-&>val;
vec.push_back(temp-&>right);
}
vec.erase(vec.begin());
}
vec.clear();
vec.push_back(root);
while (!vec.empty()){
auto temp = *vec.begin();
if (temp-&>left){
delete map[temp-&>left];
vec.push_back(temp-&>left);
}
if (temp-&>right){
delete map[temp-&>right];
vec.push_back(temp-&>right);
}
vec.erase(vec.begin());
}
return ans;
}
寫規範美麗大方的代碼,俺的經驗是「術」「道」並用。
「術」,就是code complete這類書上、google編碼規範這類規範文檔中所說的取好名字啊、縮進啊,操作符左右空格啊之類的,這類經驗,可以通過看書得到,你所需要做的就是相信它。
相信俺,相信它的人實在不夠多。
當然也可能是他們對於編碼規範、最佳實踐、可讀性的標準不一樣就是了。。。言歸正傳,像上面的代碼中,第一個問題就是
ans這個名字不好,既然是paths,就叫paths然後這裡if (root == 0) return ans;
- 不應該用指針和數字比較,撇開編譯器那邊的問題,你也不容易看出來它是一個指針。它是什麼類型就怎樣去操作,換成下面的就比較好。
- 另外,俺的觀點是無論如何都要加花括弧,當然有很多人認為有時候不加很方便,俺只能說各人有各人的喜好,但是俺認為加上比較好,蘋果的SSL漏洞,不就是沒加花括弧引起的嗎。
- (但是,有一類人,多以高校教師為主,他們寫的代碼,能夠不加花括弧的地方都沒有加。。。反正俺是覺得很厲害啦,辣么厲害的東西,各位別學,駕馭不動)
改成這樣就很好
if (root == nullptr) {
return paths;
}
auto temp = *vec.begin();
應該改成
auto tree;
// 或者
auto t;
還有一些,但「術」的原則就是這樣,而且有很多書可以看,俺就不繼續講了
「道」代碼應該描述what,而不是how,因為看代碼的人不需要關心一棵樹你是怎麼去解析的。所以對於大段代碼什麼的,細節什麼的,模塊化直接砸過去vector&
pathSum(TreeNode *root, int sum) {
// 指針不要用 == 0,是不對的,並且這種條件放最開始
if (root == nullptr) {
return paths;
}
vector&
vector&
vector&
unordered_map&
// init
vec.push_back(root);
add.push_back(root-&>val);
map[root] = add;
// 應該用遞歸,這樣處理樹太費事了。。。但既然用循環就按循環改吧
// 其實這個循環也應該用函數。。。
while (!vec.empty()){
// 用tree、node或者t、n都比temp好。。永遠不要用tmp和temp
auto temp = *vec.begin();
value = temp.val;
// 函數名我取得不好,因為懶得看它是幹啥用的。。。但表達的意思是這麼一大塊的東西應該用函數
pushPaths(temp, paths);
pushNode(temp-&>left, value, map, vec);
pushNode(temp-&>right, value, map, vec);
vec.erase(vec.begin());
}
vec.clear();
vec.push_back(root);
// clean,誰也沒空關心你怎麼clean的,用個函數包起來說了有這麼回事就好了。。
cleanVector(vec);
return paths;
}
void
pushPaths(t, paths) {
# 接住變數,不要直接使用
auto temp = t;
if ((temp-&>left == 0) (temp-&>right == 0)) {
if (temp-&>val == sum) {
paths.push_back(*map[temp]);
}
}
}
void
pushNode(node, value, map, vec) {
// 重複函數合併,不應該出現重複代碼,Don"t Repeat Yourself
TreeNode *n = node;
if (n) {
auto tempadd = new vector &< int &>(*map[temp]) ;
(*tempadd).push_back(n-&>val);
map[n] = tempadd;
n-&>val += value ;
vec.push_back(n);
}
}
void
cleanVector(VECTOR vec) {
// 這種用處的代碼,無需過多解釋,因為沒人願意看
while (!vec.empty()){
auto temp = *vec.begin();
if (temp-&>left){
delete map[temp-&>left];
vec.push_back(temp-&>left);
}
if (temp-&>right){
delete map[temp-&>right];
vec.push_back(temp-&>right);
}
vec.erase(vec.begin());
}
}
(其實這樣寫仍然是有很多問題的,但問題出在演算法上,而不是寫法上,俺主要談談寫法,所以俺就不繼續看了。)
還有很多啦,這裡結合例子隨便一寫,具體的還是要 看書,看好書,相信書,多實踐,多總結
所以真心不推薦刷演算法題。。。
當然了。。。其實你既然要練演算法,而不是練cpp演算法,這種東西python寫起來多簡單,用遞歸十幾行就好了。。。。
所以啊,努力也要用到點子上,不要像很多ACM玩家一樣,折騰了幾年,啥都沒學會。。
—全文完—可以看看《clean code》。
在網上看到一個《clean code》的讀書總結,隨手搬運一下吧:
一、整潔代碼
1. 概念 代碼正確 簡潔明了清晰易讀
短小精確二、命名 1.準確: 名字與意義匹配 易於區別 2.實用 使用讀的出來的名稱 使用可搜索的名稱 3.明確一個概念對應一個詞
不用雙關語 使用有意義的語境 三、函數 1.短小 2.職責單一 3.一塊代碼中,函數的抽象層級需一致 4.函數命名規範(參照二) 5.參數儘可能少 6.如果函數需要的參數要求數量有多種,應考慮將其封裝成類7.實用異常類代替返回錯誤碼,抽離try/catch代碼塊,使代碼更加簡潔
四、注釋 1.少用注釋,儘可能通過規範的代碼來表達 2.不使用無意義的注釋 3.必要的注釋: 法律信息 提供信息的注釋 對代碼意圖進行解釋的注釋 警示信息,防止踩坑 TODO注釋:未來得及完成的部分4. 對於無用的代碼應直接刪除而不是注釋
五、格式 1.為什麼需要規範格式 易維護 易拓展 2.垂直格式 行數少,短小精悍 概念隔離,不同的的概念/邏輯 代碼實用空行隔離 相關靠近:對於關係緊密的代碼,盡量寫在一起 3.水平格式縮進、對齊
六、對象與數據結構 1.區別: 過程式代碼便於在不改動既有數據結構的前提下添加新函數 面向對象代碼便於在不改動既有函數的前提下添加新類 亦即: 過程式代碼難以添加新數據結構,因為必須修改所有函數 面向對象代碼難以添加新函數,因為必須修改所有類七、錯誤處理 1.使用異常而非返回碼: 更加美觀、整潔 2.使用不可控異常 可控異常的代價是違反開放/閉合原則,因為你需要在使用的地方捕獲異常 3.在異常發生的地方添加環境說明: 這樣當異常發生的時候就可以根據這些信息定位異常原因 4.不返回null 也不傳遞null 這樣在接收的時候不需要進行空值檢查八、邊界 翻了下原書,邊界是Boundaries 對於這個名字,開始不是大理解,就算現在看來也還是覺得不直觀 標題略晦澀:作者的意思應該是讓我們讓自己的代碼和第三方庫代碼不要耦合太緊密,需有清新的邊界 對於第三方類庫給的學習建議是:探索性地學習測試,以此熟悉類庫,寫出良好的代碼九、單元測試 1. TDD定律 在編寫不能通過的單元測試前,不可編寫生產代碼 只可編寫剛好無法通過的單元測試,不能編譯也算不通過 只可編寫剛好足以通過當前失敗測試的生產代碼----- 這三條寫的有點繞,我的理解就是:要把測試代碼當生產代碼來寫,測試也很重要 2.五大規則(F.I.R.S.T) 快速:就是代碼質量要好,高效 獨立:不同測試之間應相互獨立 可重複:就是在各種環境中都可測試通過 自足驗證:測試不依賴手工操作來知曉是否通過 及時:測試應在生產代碼之前進行編寫十、類 主要注意一個點,類應短小,可通過以下兩種方式達到此目的:1. 權責單一:一個類的權責不應太多,太多則需考慮拆成多個類了2. 高內聚:類的實體變數應儘可能少,類中方法儘可能多地使用到這些變數十一、系統 1.系統構造與使用分開 工廠:使用工廠方法自行決定何時創建實例,但是構造細節卻在其他地方 依賴注入:當A對B有依賴時,A中不負責B的實例化(這就是類的權責單一原則) 2.本章的後半部分提到的AOP,AspectJ等內容都以Spring,EJB等框架為例十二、跌進 跌進這一章提到的點不多 1.運行所有的測試:為能方便測試,我們的生產代碼也要足夠短小,耦合度低 2.重構:在寫代碼過程中要及時重構,保持代碼的優雅 3.不可重複:已有的代碼要利用起來,消除重複 4.表達力強:這應該是一個目標或是結果,做好前邊的工作自然而然可以達到十三、並發編程 1.為什麼:它可以將「目的」和「時機」分離,進行解耦 2.並發防禦原則 單一權責:主要關注一點,並發相關的代碼應分離出來 為此,三點建議: 1).限制數據作用域 2).使用數據副本:這不就是ThreadLocsal么~~ 3).線程應儘可能地獨立 3.學習已有類庫:Java中的並發包之類
來源:
[讀書筆記]Clean Code (1-8章)
[讀書筆記]Clean Code (8-完結)
【先說格式風格】格式講究的是一致,比如成員變數命名規範這類的,這裡給出的建議就是:安裝適合的格式輔助工具,然後按照提示來。理解他是需要其他方面知識的,就比如說
if ( ... ){
//...
} else {
//...
}
還是
if ( ... )
{
//...
}
else
{
//...
}
有區別么?其實有,第一個基礎是如果代碼翻頁,那麼錯誤率就會快速上升,第二個則是基於如果代碼過密,則錯誤率也會上升。更進一步說,如果嚴格遵守函數不能過長的限制,第二個將非常有利,而第一個則更有利於會有長函數的情況。
你看上面的例子,除非是資深的顧問,否則你理解他有多大意義?對絕大部分程序員來說,代碼規範更多的是約定,所以讓軟體自動管控最好。【再說代碼質量】提高代碼質量的方式,改善格式風格的方式完全不同,為什麼?因為所有提升代碼質量的手段,都是有明確目的性的。比如說【永遠不要使用複製粘貼】這個建議,絕對不會有程序員在不理解其目的的情況下嚴格遵守的。這部分就需要通過實踐(其實主要是慘痛的經歷)、讀書來解決了。可選的包括《effective XXX》(你對應的語言)作為開始就不錯,說代碼大全的請憑良心給新手建議,這個解決問題的效率太低,而且絕對不適合新手。說看開源項目的注意了!這個建議絕對是禍害新手的建議,先不說質量有好有壞,很多人看不出來;就算是好代碼,你也看不出奧妙,你看看代碼之美這樣的書,一點點代碼要用多少話去解釋,作者要告訴你多少他想了什麼,才得出結論,這還是精心挑選的示例代碼。開源項目終歸是為了應用,裡面各種處理很多是就事論事的,你看得出門道,新手可是照單全收的!Google Guide Style
代碼規範和風格?
如果僅限這部分,自動化解決……
比人工背規範效率高多了……
http://zh-google-styleguide.readthedocs.org/en/latest/google-cpp-styleguide/
首先,代碼風格對於程序員來說十分重要。
以Google為例,谷歌的代碼庫是向全公司的所有程序猿的,所有的程序猿都可以看到公司所有的代碼。甚至是上古大牛jeff dean親手寫的代碼,比如Map Reduce,GFS,Bigtable的源碼。
在谷歌的代碼庫里,至少有20億行代碼。如此龐大的代碼庫,萬萬千千的程序員都要來看,如果沒有統一的代碼風格,閱讀起來是相當困難的。因此,在谷歌,所有的要提交的代碼都需要經過批閱,未經批閱是不可能交到代碼庫裡面的。
Google的代碼庫中,每一級的文件都有相應的文件主,正是由這些文件主來負責管理本文件的代碼規範。程序猿在提交代碼的時候,必須在審批者裡面加上代碼所有文件的文件主批閱。由文件主負責批閱此次交的代碼改動符合整個文件的代碼書寫規範。
文件主是如何審閱這些代碼的呢?其中代碼的書寫規範,是尤其重要的審閱內容。包括每一個小空格,標點,對齊方式,都必須遵循Google的內部書寫代碼的風格規則。以此保證代碼符合Google統一的格式。所以如果你還在哀怨演算法班老師在課堂上給你指出的「不要用Tab, 要用空格」,「變數名後面要有空格」等代碼風格規範,而且不能很好的做到這些基本的代碼風格準則,那麼你不僅會在工作中遇到和同事的溝通、協作障礙,而且你有很大的可能因為代碼風格不佳而無法通過面試
Google的代碼風格標準是給所有程序員的標準,任何人寫的代碼,如果代碼風格有問題,別人都可以要求你修改。要知道,每次的代碼提交,系統都會發郵件抄送給整個團隊。這樣做的目的是保證團隊的所有人知道每次代碼修改原因,保證團隊代碼符合相同的規範。
其次,如何養成良好的代碼風格?
想要養成規範、風格良好的代碼風格,可以參考大公司的官方Coding Style Guide。多刷Lintcode,多對比代碼風格要求,以此養成良好的coding習慣。直接上福利,這裡是Google官方几種代碼的Coding Style Guide:
C++ Style GuideObjective-C Style GuideJava Style GuidePython Style GuideR Style GuideShell Style GuideHTML/CSS Style GuideJavaScript Style GuideAngularJS Style GuideCommon Lisp Style GuideVimscript Style GuideXML Document Format Style Guide
推薦閱讀:
Google 是如何審批20億行代碼的?coding style 真的很重要!
歡迎關注我的微信公眾號:九章演算法(ninechapter),帶你全面了解IT公司,包括薪資、面試內容、職業發展、福利待遇等等。
抽空學一下python,專治你這種病。
google-styleguide -
Style guides for Google-originated open-source projects
可以嘗試多看看開源的代碼,然後自己多寫寫,主要是找到那個感覺。另外,經常把代碼放到Code Review Stack Exchange上去求虐也是不錯的。
Google 關鍵字 : 語言名稱 + code standards .
看一些大眾的編程規範把 比如 微軟編程規範
&<&
AlloyTeam/alloyteam.github.com · GitHub
這問題哪有那麼複雜。
不同的公司會有不同的代碼風格。現代公司都會使用風格校驗工具對遞交的代碼進行檢查,哪怕多一個或少一個空格的代碼,都是沒辦法合併進主分支的。
相應地,程序員只需要看看校驗結果就知道怎樣的代碼符合公司標準了。
所以題主裝個類似的工具就行了,比如各種lint工具(谷歌/百度即可)。
請參考Google C++ 編碼規範
@蕭井陌剛寫了一段代碼,求改。leetcode上面求路徑和的代碼。
vector&
vector&
vector&
if (root == 0) return ans;
vector&
unordered_map&
vec.push_back(root);
add.push_back(root-&>val);
map[root] = add;
while (!vec.empty()){
auto temp = *vec.begin();
if ((temp-&>left == 0) (temp-&>right == 0))
if (temp-&>val == sum)
ans.push_back(*map[temp]);
if (temp-&>left) {
auto tempadd = new vector &< int &>(*map[temp]) ;
(*tempadd).push_back(temp-&>left-&>val);
map[temp-&>left] = tempadd;
temp-&>left-&>val += temp-&>val ;
vec.push_back(temp-&>left);
}
if (temp-&>right) {
auto tempadd = new vector &< int &>(*map[temp]);
(*tempadd).push_back(temp-&>right-&>val);
map[temp-&>right] = tempadd;
temp-&>right-&>val += temp-&>val;
vec.push_back(temp-&>right);
}
vec.erase(vec.begin());
}
vec.clear();
vec.push_back(root);
while (!vec.empty()){
auto temp = *vec.begin();
if (temp-&>left){
delete map[temp-&>left];
vec.push_back(temp-&>left);
}
if (temp-&>right){
delete map[temp-&>right];
vec.push_back(temp-&>right);
}
vec.erase(vec.begin());
}
return ans;
}
推薦閱讀:
※數學一般的人適合學習編程嗎?
※在PowerShell中用命令運行.py文件 有黑色框閃了了一下 然後PowerShell直接跳下一行新的命令輸入了 求解?
※熱愛C語言的我該何去何從?
※如何有效閱讀Github上開源項目代碼?
※學習Rust適合寫什麼練手項目?