网络编程学习和实践的过程中,同步(synchronous)/异步(asynchronous)与阻塞(blocking)/非阻塞(non-blocking)总是会迷惑很多人。依然记得我半年之前在时,一句不经意的“非阻塞I/O则是致力于提供高效的异步I/O”便引来一番口水论争。
今天在查一些资料的时候,看到关于这几个词的论辩竟不是一般的多,细细想来,这个问题似乎也确实有解释的必要,不在于争论对错,而在于辨明是非。
讨论之前,先限定讨论的范围:此处之同步/异步仅限于I/O操作,与OS所讨论的进程/线程中的其他同步/异步没有直接关系;讨论的内容是:两对相似的术语之间的区别到底有多大。
Douglas C. Schmidt在《C++网络编程》中这样说到:
They are very different, as follows:
AIO is "asynchronous I/O", i.e., the operation is invoked asynchronously and control returns to the client while the OS kernel processes the I/O request. When the operation completes there is some mechanism for the client to retrieve the results.
Non-blocking I/O tries an operation (such as a read() or write()) and if it the operation would block (e.g., due to flow control on a TCP connection or due to lack of data in a socket), the call returns -1 and sets errno to EWOULDBLOCK.
翻译如下:
:例如,操作被异步调用时,控制权交给客户端,I/O操作请求则交由操作系统内核处理,当操作完成后,通过某种机制将结果通知客户端。
非阻塞I/O:尝试调用某操作,如果操作被阻塞,则调用返回-1并置错误值为EWOULDBLOCK。
从这两段“very different”的解释来看,我的感觉是并没有指出二者的区别,因为我们无法确定所谓AIO是如何处理的,如果AIO直接“调用返回-1并置错误值为EWOULDBLOCK”,实现“控制权交给客户端”,似乎并无任何不妥。况且,对于非阻塞I/O,我们也需要“当操作完成后,通过某种机制将结果通知客户端”这样的处理。
而在上则直接等同二者:Asynchronous I/O, or non-blocking I/O, is a form of processing that permits other processing to continue before the transmission has finished.
当然,对于recv和send,我们一般会说他们是阻塞起的,而不会说他们是同步起的,但这显然不是二者的区别,因为我们都知道,阻塞的原因正是等待同步结果的返回。
因此,二者的区别在于,阻塞/非阻塞是表现,同步/异步是原因,我们说某某操作是阻塞起的,或者某某线程是阻塞起的,是因为在等待操作结果的同步返回;我们说某某操作是非阻塞的,是因为操作结果会通过异步方式返回。
讨论到这儿,再咬文嚼字的争辩下去似乎已经没有任何实际意义。
------------------------------------------------------------
PS:纠结一些必要的概念是为了加深理解,太过纠结了反倒会滞塞理解。我之前对于其概念也并非特别清楚,所以才会再续一篇特意言明,也算弥补一下自己的过失。
--------------------next---------------------
0. Introduction
接触设计模式有两年时间了,但一直没有系统整理过,为了不至于让自己的思维被繁琐的工作一点点禁锢,还是决定总结一下,为了能够真正做到有所收获,整个系列会按照的行文思路,但不会照本宣科就是了,上关于23种设计模式的介绍非常全面,CSDN上也可以下载中/英文电子档,因此很多套话、类图一概省去。
最早接触设计模式的时候,难免被各种模式的联系和区别所困扰,从教科书的分析可以得到模式之间形式上的不同。但这样对于领会设计模式意义不大,因为我们掌握模式的目的是为了融会贯通,灵活运用,以对开发有所帮助。
稍微成规模的OO程序,会有大量对象,其中很多实体对象之间存在着父子、兄弟关系,对象的创建提升为一种模式。其好处在于设计模式本身所宣称的reusable,这就像堆积木盖房子一样,堆的好的情况下,换一换门窗便是另一番风景。
关于实现,我不会为了厘清模式间的区别而刻意使用相似代码实现,相反,我会根据模式本身的适用情况举例,而且大量代码基于。
_______________________________
1. Creational Design Patterns(DP)
创建型DP抽象了类和对象的创建过程,给出了5种创建型DP:Abstract Factory、Builder、Factory Method、Builder、Prototype、Singleton。
2. Abstract Factory
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
1) 只提供了一个创建接口,其返回值为具体产品:如AbstractProduct *Client::CreateProduct(AbstractFactory &factory);
2) 接口的参数是一个工厂对象(AbstractFactory &factory)的引用,参数类型(AbstractFactory)为抽象基类,调用时根据需要传入具体工厂对象即可;
3) 接口内部实现了一系列相关或相互依赖对象(抽象产品)的创建:当传入具体工厂时,接口实现的就是一系列具体产品的创建;
4) 创建的产品立即返回(CreateProduct)。
参与者:
• AbstractFactory
— 声明一个创建抽象产品对象的操作接口。
• ConcreteFactory
— 实现创建具体产品对象的操作。
• AbstractProduct
— 为一类产品对象声明一个接口。
• ConcreteProduct
— 定义一个将被相应的具体工厂创建的产品对象。
— 实现AbstractProduct接口。
• Client
— 仅使用由AbstractFactory和AbstractProduct类声明的接口。
代码:
class AbstractFactory
{
public:
virtual AbstractProduct *MakePartA() = 0;
virtual AbstractProduct *MakePartB() = 0;
virtual AbstractProduct *MakePartC() = 0;
virtual AbstractProduct *AddPart(const AbstractProduct *pPart) = 0;
};
AbstractProduct *Client::CreateProduct(AbstractFactory &factory)
{
AbstractProduct *pProduct = factory.CreateProduct();
AbstractProduct *pPartA = factory.MakePartA();
AbstractProduct *pPartB = factory.MakePartB();
AbstractProduct *pPartC = factory.MakePartC();
factory.AddPart(pPartA);
factory.AddPart(pPartB);
factory.AddPart(pPartC);
return pProduct;
}
int main(void)
{
Client client;
ConcreteFactory factory;
client.CreateProduct(factory);
return 0;
}
3. Builder
意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
1) director提供抽象产品创建接口:如void Director::Construct();
2) 不同产品使用同一创建过程,由director指定特定builder以生产不同产品;
3) 接口内部实现了一个复杂对象(抽象产品)的创建:当传入具体工厂时,接口实现的是一个复杂的具体产品的创建;
4) 创建的产品并不立即返回,创建完毕后返回,或使用接口(GetProduct)提取结果。
参与者:
• Builder
— 为创建一个Product对象的各个部件指定抽象接口。
• ConcreteBuilder
— 实现Builder的接口以构造和装配该产品的各个部件。
— 定义并明确它所创建的表示。
— 提供一个检索产品的接口。
• Director
— 构造一个使用Builder接口的对象。
• Product
— 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
— 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
代码:
class Builder
{
public:
virtual void MakePartA() = 0;
virtual void MakePartB() = 0;
virtual void MakePartC() = 0;
Product *GetProduct() { return _product; }
protected:
Product *_product;
};
class Director
{
public:
void setBuilder(Builder *b) { _builder = b; }
void Construct();
private:
Builder *_builder;
};
void Director::Construct()
{
_builder.MakePartA();
_builder.MakePartB();
_builder.MakePartC();
}
int main(void) {
ConcreteBuilderA concreteBuilderA;
ConcreteBuilderB concreteBuilderB;
Director director;
Product *pProduct;
director.SetBuilder(&concreteBuilderA);
director.Construct();
pProduct = concreteBuilderA.GetProduct();
pProduct->Show();
director.SetBuilder(&concreteBuilderB);
director.Construct();
pProduct = concreteBuilderB.GetProduct();
pProduct->Show();
return 0;
}
4. Factory Method
意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
1) 看得出该模式其实就是C++的多态特性,借继承实现。因此,其别名为虚构造器( Virtual Constructor);
2) 作为模式与C++多态特性不同的是,Creator可以定义工厂方法的缺省实现,完成缺省操作,MFC大量使用了这一思想。
参与者:
• Product
— 定义工厂方法所创建的对象的接口。
• ConcreteProduct
— 实现Product接口。
• Creator
— 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。
— 可以调用工厂方法以创建一个Product对象。
• ConcreteCreator
— 重定义工厂方法以返回一个ConcreteProduct实例。
代码:
ConcreteProduct *ConcreteCreator::FactoryMethod()
{
ConcreteProduct *pProduct = new ConcreteProduct;
return pProduct;
}
Product *Creator::FactoryMethod()
{
Product *pProduct = new Product;
return pProduct;
}
int main(void) {
Creator creator;
ConcreteProduct *pProduct;
pProduct = creator.FactoryMethod();
pProduct->Show();
return 0;
}
5. Prototype
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
1) 创建不再通过工厂新类继承(inheritance),而是通过委托(delegation);
2) 根通拷贝原型实例创建新对象。
参与者:
• ProtoType
— 声明一个克隆自身的接口。
• ConcreteProtoType
— 实现一个克隆自身的操作。
• Client
— 让一个原型克隆自身从而创建一个新的对象。
代码:
class ProtoType
{
public:
virtual void Draw();
virtual ProtoType *Clone() = 0;
virtual void Initialize();
};
class ProtoTypeA: public ProtoType
{
public:
virtual ProtoType *Clone()
{
return new ProtoTypeA;
}
};
class ProtoTypeB: public ProtoType
{
public:
virtual ProtoType *Clone()
{
return new ProtoTypeB;
}
};
class Client
{
public:
static ProtoType *Clone( int choice );
private:
static ProtoType *s_prototypes[3];
};
ProtoType* Client::s_prototypes[] = { 0, new ProtoTypeA, new ProtoTypeB };
ProtoType *Client::Clone(int choice)
{
return s_prototypes[choice]->Clone();
}
6. Singleton
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1) 用静态成员函数保证上述意图。
参与者:
• Singleton
— 定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(即C++中的一个静态成员函数)。
— 可能负责创建它自己的唯一实例。
代码:
class Singleton
{
public:
static Singleton *GetInstance()
{
if (!s_instance)
s_instance = new Singleton;
return s_instance;
}
void Run() {}
private:
static Singleton *s_instance;
Singleton() {} // Singleton cannot be created outside.
};
Singleton *GetSingleton(void)
{
return Singleton::GetInstance();
}
int main(void)
{
GetSingleton()->Run();
return 0;
}
______________________________________________
代码写的都比较简单,基本可以将各种模式之间的不同体现出来了。
--------------------next---------------------
一、Big-endian & Little-endian
还是好啊!可惜中文的国内看不了,愚昧啊!实在觉得中文有点难懂,看看吧:D!
关于端(endianness)的介绍,上比较全了:
关于网络字节序(network byte order)和主机字节序(host byte order),说来挺无关紧要的一点东西,因为每次总是忘掉,所以每次都要好奇的看看大端(big-endian)和小端(little-endian)。
给定unsigned long型整数十六进制形式:0x0A0B0C0D,其big-endian和little-endian形式分别为:
1) Big-endian
Memory
| ... | 8-bit atomic element size | ... | 16-bit atomic element size
| 0x0A | a | 0x0A0B | a
| 0x0B | a+1 | 0x0C0D | a+1
| 0x0C | a+2
| 0x0D | a+3
| ... |
2) Little-endian(X86)
Memory
| ... | 8-bit atomic element size | ... | 16-bit atomic element size
| 0x0D | a | 0x0C0D | a
| 0x0C | a+1 | 0x0A0B | a+1
| 0x0B | a+2
| 0x0A | a+3
| ... |
Mapping registers to memory locations (from Wikipedia)
为什么X86存储会使用little-endian,起初我想对于位运算,尤其是位移运算,little-endian很方便,但转念一想,big-endian也方便啊,无非是左移和右移的区别而已,但little-endian的优势在于unsigned char/short/int/long类型转换时,存储位置无需改变。
在网络传输中,采用big-endian序,对于0x0A0B0C0D,传输顺序就是0A 0B 0C 0D,因此big-endian作为network byte order,little-endian作为host byte order。
________________________________________________
PS:做鸡有什么不好?
上午跟某同事(为尊重虑,下文以Y称之)躲在犄角旮旯抽烟。以下为场景再现:
(忽然整出来一句)Y:听过鹰的故事没有?
(满脸疑惑)Fox:没有。
Y:一只小鹰掉到鸡窝里,#$@%……
F:我不是鹰,我就是一只鸡,做技术鸡有什么不好?
Y:做技术没有不好啊……
F:我不是说做技术,我说做鸡,我就是在地上走的,我为什么总是要抬头看天?
Y:你要往上看,没有人注定不能飞,XX以前也没有想过有一天会飞起来。
F:我不是掉到鸡窝里,我本来就在鸡窝里,我也喜欢呆在鸡窝里,别人都在地上走,我为什么要飞起来?
Y:你总要飞起来。
F:我说了我喜欢呆在鸡窝里,你见过有那只鸡飞起来了?
Y:……
F:我就是一只鸡,插了鸡翅还是飞不起来,况且,我对飞起来也没有任何兴趣。
Y:……
F:做鸡有什么不好?
Y:你看老毛,与人斗其乐无穷,他境界多高,与天斗其乐无穷,知道吧,他已经不屑与人斗了。
F:我不喜欢与人斗,我也斗不过,做鸡有什么不好?
Y:……
--------------------next---------------------
原文地址:
前面说明的编码习惯基本是强制性的,但所有优秀的规则都允许例外。
1. 现有不统一代码(Existing Non-conformant Code)
对于现有不符合既定编程风格的代码可以网开一面。
当你修改使用其他风格的代码时,为了与代码原有风格保持一致可以不使用本指南约定。如果不放心可以与代码原作者或现在的负责人员商讨,记住,一致性包括原有的一致性。
1. Windows代码(Windows Code)
Windows程序员有自己的编码习惯,主要源于Windows的一些头文件和其他Microsoft代码。我们希望任何人都可以顺利读懂你的代码,所以针对所有平台的C++编码给出一个单独的指导方案。
如果你一直使用Windows编码风格的,这儿有必要重申一下某些你可能会忘记的指南(译者注,我怎么感觉像在被洗脑:D):
1) 不要使用匈牙利命名法(Hungarian notation,如定义整型变量为iNum),使用Google命名约定,包括对源文件使用.cc扩展名;
2) Windows定义了很多原有内建类型的同义词(译者注,这一点,我也很反感),如DWORD、HANDLE等等,在调用Windows API时这是完全可以接受甚至鼓励的,但还是尽量使用原来的C++类型,例如,使用const TCHAR *而不是LPCTSTR;
3) 使用Microsoft Visual C++进行编译时,将警告级别设置为3或更高,并将所有warnings当作errors处理;
4) 不要使用#pragma once;作为包含保护,使用C++标准包含保护,包含保护的文件路径包含到项目树顶层(译者注,#include<prj_name/public/tools.h>);
5) 除非万不得已,否则不使用任何不标准的扩展,如#pragma和__declspec,允许使用__declspec(dllimport)和__declspec(dllexport),但必须通过DLLIMPORT和DLLEXPORT等宏,以便其他人在共享使用这些代码时容易放弃这些扩展。
在Windows上,只有很少一些偶尔可以不遵守的规则:
1) 通常我们禁止使用多重继承,但在使用和/类时可以使用多重继承,为了执行或/类及其接口时可以使用多重实现继承;
2) 虽然代码中不应使用异常,但在和部分(包括Visual C++的STL)中异常被广泛使用,使用时,应定义_ATL_NO_EXCEPTIONS以屏蔽异常,你要研究一下是否也屏蔽掉STL的异常,如果不屏蔽,开启编译器异常也可以,注意这只是为了编译STL,自己仍然不要写含异常处理的代码;
3) 通常每个项目的每个源文件中都包含一个名为StdAfx.h或precompile.h的头文件方便头文件预编译,为了使代码方便与其他项目共享,避免显式包含此文件(precompile.cc除外),使用编译器选项/FI以自动包含;
4) 通常名为resource.h、且只包含宏的资源头文件,不必拘泥于此风格指南。
参考常识,保持一致。
编辑代码时,花点时间看看项目中的其他代码并确定其风格,如果其他代码if语句中使用空格,那么你也要使用。如果其中的注释用星号(*)围成一个盒子状,你也这样做:
/**********************************
* Some comments are here.
* There may be many lines.
**********************************/
编程风格指南的使用要点在于提供一个公共的编码规范,所有人可以把精力集中在实现内容而不是表现形式上。我们给出了全局的风格规范,但局部的风格也很重要,如果你在一个文件中新加的代码和原有代码风格相去甚远的话,这就破坏了文件本身的整体美观也影响阅读,所以要尽量避免。
好了,关于编码风格写的差不多了,代码本身才是更有趣的,尽情享受吧!
Benjy Weinberger
Craig Silverstein
Gregory Eitzmann
Mark Mentovai
Tashana Landray
______________________________________
译者:终于翻完了,前后历时两周,整个过程中,虽因工作关系偶有懈怠,但总算不是虎头蛇尾(起码我的态度是非常认真的:D),无论是否能对你有所裨益,对我而言,至少是温习了一些以前知道的知识,也学到了一些之前不知道的知识。
刚好这两天还不是特紧张,赶紧翻完了,要开始干活了……
--------------------next---------------------
原文地址:
代码风格和格式确实比较随意,但一个项目中所有人遵循同一风格是非常容易的,作为个人未必同意下述格式规则的每一处,但整个项目服从统一的编程风格是很重要的,这样做才能让所有人在阅读和理解代码时更加容易。
1. 行长度(Line Length)
每一行代码字符数不超过80。
我们也认识到这条规则是存有争议的,但如此多的代码都遵照这一规则,我们感觉一致性更重要。
优点:提倡该原则的人认为强迫他们调整编辑器窗口大小很野蛮。很多人同时并排开几个窗口,根本没有多余空间拓宽某个窗口,人们将窗口最大尺寸加以限定,一致使用80列宽,为什么要改变呢?
缺点:反对该原则的人则认为更宽的代码行更易阅读,80列的限制是上个世纪60年代的大型机的古板缺陷;现代设备具有更宽的显示屏,很轻松的可以显示更多代码。
结论:80个字符是最大值。例外:
1) 如果一行注释包含了超过80字符的命令或URL,出于复制粘贴的方便可以超过80字符;
2) 包含长路径的可以超出80列,尽量避免;
3) 头文件保护(防止重复包含)可以无视该原则。
2. 非ASCII字符(Non-ASCII Characters)
尽量不使用非ASCII字符,使用时必须使用格式。
哪怕是英文,也不应将用户界面的文本硬编码到源代码中,因此非ASCII字符要少用。特殊情况下可以适当包含此类字符,如,代码分析外部数据文件时,可以适当硬编码数据文件中作为分隔符的非ASCII字符串;更常用的是(不需要本地化的)单元测试代码可能包含非ASCII字符串。此类情况下,应使用UTF-8格式,因为很多工具都可以理解和处理其编码,十六进制编码也可以,尤其是在增强可读性的情况下——如"\xEF\xBB\xBF"是Unicode的字符,以UTF-8格式包含在源文件中是不可见的。
3. 空格还是制表位(Spaces vs. Tabs)
只使用空格,每次缩进2个空格。
使用空格进行缩进,不要在代码中使用tabs,设定编辑器将tab转为空格。
译者注:在前段时间的一文中,曾给出针对C/C++编码使用的vim配置。
4. 函数声明与定义(Function Declarations and Definitions)
返回类型和函数名在同一行,合适的话,参数也放在同一行。
函数看上去像这样:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
如果同一行文本较多,容不下所有参数:
ReturnType ClassName::ReallyLongFunctionName(Type par_name1,
Type par_name2,
Type par_name3) {
DoSomething();
...
}
甚至连第一个参数都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); // 2 space indent
...
}
注意以下几点:
1) 返回值总是和函数名在同一行;
2) 左圆括号(open parenthesis)总是和函数名在同一行;
3) 函数名和左圆括号间没有空格;
4) 圆括号与参数间没有空格;
5) 左大括号(open curly brace)总在最后一个参数同一行的末尾处;
6) 右大括号(close curly brace)总是单独位于函数最后一行;
7) 右圆括号(close parenthesis)和左大括号间总是有一个空格;
8) 函数声明和实现处的所有形参名称必须保持一致;
9) 所有形参应尽可能对齐;
10) 缺省缩进为2个空格;
11) 独立封装的参数保持4个空格的缩进。
如果函数为const的,关键字const应与最后一个参数位于同一行。
// Everything in this function signature fits on a single line
ReturnType FunctionName(Type par) const {
...
}
// This function signature requires multiple lines, but
// the const keyword is on the line with the last parameter.
ReturnType ReallyLongFunctionName(Type par1,
Type par2) const {
...
}
如果有些参数没有用到,在函数定义处将参数名注释起来:
// Always have named parameters in interfaces.
class Shape {
public:
virtual void Rotate(double radians) = 0;
}
// Always have named parameters in the declaration.
class Circle : public Shape {
public:
virtual void Rotate(double radians);
}
// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}// Bad - if someone wants to implement later, it's not clear what the
// variable means.
void Circle::Rotate(double) {}
译者注:关于UNIX/Linux风格为什么要把左大括号置于行尾(.cc文件的函数实现处,左大括号位于行首),我的理解是代码看上去比较简约,想想行首除了函数体被一对大括号封在一起之外,只有右大括号的代码看上去确实也舒服;Windows风格将左大括号置于行首的优点是匹配情况一目了然。
5. 函数调用(Function Calls)
尽量放在同一行,否则,将实参封装在圆括号中。
函数调用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下,可断为多行,后面每一行都和第一个实参对齐,左圆括号后和右圆括号前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
如果函数参数比较多,可以出于可读性的考虑每行只放一个参数:
bool retval = DoSomething(argument1,
argument2,
argument3,
argument4);
如果函数名太长,以至于超过行最大长度,可以将所有参数独立成行:
if (...) {
...
...
if (...) {
DoSomethingThatRequiresALongFunctionName(
very_long_argument1, // 4 space indent
argument2,
argument3,
argument4);
}
6. 条件语句(Conditionals)
更提倡不在圆括号中添加空格,关键字else另起一行。
对基本条件语句有两种可以接受的格式,一种在圆括号和条件之间有空格,一种没有。
最常见的是没有空格的格式,那种都可以,还是一致性为主。如果你是在修改一个文件,参考当前已有格式;如果是写新的代码,参考目录下或项目中其他文件的格式,还在徘徊的话,就不要加空格了。
if (condition) { // no spaces inside parentheses
... // 2 space indent.
} else { // The else goes on the same line as the closing brace.
...
}
如果你倾向于在圆括号内部加空格:
if ( condition ) { // spaces inside parentheses - rare
... // 2 space indent.
} else { // The else goes on the same line as the closing brace.
...
}
注意所有情况下if和左圆括号间有个空格,右圆括号和左大括号(如果使用的话)间也要有个空格:
if(condition) // Bad - space missing after IF.
if (condition){ // Bad - space missing before {.
if(condition){ // Doubly bad.if (condition) { // Good - proper space after IF and before {.
有些条件语句写在同一行以增强可读性,只有当语句简单并且没有使用else子句时使用:
if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();
如果语句有else分支是不允许的:
// Not allowed - IF statement on one line when there is an ELSE clause
if (x) DoThis();
else DoThat();
通常,单行语句不需要使用大括号,如果你喜欢也无可厚非,也有人要求if必须使用大括号:
if (condition)
DoSomething(); // 2 space indent.
if (condition) {
DoSomething(); // 2 space indent.
}
但如果语句中哪一分支使用了大括号的话,其他部分也必须使用:
// Not allowed - curly on IF but not ELSE
if (condition) {
foo;
} else
bar;
// Not allowed - curly on ELSE but not IF
if (condition)
foo;
else {
bar;
}
// Curly braces around both IF and ELSE required because
// one of the clauses used braces.
if (condition) {
foo;
} else {
bar;
}
7. 循环和开关选择语句(Loops and Switch Statements)
switch语句可以使用大括号分块;空循环体应使用{}或continue。
switch语句中的case块可以使用大括号也可以不用,取决于你的喜好,使用时要依下文所述。
如果有不满足case枚举条件的值,要总是包含一个default(如果有输入值没有case去处理,编译器将报警)。如果default永不会执行,可以简单的使用assert:
switch (var) {
case 0: { // 2 space indent
... // 4 space indent
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
空循环体应使用{}或continue,而不是一个简单的分号:
while (condition) {
// Repeat test until it returns false.
}
for (int i = 0; i < kSomeNumber; ++i) {} // Good - empty body.
while (condition) continue; // Good - continue indicates no logic.while (condition); // Bad - looks like part of do/while loop.
8. 指针和引用表达式(Pointers and Reference Expressions)
句点(.)或箭头(->)前后不要有空格,指针/地址操作符(*、&)后不要有空格。
下面是指针和引用表达式的正确范例:
x = *p;
p = &x;
x = r.y;
x = r->y;
注意:
1) 在访问成员时,句点或箭头前后没有空格;
2) 指针操作符*或&后没有空格。
在声明指针变量或参数时,星号与类型或变量名紧挨都可以:
// These are fine, space preceding.
char *c;
const string &str;
// These are fine, space following.
char* c; // but remember to do "char* c, *d, *e, ...;"!
const string& str;
char * c; // Bad - spaces on both sides of *
const string & str; // Bad - spaces on both sides of &
同一个文件(新建或现有)中起码要保持一致。
译者注:个人比较习惯与变量紧挨的方式。
9. 布尔表达式(Boolean Expressions)
如果一个布尔表达式超过标准行宽(80字符),如果断行要统一一下。
下例中,逻辑与(&&)操作符总位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another & last_one) {
...
}
两个逻辑与(&&)操作符都位于行尾,可以考虑额外插入圆括号,合理使用的话对增强可读性是很有帮助的。
译者注:个人比较习惯逻辑运算符位于行首,逻辑关系一目了然,各人喜好而已,至于加不加圆括号的问题,如果你对优先级了然于胸的话可以不加,但可读性总是差了些。
10. 函数返回值(Return Values)
return表达式中不要使用圆括号。
函数返回时不要使用圆括号:
return x; // not return(x);
11. 变量及数组初始化(Variable and Array Initialization)
选择=还是()。
需要做二者之间做出选择,下面的形式都是正确的:
int x = 3;
int x(3);
string name("Some Name");
string name = "Some Name";
12. 预处理指令(Preprocessor Directives)
预处理指令不要缩进,从行首开始。
即使预处理指令位于缩进代码块中,指令也应从行首开始。
// Good - directives at beginning of line
if (lopsided_score) {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
DropEverything();
#endif
BackToNormal();
}// Bad - indented directives
if (lopsided_score) {
#if DISASTER_PENDING // Wrong! The "#if" should be at beginning of line
DropEverything();
#endif // Wrong! Do not indent "#endif"
BackToNormal();
}
13. 类格式(Class Format)
声明属性依次序是public:、protected:、private:,每次缩进1个空格(译者注,为什么不是两个呢?也有人提倡private在前,对于声明了哪些数据成员一目了然,还有人提倡依逻辑关系将变量与操作放在一起,都有道理:-))。
类声明(对类注释不了解的话,参考中的类注释一节)的基本格式如下:
class MyClass : public OtherClass {
public: // Note the 1 space indent!
MyClass(); // Regular 2 space indent.
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
DISALLOW_COPY_AND_ASSIGN(MyClass);
};
注意:
1) 所以基类名应在80列限制下尽量与子类名放在同一行;
2) 关键词public:、protected:、private:要缩进1个空格(译者注,MSVC多使用tab缩进,且这三个关键词没有缩进);
3) 除第一个关键词(一般是public)外,其他关键词前空一行,如果类比较小的话也可以不空;
4) 这些关键词后不要空行;
5) public放在最前面,然后是protected和private;
6) 关于声明次序参考声明次序一节。
14. 初始化列表(Initializer Lists)
构造函数初始化列表放在同一行或按四格缩进并排几行。
两种可以接受的初始化列表格式:
// When it all fits on one line:
MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) {
或
// When it requires multiple lines, indent 4 spaces, putting the colon on
// the first initializer line:
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
...
DoSomething();
...
}
15. 命名空间格式化(Namespace Formatting)
命名空间内容不缩进。
命名空间不添加额外缩进层次,例如:
namespace {
void foo() { // Correct. No extra indentation within namespace.
...
}
} // namespace
不要缩进:
namespace {
// Wrong. Indented when it should not be.
void foo() {
...
}
} // namespace
16. 水平留白(Horizontal Whitespace)
水平留白的使用因地制宜。不要在行尾添加无谓的留白。
普通:
void f(bool b) { // Open braces should always have a space before them.
...
int i = 0; // Semicolons usually have no space before them.
int x[] = { 0 }; // Spaces inside braces for array initialization are
int x[] = {0}; // optional. If you use them, put them on both sides!
// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar {
public:
// For inline function implementations, put spaces between the braces
// and the implementation itself.
Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces.
void Reset() { baz_ = 0; } // Spaces separating braces from implementation.
...
添加冗余的留白会给其他人编辑时造成额外负担,因此,不要加入多余的空格。如果确定一行代码已经修改完毕,将多余的空格去掉;或者在专门清理空格时去掉(确信没有其他人在使用)。
循环和条件语句:
if (b) { // Space after the keyword in conditions and loops.
} else { // Spaces around else.
}
while (test) {} // There is usually no space inside parentheses.
switch (i) {
for (int i = 0; i < 5; ++i) {
switch ( i ) { // Loops and conditions may have spaces inside
if ( test ) { // parentheses, but this is rare. Be consistent.
for ( int i = 0; i < 5; ++i ) {
for ( ; i < 5 ; ++i) { // For loops always have a space after the
... // semicolon, and may have a space before the
// semicolon.
switch (i) {
case 1: // No space before colon in a switch case.
...
case 2: break; // Use a space after a colon if there's code after it.
操作符:
x = 0; // Assignment operators always have spaces around
// them.
x = -5; // No spaces separating unary operators and their
++x; // arguments.
if (x && !y)
...
v = w * x + y / z; // Binary operators usually have spaces around them,
v = w*x + y/z; // but it's okay to remove spaces around factors.
v = w * (x + z); // Parentheses should have no spaces inside them.
模板和转换:
vector<string> x; // No spaces inside the angle
y = static_cast<char*>(x); // brackets (< and >), before
// <, or between >( in a cast.
vector<char *> x; // Spaces between type and pointer are
// okay, but be consistent.
set<list<string> > x; // C++ requires a space in > >.
set< list<string> > x; // You may optionally make use
// symmetric spacing in < <.
17. 垂直留白(Vertical Whitespace)
垂直留白越少越好。
这不仅仅是规则而是原则问题了:不是非常有必要的话就不要使用空行。尤其是:不要在两个函数定义之间空超过2行,函数体头、尾不要有空行,函数体中也不要随意添加空行。
基本原则是:同一屏可以显示越多的代码,程序的控制流就越容易理解。当然,过于密集的代码块和过于疏松的代码块同样难看,取决于你的判断,但通常是越少越好。
函数头、尾不要有空行:
void Function() {
// Unnecessary blank lines before and after
}
代码块头、尾不要有空行:
while (condition) {
// Unnecessary blank line after
}
if (condition) {
// Unnecessary blank line before
}
if-else块之间空一行还可以接受:
if (condition) {
// Some lines of code too small to move to another function,
// followed by a blank line.
} else {
// Another block of code
}
______________________________________
译者:首先说明,对于代码格式,因人、因系统各有优缺点,但同一个项目中遵循同一标准还是有必要的:
1. 行宽原则上不超过80列,把22寸的显示屏都占完,怎么也说不过去;
2. 尽量不使用非ASCII字符,如果使用的话,参考UTF-8格式(尤其是UNIX/Linux下,Windows下可以考虑宽字符),尽量不将字符串常量耦合到代码中,比如独立出资源文件,这不仅仅是风格问题了;
3. UNIX/Linux下无条件使用空格,MSVC的话使用Tab也无可厚非;
4. 函数参数、逻辑条件、初始化列表:要么所有参数和函数名放在同一行,要么所有参数并排分行;
5. 除函数定义的左大括号可以置于行首外,包括函数/类/结构体/枚举声明、各种语句的左大括号置于行尾,所有右大括号独立成行;
6. ./->操作符前后不留空格,*/&不要前后都留,一个就可,靠左靠右依各人喜好;
7. 预处理指令/命名空间不使用额外缩进,类/结构体/枚举/函数/语句使用缩进;
8. 初始化用=还是()依个人喜好,统一就好;
9. return不要加();
10. 水平/垂直留白不要滥用,怎么易读怎么来。
--------------------next---------------------
原文地址:
注释虽然写起来很痛苦,但对保证代码可读性至为重要,下面的规则描述了应该注释什么、注释在哪儿。当然也要记住,注释的确很重要,但最好的代码本身就是文档(self-documenting),类型和变量命名意义明确要比通过注释解释模糊的命名好得多。
注释是为别人(下一个需要理解你的代码的人)而写的,认真点吧,那下一个人可能就是你!
1. 注释风格(Comment Style)
使用//或/* */,统一就好。
//或/* */都可以,//只是用的更加广泛,在如何注释和注释风格上确保统一。
2. 文件注释(File Comments)
在每一个文件开头加入版权公告,然后是文件内容描述。
法律公告和作者信息:
每一文件包含以下项,依次是:
1) 版权(copyright statement):如Copyright 2008 Google Inc.;
2) 许可版本(license boilerplate):为项目选择合适的许可证版本,如、、、;
3) 作者(author line):标识文件的原始作者。
如果你对其他人创建的文件做了重大修改,将你的信息添加到作者信息里,这样当其他人对该文件有疑问时可以知道该联系谁。
文件内容:
每一个文件版权许可及作者信息后,都要对文件内容进行注释说明。
通常,.h文件要对所声明的类的功能和用法作简单说明,.cc文件包含了更多的实现细节或算法讨论,如果你感觉这些实现细节或算法讨论对于阅读有帮助,可以把.cc中的注释放到.h中,并在.cc中指出文档在.h中。
不要单纯在.h和.cc间复制注释,复制的注释偏离了实际意义。
3. 类注释(Class Comments)
每个类的定义要附着描述类的功能和用法的注释。
// Iterates over the contents of a GargantuanTable. Sample usage:
// GargantuanTable_Iterator* iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
// delete iter;
class GargantuanTable_Iterator {
...
};
如果你觉得已经在文件顶部详细描述了该类,想直接简单的来上一句“完整描述见文件顶部”的话,还是多少在类中加点注释吧。
如果类有任何同步前提(synchronization assumptions),文档说明之。如果该类的实例可被多线程访问,使用时务必注意文档说明。
4. 函数注释(Function Comments)
函数声明处注释描述函数功能,定义处描述函数实现。
函数声明:
注释于声明之前,描述函数功能及用法,注释使用描述式("Opens the file")而非指令式("Open the file");注释只是为了描述函数而不是告诉函数做什么。通常,注释不会描述函数如何实现,那是定义部分的事情。
函数声明处注释的内容:
1) inputs(输入)及outputs(输出);
2) 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数;
3) 如果函数分配了空间,需要由调用者释放;
4) 参数是否可以为NULL;
5) 是否存在函数使用的性能隐忧(performance implications);
6) 如果函数是可重入的(re-entrant),其同步前提(synchronization assumptions)是什么?
举例如下:
// Returns an iterator for this table. It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
// Iterator* iter = table->NewIterator();
// iter->Seek("");
// return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;
但不要有无谓冗余或显而易见的注释,下面的注释就没有必要加上“returns false otherwise”,因为已经暗含其中了:
// Returns true if the table cannot hold any more entries.
bool IsTableFull();
注释构造/析构函数时,记住,读代码的人知道构造/析构函数是什么,所以“destroys this object”这样的注释是没有意义的。说明构造函数对参数做了什么(例如,是否是指针的所有者)以及析构函数清理了什么,如果都是无关紧要的内容,直接省掉注释,析构函数前没有注释是很正常的。
函数定义:
每个函数定义时要以注释说明函数功能和实现要点,如使用的漂亮代码、实现的简要步骤、如此实现的理由、为什么前半部分要加锁而后半部分不需要。
不要从.h文件或其他地方的函数声明处直接复制注释,简要说明函数功能是可以的,但重点要放在如何实现上。
5. 变量注释(Variable Comments)
通常变量名本身足以很好说明变量用途,特定情况下,需要额外注释说明。
类数据成员:
每个类数据成员(也叫实例变量或成员变量)应注释说明用途,如果变量可以接受NULL或-1等警戒值(sentinel values),须说明之,如:
private:
// Keeps track of the total number of entries in the table.
// Used to ensure we do not go over the limit. -1 means
// that we don't yet know how many entries the table has.
int num_total_entries_;
全局变量(常量):
和数据成员相似,所有全局变量(常量)也应注释说明含义及用途,如:
// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;
6. 实现注释(Implementation Comments)
对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。
代码前注释:
出彩的或复杂的代码块前要加注释,如:
// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
x = (x << 8) + (*result)[i];
(*result)[i] = x >> 1;
x &= 1;
}
行注释:
比较隐晦的地方要在行尾加入注释,可以在代码之后空两格加行尾注释,如:
// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
return; // Error already logged.
注意,有两块注释描述这段代码,当函数返回时注释提及错误已经被记入日志。
前后相邻几行都有注释,可以适当调整使之可读性更好:
...
DoSomething(); // Comment here so the comments line up.
DoSomethingElseThatIsLonger(); // Comment here so there are two spaces between
// the code and the comment.
...
NULL、true/false、1、2、3……:
向函数传入、布尔值或整数时,要注释说明含义,或使用常量让代码望文知意,比较一下:
bool success = CalculateSomething(interesting_value,
10,
false,
NULL); // What are these arguments??
和:
bool success = CalculateSomething(interesting_value,
10, // Default base value.
false, // Not the first time we're calling this.
NULL); // No callback.
使用常量或描述性变量:
const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
kDefaultBaseValue,
kFirstTimeCalling,
null_callback);
不要:
注意永远不要用自然语言翻译代码作为注释,要假设读你代码的人C++比你强:D:
// Now go through the b array and make sure that if i occurs,
// the next element is i+1.
... // Geez. What a useless comment.
7. 标点、拼写和语法(Punctuation, Spelling and Grammar)
留意标点、拼写和语法,写的好的注释比差的要易读的多。
注释一般是包含适当大写和句点(.)的完整的句子,短一点的注释(如代码行尾的注释)可以随意点,依然要注意风格的一致性。完整的句子可读性更好,也可以说明该注释是完整的而不是一点不成熟的想法。
虽然被别人指出该用分号(semicolon)的时候用了逗号(comma)有点尴尬。清晰易读的代码还是很重要的,适当的标点、拼写和语法对此会有所帮助。
8. TODO注释(TODO Comments)
对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用TODO注释。
这样的注释要使用全大写的字符串TODO,后面括号(parentheses)里加上你的大名、邮件地址等,还可以加上冒号(colon):目的是可以根据统一的TODO格式进行查找:
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
如果加上是为了在“将来某一天做某事”,可以加上一个特定的时间("Fix by November 2005")或事件("Remove this code when all clients can handle XML responses.")。
______________________________________
译者:注释也是比较人性化的约定了:
1. 关于注释风格,很多C++的coders更喜欢行注释,C coders或许对块注释依然情有独钟,或者在文件头大段大段的注释时使用块注释;
2. 文件注释可以炫耀你的成就,也是为了捅了篓子别人可以找你;
3. 注释要言简意赅,不要拖沓冗余,复杂的东西简单化和简单的东西复杂化都是要被鄙视的;
4. 对于Chinese coders来说,用英文注释还是用中文注释,it is a problem,但不管怎样,注释是为了让别人看懂,难道是为了炫耀编程语言之外的你的母语或外语水平吗;
5. 注释不要太乱,适当的缩进才会让人乐意看,但也没有必要规定注释从第几列开始(我自己写代码的时候总喜欢这样),UNIX/LINUX下还可以约定是使用tab还是space,个人倾向于space;
6. TODO很不错,有时候,注释确实是为了标记一些未完成的或完成的不尽如人意的地方,这样一搜索,就知道还有哪些活要干,日志都省了。
--------------------next---------------------
原文地址:
最重要的一致性规则是命名管理,命名风格直接可以直接确定命名实体是:类型、变量、函数、常量、宏等等,无需查找实体声明,我们大脑中的模式匹配引擎依赖于这些命名规则。
命名规则具有一定随意性,但相比按个人喜好命名,一致性更重要,所以不管你怎么想,规则总归是规则。
1. 通用命名规则(General Naming Rules)
函数命名、变量命名、文件命名应具有描述性,不要过度缩写,类型和变量应该是名词,函数名可以用“命令性”动词。
如何命名:
尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要,好的命名选择:
int num_errors; // Good.
int num_completed_connections; // Good.
丑陋的命名使用模糊的缩写或随意的字符:
int n; // Bad - meaningless.
int nerr; // Bad - ambiguous abbreviation.
int n_comp_conns; // Bad - ambiguous abbreviation.
类型和变量名一般为名词:如FileOpener、num_errors。
函数名通常是指令性的,如OpenFile()、set_num_errors(),访问函数需要描述的更细致,要与其访问的变量相吻合。
缩写:
除非放到项目外也非常明了,否则不要使用缩写,例如:
// Good
// These show proper names with no abbreviations.
int num_dns_connections; // Most people know what "DNS" stands for.
int price_count_reader; // OK, price count. Makes sense.
// Bad!
// Abbreviations can be confusing or ambiguous outside a small group.
int wgc_connections; // Only your group knows what this stands for.
int pc_reader; // Lots of things can be abbreviated "pc".
不要用省略字母的缩写:
int error_count; // Good.
int error_cnt; // Bad.
2. 文件命名(File Names)
文件名要全部小写,可以包含下划线(_)或短线(-),按项目约定来。
可接受的文件命名:
my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
C++文件以.cc结尾,头文件以.h结尾。
不要使用已经存在于/usr/include下的文件名(译者注,对UNIX、Linux等系统而言),如db.h。
通常,尽量让文件名更加明确,http_server_logs.h就比logs.h要好,定义类时文件名一般成对出现,如foo_bar.h和foo_bar.cc,对应类FooBar。
内联函数必须放在.h文件中,如果内联函数比较短,就直接放在.h中。如果代码比较长,可以放到以-inl.h结尾的文件中。对于包含大量内联代码的类,可以有三个文件:
url_table.h // The class declaration.
url_table.cc // The class definition.
url_table-inl.h // Inline functions that include lots of code.
参考-inl.h文件一节。
3. 类型命名(Type Names)
类型命名每个单词以大写字母开头,不包含下划线:MyExcitingClass、MyExcitingEnum。
所有类型命名——类、结构体、类型定义(typedef)、枚举——使用相同约定,例如:
// classes and structs
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// enums
enum UrlTableErrors { ...
4. 变量命名(Variable Names)
变量名一律小写,单词间以下划线相连,类的成员变量以下划线结尾,如my_exciting_local_variable、my_exciting_member_variable_。
普通变量命名:
举例:
string table_name; // OK - uses underscore.
string tablename; // OK - all lowercase.
string tableName; // Bad - mixed case.
类数据成员:
结构体的数据成员可以和普通变量一样,不用像类那样接下划线:
struct UrlTableProperties {
string name;
int num_entries;
}
结构体与类的讨论参考结构体vs.类一节。
全局变量:
对全局变量没有特别要求,少用就好,可以以g_或其他易与局部变量区分的标志为前缀。
5. 常量命名(Constant Names)
在名称前加k:kDaysInAWeek。
所有编译时常量(无论是局部的、全局的还是类中的)和其他变量保持些许区别,k后接大写字母开头的单词:
const int kDaysInAWeek = 7;
6. 函数命名(Function Names)
普通函数(regular functions,译者注,这里与访问函数等特殊函数相对)大小写混合,存取函数(accessors and mutators)则要求与变量名匹配:MyExcitingFunction()、MyExcitingMethod()、my_exciting_member_variable()、set_my_exciting_member_variable()。
普通函数:
函数名以大写字母开头,每个单词首字母大写,没有下划线:
AddTableEntry()
DeleteUrl()
存取函数:
存取函数要与存取的变量名匹配,这儿摘录一个拥有实例变量num_entries_的类:
class MyClass {
public:
...
int num_entries() const { return num_entries_; }
void set_num_entries(int num_entries) { num_entries_ = num_entries; }
private:
int num_entries_;
};
其他短小的内联函数名也可以使用小写字母,例如,在循环中调用这样的函数甚至都不需要缓存其值,小写命名就可以接受。
译者注:从这一点上可以看出,小写的函数名意味着可以直接内联使用。
7. 命名空间(Namespace Names)
命名空间的名称是全小写的,其命名基于项目名称和目录结构:google_awesome_project。
关于命名空间的讨论和如何命名,参考命名空间。
8. 枚举命名(Enumerator Names)
枚举值应全部大写,单词间以下划线相连:MY_EXCITING_ENUM_VALUE。
枚举名称属于类型,因此大小写混合:UrlTableErrors。
enum UrlTableErrors {
OK = 0,
ERROR_OUT_OF_MEMORY,
ERROR_MALFORMED_INPUT,
};
9. 宏命名(Macro Names)
你并不打算使用宏,对吧?如果使用,像这样:MY_MACRO_THAT_SCARES_SMALL_CHILDREN。
参考预处理宏,通常是不使用宏的,如果绝对要用,其命名像枚举命名一样全部大写、使用下划线:
#define ROUND(x) ...
#define PI_ROUNDED 3.0
MY_EXCITING_ENUM_VALUE
10. 命名规则例外(Exceptions to Naming Rules)
当命名与现有C/C++实体相似的对象时,可参考现有命名约定:
bigopen()
- 函数名,参考
open()
uint
typedef类型定义
bigpos
struct或class,参考pos
sparse_hash_map
- STL相似实体;参考STL命名约定
LONGLONG_MAX
- 常量,类似
INT_MAX
______________________________________
译者:命名约定就相对轻松许多,在遵从代码一致性、可读性的前提下,略显随意:
1. 总体规则:不要随意缩写,如果说ChangeLocalValue写作ChgLocVal还有情可原的话,把ModifyPlayerName写作MdfPlyNm就太过分了,除函数名可适当为动词外,其他命名尽量使用清晰易懂的名词;
2. 宏、枚举等使用全部大写+下划线;
3. 变量(含类、结构体成员变量)、文件、命名空间、存取函数等使用全部小写+下划线,类成员变量以下划线结尾,全局变量以g_开头;
4. 普通函数、类型(含类与结构体、枚举类型)、常量等使用大小写混合,不含下划线;
5. 参考现有或相近命名约定。
--------------------next---------------------
原文地址:
Google有很多自己实现的使C++代码更加健壮的技巧、功能,以及有异于别处的C++的使用方式。
1. 智能指针(Smart Pointers)
如果确实需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器中对象,你应该只使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。
“智能”指针看上去是指针,其实是附加了语义的对象。以scoped_ptr为例,scoped_ptr被销毁时,删除了它所指向的对象。shared_ptr也是如此,而且,shared_ptr实现了引用计数(reference-counting),从而只有当它所指向的最后一个对象被销毁时,指针才会被删除。
一般来说,我们倾向于设计对象隶属明确的代码,最明确的对象隶属是根本不使用指针,直接将对象作为一个域(field)或局部变量使用。另一种极端是引用计数指针不属于任何对象,这样设计的问题是容易导致循环引用或其他导致对象无法删除的诡异条件,而且在每一次拷贝或赋值时连原子操作都会很慢。
虽然不推荐这么做,但有些时候,引用计数指针是最简单有效的解决方案。
译者注:看来,Google所谓的不同之处,在于尽量避免使用智能指针:D,使用时也尽量局部化,并且,安全第一。
1. 引用参数(Reference Arguments)
所以按引用传递的参数必须加上const。
定义:在C语言中,如果函数需要修改变量的值,形参(parameter)必须为指针,如int foo(int *pval)。在C++中,函数还可以声明引用形参:int foo(int &val)。
优点:定义形参为引用避免了像(*pval)++这样丑陋的代码,像拷贝构造函数这样的应用也是必需的,而且不像指针那样不接受空指针NULL。
缺点:容易引起误解,因为引用在语法上是值却拥有指针的语义。
结论:
函数形参表中,所有引用必须是const:
void Foo(const string &in, string *out);
事实上这是一个硬性约定:输入参数为值或常数引用,输出参数为指针;输入参数可以是常数指针,但不能使用非常数引用形参。
在强调参数不是拷贝而来,在对象生命期内必须一直存在时可以使用常数指针,最好将这些在注释中详细说明。bind2nd和mem_fun等STL适配器不接受引用形参,这种情况下也必须以指针形参声明函数。
2. 函数重载(Function Overloading)
仅在输入参数类型不同、功能相同时使用重载函数(含构造函数),不要使用函数重载模仿缺省函数参数。
定义:可以定义一个函数参数类型为const string&,并定义其重载函数类型为const char*。
class MyClass {
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};
优点:通过重载不同参数的同名函数,令代码更加直观,模板化代码需要重载,同时为访问者带来便利。
缺点:限制使用重载的一个原因是在特定调用处很难确定到底调用的是哪个函数,另一个原因是当派生类只重载函数的部分变量会令很多人对继承语义产生困惑。此外在阅读库的客户端代码时,因缺省函数参数造成不必要的费解。
结论:如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用AppendString()、AppendInt()而不是Append()。
3. 缺省参数(Default Arguments)
禁止使用缺省函数参数。
优点:经常用到一个函数带有大量缺省值,偶尔会重写一下这些值,缺省参数为很少涉及的例外情况提供了少定义一些函数的方便。
缺点:大家经常会通过查看现有代码确定如何使用API,缺省参数使得复制粘贴以前的代码难以呈现所有参数,当缺省参数不适用于新代码时可能导致重大问题。
结论:所有参数必须明确指定,强制程序员考虑API和传入的各参数值,避免使用可能不为程序员所知的缺省参数。
4. 变长数组和alloca(Variable-Length Arrays and alloca())
禁止使用变长数组和alloca()。
优点:变长数组具有浑然天成的语法,变长数组和alloca()也都很高效。
缺点:变长数组和alloca()不是标准C++的组成部分,更重要的是,它们在堆栈(stack)上根据数据分配大小可能导致难以发现的内存泄漏:“在我的机器上运行的好好的,到了产品中却莫名其妙的挂掉了”。
结论:
使用安全的分配器(allocator),如scoped_ptr/scoped_array。
5. 友元(Friends)
允许合理使用友元类及友元函数。
通常将友元定义在同一文件下,避免读者跑到其他文件中查找其对某个类私有成员的使用。经常用到友元的一个地方是将FooBuilder声明为Foo的友元,FooBuilder以便可以正确构造Foo的内部状态,而无需将该状态暴露出来。某些情况下,将一个单元测试用类声明为待测类的友元会很方便。
友元延伸了(但没有打破)类的封装界线,当你希望只允许另一个类访问某个成员时,使用友元通常比将其声明为public要好得多。当然,大多数类应该只提供公共成员与其交互。
6. 异常(Exceptions)
不要使用C++异常。
优点:
1) 异常允许上层应用决定如何处理在底层嵌套函数中发生的“不可能发生”的失败,不像出错代码的记录那么模糊费解;
2) 应用于其他很多现代语言中,引入异常使得C++与Python、Java及其他与C++相近的语言更加兼容;
3) 许多C++第三方库使用异常,关闭异常将导致难以与之结合;
4) 异常是解决构造函数失败的唯一方案,虽然可以通过工厂函数(factory function)或Init()方法模拟异常,但他们分别需要堆分配或新的“非法”状态;
5) 在测试框架(testing framework)中,异常确实很好用。
缺点:
1) 在现有函数中添加throw语句时,必须检查所有调用处,即使它们至少具有基本的异常安全保护,或者程序正常结束,永远不可能捕获该异常。例如:if f() calls g() calls h(),h抛出被f捕获的异常,g就要当心了,避免没有完全清理;
2) 通俗一点说,异常会导致程序控制流(control flow)通过查看代码无法确定:函数有可能在不确定的地方返回,从而导致代码管理和调试困难,当然,你可以通过规定何时何地如何使用异常来最小化的降低开销,却给开发人员带来掌握这些规定的负担;
3) 异常安全需要RAII和不同编码实践。轻松、正确编写异常安全代码需要大量支撑。允许使用异常;
4) 加入异常使二进制执行代码体积变大,增加了编译时长(或许影响不大),还可能增加地址空间压力;
5) 异常的实用性可能会刺激开发人员在不恰当的时候抛出异常,或者在不安全的地方从异常中恢复,例如,非法用户输入可能导致抛出异常。如果允许使用异常会使得这样一篇编程风格指南长出很多(译者注,这个理由有点牵强:-()!
结论:
从表面上看,使用异常利大于弊,尤其是在新项目中,然而,对于现有代码,引入异常会牵连到所有依赖代码。如果允许异常在新项目中使用,在跟以前没有使用异常的代码整合时也是一个麻烦。因为Google现有的大多数C++代码都没有异常处理,引入带有异常处理的新代码相当困难。
鉴于Google现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大一点,迁移过程会比较慢,也容易出错。我们也不相信异常的有效替代方案,如错误代码、断言等,都是严重负担。
我们并不是基于哲学或道德层面反对使用异常,而是在实践的基础上。因为我们希望使用Google上的开源项目,但项目中使用异常会为此带来不便,因为我们也建议不要在Google上的开源项目中使用异常,如果我们需要把这些项目推倒重来显然不太现实。
对于Windows代码来说,这一点有个例外(等到最后一篇吧:D)。
译者注:对于异常处理,显然不是短短几句话能够说清楚的,以构造函数为例,很多C++书籍上都提到当构造失败时只有异常可以处理,Google禁止使用异常这一点,仅仅是为了自身的方便,说大了,无非是基于软件管理成本上,实际使用中还是自己决定。
7. 运行时类型识别(Run-Time Type Information, RTTI)
我们禁止使用RTTI。
定义:RTTI允许程序员在运行时识别C++类对象的类型。
优点:
RTTI在某些单元测试中非常有用,如在进行工厂类测试时用于检验一个新建对象是否为期望的动态类型。
除测试外,极少用到。
缺点:运行时识别类型意味著设计本身有问题,如果你需要在运行期间确定一个对象的类型,这通常说明你需要重新考虑你的类的设计。
结论:
除单元测试外,不要使用RTTI,如果你发现需要所写代码因对象类型不同而动作各异的话,考虑换一种方式识别对象类型。
虚函数可以实现随子类类型不同而执行不同代码,工作都是交给对象本身去完成。
如果工作在对象之外的代码中完成,考虑双重分发方案,如Visitor模式,可以方便的在对象本身之外确定类的类型。
如果你认为上面的方法你掌握不了,可以使用RTTI,但务必请三思,不要去手工实现一个貌似RTTI的方案(RTTI-like workaround),我们反对使用RTTI,同样反对贴上类型标签的貌似类继承的替代方案(译者注,使用就使用吧,不使用也不要造轮子:D)。
8. 类型转换(Casting)
使用static_cast<>()等C++的类型转换,不要使用int y = (int)x或int y = int(x);。
定义:C++引入了有别于C的不同类型的类型转换操作。
优点:C语言的类型转换问题在于操作比较含糊:有时是在做强制转换(如(int)3.5),有时是在做类型转换(如(int)"hello")。另外,C++的类型转换查找更容易、更醒目。
缺点:语法比较恶心(nasty)。
结论:使用C++风格而不要使用C风格类型转换。
1) static_cast:和C风格转换相似可做值的强制转换,或指针的父类到子类的明确的向上转换;
2) const_cast:移除const属性;
3) reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做一切了然于心时使用;
4) dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信息,说明设计有缺陷(参考RTTI)。
9. 流(Streams)
只在记录日志时使用流。
定义:流是printf()和scanf()的替代。
优点:有了流,在输出时不需要关心对象的类型,不用担心格式化字符串与参数列表不匹配(虽然在gcc中使用printf也不存在这个问题),打开、关闭对应文件时,流可以自动构造、析构。
缺点:流使得pread()等功能函数很难执行,如果不使用printf之类的函数而是使用流很难对格式进行操作(尤其是常用的格式字符串%.*s),流不支持字符串操作符重新定序(%1s),而这一点对国际化很有用。
结论:
不要使用流,除非是日志接口需要,使用printf之类的代替。
使用流还有很多利弊,代码一致性胜过一切,不要在代码中使用流。
拓展讨论:
对这一条规则存在一些争论,这儿给出深层次原因。回忆唯一性原则(Only One Way):我们希望在任何时候都只使用一种确定的I/O类型,使代码在所有I/O处保持一致。因此,我们不希望用户来决定是使用流还是printf + read/write,我们应该决定到底用哪一种方式。把日志作为例外是因为流非常适合这么做,也有一定的历史原因。
流的支持者们主张流是不二之选,但观点并不是那么清晰有力,他们所指出流的所有优势也正是其劣势所在。流最大的优势是在输出时不需要关心输出对象的类型,这是一个亮点,也是一个不足:很容易用错类型,而编译器不会报警。使用流时容易造成的一类错误是:
cout << this; // Prints the address
cout << *this; // Prints the contents
编译器不会报错,因为<<被重载,就因为这一点我们反对使用操作符重载。
有人说printf的格式化丑陋不堪、易读性差,但流也好不到哪儿去。看看下面两段代码吧,哪个更加易读?
cerr << "Error connecting to '" << foo->bar()->hostname.first
<< ":" << foo->bar()->hostname.second << ": " << strerror(errno);
fprintf(stderr, "Error connecting to '%s:%u: %s",
foo->bar()->hostname.first, foo->bar()->hostname.second,
strerror(errno));
你可能会说,“把流封装一下就会比较好了”,这儿可以,其他地方呢?而且不要忘了,我们的目标是使语言尽可能小,而不是添加一些别人需要学习的新的内容。
每一种方式都是各有利弊,“没有最好,只有更好”,简单化的教条告诫我们必须从中选择其一,最后的多数决定是printf + read/write。
10. 前置自增和自减(Preincrement and Predecrement)
对于迭代器和其他模板对象使用前缀形式(++i)的自增、自减运算符。
定义:对于变量在自增(++i或i++)或自减(--i或i--)后表达式的值又没有没用到的情况下,需要确定到底是使用前置还是后置的自增自减。
优点:不考虑返回值的话,前置自增(++i)通常要比后置自增(i++)效率更高,因为后置的自增自减需要对表达式的值i进行一次拷贝,如果i是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自增方式动作一样(译者注,不考虑表达式的值,相信你知道我在说什么),为什么不直接使用前置自增呢?
缺点:C语言中,当表达式的值没有使用时,传统的做法是使用后置自增,特别是在for循环中,有些人觉得后置自增更加易懂,因为这很像自然语言,主语(i)在谓语动词(++)前。
结论:对简单数值(非对象)来说,两种都无所谓,对迭代器和模板类型来说,要使用前置自增(自减)。
11. const的使用(Use of const)
我们强烈建议你在任何可以使用的情况下都要使用const。
定义:在声明的变量或参数前加上关键字const用于指明变量值不可修改(如const int foo),为类中的函数加上const限定表明该函数不会修改类成员变量的状态(如class Foo { int Bar(char c) const; };)。
优点:人们更容易理解变量是如何使用的,编辑器可以更好地进行类型检测、更好地生成代码。人们对编写正确的代码更加自信,因为他们知道所调用的函数被限定了能或不能修改变量值。即使是在无锁的多线程编程中,人们也知道什么样的函数是安全的。
缺点:如果你向一个函数传入const变量,函数原型中也必须是const的(否则变量需要const_cast类型转换),在调用库函数时这尤其是个麻烦。
结论:const变量、数据成员、函数和参数为编译时类型检测增加了一层保障,更好的尽早发现错误。因此,我们强烈建议在任何可以使用的情况下使用const:
1) 如果函数不会修改传入的引用或指针类型的参数,这样的参数应该为const;
2) 尽可能将函数声明为const,访问函数应该总是const,其他函数如果不会修改任何数据成员也应该是const,不要调用非const函数,不要返回对数据成员的非const指针或引用;
3) 如果数据成员在对象构造之后不再改变,可将其定义为const。
然而,也不要对const过度使用,像const int * const * const x;就有些过了,即便这样写精确描述了x,其实写成const int** x就可以了。
关键字mutable可以使用,但是在多线程中是不安全的,使用时首先要考虑线程安全。
const位置:
有人喜欢int const *foo形式不喜欢const int* foo,他们认为前者更加一致因此可读性更好:遵循了const总位于其描述的对象(int)之后的原则。但是,一致性原则不适用于此,“不要过度使用”的权威抵消了一致性使用。将const放在前面才更易读,因为在自然语言中形容词(const)是在名词(int)之前的。
这是说,我们提倡const在前,并不是要求,但要兼顾代码的一致性!
12. 整型(Integer Types)
C++内建整型中,唯一用到的是int,如果程序中需要不同大小的变量,可以使用<stdint.h>中的精确宽度(precise-width)的整型,如int16_t。
定义:C++没有指定整型的大小,通常人们认为short是16位,int是32位,long是32位,long long是64位。
优点:保持声明统一。
缺点:C++中整型大小因编译器和体系结构的不同而不同。
结论:
<stdint.h>定义了int16_t、uint32_t、int64_t等整型,在需要确定大小的整型时可以使用它们代替short、unsigned long long等,在C整型中,只使用int。适当情况下,推荐使用标准类型如size_t和ptrdiff_t。
最常使用的是,对整数来说,通常不会用到太大,如循环计数等,可以使用普通的int。你可以认为int至少为32位,但不要认为它会多于32位,需要64位整型的话,可以使用int64_t或uint64_t。
对于大整数,使用int64_t。
不要使用uint32_t等无符号整型,除非你是在表示一个位组(bit pattern)而不是一个数值。即使数值不会为负值也不要使用无符号类型,使用断言(assertion,译者注,这一点很有道理,计算机只会根据变量、返回值等有无符号确定数值正负,仍然无法确定对错)来保护数据。
无符号整型:
有些人,包括一些教科书作者,推荐使用无符号类型表示非负数,类型表明了数值取值形式。但是,在C语言中,这一优点被由其导致的bugs所淹没。看看:
for (unsigned int i = foo.Length()-1; i >= 0; --i) ...
上述代码永远不会终止!有时gcc会发现该bug并报警,但通常不会。类似的bug还会出现在比较有符合变量和无符号变量时,主要是C的类型提升机制(type-promotion scheme,C语言中各种内建类型之间的提升转换关系)会致使无符号类型的行为出乎你的意料。
因此,使用断言声明变量为非负数,不要使用无符号型。
13. 64位下的可移植性(64-bit Portability)
代码在64位和32位的系统中,原则上应该都比较友好,尤其对于输出、比较、结构对齐(structure alignment)来说:
1) printf()指定的一些类型在32位和64位系统上可移植性不是很好,C99标准定义了一些可移植的格式。不幸的是,MSVC 7.1并非全部支持,而且标准中也有所遗漏。所以有时我们就不得不自己定义丑陋的版本(使用标准风格要包含文件inttypes.h):
// printf macros for size_t, in the style of inttypes.h
#ifdef _LP64
#define __PRIS_PREFIX "z"
#else
#define __PRIS_PREFIX
#endif
// Use these macros after a % in a printf format string
// to get correct 32/64 bit behavior, like this:
// size_t size = records.size();
// printf("%"PRIuS"\n", size);
#define PRIdS __PRIS_PREFIX "d"
#define PRIxS __PRIS_PREFIX "x"
#define PRIuS __PRIS_PREFIX "u"
#define PRIXS __PRIS_PREFIX "X"
#define PRIoS __PRIS_PREFIX "o"
| 类型 |
不要使用 |
使用 |
备注 |
void *(或其他指针类型) |
%lx |
%p |
|
int64_t |
%qd, %lld |
%"PRId64" |
|
uint64_t |
%qu, %llu, %llx |
%"PRIu64", %"PRIx64" |
|
size_t |
%u |
%"PRIuS", %"PRIxS" |
C99指定%zu |
ptrdiff_t |
%d |
%"PRIdS" |
C99指定%zd |
注意宏PRI*会被编译器扩展为独立字符串,因此如果使用非常量的格式化字符串,需要将宏的值而不是宏名插入格式中,在使用宏PRI*时同样可以在%后指定长度等信息。例如,printf("x = %30"PRIuS"\n", x)在32位Linux上将被扩展为printf("x = %30" "u" "\n", x),编译器会处理为printf("x = %30u\n", x)。
2) 记住sizeof(void *) != sizeof(int),如果需要一个指针大小的整数要使用intptr_t。
3) 需要对结构对齐加以留心,尤其是对于存储在磁盘上的结构体。在64位系统中,任何拥有int64_t/uint64_t成员的类/结构体将默认被处理为8字节对齐。如果32位和64位代码共用磁盘上的结构体,需要确保两种体系结构下的结构体的对齐一致。大多数编译器提供了调整结构体对齐的方案。gcc中可使用__attribute__((packed)),MSVC提供了#pragma pack()和__declspec(align())(译者注,解决方案的项目属性里也可以直接设置)。
4) 创建64位常量时使用LL或ULL作为后缀,如:
int64_t my_value = 0x123456789LL;
uint64_t my_mask = 3ULL << 48;
5) 如果你确实需要32位和64位系统具有不同代码,可以在代码变量前使用。(尽量不要这么做,使用时尽量使修改局部化)。
14. 预处理宏(Preprocessor Macros)
使用宏时要谨慎,尽量以内联函数、枚举和常量代替之。
宏意味着你和编译器看到的代码是不同的,因此可能导致异常行为,尤其是当宏存在于全局作用域中。
值得庆幸的是,C++中,宏不像C中那么必要。宏内联效率关键代码(performance-critical code)可以内联函数替代;宏存储常量可以const变量替代;宏“缩写”长变量名可以引用替代;使用宏进行条件编译,这个……,最好不要这么做,会令测试更加痛苦(#define防止头文件重包含当然是个例外)。
宏可以做一些其他技术无法实现的事情,在一些代码库(尤其是底层库中)可以看到宏的某些特性(如字符串化(stringifying,译者注,使用#)、连接(concatenation,译者注,使用##)等等)。但在使用前,仔细考虑一下能不能不使用宏实现同样效果。
译者注:关于宏的高级应用,可以参考。
下面给出的用法模式可以避免一些使用宏的问题,供使用宏时参考:
1) 不要在.h文件中定义宏;
2) 使用前正确#define,使用后正确#undef;
3) 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
4) 不使用会导致不稳定的C++构造(unbalanced C++ constructs,译者注)的宏,至少文档说明其行为。
15. 0和NULL(0 and NULL)
整数用0,实数用0.0,指针用NULL,字符(串)用'\0'。
整数用0,实数用0.0,这一点是毫无争议的。
对于指针(地址值),到底是用0还是NULL,Bjarne Stroustrup建议使用最原始的0,我们建议使用看上去像是指针的NULL,事实上一些C++编译器(如gcc 4.1.0)专门提供了NULL的定义,可以给出有用的警告,尤其是sizeof(NULL)和sizeof(0)不相等的情况。
字符(串)用'\0',不仅类型正确而且可读性好。
16. sizeof(sizeof)
尽可能用sizeof(varname)代替sizeof(type)。
使用sizeof(varname)是因为当变量类型改变时代码自动同步,有些情况下sizeof(type)或许有意义,还是要尽量避免,如果变量类型改变的话不能同步。
Struct data;
memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct));
17. Boost库(Boost)
只使用Boost中被认可的库。
定义:Boost库集是一个非常受欢迎的、同级评议的(peer-reviewed)、免费的、开源的C++库。
优点:Boost代码质量普遍较高、可移植性好,填补了C++标准库很多空白,如型别特性(type traits)、更完善的绑定(binders)、更好的智能指针,同时还提供了TR1(标准库的扩展)的实现。
缺点:某些Boost库提倡的编程实践可读性差,像元程序(metaprogramming)和其他高级模板技术,以及过度“函数化”("functional")的编程风格。
结论:为了向阅读和维护代码的人员提供更好的可读性,我们只允许使用Boost特性的一个成熟子集,当前,这些库包括:
1) :boost/compressed_pair.hpp;
2) :boost/ptr_container不包括ptr_array.hpp和序列化(serialization)。
我们会积极考虑添加可以的Boost特性,所以不必拘泥于该规则。
______________________________________
译者:关于C++特性的注意事项,总结一下:
1. 对于智能指针,安全第一、方便第二,尽可能局部化(scoped_ptr);
2. 引用形参加上const,否则使用指针形参;
3. 函数重载的使用要清晰、易读;
4. 鉴于容易误用,禁止使用缺省函数参数(值得商榷);
5. 禁止使用变长数组;
6. 合理使用友元;
7. 为了方便代码管理,禁止使用异常(值得商榷);
8. 禁止使用RTTI,否则重新设计代码吧;
9. 使用C++风格的类型转换,除单元测试外不要使用dynamic_cast;
10. 使用流还printf + read/write,it is a problem;
11. 能用前置自增/减不用后置自增/减;
12. const能用则用,提倡const在前;
13. 使用确定大小的整型,除位组外不要使用无符号型;
14. 格式化输出及结构对齐时,注意32位和64位的系统差异;
15. 除字符串化、连接外尽量避免使用宏;
16. 整数用0,实数用0.0,指针用NULL,字符(串)用'\0';
17. 用sizeof(varname)代替sizeof(type);
18. 只使用Boost中被认可的库。
--------------------next---------------------
一个好的日志系统,除了可以记录尽可能多的必要信息,方便trace bugs、提供data analysis source这些基本功能之外,其他的貌似不必太在意。但真正当bugs冒出来的时候,要命的是既没有dump,也没有有价值的日志,更要命的是日志居然已经记录了那么多,居然让你查了半天,居然都是没有价值的!
悲剧啊!
日志需要记录的信息大概分为两类:
1) 系统运行情况:启动、加载、读写、关闭、异常;
2) 用户使用情况:进入、操作、离开、异常。
我可以想到的关于日志系统的要求大致以下几点:
1) 日志系统使用目录树结构:系统日志和用户日志分别记录,正常日志和异常日志分别记录,不置于同一文件夹下,日志文件命名做到令观者一目了然;
2) 记录详尽但不冗余:正确记录日志时间、位置、事件、因果,有可能的话,记录上下文(这要求有点高了);
3) 格式统一但严禁千篇一律:格式统一是指记录内容遵循一定格式,方便查看,严禁千篇一律是指记录要有层次、轻重,不同事件导致的“同一”异常日志不应不加区别,同样是为了方便查看;
4) 与异常处理相辅相成:有dump时,以日志辅助快速定位,没有dump时,日志应尽可能提供有效信息,离系统崩溃的地方越近越好(这一点似乎也有难度)。
________________________________________________
突然想到的,也还没有动手去做,先记下了,欢迎补充。
_____Added on Jul.25th, 2008_______________________
还看到一位兄弟在为我说话,谢谢!
今天在考虑实现时,想到一个很现实的问题,日志几乎是无处不在的,随时随地会有日志记录。不知道有谁对I/O(当然主要是Output)消耗和对系统的影响做过专门测试,猜测就算了:-),我很想知道有没有必要放到专门的线程中,如果放到独立线程中的话,问题就出来了,多长时间写一次?毕竟,记录日志的主要目的就是为了全面记录系统运行和用户使用情况,如果在服务器crash的时候,还有日志(尤其是crash上下文日志)没有被顺利写入,日志的意义也就大打折扣。
谁给点建议?
--------------------next---------------------