ESP8266開發之旅 進階篇⑤ 代碼規範 —— 像寫文章同樣優美

1.前言

    以前,一直在跟大夥分享怎麼去玩藍牙模塊,怎麼去玩wifi模塊,怎麼去玩json,而後有不少小夥伴就留言各類問題或者說直接懟他的代碼過來讓我看,而後我就一臉懵逼(代碼中處處各類abcd變量,各類一個方法幾百行,也沒有什麼註釋,我心中一萬隻萬馬奔騰)。因此就有了此次的主題,代碼規範(固然,這是我本身的代碼規範經驗,只是借鑑經驗),教了你們怎麼去作東西,反而忽略了最基本的東西。
    特別是出來工做以後,我就以爲代碼規範比作需求業務還要重要。
    首先,方便他人。一個項目每每是由多人一塊兒完成的,代碼規範,會讓你們交流起來更加簡單有效,別人接手你的代碼也比較容易。
    其次,方便擴展。一個好的項目,代碼仍是得具備必定的擴展性,不少時候項目是迭代開發的,分層抽象仍是很重要。
    最後,方便本身。不少開發人員都會有這種感受,本身寫的代碼,過一段時間回來再看的時候,就會有點懵逼的感受(每每就是由於代碼不夠規範,也沒什麼註釋,連本身都看不懂了)。
    此次計劃分三篇來分享,儘可能歸納常見的規範點,主題分爲三部分:json

  • 工程代碼規範
  • 函數方法規範
  • 兼容性規範

2.工程代碼規範

    工程代碼規範,主要是針對整個項目來規範。api

2.1 文件命名

2.1.1 錯誤寫法

  • 博主還記得大學的時候很是喜歡用abcdxy這些單一的字母來命名文件,隔一段時間再去查看代碼,有點懵逼。

2.1.2 推薦寫法

  • 駝峯式大小寫法(每一個單詞的首字符是大寫,其他用小學),好比藍牙燈項目,博主喜歡命名爲BlueToothLedPro,8266燈項目,博主喜歡命名爲ESP8266LedPro,簡單明瞭。

2.2 文件註釋

    博主常常看到這樣的現象——不少童鞋發過來的主工程代碼基本上都是沒有註釋的。數組

2.2.1 錯誤演示

    好比一個藍牙燈項目,不合理代碼基本上是這樣的:函數

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
............

    是否是很是懵逼,徹底不知道這個項目是作什麼的?工具

2.2.2 推薦寫法

參考寫法以下:ui

/**
* 日期:2017/09/25
* 功能:wifi+weather+oled   8266端
* 做者:單片機菜鳥
* 16X16點陣顯示 取模方式 陰碼+逐行式+順向
**/
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
.............

    通常規範的寫法就是三個w(who做者,what time什麼時間寫的,doing what 這段代碼主要是什麼功能,固然若是有版本迭代的,也請加入版本說明)。
    這樣別人其實不用深刻看你的代碼就知道你這個文件的代碼主要有什麼用,完成什麼功能。this

2.3 常量

    常量,基本上都是一些固定的配置數據(波特率,數組大小等)或者經常使用不變的字符串,我推薦用全大寫字母寫法。請看代碼:debug

const unsigned long BAUD_RATE = 115200;                   // serial connection speed
const unsigned long HTTP_TIMEOUT = 5000;               // max respone time from server
const size_t MAX_CONTENT_SIZE = 1000;                   // max size of the HTTP response
 
const char* host = "api.seniverse.com";
const char* APIKEY = "wcmquevztdy1jpca";        //API KEY
const char* city = "guangzhou";
const char* language = "en";//zh-Hans 簡體中文  會顯示亂碼

    推薦全大寫寫法的緣由就是咱們的變量基本上都是小寫居多,而爲了區分常量,就全大寫最爲穩妥。
    固然我這裏仍是有問題,有大寫,也有小寫,做爲錯誤示範了,儘可能全大寫規範。設計

2.4 變量

    不少人初學者習慣寫法都是abdcxyi這些常見字母去命名變量,好比:調試

int a = 0;
char b;
int i = 0;//循環常常見到這個

    我我的推薦的寫法就是前綴m+名稱。
    好比用戶數據,就能夠命名爲 mUserData。
    好比最近時間,就能夠命名爲mLastTime。

int mUserData;//用戶數據
int mLastTime;//上一次時間

