目錄

前言
“Clean Code That Works”,來(lái)自于Ron Jeffries這句箴言指導(dǎo)我們寫(xiě)的代碼要整潔有效,Kent Beck把它作為T(mén)DD(Test Driven Development)追求的目標(biāo),BoB大叔(Robert C. Martin)甚至寫(xiě)了一本書(shū)來(lái)闡述他的理解。
整潔的代碼不一定能帶來(lái)更好的性能,更優(yōu)的架構(gòu),但它卻更容易找到性能瓶頸,更容易理解業(yè)務(wù)需求,驅(qū)動(dòng)出更好的架構(gòu)。整潔的代碼是寫(xiě)代碼者對(duì)自己技藝的在意,是對(duì)讀代碼者的尊重。
本文是對(duì)BOB大叔《Clen Code》[1] 一書(shū)部分章節(jié)的一個(gè)簡(jiǎn)單抽取、分層,目的是整潔代碼可以在團(tuán)隊(duì)中更容易推行,本文不會(huì)重復(fù)書(shū)中內(nèi)容,僅提供對(duì)模型的一個(gè)簡(jiǎn)單解釋?zhuān)绻麑?duì)于模型中的細(xì)節(jié)有疑問(wèn),請(qǐng)參考《代碼整潔之道》[1] 。
致謝
本文由李永順編寫(xiě),并由丁輝、范璟瑋、王博、金華、張超、曾亮亮、尉剛強(qiáng)等參與評(píng)審,并提出了非常好的修改意見(jiàn),在此表示誠(chéng)摯的感謝。
但由于時(shí)間的倉(cāng)促及作者水平有限,如果您發(fā)現(xiàn)了本文的錯(cuò)誤,或者有其他更好的意見(jiàn),請(qǐng)第一時(shí)間告訴我們,我們將非常感激.
I 基礎(chǔ)級(jí)
基礎(chǔ)級(jí)主要包括代碼格式、注釋、物理設(shè)計(jì)三部分,這三部分比較容易做到,甚至可以制定為團(tuán)隊(duì)的編碼規(guī)范,保證團(tuán)隊(duì)代碼保持統(tǒng)一風(fēng)格。
1.1 格式
遵循原則:
- 關(guān)系密切內(nèi)容聚合
- 關(guān)系松散內(nèi)容分隔
注意事項(xiàng):
- 編碼時(shí)使用等寬字體
- 替換Tab為4個(gè)空格
- 使用統(tǒng)一的編碼格式:UTF-8, GB2312, GBK
- 使用統(tǒng)一的代碼格式化風(fēng)格。例如經(jīng)典風(fēng)格 K&R, BSD/Allman, GNU, Whitesmiths
- 控制行寬,不需要拖動(dòng)水平滾動(dòng)條查看代碼
- 使用衛(wèi)語(yǔ)句取代嵌套表達(dá)式
1.1.1 橫向格式
使用空格對(duì)內(nèi)容進(jìn)行分隔,使用鋸齒縮緊對(duì)代碼段進(jìn)分隔
反例:
public String toString() {
Point point=new Point();
StringBuilder sb=new StringBuilder();
sb.append("A B C D E F G H");
for(point.x=0;point.x<BOARD_LENGTH;point.x++){
sb.append('\n').append(point.x + 1);
for(point.y=0;point.y<BOARD_WIDTH;point.y++){
sb.append(' ').append(board.get(point).symbol());
}
}
sb.append('\n');
return sb.toString();
}
正例:
public String toString() {
Point point = new Point();
StringBuilder sb = new StringBuilder();
sb.append(" A B C D E F G H");
for (point.x = 0; point.x < BOARD_LENGTH; point.x++) {
sb.append('\n').append(point.x + 1);
for (point.y = 0; point.y < BOARD_WIDTH; point.y++) {
sb.append(' ').append(board.get(point).symbol());
}
}
sb.append('\n');
return sb.toString();
}
1.1.2 縱向格式
使用空行對(duì)內(nèi)容進(jìn)行分隔,函數(shù)或類(lèi)的方法不要太長(zhǎng),盡量能在視野范圍內(nèi)一覽無(wú)余
反例:
public class ComparisonCompactor {
private static final String ELLIPSIS = "...";
private static final String DELTA_END = "]";
private static final String DELTA_START = "[";
private int fContextLength;
private String fExpected;
private String fActual;
private int fPrefix;
private int fSuffix;
@SuppressWarnings("deprecation")
public String compact(String message) {
if (fExpected == null || fActual == null || areStringsEqual()) {
return Assert.format(message, fExpected, fActual);
}
findCommonPrefix();
findCommonSuffix();
String expected = compactString(fExpected);
String actual = compactString(fActual);
return Assert.format(message, expected, actual);
}
private boolean areStringsEqual() {
return fExpected.equals(fActual);
}
}
正例:
public class ComparisonCompactor {
private static final String ELLIPSIS = "...";
private static final String DELTA_END = "]";
private static final String DELTA_START = "[";
private int fContextLength;
private String fExpected;
private String fActual;
private int fPrefix;
private int fSuffix;
@SuppressWarnings("deprecation")
public String compact(String message) {
if (fExpected == null || fActual == null || areStringsEqual()) {
return Assert.format(message, fExpected, fActual);
}
findCommonPrefix();
findCommonSuffix();
String expected = compactString(fExpected);
String actual = compactString(fActual);
return Assert.format(message, expected, actual);
}
private boolean areStringsEqual() {
return fExpected.equals(fActual);
}
}
1.2 注釋
遵循原則:
- 盡量不寫(xiě)注釋?zhuān)瑖L試用代碼自闡述
- 必要時(shí)增加注釋
注意事項(xiàng):
- 擅用源碼管理工具
- 提交代碼時(shí),日志要詳細(xì)
- 避免使用中文注釋?zhuān)ㄒ滓鹱址瘑?wèn)題)
- 確認(rèn)編譯器支持
// -
/*之后有空格,*/之前有空格
1.2.1 好的注釋
- 法律、版權(quán)信息
# /* **************************************************************************
# * *
# * (C) Copyright Paul Mensonides 2002.
# * Distributed under the Boost Software License, Version 1.0. (See
# * accompanying file LICENSE_1_0.txt or copy at
# * http://www.boost.org/LICENSE_1_0.txt)
# * *
# ************************************************************************** */
#
# /* See http://www.boost.org for most recent version. */
#
# ifndef BOOST_PREPROCESSOR_SEQ_FOR_EACH_HPP
# define BOOST_PREPROCESSOR_SEQ_FOR_EACH_HPP
#
# include <boost/preprocessor/arithmetic/dec.hpp>
# include <boost/preprocessor/config/config.hpp>
# include <boost/preprocessor/repetition/for.hpp>
# include <boost/preprocessor/seq/seq.hpp>
# include <boost/preprocessor/seq/size.hpp>
# include <boost/preprocessor/tuple/elem.hpp>
# include <boost/preprocessor/tuple/rem.hpp>
#
- 陷阱、警示
#if (defined(BOOST_MSVC) || (defined(BOOST_INTEL) && defined(_MSC_VER))) && _MSC_VER >= 1300
//
// MSVC supports types which have alignments greater than the normal
// maximum: these are used for example in the types __m64 and __m128
// to provide types with alignment requirements which match the SSE
// registers. Therefore we extend type_with_alignment<> to support
// such types, however, we have to be careful to use a builtin type
// whenever possible otherwise we break previously working code:
// see http://article.gmane.org/gmane.comp.lib.boost.devel/173011
// for an example and test case. Thus types like a8 below will
// be used *only* if the existing implementation can't provide a type
// with suitable alignment. This does mean however, that type_with_alignment<>
// may return a type which cannot be passed through a function call
// by value (and neither can any type containing such a type like
// Boost.Optional). However, this only happens when we have no choice
// in the matter because no other "ordinary" type is available.
//
- 意圖解釋
// Borland specific version, we have this for two reasons:
// 1) The version above doesn't always compile (with the new test cases for example)
// 2) Because of Borlands #pragma option we can create types with alignments that are
// greater that the largest aligned builtin type.
namespace align
{
#pragma option push -a16
struct a2{ short s; };
struct a4{ int s; };
struct a8{ double s; };
struct a16{ long double s; };
#pragma option pop
}
- 性能優(yōu)化代碼
// Fast version of "hash = (65599 * hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c;
- 不易理解代碼
// kk::mm::ss, MM dd, yyyy
std::string timePattern = "\\d{2}:\\d{2}:\\d{2}, \\d{2} \\d{2}, \\d{4}";
1.2.2 不好的注釋
- 日志型注釋 -> 刪除,使用源碼管理工具記錄
反例:
正例:/** *c00kiemon5ter 2015-9-20 add SquareState * c00kiemon5ter 2015-10-1 change the symbol */ public enum SquareState { BLACK('●'), WHITE('○'), // BLACK('x'), // WHITE('o'), PSSBL('.'), EMPTY(' '); private final char symbol; SquareState(char symbol) { this.symbol = symbol; } public char symbol() { return this.symbol; } }public enum SquareState { BLACK('●'), WHITE('○'), PSSBL('.'), EMPTY(' '); private final char symbol; SquareState(char symbol) { this.symbol = symbol; } public char symbol() { return this.symbol; } }
$git commit -m "change BLACK symbol from x to ●, WHITE from ○ to O"
-
歸屬、簽名 -> 刪除,源碼管理工具自動(dòng)記錄
反例:/** * @author c00kiemon5ter */ public enum Player { BLACK(SquareState.BLACK), WHITE(SquareState.WHITE); ... }正例:
public enum Player { BLACK(SquareState.BLACK), WHITE(SquareState.WHITE); ... }$git config --global user.name c00kiemon5ter 注釋掉的代碼 -> 刪除,使用源碼管理工具保存
反例:
public Point evalMove() {
AbstractSearcher searcher;
Evaluation evalfunc;
searcher = new NegaMax();
//evalfunc = new ScoreEval();
evalfunc = new ScoreDiffEval();
//evalfunc = new ScoreCornerWeightEval();
return searcher.simpleSearch(board, player, depth, evalfunc).getPoint();
}
正例:
public Point evalMove() {
AbstractSearcher searcher;
Evaluation evalfunc;
searcher = new NegaMax();
evalfunc = new ScoreDiffEval();
return searcher.simpleSearch(board, player, depth, evalfunc).getPoint();
}
-
函數(shù)頭 -> 嘗試使用更好的函數(shù)名,更好參數(shù)名,更少參數(shù)替換注釋
反例:/*********************************************************************** * function Name: GetCharge * function description:get total Rental charge * return value:WORD32 * other * date version author contents * ----------------------------------------------- * 2014/11/28 V1.0 XXXX XXXX *************************************************************************/ WORD32 GetCharge(T_Customer* tCustomer) { ... }正例:
WORD32 GetTotalRentalCharge(Customer* customer) { ... } 位置標(biāo)記 -> 刪除,簡(jiǎn)化邏輯
反例:
double getPayAmount(){
double result;
if(isDead){
result = deadAmount();
}
else{//!isDead
if(isSeparated){
result = separatedAmount();
}
else{ //!isSeparated && !//!isDead
if(isRetired){
result = retiredAmount();
}
else{ //!isSeparated && !//!isDead && !isRetired
result = normalPayAmount();
}
}
}
return result;
}
正例:
double getPayAmount(){
if(isDead) return deadAmount();
if(isSeparated) return separatedAmount();
if(isRetired) return retiredAmount();
return normalPayAmount();
}
- 過(guò)時(shí)、誤導(dǎo)性注釋 -> 刪除
反例:
// Utility method that returns when this.closed is true. Throws an exception
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis) throws Exception
{
if(!closed)
{
wait(timeoutMillis);
if(!closed)
throw new Exception("MockResponseSender could not be closed");
}
}
正例:
public synchronized void waitForClose(final long timeoutMillis) throws Exception
{
if(!closed)
{
wait(timeoutMillis);
if(!closed)
throw new Exception("MockResponseSender could not be closed");
}
}
- 多余、廢話注釋 -> 刪除
反例:
class GTEST_API_ AssertionResult
{
public:
// Copy constructor.
// Used in EXPECT_TRUE/FALSE(assertion_result).
AssertionResult(const AssertionResult& other);
// Used in the EXPECT_TRUE/FALSE(bool_expression).
explicit AssertionResult(bool success) : success_(success) {}
// Returns true iff the assertion succeeded.
operator bool() const { return success_; } // NOLINT
private:
// Stores result of the assertion predicate.
bool success_;
};
正例:
class GTEST_API_ AssertionResult
{
public:
AssertionResult(const AssertionResult& other);
explicit AssertionResult(bool success) : success_(success) {}
operator bool() const { return success_; }
private:
bool success_;
};
1.3 物理設(shè)計(jì)
遵循原則[2]:
- 頭文件編譯自滿足(C/C++)
- 文件職責(zé)單一
- 文件最小依賴
- 文件信息隱藏
注意事項(xiàng):
- 包含文件時(shí),確保路徑名、文件名大小寫(xiě)敏感
- 文件路徑分隔符使用
/,不使用\ - 路徑名一律使用小寫(xiě)、下劃線(
_)或中劃線風(fēng)格(-) - 文件名與程序?qū)嶓w名稱(chēng)一致
1.3.1 頭文件編譯自滿足(C/C++)
對(duì)于C/C++語(yǔ)言頭文件編譯自滿足,即頭文件可以單獨(dú)編譯成功。
反例:
#ifndef _INCL_POSITION_H_
#define _INCL_POSITION_H_
#include "base/Role.h"
struct Position : Coordinate, Orientation
{
Position(int x, int y, int z, const Orientation& d);
bool operator==(const Position& rhs) const;
IMPL_ROLE(Coordinate);
IMPL_ROLE(Orientation);
};
#endif
正例:
#ifndef _INCL_POSITION_H_
#define _INCL_POSITION_H_
#include "Coordinate.h"
#include "Orientation.h"
#include "base/Role.h"
struct Position : Coordinate, Orientation
{
Position(int x, int y, int z, const Orientation& d);
bool operator==(const Position& rhs) const;
IMPL_ROLE(Coordinate);
IMPL_ROLE(Orientation);
};
#endif
1.3.2 文件設(shè)計(jì)職責(zé)單一
文件設(shè)計(jì)職責(zé)單一,是指文件中對(duì)于對(duì)于用戶公開(kāi)的信息,應(yīng)該是一個(gè)概念,避免把不相關(guān)的概念糅合在一個(gè)文件中,文件間增加不必要的依賴
反例:
UnmannedAircraft.h
#ifndef _INCL_UNMANNED_AIRCRAFT_H_
#define _INCL_UNMANNED_AIRCRAFT_H_
#include "Coordinate.h"
#include "Orientation.h"
#include "base/Role.h"
struct Instruction;
struct Position : Coordinate, Orientation
{
Position(int x, int y, int z, const Orientation& d);
bool operator==(const Position& rhs) const;
IMPL_ROLE(Coordinate);
IMPL_ROLE(Orientation);
};
struct UnmannedAircraft
{
UnmannedAircraft();
void on(const Instruction&);
const Position& getPosition() const;
private:
Position position;
};
#endif
正例:
Position.h
#ifndef _INCL_POSITION_H_
#define _INCL_POSITION_H_
#include "Coordinate.h"
#include "Orientation.h"
#include "base/Role.h"
struct Position : Coordinate, Orientation
{
Position(int x, int y, int z, const Orientation& d);
bool operator==(const Position& rhs) const;
IMPL_ROLE(Coordinate);
IMPL_ROLE(Orientation);
};
#endif
UnmannedAircraft.h
#ifndef _INCL_UNMANNED_AIRCRAFT_H_
#define _INCL_UNMANNED_AIRCRAFT_H_
#include "Position.h"
struct Instruction;
struct UnmannedAircraft
{
UnmannedAircraft();
void on(const Instruction&);
const Position& getPosition() const;
private:
Position position;
};
#endif
1.3.3 僅包含需要的文件
- 文件設(shè)計(jì)時(shí),應(yīng)遵循最小依賴原則,僅包含必須的文件即可。
反例:
正例:#ifndef _INCL_UNMANNED_AIRCRAFT_H_ #define _INCL_UNMANNED_AIRCRAFT_H_ #include "Position.h" #include "Orientation.h" #include "Coordinate.h" #include "Instruction.h" struct UnmannedAircraft { UnmannedAircraft(); void on(const Instruction&); const Position& getPosition() const; private: Position position; }; #endif#ifndef _INCL_UNMANNED_AIRCRAFT_H_ #define _INCL_UNMANNED_AIRCRAFT_H_ #include "Position.h" struct Instruction; struct UnmannedAircraft { UnmannedAircraft(); void on(const Instruction&); const Position& getPosition() const; private: Position position; }; #endif - 特別的,對(duì)于C++而言,可以使用類(lèi)或者結(jié)構(gòu)體前置聲明,而不包含頭文件,降低編譯依賴。該類(lèi)依賴被稱(chēng)為弱依賴,編譯時(shí)不需要知道實(shí)體的真實(shí)大小,僅提供一個(gè)符號(hào)即可,主要有:
- 指針
- 引用
- 返回值
- 函數(shù)參數(shù)
反例:
#ifndef _INCL_INSTRUCTION_H_
#define _INCL_INSTRUCTION_H_
#include "Coordinate.h"
#include "Orientation.h"
struct Instruction
{
virtual void exec(Coordinate&, Orientation&) const = 0;
virtual ~Instruction() {}
};
#endif
正例:
#ifndef _INCL_INSTRUCTION_H_
#define _INCL_INSTRUCTION_H_
struct Coordinate;
struct Orientation;
struct Instruction
{
virtual void exec(Coordinate&, Orientation&) const = 0;
virtual ~Instruction() {}
};
#endif
1.3.4 僅公開(kāi)用戶需要的接口
- 文件設(shè)計(jì)時(shí),應(yīng)遵循信息隱藏原則,僅公開(kāi)用戶需要的接口,對(duì)于其他信息盡量隱藏,以減少不必要的依賴。 特別的,對(duì)于C語(yǔ)言頭文件中僅公開(kāi)用戶需要接口,其他函數(shù)隱藏在源文件中。
反例:
struct RepeatableInstruction : Instruction
{
RepeatableInstruction(const Instruction&, int n);
virtual void exec(Coordinate&, Orientation&) const;
bool isOutOfBound() const;
private:
const Instruction& ins;
const int n;
};
正例:
struct RepeatableInstruction : Instruction
{
RepeatableInstruction(const Instruction&, int n);
private:
virtual void exec(Coordinate&, Orientation&) const;
bool isOutOfBound() const;
private:
const Instruction& ins;
const int n;
};
-
特別的,對(duì)于C可以使用static對(duì)編譯單元中全局變量、函數(shù)等進(jìn)行隱藏,對(duì)于支持面向?qū)ο笳Z(yǔ)言則使用其封裝特性即可。
反例:
BOOLEAN isGbr(BYTE qci) { return qci >= 1 && qci <= 4; } BOOLEAN isGbrBitRateValid(const GbrIE* gbrIE) { ASSERT_VALID_PTR_BOOL(gbrIE); return gbrIE->dlGbr <= gbrIE->dlMbr && gbrIE->ulGbr <= gbrIE->ulMbr; } BOOLEAN isGbrIEValid(const QosPara* qosPara) { if(!isGbr(qosPara->qci)) return TRUE; if(qosPara->grbIEPresent == 0) return TRUE; return isGbrBitRateValid(&qosPara->gbrIE); }正例:
static BOOLEAN isGbr(BYTE qci) { return qci >= 1 && qci <= 4; } static BOOLEAN isGbrBitRateValid(const GbrIE* gbrIE) { ASSERT_VALID_PTR_BOOL(gbrIE); return gbrIE->dlGbr <= gbrIE->dlMbr && gbrIE->ulGbr <= gbrIE->ulMbr; } BOOLEAN isGbrIEValid(const QosPara* qosPara) { if(!isGbr(qosPara->qci)) return TRUE; if(qosPara->grbIEPresent == 0) return TRUE; return isGbrBitRateValid(&qosPara->gbrIE); }
Clean Code Style 進(jìn)階篇
Clean Code Style 高階篇
參考文獻(xiàn):