结合工程实践的选题,我选择的是一个开源的C++轻量级网络框架——ZLToolKit。下面按照所给的要求依次展开(以下均以Google的C++编码规范为标准):
1.根据其编程语言或项目特点,分析其在源代码目录结构、文件名/类名/函数名/变量名等命名、接口定义规范和单元测试组织形式等方面的做法和特点
src文件夹下的源代码目录结构如下:
src
|
|-- NetWork # 网络模块
| |-- Socket.cpp # 套接字抽象封装,包含了TCP服务器/客户端,UDP套接字
| |-- Socket.h
| |-- sockutil.cpp # 系统网络相关API的统一封装
| |-- sockutil.h
| |-- TcpClient.cpp # TCP客户端封装,派生该类可以很容易实现客户端程序
| |-- TcpClient.h
| |-- TcpLimitedSession.h # 派生于TcpSession,该模板类可以全局限制会话数量
| |-- TcpServer.h # TCP服务器模板类,可以很容易就实现一个高性能私有协议服务器
| |-- TcpSession.h # TCP服务私有协议实现会话基类,用于处理TCP长连接数据及响应
|
|-- Poller # 主线程事件轮询模块
| |-- EventPoller.cpp # 主线程,所有网络事件由此线程轮询并触发
| |-- EventPoller.h
| |-- Pipe.cpp # 管道的对象封装
| |-- Pipe.h
| |-- PipeWrap.cpp # 管道的包装,windows下由socket模拟
| |-- SelectWrap.cpp # select 模型的简单包装
| |-- SelectWrap.h
| |-- Timer.cpp # 在主线程触发的定时器
| |-- Timer.h
|
|-- Thread # 线程模块
| |-- AsyncTaskThread.cpp # 后台异步任务线程,可以提交一个可定时重复的任务后台执行
| |-- AsyncTaskThread.h
| |-- rwmutex.h # 读写锁,实验性质的
| |-- semaphore.h # 信号量,由条件变量实现
| |-- spin_mutex.h # 自旋锁,在低延时临界区适用,单核/低性能设备慎用
| |-- TaskQueue.h # functional的任务列队
| |-- threadgroup.h # 线程组,移植自boost
| |-- ThreadPool.h # 线程池,可以输入functional任务至后台线程执行
| |-- WorkThreadPool.cpp # 获取一个可用的线程池(可以加入线程负载均衡分配算法)
| |-- WorkThreadPool.h
|
|-- Util # 工具模块
|-- File.cpp # 文件/目录操作模块
|-- File.h
|-- function_traits.h # 函数、lambda转functional
|-- logger.h # 日志模块
|-- MD5.cpp # md5加密模块
|-- MD5.h
|-- mini.h # ini配置文件读写模块,支持unix/windows格式的回车符
|-- NoticeCenter.h # 消息广播器,可以广播传递任意个数任意类型参数
|-- onceToken.h # 使用RAII模式实现,可以在对象构造和析构时执行一段代码
|-- ResourcePool.h # 基于智能指针实现的一个循环池,不需要手动回收对象
|-- RingBuffer.h # 环形缓冲,可以自适应大小,适用于GOP缓存等
|-- SqlConnection.cpp # mysql客户端
|-- SqlConnection.h
|-- SqlPool.h # mysql连接池,以及简单易用的sql语句生成工具
|-- SSLBox.cpp # openssl的黑盒封装,屏蔽了ssl握手细节,支持多线程
|-- SSLBox.h
|-- TimeTicker.h # 计时器,可以用于统计函数执行时间
|-- util.cpp # 其他一些工具代码,适配了多种系统
|-- util.h
|-- uv_errno.cpp # 提取自libuv的错误代码系统,主要是为了兼容windows
|-- uv_errno.h
可以看到,源码的模块划分主要以原作者设计实现时的功能模块划分为导向。
文件名:按照Google的C++编码标准,文件名应当全部使用小写。而该源码文件命名较为随意,一些采用大写,一些采用小写,不符合规范要求
类型名:按照Google的C++编码标准,类型命名应当每个单词首字母大写,不含下划线,以名词形式,且所有类型命名 —— 类, 结构体, 类型定义 (typedef
), 枚举等均使用相同约定。
该源码一些类型的代码如下所示,可以看出类型命名基本依照现有的标准。
class SocketFlags{ public: SocketFlags(int flags):_flags(flags){}; ~SocketFlags(){} int _flags; }; class MutexWrapper { public: MutexWrapper(bool enable){ _enable = enable; } ~MutexWrapper(){} inline void lock(){ if(_enable){ _mtx.lock(); } } inline void unlock(){ if(_enable){ _mtx.unlock(); } } private: bool _enable; Mtx _mtx; }; typedef enum { Err_success = 0, //成功 Err_eof, //eof Err_timeout, //超时 Err_refused,//连接别拒绝 Err_dns,//dns解析失败 Err_shutdown,//主动关闭 Err_other = 0xFF,//其他错误 } ErrCode;
函数名:按照标准,常规函数每个单词首字母大写,使用命令式语气,比如:OpenFile() CheckFileName(),而存取函数或短小的内联函数使用小写加下 划线,且与访问变量相吻合,比如 set_num_errors()。该源码的部分函数声明或调用如下:
void Socket::setOnErr(const onErrCB &cb); void Socket::setOnAccept(const onAcceptCB &cb); void Socket::setOnFlush(const onFlush &cb); //设置Socket生成拦截器 void Socket::setOnBeforeAccept(const onBeforeAcceptCB &cb);
setsockopt(sockFd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&imr, sizeof (struct ip_mreq));
可以看到,该源码中函数的命名主要依据的时驼峰式的命名法,但并未统一,时而依照驼峰命名法,时而全部小写,不符合标准。
变量名:按照标准,变量名一律小写,单词用下划线相连,例如:int player_id; string table_name;特殊的是类成员变量,后跟下划线区别普通变量,比 player_name_ player_id_。该源码的变量命名基本按照了小写的要求,类中的变量命名也通过下划线与普通变量区别开来:
class SockException: public std::exception { public: SockException(ErrCode errCode = Err_success, const string &errMsg = "", int customCode = 0) { _errMsg = errMsg; _errCode = errCode; _customCode = customCode; } //重置错误 void reset(ErrCode errCode, const string &errMsg) { _errMsg = errMsg; _errCode = errCode; } //错误提示 virtual const char* what() const noexcept { return _errMsg.c_str(); } //错误代码 ErrCode getErrCode() const { return _errCode; } //判断是否真的有错 operator bool() const{ return _errCode != Err_success; } //用户自定义错误代码 int getCustomCode () const{ return _customCode; } //获取用户自定义错误代码 void setCustomCode(int code) { _customCode = code; }; private: string _errMsg; ErrCode _errCode; int _customCode = 0; };
2.列举哪些做法符合代码规范和风格一般要求
根据上面的分析,我们可以看出:
2.1 该源码文件名的命名规则不符合Google的C++标准中规定的——文件名应当全部使用小写。
2.2 该源码的类型名的命名规则基本符合Google的C++标准中规定的——类型命名应当每个单词首字母大写,不含下划线,以名词形式,且所有类型命名 —— 类, 结构体, 类型定义 (
typedef
), 枚举等均使用相同约定。
2.3 该源码函数的命名不符合一致性的要求
2.4 该源码变量名基本符合Google的C++标准
3.列举哪些做法有悖于“代码的简洁、清晰、无歧义”的基本原则,及如何进一步优化改进
1.源码整体注释较少,远低于注释要占代码20%的要求,尤其是对宏的注释,基本没有。改进:添加注释——每一条宏都要加注释;在函数定义的开头添加注释以说明该函数的作用
2.源码中部分使用了宏定义函数,这是C++不提倡的做法。改进:使用内联函数代替宏函数
4.总结同类编程语言或项目在代码规范和风格的一般要求
代码风格因人而异,最重要的是保持自己代码风格从一而终的一致性。但无可厚非,有一些出自大厂的且大家都认可代码风格规范是值得借鉴和学习的,因为它符 合大多数人的阅读习惯,以下根据相关代码规范对代码的基本书写列出一些一般要求:
1.命名:
文件名全部小写;函数名全部按照单词首字母大写;普通变量名一律小写;成员变量名小写且以_为结束作为标记。
2.空行
定义变量后要空行
每个函数定义结束之后都要加空行
两个相对独立的程序块之间要空行
3.空格
函数名之后不要留空格
(
向后紧跟;)
、,
、;
这三个向前紧跟;紧跟处不留空格
值运算符、关系运算符、算术运算符、逻辑运算符、位运算符等前后应当加空格
单目运算符前后不加空格
4.缩进
如果地位相等,则不需要缩进;如果属于某一个代码的内部代码就需要缩进。
5.注释
注意注释的数量,注释太多会让人眼花缭乱
当代码比较长,特别是有多重嵌套的时候,应当在段落的结束处加注释
每一条宏定义的右边必须要有注释,说明其作用