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.总结

代码规范,就跟写文章同样,一样的题材,不同的写做方式,获得的分数也是不同的。虽然篇章简洁,可是内容仍是须要仔细斟酌的。

相关文章
相关标签/搜索