Clean Code Style - 基礎(chǔ)篇

目錄

前言

“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 好的注釋

  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>
#
  1. 陷阱、警示
#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.
//
  1. 意圖解釋
// 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
    }
  1. 性能優(yōu)化代碼
// Fast version of "hash = (65599 * hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c;
  1. 不易理解代碼
// kk::mm::ss, MM dd, yyyy
std::string timePattern = "\\d{2}:\\d{2}:\\d{2}, \\d{2} \\d{2}, \\d{4}";

1.2.2 不好的注釋

  1. 日志型注釋 -> 刪除,使用源碼管理工具記錄
    反例:
    /**
     *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"
  1. 歸屬、簽名 -> 刪除,源碼管理工具自動(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
    
  2. 注釋掉的代碼 -> 刪除,使用源碼管理工具保存
    反例:

    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();
    }
  1. 函數(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)
    {
        ...
    }
    
  2. 位置標(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();
    }
  1. 過(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"); 
    }
}
  1. 多余、廢話注釋 -> 刪除
    反例:
    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 僅包含需要的文件

  1. 文件設(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
    
  2. 特別的,對(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)用戶需要的接口

  1. 文件設(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;
  };
  1. 特別的,對(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):


  1. Robert C.Martin-代碼整潔之道 ? ?

  2. 劉光聰-cpp programming style ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ## 可重入函數(shù) ### 可重入性的理解 若一個(gè)程序或子程序可以安全的被并行執(zhí)行,則稱(chēng)其為可重入的;即當(dāng)該子程序正...
    夏至亦韻閱讀 807評(píng)論 0 0
  • 目錄 前言 “Clean Code That Works”,來(lái)自于Ron Jeffries這句箴言指導(dǎo)我們寫(xiě)的代碼...
    李永順閱讀 1,241評(píng)論 0 6
  • 說(shuō)明本次redis集群安裝在rhel6.8 64位機(jī)器上,redis版本為3.2.8,redis的gem文件版本為...
    讀或?qū)?/span>閱讀 15,621評(píng)論 3 9
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,551評(píng)論 19 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,049評(píng)論 0 9

友情鏈接更多精彩內(nèi)容