Clean Code Style - 基础篇

目录

前言

“Clean Code That Works”,来自于Ron Jeffries这句箴言指导我们写的代码要整洁有效,Kent Beck把它作为TDD(Test Driven Development)追求的目标,BoB大叔(Robert C. Martin)甚至写了一本书来阐述他的理解。
整洁的代码不一定能带来更好的性能,更优的架构,但它却更容易找到性能瓶颈,更容易理解业务需求,驱动出更好的架构。整洁的代码是写代码者对自己技艺的在意,是对读代码者的尊重。
本文是对BOB大叔《Clen Code》[1] 一书部分章节的一个简单抽取、分层,目的是整洁代码可以在团队中更容易推行,本文不会重复书中内容,仅提供对模型的一个简单解释,如果对于模型中的细节有疑问,请参考《代码整洁之道》[1]

致谢

本文由李永顺编写,并由丁辉、范璟玮、王博、金华、张超、曾亮亮、尉刚强等参与评审,并提出了非常好的修改意见,在此表示诚挚的感谢。
但由于时间的仓促及作者水平有限,如果您发现了本文的错误,或者有其他更好的意见,请第一时间告诉我们,我们将非常感激.


I 基础级

基础级主要包括代码格式、注释、物理设计三部分,这三部分比较容易做到,甚至可以制定为团队的编码规范,保证团队代码保持统一风格。

1.1 格式

遵循原则:

  • 关系密切内容聚合
  • 关系松散内容分隔

注意事项:

  • 编码时使用等宽字体
  • 替换Tab为4个空格
  • 使用统一的编码格式:UTF-8, GB2312, GBK
  • 使用统一的代码格式化风格。例如经典风格 K&R, BSD/Allman, GNU, Whitesmiths
  • 控制行宽,不需要拖动水平滚动条查看代码
  • 使用卫语句取代嵌套表达式

1.1.1 横向格式

使用空格对内容进行分隔,使用锯齿缩紧对代码段进分隔
反例:

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 纵向格式

使用空行对内容进行分隔,函数或类的方法不要太长,尽量能在视野范围内一览无余
反例:

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 注释

遵循原则:

  • 尽量不写注释,尝试用代码自阐述
  • 必要时增加注释

注意事项:

  • 擅用源码管理工具
  • 提交代码时,日志要详细
  • 避免使用中文注释(易引起字符集问题)
  • 确认编译器支持//
  • /*之后有空格, */之前有空格

1.2.1 好的注释

  1. 法律、版权信息
# /* **************************************************************************
#  *                                                                          *
#  *     (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. 性能优化代码
// 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. 归属、签名 -> 删除,源码管理工具自动记录
    反例:

    /**
     * @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. 函数头 -> 尝试使用更好的函数名,更好参数名,更少参数替换注释
    反例:

    /***********************************************************************
    * 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. 位置标记 -> 删除,简化逻辑
    反例:

 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. 过时、误导性注释 -> 删除
    反例:
// 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 物理设计

遵循原则[2]

  • 头文件编译自满足(C/C++)
  • 文件职责单一
  • 文件最小依赖
  • 文件信息隐藏

注意事项:

  • 包含文件时,确保路径名、文件名大小写敏感
  • 文件路径分隔符使用/,不使用\
  • 路径名一律使用小写、下划线(_)或中划线风格(-)
  • 文件名与程序实体名称一致

1.3.1 头文件编译自满足(C/C++)

对于C/C++语言头文件编译自满足,即头文件可以单独编译成功。
反例:

#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 文件设计职责单一

文件设计职责单一,是指文件中对于对于用户公开的信息,应该是一个概念,避免把不相关的概念糅合在一个文件中,文件间增加不必要的依赖
反例:
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. 文件设计时,应遵循最小依赖原则,仅包含必须的文件即可。
    反例:
    #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. 特别的,对于C++而言,可以使用类或者结构体前置声明,而不包含头文件,降低编译依赖。该类依赖被称为弱依赖,编译时不需要知道实体的真实大小,仅提供一个符号即可,主要有:
    • 指针
    • 引用
    • 返回值
    • 函数参数

反例:

    #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 仅公开用户需要的接口

  1. 文件设计时,应遵循信息隐藏原则,仅公开用户需要的接口,对于其他信息尽量隐藏,以减少不必要的依赖。 特别的,对于C语言头文件中仅公开用户需要接口,其他函数隐藏在源文件中。

反例:

    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. 特别的,对于C可以使用static对编译单元中全局变量、函数等进行隐藏,对于支持面向对象语言则使用其封装特性即可。

    反例:

    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 进阶篇
Clean Code Style 高阶篇

参考文献:


  1. Robert C.Martin-代码整洁之道

  2. 刘光聪-cpp programming style

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容