3.函數方法規範

    函數方法是一個功能的描述,接下來我會主要分幾個方面講解個人函數規範。

3.1 函數方法註釋

    這是最重要的一點,首先你最起碼得讓別人知道這個函數功能是什麼,須要傳入什麼參數,若是有返回值,返回什麼內容。

/**
* @desc 發送請求指令
* @param host 請求後臺接口
* @param cityid 城市id
* @param apiKey 請求後臺接口須要識別的密鑰
* @return 請求是否成功
*/
bool sendRequest(const char* host, const char* cityid, const char* apiKey) {
  // We now create a URI for the request
  //心知天氣
  String GetUrl = "/v3/weather/now.json?key=";
  GetUrl += apiKey;
  GetUrl += "&location=";
  GetUrl += city;
  GetUrl += "&language=";
  GetUrl += language;
  // This will send the request to the server
  client.print(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");
  DebugPrintln("create a request:");
  DebugPrintln(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n");
  delay(1000);
  return true;
}
  • desc 表示方法功能描述
  • param 表示輸入參數描述
  • return 有返回值的話,返回值描述

3.2 函數方法體

    通常來講,一個方法體功能越簡單越好,這是設計原則裏面的單一原則,儘可能去細化。
    我常常看到不少人寫的函數方法,都是從頭至尾一口氣把全部功能的寫進去了,估計有個幾百行。
    個人建議就是一個方法體儘可能控制代碼在幾十行左右,一個電腦屏幕就能看完。實在代碼太多了,考慮函數方法拆分。

3.3 函數拆分

    函數拆分的前提是函數完成單一的功能。好比我比較喜歡這樣的寫法:

/**
* @desc 方法功能描述
* @param param 參數
* @return 無
*/
void handleFunction(param) {
  handleFunctionA(param);
    handleFunctionB(param);
    handleFunctionC(param);
}
 
/**
* @desc 方法A功能描述
* @param param 參數
* @return 無
*/
void handleFunctionA(param) {
  
}
 
/**
* @desc 方法B功能描述
* @param param 參數
* @return 無
*/
void handleFunctionB(param) {
  
}
 
/**
* @desc 方法C功能描述
* @param param 參數
* @return 無
*/
void handleFunctionC(param) {
  
}

    每一個功能是一個小方法,而後一個大方法去調用這些小方法完成所需功能。好比wifi模塊獲取天氣信息這個功能,咱們的方法就能夠分爲這樣:

/**
* @desc 獲取天氣信息
* @param param 參數
* @return 無
*/
void getWeather(param) {
  sendRequest(param);
  readReponseContent(param);
  parseReponseContent(param);
}
 
 
/**
* @desc 發送請求
* @param param 參數
* @return 無
*/
void sendRequest(param) {
  
}
 
/**
* @desc 讀取響應數據
* @param param 參數
* @return 無
*/
void readReponseContent(param) {
  
}
 
/**
* @desc 解析響應數據
* @param param 參數
* @return 無
*/
void parseReponseContent(param) {
  
}

    大方法負責管理整個獲取天氣流程,小方法負責各自的業務,包括髮起請求,獲取請求響應數據,解析響應數據。
    重點說一下,拆分方法的前提是功能的細分。

3.4 函數參數

    可能不少人不會在乎這一點,反正是參數,只須要不斷往裏面加就能夠了。
    其實參數在5個之內還好,超過5個以後你就會發現方法名字後面連着一坨長長的東西。
    這還不是問題所在,那麼問題來了。假設你這個方法在不少地方都調用了(工具類方法常常會這樣,就是那麼浪),那麼你就得考慮假設我要加多幾個參數,是否是意味着我每一個調用的地方都得改一下(固然,你可能會說,我能夠從新寫一個新方法,固然你喜歡也能夠了,可是恰好這個方法是須要全局改動的,那麼你就準備gg)。
    對於這種沒有超過5個參數的,個人作法仍是直接放在方法裏面。
    對於超過5個參數的,我建議可使用一個結構體變量。廢話少說,看我例子。

/**
* @desc 方法功能描述
* @param param1 參數1
* @param param2 參數2
* @param param3 參數3
* @param param4 參數4
* @param param5 參數5
* @return 無
*/
void handleFunctionA(param1,param2,param3,param4,param5){
}
 
 
/**
* @desc 方法B輸入參數
*/
struct FunctionBParams {
    param1;//參數1
    param2;//參數2
    param3;//參數3
    param4;//參數4
    param5;//參數5
    param6;//參數6
};
 
/**
* @desc 方法功能描述
* @param FunctionBParams 參數
* @return 無
*/
void handleFunctionB(FunctionBParams param){
}

    這樣就對參數作了一個擴展,不須要改動原來的調用邏輯。

3.5 if else or switch case 選擇困難症

    個人建議就是三個之內的判斷用 if else,超過三個以後的判斷就用switch case(由於每每超過三個的後面還會陸續有判斷)。
    並且,出現頻率比較高的條件優先排在前面,減小判斷次數。
    其次,建議case 後面的常量不要是 0 1 2, 最好仍是定義好具體的常量含義,這樣比較清晰,避免使用魔法數字

4.兼容性規範

    兼容性講究的是多平臺性,也就是時常說的一套代碼多個平臺(arduino)上運行。
    咱們都知道,arduino有uno mega mini等,每一款都有它本身規定好的硬件資源,要想一套代碼能在多個平臺上運行,那就得意味着你的代碼得適應多平臺。
    解決以上的問題,關鍵仍是利用好預編譯指令 #define #ifdef #endif。
    預編譯命令的好處在於根據你的配置來把不一樣的代碼段編譯進最終的代碼中去,能夠實現咱們要求的多平臺性。
    接下來我截取了一下我在wifi lamp裏面的代碼:

/**
* 日期:2017/09/14
* 功能:wifi lamp arduino端
* 做者:單片機菜鳥
**/
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
  
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
const size_t MAX_CONTENT_SIZE = 50;
const size_t t_bright=1,t_color=2,t_frequency=3,t_switch=4;
 
//#define UNO      //uncomment this line when you use it with UNO board
#define MEGA    //uncomment this line when you use it with MEGA board
 
#ifdef UNO
 SoftwareSerial mySerial(10,11);
#endif
  
#ifdef UNO
#define WifiSerial  Serial
#define MyDebugSerial mySerial
#endif
   
#ifdef MEGA
#define WifiSerial Serial1
#define MyDebugSerial Serial
#endif  
 
//該條語句用於使能DEBUG輸出信息,屏蔽掉就不會輸出debug調試信息
#define DEBUG
//該條語句用於使能是共陰RGB  屏蔽掉就是共陽RGB
//#define COMMON_GND
 
#ifdef DEBUG
#define DBGLN(message)    MyDebugSerial.println(message)
#else
#define DBGLN(message)
#endif
 
#ifdef UNO
#define PIN_RED 3 //red 引腳
#define PIN_GREEN 5 //green 引腳
#define PIN_BLUE 6 //blue 引腳
#define PIN_ENABLE 9  //使能引腳 pwm控制亮度
#else
#define PIN_RED 2
#define PIN_GREEN 3
#define PIN_BLUE 4
#define PIN_ENABLE 5  
#endif

    裏面作了一個開關,主要是兼容UNO和MEGA兩個平臺,實現一鍵切換。
    而後在使用的時候就不用考慮是哪個平臺。
    而後就是使用DEBUG功能,以及不刪掉調試代碼的狀況下去掉調試信息(把define debug那句代碼註釋掉)。
    接下來,由於我在作RGB燈的時候也考慮共陰共陽的問題,那麼怎麼去考慮兼容性呢,仍是利用強大的預編譯三劍客命令。

/**
* 控制RGB顏色
*/
void colorRGB(int red, int green, int blue){
  #ifdef COMMON_GND
     analogWrite(PIN_RED,constrain(red,0,255));
     analogWrite(PIN_GREEN,constrain(green,0,255));
     analogWrite(PIN_BLUE,constrain(blue,0,255));
  #else
     analogWrite(PIN_RED,constrain(255-red,0,255));
     analogWrite(PIN_GREEN,constrain(255-green,0,255));
     analogWrite(PIN_BLUE,constrain(255-blue,0,255));
  #endif
}

    RGB代碼中兼容共陰共陽,而後設置一個開關切換就能夠了,預編譯的好處就是根據你的配置來編譯出你要基於某個平臺的代碼而實現多平臺。

5.總結

代碼規範,就跟寫文章同樣,一樣的題材,不同的寫做方式,獲得的分數也是不同的。雖然篇章簡潔,可是內容仍是須要仔細斟酌的。

相關文章
相關標籤/搜索