从开源项目学习 C 语言基本的编码规则
每个项目都有自己的风格指南:一组有关怎样为那个项目编码约定。一些经理选择基本的编码规则,另一些经理则更偏好非常高级的规则,对许多项目而言则没有特定的编码规则,项目中的每个开发者使用他自己的风格。
所有代码都保持一致风格的大型库,更容易让人理解。
有许多资源是关于能让人采取的更好的编码规则的,我们可以通过以下方式学到好的编码规则:
- 阅读书或杂志
- 浏览网站
- 与同事交流
- 参加培训
另一个更有趣的方法是通过研究一个成熟的知名开源项目来得知其开发者是怎样编写代码的。对于C语言来说,Linux内核是个不错的选择。
对初级甚至中级的C开发者而言,可能不太容易深入到Linux内核的代码中,但是我们的目标不一定得是对它的源码做出贡献,而是探索它是怎样实现的。
让我们以来自Linux源码中的一个函数实现为例:
这段代码看起来非常干净,实际上这个函数
- 仅有几行代码
- 签名定义得好
- 注释写得好
- 缩进安排得好
- 变量名非常清晰
同样的功能可以被另一个开发者以下面这种方式实现
编码风格对源码的可读性影响甚巨,投入一些时间培训开发者以及周期性评审代码总有利于轻松地维护和升级代码。
让我们使用CppDepend深入到Linux内核的源码中,发现一些它的开发者们采用的编码规则。
模块化
模块化是一种提升使用不同独立部分构建软件的程度的设计技巧,你可以轻松地管理维护模块化的代码。
对于像C这种没有名字空间(namespace)、组件(component)或者类(class)等逻辑构件的过程语言,我们可以使用目录和文件实现模块化。
下面是一些可能的情形:
- 把所有的代码放在一个目录中
- 分离出与一个模块或子模块有关的文件,再放入一个特定的目录
Linux内核使用目录和子目录来模块化核心源码:
封装
封装是指隐藏一份实现内部的功能与数据。在C中,封装是通过使用关键字static实现的。这些实体称为文件域的函数和变量。
让我们通过执行下面的CQLinq查询语句查找一下所有的静态函数:
使用Metric视图,我们可以清楚地看到有多少个函数是静态的。在Metric视图中,代码库由树形图(Treemap)描述。树形图(Treemap)是一种使用嵌套矩形来展示树结构数据的方法。CppDepend中树形图使用的树结构就是通常的代码层次结构:
- 项目包含目录
- 目录包含文件
- 文件包含结构体,函数和变量
树形图视图为描述一条CQLinq请求的结果提供了一种有用的方式,所以我们能够可视化地看到与该请求有关的类型。
正如我们所见,许多函数都声明为静态的
现在我们来查询静态域:
和函数的标记一样,许多变量都声明为静态属性。
在Linux内核源码中,只要函数和变量对于文件域而言是私有的,就使用封装的手法。
使用结构体存储你的数据模型
在C编程中,函数使用变量达到不同的处理要求,这些变量可以是:
- 静态变量
- 全局变量
- 局部变量
- 结构体变量
每个项目都有一些可以被很多源文件使用的数据模型,可以使用全局变量,但这不是好的解决方案,更推荐使用结构体对数据分组。
让我们搜索一下属于基本类型的全局变量:
仅有几个变量与该查询匹配,也许我们可以把这些变量中的一些分组到结构体中,例如(elfcorehdr_addr and elfcorehdr_size),或者是(pm_freezing and pm_nosig_freezing)这样。
让函数短小而干练
下面是来自linux编码风格网页的一份关于函数长度的建议:
函数应该短小而干练,并且一个函数只做一件事。它们应该恰好占据一屏到两屏(我们都知道ISO/ANSI标准屏幕的大小为80×24),只做一件事并且做得很好。
一个函数的最大长度与它的复杂程度和缩进层级成反比。所以,如果你有一个概念上简单的函数,这个函数包含着冗长的(但是简单)case语句,而每个case分支都对应着为不同情况而不得不进行简单处理,那么这个函数长点也没多大关系。
让我们找找多于30行的函数:
多于30行的方法仅有几个。
函数参数的个数
拥有多于8个参数的函数调用起来可能很伤神,还可能降低函数的性能。一个替代方案是提供一个专职传参的结构体。
仅有2个方法的参数多于8个。
局部变量的个数
方法的局部变量超过8个,会让方法难以理解,也难以维护。超过15个就非常复杂,这时应该拆分成两个更小的方法(使用工具自动生成的除外)。
仅仅5个函数的局部变量超过15个。
避免定义复杂的函数
有许多度量复杂函数的指标,上面提到的代码行数、参数个数和局部变量个数是基本的。
还有些其他有趣的指标:
- 环路复杂度在过程软件中是一个广为使用的指标,等于一个过程可以接纳的决议的个数。
- 嵌套深度是一个定义在方法上的指标,该指标与方法体中最深嵌套作用域成正比。
- 最大嵌套循环等于嵌套在一个函数中最深循环层级。
这些指标的可接受最大值更多依赖于团队的选择,并没有标准参考值。
让我们找找可能需要重构的函数:
只有很少几个可以被认为是复杂的函数。
命名约定
命名约定没有标准,每个项目经理都可以选择他们认为更好的规范,重要的是遵守选择的约定,让项目拥有一致的命名。
例如在Linux中,结构体必须以小写字母开头,我们可以验证一下这条规则在整个核心代码中是否成立,执行下面的查询:
仅有4个结构体以“_”代替小写字母开头。
缩进
缩进非常有利于让代码容易阅读,下面这段摘自linux编码风格网页的文字,说明了隐藏在使用缩进背后的动机。
原理阐释:缩进背后整体思想是为了清楚地定义一个控制块的开始与结束。特别是当你已经持续对着屏幕长达20个小时的时候,你更容易发现缩进是怎样发挥功效的(如果你有大量的缩进)
当前,有的人会声称,八字符的缩进会让代码行右端伸太远,这会是代码在80字符宽度的终端屏幕上难以阅读。对此的答案是:如果你需要多于3级缩进的话,不管怎么说,你已经陷入囹圄,你应该修改你的程序。
结论
探究一些知名开源项目总是有利于提高你的编程技巧,没必要下载生成该项目,你从——比如说——GitHub上就可以发现这些代码。
转:从开源项目学习 C 语言基本的编码规则的更多相关文章
-
android开源项目学习
FBReaderJ FBReaderJ用于Android平台的电子书阅读器,它支持多种电子书籍格式包括:oeb.ePub和fb2.此外还支持直接读取zip.tar和gzip等压缩文档. 项目地址:ht ...
-
php开源项目学习二次开发的计划
开源项目: cms 国内 dedecms cmstop 国外 joomla, drupal 电商 国内 ecshop 国外 Magento 论坛 discuz 博客 wordpress 学习时 ...
-
github开源项目学习-front-end-collect
About 项目地址 项目预览demo(githubio加载较慢) 开源项目fork自:https://github.com/foru17/front-end-collect 此文章是对此开源项目使用 ...
-
如何高效地在github上找开源项目学习?
1.高级条件组合(精确搜索) in:readme 微服务 stars:>1000 in:readme spring security stars:>3000 in:name python ...
-
ASP.NET MVC 开源项目学习之ProDinner (二)
下面我们来看第二层:Data 这一层相对来说是对Core层的具体实现了. 从命名可以看出来,这和数据库相关. 1.Db.cs CodeFirst模式的本地数据库类,继承了DbContext. pr ...
-
[Android 开源项目学习]Android的UITableView(1)
最近由于项目加急,手里有好多看了差不多的开源项目,其中好多是大家经常用到的.图片的缓存BitmapFun(Android的文档中),AfinalMap,下拉刷新PullToRefresh等等 ...
-
caffe_ocr开源项目学习笔记
本机配置cuda8.0使用的cudnn是下面要说的重点,vs2015,win10,1080Ti 下载了开源项目:https://github.com/senlinuc/caffe_ocr 编译的时候报 ...
-
适合学习C语言开源项目——嵌入式脚本语言 Berry
嵌入式脚本语言 Berry github网址 :https://github.com/Skiars/berry Berry 是一款面向小型嵌入式系统的脚本语言,目前发布了 0.1.0 版本.相比于其他 ...
-
NumberProgressBar开源项目学习
1.概述 多看多学涨姿势, github真是个宝库 这个项目主要是实现数字进度条效果 github地址在https://github.com/daimajia/NumberProgressBar 感谢 ...
随机推荐
-
android基础开发之WebView
WebView 是android平台沟通 http & H5 页面的桥梁. 但是google对这块的表述不是很清晰,而且SDK里面基本看不到源码,只有一个接口而已. 传送:http://dev ...
-
jQuery增加删除修改tab导航特效
HTML: <div class="container iden_top"> <ul> ...
-
Oracle学习之集合运算
一.集合运算操作符 UNION:(并集)返回两个集合去掉重复值的所有的记录 UNION ALL:(并集)返回两个集合去掉重复值的所有的记录 INTERSECT:(交集)返回两个集合的所有记录,重复 ...
-
TCP/IP详解学习笔记(9)-TCP协议概述
终于看到了TCP协议,这是TCP/IP详解里面最重要也是最精彩的部分,要花大力气来读.前面的TFTP和BOOTP都是一些简单的协议,就不写笔记了,写起来也没啥东西. TCP和UDP处在同一层---运输 ...
-
SpringMVC Controller详解
SpringMVC Controller 介绍 一.简介 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理 ...
-
QueryPerformanceFrequency使用方法--Windows高精度定时计数
在多核心或多处理器的计算机上.特别是在支持CPU频率动态调整的计算机上,windows系统下的QueryPerformanceFrequency()获取HPET(假设存在)的频率,而QueryPerf ...
-
android studio多渠道多包名多apk打包
转自 利用 Android Studio 和 Gradle 打包多版本APK 搬砖的道路上,经常会有各种不同的需求,比如今天就碰到过一个打包版本的apk的要求, 比如一个apk给多个客户使用,如张三 ...
-
Alienware R8外星人台式机安装双系统(WIN10+Ubuntu)的总结
新电脑终于到了,然而外星人的系统比较特殊,很多东西和别的品牌(包括DELL)不一样, 同时NVIDIA显卡也带来了很多问题.重装了十几遍,查阅了上百篇文章后之后终于搞定了双系统. 其实核心问题很傻,就 ...
-
Azure CosmosDB (9) Unique Key Constraints
<Windows Azure Platform 系列文章目录> 在Azure Cosmos DB中,还支持Unique Key Constraints(唯一键约束). 我们可以在Azure ...
-
C++ 重载运算符和重载函数
C++ 重载运算符和重载函数 C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是 ...