1、編程規約
(一)命名規範
- 代碼中的命名嚴禁使用拼音與英文混合的方式,更不容許直接使用中文。
說明:正確的英語拼寫和語法可讓閱讀者易於理解,避免歧義。注意即便純拼音命名的方式也要避免採用。
正例:name / order / baidu / alibaba 等國際通用的名稱可視爲英文。
反例:zhekou(折扣)/Shuliang(數量)/ int 變量=1
- 類名使用Pascal風格,某些狀況例外:DTO/UID等模塊功能縮寫。
正例: UserDTO / XmlService / TFlowInfo /TTouchInfo
反例: userDto / XMLService / tflowInfo / ttouchInfo
- 方法名、參數名、成員變量、局部變量都統一使用Camel風格,必須聽從駝峯形式。
正例: name / getUserInfo() / userId
- 常量的命名所有大寫,單詞間用下劃線隔開,力求語義表達要完整,不要嫌名字長。
正例:MAX_STOCK_COUNT
反例:Max_Count
- 抽象類命名使用Abstract或Base開頭,異常類命名使用Exception結尾,測試類命名以它要測試的類的名稱開始,以Test結尾。
- 杜毫不規範的縮寫,避免望文不知義。
反例:AbstractClass 縮寫命名爲 AbsClass
- 爲了達到代碼自解釋的目標,任何自定義編程元素在命名時,儘可能使用完整的單詞組合來表達其意思。
反例:int a的隨意命名方式
- 若是模塊、接口、類、方法使用了設計模式,在命名時需體現出具體模式。將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念。
正例:public class OrderFactory / public class LoginProxy
- 接口和實現類的命名須遵循如下兩點規則:
(1)對於Service和DAO類,暴露出來的服務必定是接口,內部的實現類用Impl的後綴與接口區分。
正例:CacheServiceImpl實現ICacheService接口。
(2)若是形容能力的接口名稱,取對應的形容詞爲接口名(通常爲-able)
正例:AbstractTranslator 實現ITranstable接口。
- 枚舉類名建議帶上Enum後綴,枚舉成員名稱須要全大寫,單詞間用下劃線隔開。枚舉爲特殊的類,成員均爲常量。
正例:枚舉名字爲ProcessStatusEnum成員名稱:SUCCESS/UNKNOWN_REASON
- 各層命名規約:
A) Service/DAO 層方法命名規約
1)獲取單個對象的方法用 get 作前綴。
2) 獲取多個對象的方法用list作前綴,複數形式結尾如:listObjects。
3)獲取統計值的方法用 count 作前綴。
4)插入的方法用 save/insert 作前綴。
5) 刪除的方法用 remove/delete 作前綴。
6) 修改的方法用 update 作前綴。
B) 領域模型命名規約
1) 數據對象:xxxDO,xxx 即爲數據表名。
2) 數據傳輸對象:xxxDTO,xxx 爲業務領域相關的名稱。
3) 展現對象:xxxVO,xxx 通常爲網頁名稱。
(二)常量定義
- 不容許任何魔法值(即未經預先定義的常量)直接出如今代碼中。
反例:string name =」tflow」 + userId;
- 在long 或者 float賦值時,數值後使用大寫L或者F,特別是L不能寫成小寫的l,小寫的l容易跟數字1混淆,形成誤解。
- 不要使用一個常量類維護全部常量,要按常量功能進行歸類,分開維護。由於大而全的常量類,雜亂無章,使用查找才能定位到常量,不利於理解和維護。
正例:緩存相關的常量放在類CacheConsts下;系統配置常量放ConfigConsts下。
- 若是變量值僅在一個固定範圍內變化用 enum 類型來定義。
正例:
public enum SeasonEnum {
SPRING=1, SUMMER=2, AUTUMN=3, WINTER=4;
}
複製代碼
(三)代碼格式
- 大括號的使用約定,在代碼中垂直對齊左括號和右括號。
正例:
if (x == 0)
{
Response.Write("用戶編號必須輸入!");
}
複製代碼
反例:程序員
if(x == 0) {
Response.Write("用戶編號必須輸入!");
}
複製代碼
或者:數據庫
if(x == 0){ Response.Write("用戶編號必須輸入!"); }
複製代碼
- if/for/while/switch/do 等保留字與括號之間都必須加空格。
- 採用Tab縮進代碼,禁止使用空格。
- 適當的增長空行,來增長代碼的可讀性。
- 單行字符數限制不超過 120 個,超出須要換行,換行時遵循以下原則:
1) 第二行相對第一行縮進 4 個空格,從第三行開始,再也不繼續縮進,參考示例。
2) 運算符與下文一塊兒換行。
3) 方法調用的點符號與下文一塊兒換行。
4) 方法調用中的多個參數須要換行時,在逗號後進行。
5) 在括號前不要換行,見反例。
反例:
StringBuider sb = new StringBuider();
// 超過 120 個字符的狀況下,不要在括號前換行
sb.append("zi").append("xin")...append
("huang");
// 參數不少的方法調用可能超過 120 個字符,不要在逗號前換行
method(args1, args2, args3, ...
, argsX);
複製代碼
- 避免寫太長的方法。一個典型的方法代碼在1~25行之間。若是一個方法發代碼超過25行,應該考慮將其分解爲不一樣的方法。
- 沒有必要增長若干空格來使某一行的字符與上一行對應位置的字符對齊。
正例:
int one = 1;
long two = 2L;
float three = 3F;
StringBuilder sb = new StringBuilder ();
複製代碼
- 不一樣邏輯、不一樣語義、不一樣業務的代碼之間插入一個空行分隔開來以提高可讀性。
(四)註釋規約
- 類、類屬性、類方法的註釋必須使用 vs 規範,在VS編譯器中打出/// 便可生成註釋。
正例:
/// <summary>
/// 註釋
/// </summary>
/// <returns></returns>
複製代碼
- 全部的抽象方法(包括接口中的方法)必需要用vs註釋、除了返回值、參數、異常說明外,還必須指出該方法作什麼事情,實現什麼功能。
- 全部的類都必須添加建立者和建立日期。
- 方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋,使用/* */註釋,注意與代碼對齊。
- 全部的枚舉類型字段必需要有註釋,說明每一個數據項的用途。
- 與其「半吊子」英文來註釋,不如用中文註釋把問題說清楚。專有名詞與關鍵字保持英文原文便可。
- 代碼修改的同時,註釋也要進行相應的修改,尤爲是參數、返回值、異常、核心邏輯等的修改。
謹慎註釋掉代碼。在上方詳細說明,而不是簡單地註釋掉。若是無用,則刪除。代碼被註釋掉有兩種可能性:
1)後續會恢復此段代碼邏輯。
2)永久不用。前者若是沒有備註信息,難以知曉註釋動機。後者建議直接 刪掉(代碼倉庫保存了歷史代碼)。
- 對於註釋的要求:第1、可以準確反應設計思想和代碼邏輯;第2、可以描述業務含義,使別的程序員可以迅速瞭解到代碼背後的信息。徹底沒有註釋的大段代碼對於閱讀者形同天書,註釋是給本身看的,即便隔很長時間,也能清晰理解當時的思路;註釋也是給繼任者看的,使其可以快速接替本身的工做。
- 好的命名、代碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋,代碼的邏輯一旦修改,修改註釋是至關大的負擔。
(五)集合處理
- 關於 hashCode 和 equals 的處理,遵循以下規則:
(1)只要重寫 equals,就必須重寫 hashCode。
(2) 由於 Set 存儲的是不重複的對象,依據 hashCode和equals進行判斷,因此 Set 存儲的對象必須重寫這兩個方法。
- 不要在 foreach 循環裏進行元素的 remove/add 操做。remove 元素請使用 Linq
方式,若是併發操做,須要對 List對象加鎖。
正例:
List<string> list = new List<string>();
list.add("1");
list.add("2");
List.RemoveAll(o=>o.id==0);
複製代碼
- 集合初始化時,指定集合初始值大小。
(六)併發處理
- 獲取單例對象須要保證線程安全,其中的方法也要保證線程安全。資源驅動類、工具類、單例工廠類都須要注意。
- 建立線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
- 高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。儘量使加鎖的代碼塊工做量儘量的小,避免在鎖代碼塊中調用 RPC 方法。
- 對多個資源、數據庫表、對象同時加鎖時,須要保持一致的加鎖順序,不然可能會形成死鎖。線程一須要對錶 A、B、C 依次所有加鎖後才能夠進行更新操做,那麼線程二的加鎖順序也必須是 A、B、C,不然可能出現死鎖。
- 併發修改同一記錄時,避免更新丟失,須要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用 version 做爲更新依據。若是每次訪問衝突機率小於 20%,推薦使用樂觀鎖,不然使用悲觀鎖。樂觀鎖的重試次數不得小於 3 次。
- 在高併發場景中,避免使用」等於」判斷做爲中斷或退出的條件。
(七)控制語句
- 在一個 switch 塊內,每一個 case 要麼經過 break/return 等來終止,要麼註釋說明程序將繼續執行到哪個 case 爲止;在一個 switch 塊內,都必須包含一個 default 語句而且放在最後,即便空代碼。
- 在 if/else/for/while/do 語句中必須使用大括號。即便只有一行代碼,避免採用單行的編碼方式:if (condition) statements;
- 表達異常的分支時,少用 if-else 方式,這種方式能夠改寫成:
if (condition)
{
...
return obj;
}
// 接着寫 else 的業務邏輯代碼;
複製代碼
若是非得使用 if()...else if()...else...方式表達邏輯,避免後續代碼維護困難,請勿超過 3 層。
正例:超過 3 層的 if-else 的邏輯判斷代碼可使用衛語句、策略模式、狀態模式等來實現,其中衛語句示例以下:編程
public void today()
{
if (isBusy())
{
return;
}
if (isFree())
{
return;
}
return;
}
複製代碼
- 除經常使用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。不少 if語句內的邏輯至關複雜,閱讀者須要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,若是閱讀者分析邏輯表達式錯誤呢?
正例:
bool existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed)
{
...
}
反例:
if ((file.open(fileName, "w") != null) && (...) || (...))
{
...
}
複製代碼
- 循環體中的語句要考量性能,如下操做盡可能移至循環體外處理,如定義對象、變量、獲取數據庫鏈接,進行沒必要要的 try-catch 操做(這個 try-catch 是否能夠移至循環體外)。
- 避免採用取反邏輯運算符。取反邏輯不利於快速理解,而且取反邏輯寫法必然存在對應的正向邏輯寫法。
正例:使用 if (x < 628) 來表達 x 小於 628。
反例:使用 if (!(x >= 628)) 來表達 x 小於 628。
- 下列情形,須要進行參數校驗:
1) 調用頻次低的方法。
2) 執行時間開銷很大的方法。此情形中,參數校驗時間幾乎能夠忽略不計,但若是由於參數錯誤致使中間執行回退,或者錯誤,那得不償失。
3) 須要極高穩定性和可用性的方法。
4) 對外提供的開放接口,不論是 RPC/API/HTTP 接口。
5) 敏感權限入口。
- 下列情形,不須要進行參數校驗:
1) 極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。
2) 底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。通常 DAO 層與 Service 層都在同一個應用中,部署在同一臺服務器中,因此 DAO 的參數校驗,能夠省略。
3) 被聲明成 private 只會被本身代碼所調用的方法,若是可以肯定調用方法的代碼傳入參數已經作過檢查或者確定不會有問題,此時能夠不校驗參數。
2、異常
(一)異常處理
- 異常不要用來作流程控制,條件控制。異常設計的初衷是解決程序運行中的各類意外狀況,且異常的處理效率比條件判斷方式要低不少。
- catch 時請分清穩定代碼和非穩定代碼,穩定代碼指的是不管如何不會出錯的代碼。對於非穩定代碼的 catch 儘量進行區分異常類型,再作對應的異常處理。對大段代碼進行try-catch,使程序沒法根據不一樣的異常作出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。
正例:用戶註冊的場景中,若是用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上做出分門別類的判斷,並提示給用戶。
- 捕獲異常是爲了處理它,不要捕獲了卻什麼都不處理而拋棄之,若是不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶能夠理解的內容。
- try塊放到了事務代碼中,catch異常後,若是須要回滾事務,必定要注意手動回滾事務。
- finally 塊必須對資源對象、流對象進行關閉,有異常也要作 try-catch。
- 方法的返回值能夠爲 null,不強制返回空集合,或者空對象等,必須添加註釋充分說明什麼狀況下會返回 null 值。
- 對於公司外的 http/api 開放接口必須使用「錯誤碼」;而應用內部推薦異常拋出;跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess()方法、「錯誤碼」、「錯誤簡短信息」。
關於 RPC 方法返回方式使用 Result 方式的理由:
1)使用拋異常返回方式,調用方若是沒有捕獲到就會產生運行時錯誤。
2)若是不加棧信息,只是 new 自定義異常,加入本身的理解的 error message,對於調用端解決問題的幫助不會太多。若是加了棧信息,在頻繁調用出錯的狀況下,數據序列化和傳輸的性能損耗也是問題。
- 避免出現重複的代碼(Don’t Repeat Yourself),即 DRY 原則。
隨意複製和粘貼代碼,必然會致使代碼的重複,在之後須要修改時,須要修改全部的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個類中有多個 public 方法,都須要進行數行相同的參數校驗操做,這個時候請抽取:private bool CheckParam(DTO dto) {...}
(二)日誌規範
- 應用中不可直接使用日誌系統(Log4j、Logback)中的 API,而應依賴使用日誌框架log 中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。
- 日誌文件至少保存 15 天,由於有些異常具有以「周」爲頻次發生的特色。
- 應用中的擴展日誌(如打點、臨時監控、訪問日誌等)命名方式:
appName_logType_logName.log。
logType:日誌類型,如 stats/monitor/access等;
logName:日誌描述。這種命名的好處:經過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找。
複製代碼
- 異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。若是不處理,那麼經過關鍵字 throw 往上拋出。
- 謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info 日誌;若是使用 warn 來記錄剛上線時的業務行爲信息,必定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。大量地輸出無效日誌,不利於系統性能提高,也不利於快速定位錯誤點。記錄日誌時請思考這些日誌真的有人看嗎?看到這條日誌你能作什麼?能不能給問題排查帶來好處?
- 可使用 warn 日誌級別來記錄用戶輸入參數錯誤的狀況,避免用戶投訴時,無所適從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。注意日誌輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。
- 儘可能用英文來描述日誌錯誤信息,若是日誌中的錯誤信息用英文描述不清楚的話使用中文描述便可,不然容易產生歧義。國際化團隊或海外部署的服務器因爲字符集問題,使用全英文來註釋和描述日誌錯誤信息。
3、安全規約
(一)權限規範
- 隸屬於用戶我的的頁面或者功能必須進行權限控制校驗。防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單。
- 用戶敏感數據禁止直接展現,必須對展現數據進行脫敏。中國大陸我的手機號碼顯示爲:156****1234,隱藏中間 4 位,防止隱私泄露。
- 用戶輸入的 SQL 參數嚴格使用參數綁定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 訪問數據庫。
- 用戶請求傳入的任何參數必須作有效性驗證。忽略參數校驗可能致使:
1)page size 過大致使內存溢出
2)惡意 order by 致使數據庫慢查詢
3)任意重定向
4)SQL 注入
5)反序列化注入
6)正則輸入源串拒絕服務 ReDoS
說明:代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,可是若是攻擊人員使用的是特殊構造的字符串來驗證,有可能致使死循環的結果。
- 禁止向 HTML 頁面輸出未經安全過濾或未正確轉義的用戶數據。
- 表單、AJAX 提交必須執行 CSRF 安全驗證。
說明:CSRF(Cross-site request forgery)跨站請求僞造是一類常見編程漏洞。對於存在CSRF漏洞的應用/網站,攻擊者能夠事先構造好URL,只要受害者用戶一訪問,後臺便在用戶不知情的狀況下對數據庫中用戶參數進行相應修改。
- 在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放的機制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷而致使資損。如註冊時發送驗證碼到手機,若是沒有限制次數和頻率,那麼能夠利用此功能騷擾到其它用戶,形成短信資源浪費。
- 發貼、評論、發送即時消息等用戶生成內容的場景必須實現防刷、文本內容違禁詞過濾等風控策略。