close
1. 前言
介紹高質量C++編程的書籍很多,而且都非常好,這裏主要針對已有書籍較少涉及到的代碼格式條款進行補充。代碼是程序員臉面,清清爽爽和幹幹凈凈的代碼是程序員高職業素質的體現,清爽的代碼需要從細節做起,用心呵護。
2. 條款:避免使用非眾所周知的縮略語
本條款非新鮮的,但實際很少有人真正遵循,在代碼中總能見到一些自創的縮略語,後來接手代碼的人常常需要去猜測是啥意思。因此提出來重點強調一下,如:GeneralServer,不要寫成CGSConfig,而應當使用長名CGeneralServerConfig,長名是自註釋的,前者在上下文環境不足和缺少文檔的情況(除了華為那樣文檔要求非常嚴格的企業,可能一般公司都存在這樣的情況)下可能需要去猜測。
3. 條款:規範好#include
寫#include也有講究,通常<>在前,""在後,如:
#include
#include "mooon.h"
而且非隸屬本編譯工程中的頭文件,一律使用<>,隸屬本編譯工程中的頭文件使用""。
4. 條款:避免長短語句無規律交錯
下面這段代碼無規律的交錯著,容易給人以混亂的感覺:
void reset_current_message(bool finish);
void free_current_message();
void inc_resend_times();
util::handle_result_t do_handle_reply();
void clear_message();
net::epoll_event_t do_send_message(void* ptr, uint32_t events);
使用這一條款後,變成成如下:
void clear_message();
void inc_resend_times();
void free_current_message();
void reset_current_message(bool finish);
util::handle_result_t do_handle_reply();
net::epoll_event_t do_send_message(void* ptr, uint32_t events);
從短到長,明顯清爽清晰了很多,變量了定義等也應當盡量遵守此條款。
#include段也應當盡量遵循這個規律,如:
#include
#include
#include
#include
#include
#include
#include
#include "sys/fs_util.h"
#include "sys/close_helper.h"
如果一些變量是相關的,則可以使用空行分開,在同一組內實施這一條款。
5. 條款:避免頭重腳輕
char* str = get_value("thread_number");
if (str != NULL)
{
thread_number = string2int(str);
if (0 == thread_number)
_thread_number = 1;
else
_thread_number = thread_number;
}
else
{
_thread_number = 1;
}
上面的代碼段,就顯得頭重腳輕,if塊比else塊大了很多。特別是當if塊超過50行時,會導致else塊較難看,甚至可能難以一下確定else對應哪個if語句。將兩者跌倒一下,就可以消除頭重腳輕的問題,如下:
char* str = get_value("thread_number");
if (NULL == str)
{
_thread_number = 1;
}
else
{
thread_number = string2int(str);
if (0 == thread_number)
_thread_number = 1;
else
_thread_number = thread_number;
}
6. 條款:充分利用public和private等
C++允許public等修飾符在一個類的定義中多次重復出現,充分利用這一特性,可使得類的定義代碼變得更清爽。下面這段代碼充分利用了這一特性,對類的定義進行了歸類,使得整個定義顯得較為清爽不淩亂交錯:
class CSender: public net::CTcpClient
{
public: // 公有函數
~CSender();
CSender(CSendThreadPool* thread_pool, int32_t route_id, uint32_t queue_max, IReplyHandler* reply_handler);

int32_t get_node_id() const;
bool push_message(dispatch_message_t* message, uint32_t milliseconds);
public: // 公有的虛擬函數
virtual void after_connect();
private: // 重寫的虛擬函數
virtual void before_close();
virtual void connect_failure();

private: // 非重寫的私有函數
void clear_message();
void inc_resend_times();
void free_current_message();
void reset_current_message(bool finish);
util::handle_result_t do_handle_reply();
net::epoll_event_t do_send_message(void* ptr, uint32_t events);

protected: // 提供給不同子類使用的公共函數
void do_set_resend_times(int8_t resend_times);
net::epoll_event_t do_handle_epoll_event(void* ptr, uint32_t events);

private: // 非狀態成員
int32_t _route_id;
CSendQueue _send_queue;
IReplyHandler* _reply_handler;
CSendThreadPool* _thread_pool;
private: // 發送狀態相關的
int8_t _cur_resend_times;  // 當前已經連續重發的次數
int8_t _max_resend_times; // 失敗後最多重發的次數,負數表示永遠重發,0表示不重發
uint32_t _current_offset;             // 當前已經發送的字節數
dispatch_message_t* _current_message; // 當前正在發送的消息
};
7. 條款:類成員優先使用對象類型
按照UML上的術語來說,就是優先使用組合,而非聚合,雖然從依賴性上講聚合低於組合,但這只是理論上,對於一個對象的生命周期由別一個類來掌握時,使用組合更好,原因是組合使得該類對象的內存空間連續,而聚合通常需要在構造函數中new,在析構中delete,容易造成更多的內存碎片,總是連續的比非連續的好,如:
class CAgentThread
{
private:
CMasterConnector _connector; // 建議使用對象類型
};
當然如果只是關聯關系,那肯定只能使用指針類型了,如:
class CAgentThread
{
private:
CAgentContext* _contexnt; // 只能使用指針類型
}
8. 條款:名字空間的使用
杜絕在頭文件使用using,包括using namespace std和using std::vector兩種形式。這樣做完全失去了名字空間的意義,減少名字間的沖突。
9. 條款:巧用do...while(false)替代goto
先看下段代碼:
int CTcpClient::timed_connect()
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == fd)
{
return errno; // goto CONNECT_ERROR:
}
if (-1 == connect(fd, peer_addr, addr_length))
{
close(fd);
return errno;
}
if (!CNetUtil::timed_poll(fd, POLLIN | POLLOUT | POLLERR, _milli_seconds))
{
close(fd);
return errno;
}
if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &errcode_length))
{
close(fd);
return errno;
}
set_fd(fd);
return 0;
}
上面這段代碼,在出錯的地方,有多處return,代碼基本相同,通常大家會想到使用goto語句來解決這個問題。goto總是應當只作為最後不得已的一種選擇,通過下面這段代碼我們來看看如何使用do...while(false)優雅的解決這個問題:
int CTcpClient::timed_connect()
{
int fd = -1;
do
{
fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == fd)
{
break; // goto CONNECT_ERROR:
}
if (-1 == connect(fd, peer_addr, addr_length))
{
close(fd);
break;
}
if (!CNetUtil::timed_poll(fd, POLLIN | POLLOUT | POLLERR, _milli_seconds))
{
close(fd);
break;
}
if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &errcode_length))
{
close(fd);
break;
}
set_fd(fd);
return 0;
}
while (false);
// 相當於goto到這裏
if (fd != -1) close(fd);
return errno;
}
使用do...while(false)後,整個函數就只有兩個return出口了。
10. 條款:利用typedef增強代碼的自註釋
在一些開源和C++標準庫stl中,可以見到大量的typedef使用,除了使用typedef來簡化長類型的定義,如:typedef basic_string string;外,還有增強代碼自註釋的目的。
假設需要一個存儲IP端口號的列表,可以定義如下:
std::list port_list_t;
11. 條款:不要失去對進程和線程的控制權
在設計和代碼中,應當杜絕時長未定或較長的sleep調用,以及完全阻塞的accept/read等調用,因為這會使你失去對進程和線程的控制權。當你需要進行死鎖檢測,將不容易區分,當程序需要退出,會比較麻煩。正確的做法是保證sleep的時間盡可能短而且最長時間明確,通常不要超過10秒,甚至可以考慮使用可喚醒的條件等替代,而accept/read應當改用帶超時的,或使用非阻塞的,這樣就能牢牢把握對進程和線程的控制權。
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 成功运行 的頭像
    成功运行

    成功运行的部落格

    成功运行 發表在 痞客邦 留言(0) 人氣()